This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| en:tech:jkbmscan [2024/05/12 05:04] – [Conclusion] bullar | en:tech:jkbmscan [2024/05/18 04:52] (current) – [Software] bullar | ||
|---|---|---|---|
| Line 3: | Line 3: | ||
| ===== Motivation ===== | ===== Motivation ===== | ||
| - | Part of the [[en: | + | Part of the [[en: |
| Line 10: | Line 10: | ||
| ==== INVERTER ==== | ==== INVERTER ==== | ||
| - | In order to allow two systems to talk to each other we have to find a compatible protocol or adapt one side to fit to the other. The inverter [[https:// | + | In order to allow two systems to talk to each other we have to find a compatible protocol or adapt one side to fit to the other. The inverter [[https:// |
| <WRAP group> | <WRAP group> | ||
| Line 21: | Line 21: | ||
| </ | </ | ||
| - | Unfortunately the battery manufacturer use own proprietary protocols. At least the protocol from {{https:// | + | Unfortunately the battery manufacturer use own proprietary protocols. At least the protocol from [[https:// |
| ==== BMS ==== | ==== BMS ==== | ||
| Line 38: | Line 38: | ||
| A lot of clever makers have already challenged this task. | A lot of clever makers have already challenged this task. | ||
| Here an incomplete list: | Here an incomplete list: | ||
| - | * {{https:// | + | * [[https:// |
| - | * {{https:// | + | * [[https:// |
| - | * {{https:// | + | * [[https:// |
| - | * {{https:// | + | * [[https:// |
| - | * {{https:// | + | * [[https:// |
| - | As I use {{https:// | + | As I use [[https:// |
| ===== Hardware ===== | ===== Hardware ===== | ||
| Line 63: | Line 63: | ||
| </ | </ | ||
| - | The converter should be always powered (as the BMS) to avoid dead locks. | + | The converter should be always powered (as the BMS) to avoid dead locks. Therefore I like to use VBAT as power source. |
| - | The power supply is based on a XL7015 Buck DC to DC Converter (0.8A 150KHz 80V) from {{https:// | + | The power supply is based on a XL7015 Buck DC to DC Converter (0.8A 150KHz 80V) from [[https:// |
| <WRAP group> | <WRAP group> | ||
| Line 75: | Line 75: | ||
| </ | </ | ||
| - | The final module in a 3D-printed | + | The final module in my 3D-printed |
| {{ : | {{ : | ||
| Line 81: | Line 81: | ||
| ===== Software ===== | ===== Software ===== | ||
| + | As mentioned the software is based on [[https:// | ||
| + | ++++ jk-bms-can.yaml | | ||
| + | <file YAML jk-bms-can.yaml> | ||
| + | JK-BMS-CAN ( PYLON, Seplos, GoodWe, SMA and Victron CAN bus protocol ) | ||
| + | |||
| + | # esp32_wire_jk-bms-can.yaml is free software: you can redistribute it | ||
| + | # and/or modify it under the terms of the GNU General Public License | ||
| + | # as published by the Free Software Foundation, either version 3 | ||
| + | # of the License, or (at your option) any later version. | ||
| + | # | ||
| + | # This program is distributed in the hope that it will be useful, | ||
| + | # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
| + | # See the GNU General Public License for more details. | ||
| + | # | ||
| + | # You should have received a copy of the GNU General Public License | ||
| + | # along with this program. If not, see < | ||
| + | |||
| + | # V1.16.3 Sleeper85 : ID 0x379 will be sent when choosing protocol 2 or 4 (Battery Capacity for Victron, Sol-Ark and Luxpower) | ||
| + | # V1.16.2 Sleeper85 : Split the " | ||
| + | # V1.16.1 Sleeper85 : Slider charging_current max value = ${charge_a}, | ||
| + | # V1.15.5 Sleeper85 : Improved code and set api " | ||
| + | # V1.15.4 Sleeper85 : Improved documentation for API, Web Server and WiFi settings | ||
| + | # V1.15.3 Sleeper85 : Add 'CAN Protocol Settings' | ||
| + | # V1.15.2 Sleeper85 : Improved Alarm handling, all alarms will set charge/ | ||
| + | # V1.15.1 Sleeper85 : New CANBUS script with CANBUS Status in HA, stop sending CAN messages if the inverter is not responding (fix WDT reboot issues) | ||
| + | # V1.14.3 Sleeper85 : Improved documentation + Charging Voltage tips for Deye | ||
| + | # V1.14.2 Sleeper85 : Improve ' | ||
| + | # V1.14.1 Sleeper85 : Add 'Float charge function' | ||
| + | # V1.13.6 Sleeper85 : Add ' | ||
| + | # V1.13.5 Sleeper85 : Set CAN manufacter to " | ||
| + | # V1.13.4 Sleeper85 : Improve ' | ||
| + | # V1.13.3 uksa007 | ||
| + | # V1.13.2 uksa007 | ||
| + | # V1.13.1 uksa007 | ||
| + | |||
| + | substitutions: | ||
| + | # +--------------------------------------+ | ||
| + | # name that will appear in esphome and homeassistant. | ||
| + | name: jk-bms-can | ||
| + | device_description: | ||
| + | # +--------------------------------------+ | ||
| + | # Number of Battery modules max 8. Each LX U5.4-L battery is 5.4kWh, select the number closest to your capactiy eg 3.2V * 280Ah * 16 = 14.3kWh | ||
| + | # Each PYLON US2000 battery has 48V/2.4kWh, Installed battery set: 3.2V * 310Ah * 16 = 15.87kWh --> 7 (7 * 2.4kWh = 16.8kWh) | ||
| + | batt_modules: | ||
| + | # +--------------------------------------+ | ||
| + | # | Battery Charge Settings | ||
| + | # +--------------------------------------+ | ||
| + | # Tips for Deye inverter : Add 0.1v to the settings below because the Deye charging voltage is always 0.1v lower than requested. | ||
| + | # Float V. : 53.7v (3.35v/cell - Natural voltage of a fully charged cell at rest, I advise you not to go higher.) | ||
| + | # Absorption V : 55.3v (3.45v/cell - It's not necessary to use a charging voltage higher than 55.2V for a full charge.) | ||
| + | # Absorption Offset V. : 0.15v (The absorption phase will start at 55.15v (BMS voltage). Warning: the BMS voltage must be correctly calibrated.) | ||
| + | # +--------------------------------------+ | ||
| + | # This is max charging amps eg 100A, for Bulk - Constant Current charging(CC), | ||
| + | # 100A * 50V = 5000W | ||
| + | charge_a: " | ||
| + | # Float Voltage : corresponds to the voltage at which the battery would be maintained at the end of the absorption phase. (53.6v eg 3.35v/cell for 16 cells 48V battery) | ||
| + | float_v: " | ||
| + | # Absorption Voltage : corresponds to the Bulk voltage that will be used to charge the battery. (55.2v eg 3.45v/cell for 16 cells 48V battery) | ||
| + | absorption_v: | ||
| + | # Absorption time in minutes to hold charge voltage after charge voltage is reached eg 30 | ||
| + | absorption_time: | ||
| + | # Absorption offset, x Volts below absorption voltage battery will start the absorption timer, eg 55.2-0.05 = 52.15v | ||
| + | absorption_offset_v: | ||
| + | # Rebulk offset, x Volts below absorption voltage battery will request rebulk, eg 55.2-2.5 = 52.7v | ||
| + | rebulk_offset_v: | ||
| + | # +--------------------------------------+ | ||
| + | # | Battery Discharge Settings | ||
| + | # +--------------------------------------+ | ||
| + | # Max discharge amps eg 120, should be at least 10A less than BMS over discharge current protection, 0.5C max | ||
| + | # 120A * 50V = 6000W | ||
| + | discharge_a: | ||
| + | # Minimum discharge voltage eg 48v/16 = 3V per cell | ||
| + | min_discharge_v: | ||
| + | # +--------------------------------------+ | ||
| + | # | Battery State of Health (SOH) | | ||
| + | # +--------------------------------------+ | ||
| + | # Maximum charging cycles is used to calculate the battey SOH, LF280K v3 =8000.0, LF280K v2 =6000.0, LF280=3000.0 (decimal is required) | ||
| + | max_cycles: " | ||
| + | # +--------------------------------------+ | ||
| + | # | CAN Protocol Settings | ||
| + | # +--------------------------------------+ | ||
| + | # CAN BMS Name (0x35E) : 0 NoSent / 1 PYLON / 2 GOODWE / 3 SEPLOS | ||
| + | can_bms_name: | ||
| + | # CAN Protocol | ||
| + | # 1 : PYLON 1.2 (Deye) | ||
| + | # 2 : SEPLOS 1.0, PYLON 1.3, GOODWE 1.5 (GoodWe, Sol-Ark, Luxpower) | ||
| + | # 3 : SMA (Sunny Island) | ||
| + | # 4 : VICTRON | ||
| + | can_protocol: | ||
| + | # +--------------------------------------+ | ||
| + | # | ESP32 CAN/serial port pins | | ||
| + | # +--------------------------------------+ | ||
| + | # GPIO pins your CAN bus transceiver (TJA1050, TJA1051T or SN65HVD230) is connected to the ESP32 TX->TX and RX->RX ! | ||
| + | can_tx_pin: GPIO23 | ||
| + | can_rx_pin: GPIO22 | ||
| + | # GPIO pins your JK-BMS UART-TTL is connected to the ESP32 TX->RX and RX->TX ! | ||
| + | tx_pin: GPIO17 | ||
| + | rx_pin: GPIO16 | ||
| + | |||
| + | # +------------------------------------------------------------------+ | ||
| + | # | ** The settings below can be modified according to your needs ** | | ||
| + | # +------------------------------------------------------------------+ | ||
| + | external_components_source: | ||
| + | # components | ||
| + | # github:// | ||
| + | | ||
| + | esphome: | ||
| + | name: ${name} | ||
| + | on_boot: | ||
| + | then: | ||
| + | - switch.turn_on: | ||
| + | - switch.turn_on: | ||
| + | - switch.turn_on: | ||
| + | |||
| + | # +--------------------------------------+ | ||
| + | # | ESP32 settings | ||
| + | # +--------------------------------------+ | ||
| + | # For a stable Bluetooth connection keep the " | ||
| + | esp32: | ||
| + | board: esp32doit-devkit-v1 | ||
| + | framework: | ||
| + | type: esp-idf | ||
| + | |||
| + | external_components: | ||
| + | - source: ${external_components_source} | ||
| + | refresh: 0s | ||
| + | | ||
| + | logger: | ||
| + | # level: DEBUG | ||
| + | |||
| + | ota: | ||
| + | password: " | ||
| + | | ||
| + | # Please use the native `api` component instead of the `mqtt` section. | ||
| + | # If you use Home Assistant, the native API is more lightweight. | ||
| + | # If there is no HA server connected to this API, the ESP32 reboots every 15 minutes to try to resolve the problem. | ||
| + | # If you don't use Home Assistant please uncomment the " | ||
| + | api: | ||
| + | reboot_timeout: | ||
| + | |||
| + | # If you don't want to use ESPHome' | ||
| + | # In this case don't forget to remove the ' | ||
| + | # mqtt: | ||
| + | # broker: !secret mqtt_host | ||
| + | # username: !secret mqtt_username | ||
| + | # password: !secret mqtt_password | ||
| + | # id: mqtt_client | ||
| + | |||
| + | # In the event of problems with the WiFi network, the ESP32 will reboot every 15 minutes to try to resolve the problem. | ||
| + | # If we don't want to connect the ESP32 to the WiFi network please remove the 4 lines below. | ||
| + | wifi: | ||
| + | ssid: !secret wifi_ssid | ||
| + | password: !secret wifi_password | ||
| + | # domain: !secret domain | ||
| + | |||
| + | manual_ip: | ||
| + | static_ip: 192.168.1.224 | ||
| + | gateway: 192.168.1.1 | ||
| + | subnet: 255.255.255.0 | ||
| + | # | ||
| + | # port: 80 | ||
| + | # log: false | ||
| + | # ota: false | ||
| + | |||
| + | # +--------------------------------------+ | ||
| + | # | ** Don't make changes below this ** | | ||
| + | # +--------------------------------------+ | ||
| + | |||
| + | globals: | ||
| + | - id: can_ack_counter | ||
| + | type: int | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | - id: charge_status | ||
| + | type: std::string | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | - id: can_status | ||
| + | type: std::string | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | - id: alarm_status | ||
| + | type: std::string | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | - id: charging_v | ||
| + | type: float | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | - id: charging_a | ||
| + | type: int | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | - id: discharging_a | ||
| + | type: int | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | - id: can_msg_counter | ||
| + | type: int | ||
| + | restore_value: | ||
| + | initial_value: | ||
| + | |||
| + | output: | ||
| + | - platform: gpio | ||
| + | pin: 2 | ||
| + | id: led | ||
| + | inverted: true | ||
| + | |||
| + | light: | ||
| + | - platform: binary | ||
| + | output: led | ||
| + | id: led_buitin | ||
| + | name: " | ||
| + | internal: true | ||
| + | |||
| + | # +--------------------------------------+ | ||
| + | # | JK-BMS UART connection | ||
| + | # +--------------------------------------+ | ||
| + | |||
| + | uart: | ||
| + | id: uart_0 | ||
| + | baud_rate: 115200 | ||
| + | rx_buffer_size: | ||
| + | tx_pin: ${tx_pin} | ||
| + | rx_pin: ${rx_pin} | ||
| + | # debug: | ||
| + | # direction: BOTH | ||
| + | |||
| + | jk_modbus: | ||
| + | id: modbus0 | ||
| + | uart_id: uart_0 | ||
| + | |||
| + | jk_bms: | ||
| + | id: bms0 | ||
| + | jk_modbus_id: | ||
| + | # enable_fake_traffic: | ||
| + | |||
| + | # +--------------------------------------+ | ||
| + | |||
| + | binary_sensor: | ||
| + | - platform: jk_bms | ||
| + | balancing: | ||
| + | name: " | ||
| + | balancing_switch: | ||
| + | name: " | ||
| + | charging: | ||
| + | name: " | ||
| + | charging_switch: | ||
| + | id: charging_switch | ||
| + | name: " | ||
| + | discharging: | ||
| + | name: " | ||
| + | discharging_switch: | ||
| + | id: discharging_switch | ||
| + | name: " | ||
| + | dedicated_charger_switch: | ||
| + | id: dedicated_charger_switch | ||
| + | name: " | ||
| + | |||
| + | sensor: | ||
| + | - platform: jk_bms | ||
| + | min_cell_voltage: | ||
| + | id: min_cell_voltage | ||
| + | name: " | ||
| + | max_cell_voltage: | ||
| + | id: max_cell_voltage | ||
| + | name: " | ||
| + | min_voltage_cell: | ||
| + | id: min_voltage_cell | ||
| + | name: " | ||
| + | max_voltage_cell: | ||
| + | id: max_voltage_cell | ||
| + | name: " | ||
| + | delta_cell_voltage: | ||
| + | name: " | ||
| + | average_cell_voltage: | ||
| + | name: " | ||
| + | cell_voltage_1: | ||
| + | name: " | ||
| + | cell_voltage_2: | ||
| + | name: " | ||
| + | cell_voltage_3: | ||
| + | name: " | ||
| + | cell_voltage_4: | ||
| + | name: " | ||
| + | cell_voltage_5: | ||
| + | name: " | ||
| + | cell_voltage_6: | ||
| + | name: " | ||
| + | cell_voltage_7: | ||
| + | name: " | ||
| + | cell_voltage_8: | ||
| + | name: " | ||
| + | cell_voltage_9: | ||
| + | name: " | ||
| + | cell_voltage_10: | ||
| + | name: " | ||
| + | cell_voltage_11: | ||
| + | name: " | ||
| + | cell_voltage_12: | ||
| + | name: " | ||
| + | cell_voltage_13: | ||
| + | name: " | ||
| + | cell_voltage_14: | ||
| + | name: " | ||
| + | cell_voltage_15: | ||
| + | name: " | ||
| + | cell_voltage_16: | ||
| + | name: " | ||
| + | # cell_voltage_17: | ||
| + | # name: " | ||
| + | # cell_voltage_18: | ||
| + | # name: " | ||
| + | # cell_voltage_19: | ||
| + | # name: " | ||
| + | # cell_voltage_20: | ||
| + | # name: " | ||
| + | # cell_voltage_21: | ||
| + | # name: " | ||
| + | # cell_voltage_22: | ||
| + | # name: " | ||
| + | # cell_voltage_23: | ||
| + | # name: " | ||
| + | # cell_voltage_24: | ||
| + | # name: " | ||
| + | power_tube_temperature: | ||
| + | id: power_tube_temperature | ||
| + | name: " | ||
| + | temperature_sensor_1: | ||
| + | id: temperature_sensor_1 | ||
| + | name: " | ||
| + | temperature_sensor_2: | ||
| + | id: temperature_sensor_2 | ||
| + | name: " | ||
| + | total_voltage: | ||
| + | id: total_voltage | ||
| + | name: " | ||
| + | current: | ||
| + | id: current | ||
| + | name: " | ||
| + | power: | ||
| + | name: " | ||
| + | charging_power: | ||
| + | name: " | ||
| + | discharging_power: | ||
| + | name: " | ||
| + | capacity_remaining: | ||
| + | id: capacity_remaining | ||
| + | name: " | ||
| + | capacity_remaining_derived: | ||
| + | name: " | ||
| + | temperature_sensors: | ||
| + | name: " | ||
| + | charging_cycles: | ||
| + | id: charging_cycles | ||
| + | name: " | ||
| + | total_charging_cycle_capacity: | ||
| + | name: " | ||
| + | battery_strings: | ||
| + | name: " | ||
| + | errors_bitmask: | ||
| + | id: errors_bitmask | ||
| + | name: " | ||
| + | operation_mode_bitmask: | ||
| + | name: " | ||
| + | total_voltage_overvoltage_protection: | ||
| + | name: " | ||
| + | total_voltage_undervoltage_protection: | ||
| + | name: " | ||
| + | cell_voltage_overvoltage_protection: | ||
| + | name: " | ||
| + | cell_voltage_overvoltage_recovery: | ||
| + | name: " | ||
| + | cell_voltage_overvoltage_delay: | ||
| + | name: " | ||
| + | cell_voltage_undervoltage_protection: | ||
| + | name: " | ||
| + | cell_voltage_undervoltage_recovery: | ||
| + | name: " | ||
| + | cell_voltage_undervoltage_delay: | ||
| + | name: " | ||
| + | cell_pressure_difference_protection: | ||
| + | name: " | ||
| + | discharging_overcurrent_protection: | ||
| + | name: " | ||
| + | discharging_overcurrent_delay: | ||
| + | name: " | ||
| + | charging_overcurrent_protection: | ||
| + | name: " | ||
| + | charging_overcurrent_delay: | ||
| + | name: " | ||
| + | balance_starting_voltage: | ||
| + | name: " | ||
| + | balance_opening_pressure_difference: | ||
| + | name: " | ||
| + | power_tube_temperature_protection: | ||
| + | name: " | ||
| + | power_tube_temperature_recovery: | ||
| + | name: " | ||
| + | temperature_sensor_temperature_protection: | ||
| + | name: " | ||
| + | temperature_sensor_temperature_recovery: | ||
| + | name: " | ||
| + | temperature_sensor_temperature_difference_protection: | ||
| + | name: " | ||
| + | charging_high_temperature_protection: | ||
| + | name: " | ||
| + | discharging_high_temperature_protection: | ||
| + | name: " | ||
| + | charging_low_temperature_protection: | ||
| + | name: " | ||
| + | charging_low_temperature_recovery: | ||
| + | name: " | ||
| + | discharging_low_temperature_protection: | ||
| + | name: " | ||
| + | discharging_low_temperature_recovery: | ||
| + | name: " | ||
| + | total_battery_capacity_setting: | ||
| + | id: total_battery_capacity_setting | ||
| + | name: " | ||
| + | current_calibration: | ||
| + | name: " | ||
| + | device_address: | ||
| + | name: " | ||
| + | sleep_wait_time: | ||
| + | name: " | ||
| + | alarm_low_volume: | ||
| + | name: " | ||
| + | manufacturing_date: | ||
| + | name: " | ||
| + | total_runtime: | ||
| + | name: " | ||
| + | # start_current_calibration: | ||
| + | # name: " | ||
| + | actual_battery_capacity: | ||
| + | name: " | ||
| + | # protocol_version: | ||
| + | # name: " | ||
| + | # +--------------------------------------+ | ||
| + | # | Uptime sensor | ||
| + | # +--------------------------------------+ | ||
| + | - platform: uptime | ||
| + | name: ${name} Uptime Sensor | ||
| + | id: uptime_sensor | ||
| + | update_interval: | ||
| + | on_raw_value: | ||
| + | then: | ||
| + | - text_sensor.template.publish: | ||
| + | id: uptime_human | ||
| + | state: !lambda |- | ||
| + | int seconds = round(id(uptime_sensor).raw_state); | ||
| + | int days = seconds / (24 * 3600); | ||
| + | seconds = seconds % (24 * 3600); | ||
| + | int hours = seconds / 3600; | ||
| + | seconds = seconds % 3600; | ||
| + | int minutes = seconds / 60; | ||
| + | seconds = seconds % 60; | ||
| + | return ( | ||
| + | (days ? to_string(days) + "d " : "" | ||
| + | (hours ? to_string(hours) + "h " : "" | ||
| + | (minutes ? to_string(minutes) + "m " : "" | ||
| + | (to_string(seconds) + " | ||
| + | ).c_str(); | ||
| + | |||
| + | text_sensor: | ||
| + | - platform: jk_bms | ||
| + | errors: | ||
| + | name: " | ||
| + | operation_mode: | ||
| + | name: " | ||
| + | battery_type: | ||
| + | name: " | ||
| + | # password: | ||
| + | # name: " | ||
| + | device_type: | ||
| + | name: " | ||
| + | software_version: | ||
| + | name: " | ||
| + | manufacturer: | ||
| + | name: " | ||
| + | total_runtime_formatted: | ||
| + | name: " | ||
| + | # +--------------------------------------+ | ||
| + | # | Template text sensors | ||
| + | # +--------------------------------------+ | ||
| + | - platform: template | ||
| + | name: ${name} Uptime Human Readable | ||
| + | id: uptime_human | ||
| + | icon: mdi: | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: charging_status | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: canbus_status | ||
| + | |||
| + | # +--------------------------------------+ | ||
| + | # | Slider | ||
| + | # +--------------------------------------+ | ||
| + | number: | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: " | ||
| + | step: 0.1 | ||
| + | min_value: 52.8 | ||
| + | max_value: 57.6 | ||
| + | mode: slider | ||
| + | initial_value: | ||
| + | unit_of_measurement: | ||
| + | icon: mdi: | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: " | ||
| + | step: 0.1 | ||
| + | min_value: 52.8 | ||
| + | max_value: 57.6 | ||
| + | mode: slider | ||
| + | initial_value: | ||
| + | unit_of_measurement: | ||
| + | icon: mdi: | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: " | ||
| + | step: 1 | ||
| + | min_value: 0 | ||
| + | max_value: " | ||
| + | mode: slider | ||
| + | initial_value: | ||
| + | unit_of_measurement: | ||
| + | icon: mdi: | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: " | ||
| + | step: 0.1 | ||
| + | min_value: 0 | ||
| + | max_value: 5 | ||
| + | mode: slider | ||
| + | initial_value: | ||
| + | unit_of_measurement: | ||
| + | icon: mdi: | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: " | ||
| + | step: 1 | ||
| + | min_value: 0 | ||
| + | max_value: 180 | ||
| + | mode: slider | ||
| + | initial_value: | ||
| + | unit_of_measurement: | ||
| + | icon: mdi: | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: " | ||
| + | id: " | ||
| + | step: 0.05 | ||
| + | min_value: 0 | ||
| + | max_value: 1 | ||
| + | mode: slider | ||
| + | initial_value: | ||
| + | unit_of_measurement: | ||
| + | icon: mdi: | ||
| + | optimistic: true | ||
| + | |||
| + | script: | ||
| + | - id: absorption_script | ||
| + | then: | ||
| + | - lambda: id(charge_status) = " | ||
| + | # delay value in ms | ||
| + | - delay: !lambda " | ||
| + | - lambda: id(charge_status) = " | ||
| + | |||
| + | switch: | ||
| + | - platform: template | ||
| + | name: ${name} Charging enabled | ||
| + | id: switch_charging | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: ${name} Discharge enabled | ||
| + | id: switch_discharging | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: ${name} Charging manually (top bal) | ||
| + | id: switch_chg_bulk | ||
| + | optimistic: true | ||
| + | - platform: template | ||
| + | name: ${name} Float charge | ||
| + | id: switch_chg_float | ||
| + | optimistic: true | ||
| + | |||
| + | # +--------------------------------------+ | ||
| + | # | CAN bus script | ||
| + | # +--------------------------------------+ | ||
| + | canbus: | ||
| + | - platform: esp32_can | ||
| + | tx_pin: ${can_tx_pin} | ||
| + | rx_pin: ${can_rx_pin} | ||
| + | can_id: 4 | ||
| + | bit_rate: 500kbps | ||
| + | on_frame: | ||
| + | - can_id: 0x305 # Inverter ACK - SMA/ | ||
| + | then: | ||
| + | - light.toggle: | ||
| + | id: led_buitin | ||
| + | - lambda: |- | ||
| + | id(can_ack_counter) = 0; // Reset ACK counter | ||
| + | id(can_status) = " | ||
| + | id(canbus_status).publish_state(id(can_status)); | ||
| + | ESP_LOGI(" | ||
| + | |||
| + | interval: | ||
| + | - interval: 120s | ||
| + | then: | ||
| + | - lambda: id(can_ack_counter) = 0; // Reset ACK counter for test inverter ACK | ||
| + | | ||
| + | - interval: 100ms | ||
| + | then: | ||
| + | # Start CAN Handling | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: |- | ||
| + | | ||
| + | if (id(can_ack_counter) < 20) { // Inverter ACK ? => CANBUS ON | ||
| + | | ||
| + | id(can_ack_counter)++; | ||
| + | id(can_msg_counter)++; | ||
| + | return true; // Condition OK | ||
| + | | ||
| + | } | ||
| + | else if (id(can_status) == " | ||
| + | | ||
| + | return false; | ||
| + | | ||
| + | } | ||
| + | else { | ||
| + | | ||
| + | id(can_status) = " | ||
| + | id(canbus_status).publish_state(id(can_status)); | ||
| + | ESP_LOGI(" | ||
| + | return false; | ||
| + | | ||
| + | } | ||
| + | |||
| + | then: | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 1) | (${can_protocol} == 2))); | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x359 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // +---------------------------+ | ||
| + | // | JK-BMS errors bitmask | ||
| + | // +---------------------------+ | ||
| + | | ||
| + | // 0x8B 0x00 0x00: Battery warning message | ||
| + | // | ||
| + | // Bit 0 Low capacity | ||
| + | // Bit 1 Power tube overtemperature | ||
| + | // Bit 2 Charging overvoltage | ||
| + | // Bit 3 Discharging undervoltage | ||
| + | // Bit 4 Battery over temperature | ||
| + | // Bit 5 Charging overcurrent | ||
| + | // Bit 6 Discharging overcurrent | ||
| + | // Bit 7 Cell pressure difference | ||
| + | // Bit 8 Overtemperature alarm in the battery box 1 (alarm), 0 (normal) | ||
| + | // Bit 9 Battery low temperature | ||
| + | // Bit 10 Cell overvoltage | ||
| + | // Bit 11 Cell undervoltage | ||
| + | // Bit 12 309_A protection | ||
| + | // Bit 13 309_A protection | ||
| + | // Bit 14 | ||
| + | // Bit 15 | ||
| + | // | ||
| + | // Examples: | ||
| + | // 0x0001 = 00000000 00000001: Low capacity alarm | ||
| + | // 0x0002 = 00000000 00000010: MOS tube over-temperature alarm | ||
| + | // 0x0003 = 00000000 00000011: Low capacity alarm AND power tube over-temperature alarm | ||
| + | | ||
| + | // +---------------------------+ | ||
| + | // | Protection : byte 0 and 1 | | ||
| + | // +---------------------------+ | ||
| + | | ||
| + | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||
| + | |||
| + | // JK-BMS alarm ? | ||
| + | if (id(errors_bitmask).state > 1) { | ||
| + | | ||
| + | |||
| + | if ((jk_errormask & 0x04) | (jk_errormask & 0x80) | (jk_errormask & 0x400)) { // Hight.Voltage.Alarm JK bit 2,7,10 | ||
| + | can_mesg[0] = 0x02; // byte0_bit1 (0x02 = bin 10) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x08) | (jk_errormask & 0x800)) { // Low.Voltage.Alarm JK bit 3,11 | ||
| + | can_mesg[0] = can_mesg[0] | 0x04; // byte0_bit2 (0x04 = bin 100) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x02) | (jk_errormask & 0x10) | (jk_errormask & 0x100)) { // Hight.Temp.Alarm JK bit 1,4,8 | ||
| + | can_mesg[0] = can_mesg[0] | 0x08; // byte0_bit3 (0x08 = bin 1000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x200)) { // Low.Temp.Alarm JK bit 9 | ||
| + | can_mesg[0] = can_mesg[0] | 0x10; // byte0_bit4 (0x10 = bin 10000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x40)) { // Discharge.Over.Current JK bit 6 | ||
| + | can_mesg[0] = can_mesg[0] | 0x80; // byte0_bit7 (0x80 = bin 10000000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 | ||
| + | can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x1000) | (jk_errormask & 0x2000)) { // BMS internal error JK bit 12,13 | ||
| + | can_mesg[1] = can_mesg[1] | 0x08; // byte1_bit3 (0x08 = bin 1000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x80)) { // Cell Imbalance JK bit 7 | ||
| + | can_mesg[1] = can_mesg[1] | 0x10; // byte1_bit4 (0x10 = bin 10000) | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | } | ||
| + | // No Alarm | ||
| + | else id(alarm_status) = " | ||
| + | |||
| + | // +---------------------------+ | ||
| + | // | Warning : byte 2 and 3 | | ||
| + | // +---------------------------+ | ||
| + | | ||
| + | can_mesg[2] = 0x00; // byte2 (JK-BMS infos not available) | ||
| + | can_mesg[3] = 0x00; // byte3 (JK-BMS infos not available) | ||
| + | | ||
| + | // +---------------------------+ | ||
| + | // | Flags : byte 4 to 7 | | ||
| + | // +---------------------------+ | ||
| + | | ||
| + | int batt_mods = ${batt_modules}; | ||
| + | | ||
| + | can_mesg[4] = batt_mods; | ||
| + | can_mesg[5] = 0x00; // byte5 | ||
| + | can_mesg[6] = 0x00; // byte6 | ||
| + | can_mesg[7] = 0x00; // byte7 - DIP switches 1,3 10000100 0x84 | ||
| + | | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 3) | (${can_protocol} == 4))); | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x35A | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // +---------------------------+ | ||
| + | // | Protection : byte 0,1,2,3 | | ||
| + | // +---------------------------+ | ||
| + | | ||
| + | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||
| + | |||
| + | // JK-BMS alarm ? | ||
| + | if (id(errors_bitmask).state > 1) { | ||
| + | | ||
| + | |||
| + | if ((jk_errormask & 0x04) | (jk_errormask & 0x80) | (jk_errormask & 0x400)) { // Hight.Voltage.Alarm JK bit 2,7,10 | ||
| + | can_mesg[0] = 0x04; // byte0_bit2 (0x04 = bin 100) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x08) | (jk_errormask & 0x800)) { // Low.Voltage.Alarm JK bit 3,11 | ||
| + | can_mesg[0] = can_mesg[0] | 0x10; // byte0_bit4 (0x10 = bin 10000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x02) | (jk_errormask & 0x10) | (jk_errormask & 0x100)) { // Hight.Temp.Alarm JK bit 1,4,8 | ||
| + | can_mesg[0] = can_mesg[0] | 0x40; // byte0_bit6 (0x40 = bin 1000000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x200)) { // Low.Temp.Alarm JK bit 9 | ||
| + | can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x40)) { // Discharge.Over.Current JK bit 6 | ||
| + | can_mesg[1] = can_mesg[1] | 0x40; // byte1_bit6 (0x40 = bin 1000000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 | ||
| + | can_mesg[2] = 0x01; // byte2_bit0 (0x01 = bin 1) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x1000) | (jk_errormask & 0x2000)) { // BMS.Internal.Error JK bit 12,13 | ||
| + | can_mesg[2] = can_mesg[2] | 0x40; // byte2_bit6 (0x40 = bin 1000000) | ||
| + | id(alarm_status) = " | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | if ((jk_errormask & 0x80)) { // Cell.Imbalance JK bit 7 | ||
| + | can_mesg[3] = 0x01; // byte3_bit0 (0x01 = bin 1) | ||
| + | ESP_LOGI(" | ||
| + | } | ||
| + | } | ||
| + | // No Alarm | ||
| + | else id(alarm_status) = " | ||
| + | | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return id(can_msg_counter) == 2; | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x351 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // +--------------------------------------+ | ||
| + | // | Charging Logic | | ||
| + | // +--------------------------------------+ | ||
| + | |||
| + | // Warning : information from JK BMS is not available immediately after boot | ||
| + | | ||
| + | // Alarm : if JK-BMS alarm ! | ||
| + | if (id(errors_bitmask).state > 1) { | ||
| + | | ||
| + | } | ||
| + | // No Alarm => Wait | ||
| + | else if ((id(errors_bitmask).state < 2) & (id(charge_status) == " | ||
| + | | ||
| + | } | ||
| + | // Disabled : charging disabled if BMS or ESP32 switch is OFF | ||
| + | else if ((!id(charging_switch).state) | (!id(switch_charging).state)) { | ||
| + | | ||
| + | } | ||
| + | // No Disabled => Wait | ||
| + | else if ((id(charging_switch).state) & (id(switch_charging).state) & (id(charge_status) == " | ||
| + | | ||
| + | } | ||
| + | // Bulk Manually : after switch ON ' | ||
| + | else if (id(switch_chg_bulk).state) { | ||
| + | | ||
| + | } | ||
| + | // No Bulk Manually => Wait | ||
| + | else if ((!id(switch_chg_bulk).state) & (id(charge_status) == "Bulk Manually" | ||
| + | | ||
| + | } | ||
| + | // Bulk : if Batt. V. <= ( Absorption V. - Rebulk Offset V. ) ( Bulk : <= 55.2-2.5 = 52.7V by default ) | ||
| + | else if (id(total_voltage).state <= (id(bulk_voltage).state - id(rebulk_offset).state)) { | ||
| + | | ||
| + | if (id(absorption_script).is_running()) id(absorption_script).stop(); | ||
| + | } | ||
| + | // Absorption : if Batt. V >= ( Absorption V. - Absorption Offset V. ) ( Absorption : >= 55.2-0.05 = 55.15V by default ) | ||
| + | else if ((id(charge_status) == " | ||
| + | | ||
| + | if (!id(absorption_script).is_running()) id(absorption_script).execute(); | ||
| + | } | ||
| + | // Float : if Batt. V. > ( Absorption V. - Rebulk Offset V. ) and Float switch is ON ( Float : after Absorption or > 55.2-2.5 = 52.7V by default ) | ||
| + | else if ((id(switch_chg_float).state) & (id(charge_status) == " | ||
| + | | ||
| + | } | ||
| + | // No Float => Wait | ||
| + | else if ((!id(switch_chg_float).state) & (id(charge_status) == " | ||
| + | | ||
| + | } | ||
| + | | ||
| + | // +--------------------------------------+ | ||
| + | // | Charge values | ||
| + | // +--------------------------------------+ | ||
| + | |||
| + | // Bulk Charge | ||
| + | if ((id(charge_status) == " | ||
| + | | ||
| + | | ||
| + | } | ||
| + | // Float Charge | ||
| + | else if (id(charge_status) == " | ||
| + | | ||
| + | | ||
| + | } | ||
| + | // Disabled or Wait : Stop Charging | ||
| + | else if ((id(charge_status) == " | ||
| + | | ||
| + | | ||
| + | } | ||
| + | | ||
| + | // +--------------------------------------+ | ||
| + | // | Discharge values | ||
| + | // +--------------------------------------+ | ||
| + | |||
| + | // Stop Discharging if BMS or ESP32 switch is OFF | ||
| + | if ((!id(discharging_switch).state) | (!id(switch_discharging).state)) id(discharging_a) = 0; | ||
| + | // Stop Discharging if battery voltage is low | ||
| + | else if (id(total_voltage).state <= ${min_discharge_v}) id(discharging_a) = 0; | ||
| + | // Discharging is OK | ||
| + | else id(discharging_a) = ${discharge_a}; | ||
| + | |||
| + | // +--------------------------------------+ | ||
| + | // | Alarm overwrite values | ||
| + | // +--------------------------------------+ | ||
| + | |||
| + | ESP_LOGI(" | ||
| + | | ||
| + | // Alarm : Stop Charging and Discharging | ||
| + | if ((id(alarm_status) == " | ||
| + | | ||
| + | | ||
| + | | ||
| + | } | ||
| + | // Alarm : Stop Charging | ||
| + | else if ((id(alarm_status) == " | ||
| + | | ||
| + | | ||
| + | } | ||
| + | // Alarm : Stop Discharging | ||
| + | else if ((id(alarm_status) == " | ||
| + | | ||
| + | } | ||
| + | | ||
| + | // +--------------------------------------+ | ||
| + | // | CAN messages | ||
| + | // +--------------------------------------+ | ||
| + | | ||
| + | // Byte [00:01] = CVL : Charge Limit Voltage | ||
| + | // Byte [02:03] = CCL : Charge Limit Current | ||
| + | // Byte [04:05] = DCL : Discharge Limit Current | ||
| + | // Byte [06:07] = DVL : Discharge Limit Voltage | ||
| + | | ||
| + | uint8_t can_mesg[8]; | ||
| + | | ||
| + | can_mesg[0] = uint16_t(id(charging_v) * 10) & 0xff; | ||
| + | can_mesg[1] = uint16_t(id(charging_v) * 10) >> 8 & 0xff; | ||
| + | can_mesg[2] = uint16_t(id(charging_a) * 10) & 0xff; | ||
| + | can_mesg[3] = uint16_t(id(charging_a) * 10) >> 8 & 0xff; | ||
| + | can_mesg[4] = uint16_t(id(discharging_a) * 10) & 0xff; | ||
| + | can_mesg[5] = uint16_t(id(discharging_a) * 10) >> 8 & 0xff; | ||
| + | can_mesg[6] = uint16_t(${min_discharge_v} * 10) & 0xff; | ||
| + | can_mesg[7] = uint16_t(${min_discharge_v} * 10) >> 8 & 0xff; | ||
| + | | ||
| + | // Publish text sensor | ||
| + | id(charging_status).publish_state(id(charge_status)); | ||
| + | | ||
| + | // Logs | ||
| + | ESP_LOGI(" | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return id(can_msg_counter) == 3; | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x355 | ||
| + | data: !lambda |- | ||
| + | int soh = round(((id(charging_cycles).state/ | ||
| + | uint8_t can_mesg[4]; | ||
| + | can_mesg[0] = uint16_t(id(capacity_remaining).state) & 0xff; | ||
| + | can_mesg[1] = uint16_t(id(capacity_remaining).state) >> 8 & 0xff; | ||
| + | can_mesg[2] = soh & 0xff; | ||
| + | can_mesg[3] = soh >> 8 & 0xff; | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return id(can_msg_counter) == 4; | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x356 | ||
| + | data: !lambda |- | ||
| + | uint8_t can_mesg[6]; | ||
| + | can_mesg[0] = uint16_t(id(total_voltage).state * 100) & 0xff; | ||
| + | can_mesg[1] = uint16_t(id(total_voltage).state * 100) >> 8 & 0xff; | ||
| + | can_mesg[2] = int16_t(id(current).state * 10) & 0xff; | ||
| + | can_mesg[3] = int16_t(id(current).state * 10) >> 8 & 0xff; | ||
| + | can_mesg[4] = int16_t(max(id(temperature_sensor_1).state, | ||
| + | can_mesg[5] = int16_t(max(id(temperature_sensor_1).state, | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 5) & ((${can_protocol} == 1) | (${can_protocol} == 2))); | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x35C | ||
| + | data: !lambda |- | ||
| + | uint8_t can_mesg[2]; | ||
| + | can_mesg[0] = 0x00; | ||
| + | can_mesg[1] = 0x00; | ||
| + | | ||
| + | // Bit 7 : Charge enable | ||
| + | if ((id(charging_switch).state) & (id(switch_charging).state)) | ||
| + | | ||
| + | | ||
| + | // Bit 6 : Discharge enable | ||
| + | if ((id(discharging_switch).state) & (id(switch_discharging).state)) | ||
| + | | ||
| + | | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x70 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // Byte [00:01] : Max cell temperature | ||
| + | // Byte [02:03] : Min cell temperature | ||
| + | // Byte [04:05] : Max cell voltage | ||
| + | // Byte [06:07] : Min cell voltage | ||
| + | | ||
| + | int max_cell_voltage_i = id(max_cell_voltage).state * 100.0; | ||
| + | int min_cell_voltage_i = id(min_cell_voltage).state * 100.0; | ||
| + | uint8_t can_mesg[8]; | ||
| + | can_mesg[0] = int16_t(max(id(temperature_sensor_1).state, | ||
| + | can_mesg[1] = int16_t(max(id(temperature_sensor_1).state, | ||
| + | can_mesg[2] = int16_t(min(id(temperature_sensor_1).state, | ||
| + | can_mesg[3] = int16_t(min(id(temperature_sensor_1).state, | ||
| + | can_mesg[4] = max_cell_voltage_i & 0xff; | ||
| + | can_mesg[5] = max_cell_voltage_i >> 8 & 0xff; | ||
| + | can_mesg[6] = min_cell_voltage_i & 0xff; | ||
| + | can_mesg[7] = min_cell_voltage_i >> 8 & 0xff; | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x371 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // Byte [00:01] : Max cell temperature ID | ||
| + | // Byte [02:03] : Min cell temperature ID | ||
| + | // Byte [04:05] : Max cell voltage ID | ||
| + | // Byte [06:07] : Min cell voltage ID | ||
| + | | ||
| + | uint8_t can_mesg[8]; | ||
| + | |||
| + | // Min-Max Temp. Sensor ID ? | ||
| + | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ | ||
| + | can_mesg[0] = 0x01; | ||
| + | can_mesg[2] = 0x02; | ||
| + | } | ||
| + | else { | ||
| + | can_mesg[0] = 0x02; | ||
| + | can_mesg[2] = 0x01; | ||
| + | } | ||
| + | | ||
| + | can_mesg[1] = 0x00; | ||
| + | can_mesg[3] = 0x00; | ||
| + | can_mesg[4] = uint16_t(id(max_voltage_cell).state) & 0xff; | ||
| + | can_mesg[5] = uint16_t(id(max_voltage_cell).state) >> 8 & 0xff; | ||
| + | can_mesg[6] = uint16_t(id(min_voltage_cell).state) & 0xff; | ||
| + | can_mesg[7] = uint16_t(id(min_voltage_cell).state) >> 8 & 0xff; | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 7) & (${can_protocol} == 4)); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x372 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // Byte [00:01] : Nbr. of battery modules online | ||
| + | // Byte [02:03] : Nbr. of modules blocking charge | ||
| + | // Byte [04:05] : Nbr. of modules blocking discharge | ||
| + | // Byte [06:07] : Nbr. of battery modules offline | ||
| + | | ||
| + | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||
| + | can_mesg[0] = 0x01; | ||
| + | |||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | |||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); | ||
| + | then: | ||
| + | canbus.send: | ||
| + | can_id: 0x373 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // Byte [00:01] : Min cell voltage | ||
| + | // Byte [02:03] : Max cell voltage | ||
| + | // Byte [04:05] : Min cell temperature | ||
| + | // Byte [06:07] : Max cell temperature | ||
| + | | ||
| + | int min_cell_voltage_i = id(min_cell_voltage).state * 1000.0; | ||
| + | int max_cell_voltage_i = id(max_cell_voltage).state * 1000.0; | ||
| + | int min_temp_kelvin = min(id(temperature_sensor_1).state, | ||
| + | int max_temp_kelvin = max(id(temperature_sensor_1).state, | ||
| + | | ||
| + | uint8_t can_mesg[8]; | ||
| + | can_mesg[0] = min_cell_voltage_i & 0xff; | ||
| + | can_mesg[1] = min_cell_voltage_i >> 8 & 0xff; | ||
| + | can_mesg[2] = max_cell_voltage_i & 0xff; | ||
| + | can_mesg[3] = max_cell_voltage_i >> 8 & 0xff; | ||
| + | can_mesg[4] = min_temp_kelvin & 0xff; | ||
| + | can_mesg[5] = min_temp_kelvin >> 8 & 0xff; | ||
| + | can_mesg[6] = max_temp_kelvin & 0xff; | ||
| + | can_mesg[7] = max_temp_kelvin >> 8 & 0xff; | ||
| + | | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x374 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | int cell_id = id(min_voltage_cell).state; | ||
| + | |||
| + | ESP_LOGI(" | ||
| + | |||
| + | if (cell_id == 1) return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 2) return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 3) return {0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 4) return {0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 5) return {0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 6) return {0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 7) return {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 8) return {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 9) return {0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 10) return {0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 11) return {0x31, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 12) return {0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 13) return {0x31, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 14) return {0x31, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 15) return {0x31, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 16) return {0x31, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else return {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | |||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x375 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | int cell_id = id(max_voltage_cell).state; | ||
| + | |||
| + | ESP_LOGI(" | ||
| + | |||
| + | if (cell_id == 1) return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 2) return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 3) return {0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 4) return {0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 5) return {0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 6) return {0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 7) return {0x37, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 8) return {0x38, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 9) return {0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 10) return {0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 11) return {0x31, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 12) return {0x31, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 13) return {0x31, 0x33, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 14) return {0x31, 0x34, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 15) return {0x31, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else if (cell_id == 16) return {0x31, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | else return {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | |||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x376 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // Min Temp. Sensor ID ? | ||
| + | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ | ||
| + | ESP_LOGI(" | ||
| + | return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | } | ||
| + | else { | ||
| + | ESP_LOGI(" | ||
| + | return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | } | ||
| + | |||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x377 | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // Max Temp. Sensor ID ? | ||
| + | if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ | ||
| + | ESP_LOGI(" | ||
| + | return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | } | ||
| + | else { | ||
| + | ESP_LOGI(" | ||
| + | return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; | ||
| + | } | ||
| + | |||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 9) & ((${can_protocol} == 2) | (${can_protocol} == 4))); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x379 | ||
| + | data: !lambda |- | ||
| + | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||
| + | can_mesg[0] = uint16_t(id(total_battery_capacity_setting).state) & 0xff; | ||
| + | can_mesg[1] = uint16_t(id(total_battery_capacity_setting).state) >> 8 & 0xff; | ||
| + | | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 10) & (${can_protocol} == 4)); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x382 | ||
| + | data: !lambda |- | ||
| + | ESP_LOGI(" | ||
| + | return {0x4A, 0x4B, 0x2D, 0x42, 0x4D, 0x53, 0x00, 0x00}; // JK-BMS | ||
| + | |||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return ((id(can_msg_counter) == 11) & ((${can_protocol} == 3) | (${can_protocol} == 4))); | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x35F | ||
| + | data: !lambda |- | ||
| + | | ||
| + | // SMA | ||
| + | // Byte [00:01] : Bat-Type | ||
| + | // Byte [02:03] : BMS Version | ||
| + | // Byte [04:05] : Bat-Capacity | ||
| + | // Byte [06:07] : Manufacturer ID | ||
| + | | ||
| + | uint8_t can_mesg[] = {0, 0, 0, 0, 0, 0, 0, 0}; | ||
| + | can_mesg[2] = 0x01; | ||
| + | can_mesg[3] = 0x10; | ||
| + | can_mesg[4] = uint16_t(id(total_battery_capacity_setting).state) & 0xff; | ||
| + | can_mesg[5] = uint16_t(id(total_battery_capacity_setting).state) >> 8 & 0xff; | ||
| + | | ||
| + | ESP_LOGI(" | ||
| + | return {can_mesg[0], | ||
| + | | ||
| + | - if: | ||
| + | condition: | ||
| + | lambda: return id(can_msg_counter) == 12; | ||
| + | then: | ||
| + | - canbus.send: | ||
| + | can_id: 0x35E # Manufacturer name | ||
| + | data: !lambda |- | ||
| + | if (${can_bms_name} == 1){ | ||
| + | | ||
| + | | ||
| + | } | ||
| + | else if (${can_bms_name} == 2){ | ||
| + | | ||
| + | | ||
| + | } | ||
| + | else if (${can_bms_name} == 3){ | ||
| + | | ||
| + | | ||
| + | } | ||
| + | # Reset counter | ||
| + | - lambda: id(can_msg_counter) = 0; | ||
| + | |||
| + | </ | ||
| + | ++++ | ||
| ===== Conclusion ===== | ===== Conclusion ===== | ||
| - | This converter is now running since Aug-2023 without any issues. The live data in home assistant: | + | This converter is now running since Aug-2023 without any issues. The live data in [[https:// |
| - | {{ : | + | {{ : |
| =====Downloads===== | =====Downloads===== | ||
| - | * {{: | + | * {{: |
| - | * {{: | + | * {{: |
| - | * {{: | + | * {{: |
| - | * {{: | + | * {{: |
| - | * {{: | + | * {{: |
| - | * {{: | + | * {{: |
| - | * | + | * {{: |
| =====Links===== | =====Links===== | ||
| * [[https:// | * [[https:// | ||
| * [[https:// | * [[https:// | ||
| - | * [[https:// | + | * [[https:// |
| ===== Donate ===== | ===== Donate ===== | ||