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 "Charge/Discharge values" section and added instructions for "Stop Discharging" + Set "esp-idf" framework by default # V1.16.1 Sleeper85 : Slider charging_current max value = ${charge_a}, Improved Alarm/Charging/Discharging Logic, Improved CAN protocol and Victron support # V1.15.5 Sleeper85 : Improved code and set api "reboot_timout" to "0s" by default (no reboot without HA) # V1.15.4 Sleeper85 : Improved documentation for API, Web Server and WiFi settings # V1.15.3 Sleeper85 : Add 'CAN Protocol Settings' and new CAN ID based on the SMA and Victron protocol (alpha) # V1.15.2 Sleeper85 : Improved Alarm handling, all alarms will set charge/discharge current to 0A and set 'Charging Status' to Alarm # 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 'Charging Voltage' behavior # V1.14.1 Sleeper85 : Add 'Float charge function' # V1.13.6 Sleeper85 : Add 'Absorption time' and 'Absorption Offset V.' slider # V1.13.5 Sleeper85 : Set CAN manufacter to "PYLON" for improve compatibility with Deye and other inverters # V1.13.4 Sleeper85 : Improve 'Charge Status' behavior + add 'Rebulk Offset V.' slider # V1.13.3 uksa007 : Improve compatibility with Deye and other inverters # V1.13.2 uksa007 : Send Max Temperature of T1, T2 to inverter # V1.13.1 uksa007 : Fix compile issues with new version of ESPhome 2023.4.0, set rebulk offset to 2.5 substitutions: # +--------------------------------------+ # name that will appear in esphome and homeassistant. name: jk-bms-can device_description: "JK-BMS via UART-TTL and CAN" # +--------------------------------------+ # 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: "7" # +--------------------------------------+ # | 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), should be at least 10A less than BMS change current protection, 0.5C max # 100A * 50V = 5000W charge_a: "62" # 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: "53.6" # 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: "55.2" # Absorption time in minutes to hold charge voltage after charge voltage is reached eg 30 absorption_time: "30" # Absorption offset, x Volts below absorption voltage battery will start the absorption timer, eg 55.2-0.05 = 52.15v absorption_offset_v: "0.05" # Rebulk offset, x Volts below absorption voltage battery will request rebulk, eg 55.2-2.5 = 52.7v rebulk_offset_v: "2.5" # +--------------------------------------+ # | 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: "100" # Minimum discharge voltage eg 48v/16 = 3V per cell min_discharge_v: "48" # +--------------------------------------+ # | 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: "6000.0" # +--------------------------------------+ # | CAN Protocol Settings | # +--------------------------------------+ # CAN BMS Name (0x35E) : 0 NoSent / 1 PYLON / 2 GOODWE / 3 SEPLOS can_bms_name: "1" # 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: "1" # +--------------------------------------+ # | 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: github://syssi/esphome-jk-bms@main # components # github://syssi/esphome-jk-bms@main esphome: name: ${name} on_boot: then: - switch.turn_on: switch_charging - switch.turn_on: switch_discharging - switch.turn_on: switch_chg_float # +--------------------------------------+ # | ESP32 settings | # +--------------------------------------+ # For a stable Bluetooth connection keep the "esp-idf" framework esp32: board: esp32doit-devkit-v1 framework: type: esp-idf external_components: - source: ${external_components_source} refresh: 0s logger: # level: DEBUG ota: password: "06113be1d70fb74e1cc42005ceee16d5" # 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 "reboot_timeout: 0s" option. api: reboot_timeout: 0s # If you don't want to use ESPHome's native API you can use MQQT instead. # In this case don't forget to remove the 'api:' section. # 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 #web_server: # port: 80 # log: false # ota: false # +--------------------------------------+ # | ** Don't make changes below this ** | # +--------------------------------------+ globals: - id: can_ack_counter type: int restore_value: no initial_value: '0' - id: charge_status type: std::string restore_value: no initial_value: '"Alarm"' - id: can_status type: std::string restore_value: no initial_value: '"OFF"' - id: alarm_status type: std::string restore_value: no initial_value: '"NoAlarm"' - id: charging_v type: float restore_value: no initial_value: '0.0' - id: charging_a type: int restore_value: no initial_value: '0' - id: discharging_a type: int restore_value: no initial_value: '0' - id: can_msg_counter type: int restore_value: no initial_value: '0' output: - platform: gpio pin: 2 id: led inverted: true light: - platform: binary output: led id: led_buitin name: "Builtin LED" internal: true # +--------------------------------------+ # | JK-BMS UART connection | # +--------------------------------------+ uart: id: uart_0 baud_rate: 115200 rx_buffer_size: 384 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: modbus0 # enable_fake_traffic: true # +--------------------------------------+ binary_sensor: - platform: jk_bms balancing: name: "${name} balancing" balancing_switch: name: "${name} balancing switch" charging: name: "${name} charging" charging_switch: id: charging_switch name: "${name} charging switch" discharging: name: "${name} discharging" discharging_switch: id: discharging_switch name: "${name} discharging switch" dedicated_charger_switch: id: dedicated_charger_switch name: "${name} dedicated charger switch" sensor: - platform: jk_bms min_cell_voltage: id: min_cell_voltage name: "${name} min cell voltage" max_cell_voltage: id: max_cell_voltage name: "${name} max cell voltage" min_voltage_cell: id: min_voltage_cell name: "${name} min voltage cell" max_voltage_cell: id: max_voltage_cell name: "${name} max voltage cell" delta_cell_voltage: name: "${name} delta cell voltage" average_cell_voltage: name: "${name} average cell voltage" cell_voltage_1: name: "${name} cell voltage 1" cell_voltage_2: name: "${name} cell voltage 2" cell_voltage_3: name: "${name} cell voltage 3" cell_voltage_4: name: "${name} cell voltage 4" cell_voltage_5: name: "${name} cell voltage 5" cell_voltage_6: name: "${name} cell voltage 6" cell_voltage_7: name: "${name} cell voltage 7" cell_voltage_8: name: "${name} cell voltage 8" cell_voltage_9: name: "${name} cell voltage 9" cell_voltage_10: name: "${name} cell voltage 10" cell_voltage_11: name: "${name} cell voltage 11" cell_voltage_12: name: "${name} cell voltage 12" cell_voltage_13: name: "${name} cell voltage 13" cell_voltage_14: name: "${name} cell voltage 14" cell_voltage_15: name: "${name} cell voltage 15" cell_voltage_16: name: "${name} cell voltage 16" # cell_voltage_17: # name: "${name} cell voltage 17" # cell_voltage_18: # name: "${name} cell voltage 18" # cell_voltage_19: # name: "${name} cell voltage 19" # cell_voltage_20: # name: "${name} cell voltage 20" # cell_voltage_21: # name: "${name} cell voltage 21" # cell_voltage_22: # name: "${name} cell voltage 22" # cell_voltage_23: # name: "${name} cell voltage 23" # cell_voltage_24: # name: "${name} cell voltage 24" power_tube_temperature: id: power_tube_temperature name: "${name} power tube temperature" temperature_sensor_1: id: temperature_sensor_1 name: "${name} temperature sensor 1" temperature_sensor_2: id: temperature_sensor_2 name: "${name} temperature sensor 2" total_voltage: id: total_voltage name: "${name} total voltage" current: id: current name: "${name} current" power: name: "${name} power" charging_power: name: "${name} charging power" discharging_power: name: "${name} discharging power" capacity_remaining: id: capacity_remaining name: "${name} capacity remaining" capacity_remaining_derived: name: "${name} capacity remaining derived" temperature_sensors: name: "${name} temperature sensors" charging_cycles: id: charging_cycles name: "${name} charging cycles" total_charging_cycle_capacity: name: "${name} total charging cycle capacity" battery_strings: name: "${name} battery strings" errors_bitmask: id: errors_bitmask name: "${name} errors bitmask" operation_mode_bitmask: name: "${name} operation mode bitmask" total_voltage_overvoltage_protection: name: "${name} total voltage overvoltage protection" total_voltage_undervoltage_protection: name: "${name} total voltage undervoltage protection" cell_voltage_overvoltage_protection: name: "${name} cell voltage overvoltage protection" cell_voltage_overvoltage_recovery: name: "${name} cell voltage overvoltage recovery" cell_voltage_overvoltage_delay: name: "${name} cell voltage overvoltage delay" cell_voltage_undervoltage_protection: name: "${name} cell voltage undervoltage protection" cell_voltage_undervoltage_recovery: name: "${name} cell voltage undervoltage recovery" cell_voltage_undervoltage_delay: name: "${name} cell voltage undervoltage delay" cell_pressure_difference_protection: name: "${name} cell pressure difference protection" discharging_overcurrent_protection: name: "${name} discharging overcurrent protection" discharging_overcurrent_delay: name: "${name} discharging overcurrent delay" charging_overcurrent_protection: name: "${name} charging overcurrent protection" charging_overcurrent_delay: name: "${name} charging overcurrent delay" balance_starting_voltage: name: "${name} balance starting voltage" balance_opening_pressure_difference: name: "${name} balance opening pressure difference" power_tube_temperature_protection: name: "${name} power tube temperature protection" power_tube_temperature_recovery: name: "${name} power tube temperature recovery" temperature_sensor_temperature_protection: name: "${name} temperature sensor temperature protection" temperature_sensor_temperature_recovery: name: "${name} temperature sensor temperature recovery" temperature_sensor_temperature_difference_protection: name: "${name} temperature sensor temperature difference protection" charging_high_temperature_protection: name: "${name} charging high temperature protection" discharging_high_temperature_protection: name: "${name} discharging high temperature protection" charging_low_temperature_protection: name: "${name} charging low temperature protection" charging_low_temperature_recovery: name: "${name} charging low temperature recovery" discharging_low_temperature_protection: name: "${name} discharging low temperature protection" discharging_low_temperature_recovery: name: "${name} discharging low temperature recovery" total_battery_capacity_setting: id: total_battery_capacity_setting name: "${name} total battery capacity setting" current_calibration: name: "${name} current calibration" device_address: name: "${name} device address" sleep_wait_time: name: "${name} sleep wait time" alarm_low_volume: name: "${name} alarm low volume" manufacturing_date: name: "${name} manufacturing date" total_runtime: name: "${name} total runtime" # start_current_calibration: # name: "${name} start current calibration" actual_battery_capacity: name: "${name} actual battery capacity" # protocol_version: # name: "${name} protocol version" # +--------------------------------------+ # | Uptime sensor | # +--------------------------------------+ - platform: uptime name: ${name} Uptime Sensor id: uptime_sensor update_interval: 60s 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) + "s") ).c_str(); text_sensor: - platform: jk_bms errors: name: "${name} errors" operation_mode: name: "${name} operation mode" battery_type: name: "${name} battery type" # password: # name: "${name} password" device_type: name: "${name} device type" software_version: name: "${name} software version" manufacturer: name: "${name} manufacturer" total_runtime_formatted: name: "${name} total runtime formatted" # +--------------------------------------+ # | Template text sensors | # +--------------------------------------+ - platform: template name: ${name} Uptime Human Readable id: uptime_human icon: mdi:clock-start - platform: template name: "${name} Charging Status" id: charging_status - platform: template name: "${name} CANBUS Status" id: canbus_status # +--------------------------------------+ # | Slider | # +--------------------------------------+ number: - platform: template name: "${name} Bulk voltage" id: "bulk_voltage" step: 0.1 min_value: 52.8 max_value: 57.6 mode: slider initial_value: "${absorption_v}" unit_of_measurement: V icon: mdi:battery-charging optimistic: true - platform: template name: "${name} Float voltage" id: "float_voltage" step: 0.1 min_value: 52.8 max_value: 57.6 mode: slider initial_value: "${float_v}" unit_of_measurement: V icon: mdi:battery-charging optimistic: true - platform: template name: "${name} Charging current max" id: "charging_current" step: 1 min_value: 0 max_value: "${charge_a}" mode: slider initial_value: "${charge_a}" unit_of_measurement: A icon: mdi:current-dc optimistic: true - platform: template name: "${name} Rebulk Offset V." id: "rebulk_offset" step: 0.1 min_value: 0 max_value: 5 mode: slider initial_value: "${rebulk_offset_v}" unit_of_measurement: V icon: mdi:sine-wave optimistic: true - platform: template name: "${name} Absorption time" id: "absorption_time" step: 1 min_value: 0 max_value: 180 mode: slider initial_value: "${absorption_time}" unit_of_measurement: min icon: mdi:clock-start optimistic: true - platform: template name: "${name} Absorption Offset V." id: "absorption_offset" step: 0.05 min_value: 0 max_value: 1 mode: slider initial_value: "${absorption_offset_v}" unit_of_measurement: V icon: mdi:sine-wave optimistic: true script: - id: absorption_script then: - lambda: id(charge_status) = "Absorption"; # delay value in ms - delay: !lambda "return id(absorption_time).state * 60 * 1000;" - lambda: id(charge_status) = "Wait"; 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/LG/Pylon/Goodwe reply then: - light.toggle: id: led_buitin - lambda: |- id(can_ack_counter) = 0; // Reset ACK counter id(can_status) = "ON"; // Set CANBUS Status to ON id(canbus_status).publish_state(id(can_status)); // Publish text sensor ESP_LOGI("main", "received can id: 0x305 ACK"); 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)++; // CANBUS ACK counter ++ id(can_msg_counter)++; // CANBUS MSG counter ++ return true; // Condition OK } else if (id(can_status) == "OFF") { // CANBUS already OFF ? return false; // Nothing to do } else { id(can_status) = "OFF"; // Set CANBUS Status to OFF id(canbus_status).publish_state(id(can_status)); // Publish text sensor ESP_LOGI("main", "No rx can 0x305 reply, Inverter not connected/responding..."); return false; // Condition NOK } then: - if: condition: lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 1) | (${can_protocol} == 2))); then: canbus.send: # Protection Alarms, Warning and Flags ( Pylontech / Goodwe / Seplos ) can_id: 0x359 data: !lambda |- // +---------------------------+ // | JK-BMS errors bitmask | // +---------------------------+ // 0x8B 0x00 0x00: Battery warning message 0000 0000 0000 0000 // // Bit 0 Low capacity 1 (alarm), 0 (normal) warning // Bit 1 Power tube overtemperature 1 (alarm), 0 (normal) alarm // Bit 2 Charging overvoltage 1 (alarm), 0 (normal) alarm // Bit 3 Discharging undervoltage 1 (alarm), 0 (normal) alarm // Bit 4 Battery over temperature 1 (alarm), 0 (normal) alarm // Bit 5 Charging overcurrent 1 (alarm), 0 (normal) alarm // Bit 6 Discharging overcurrent 1 (alarm), 0 (normal) alarm // Bit 7 Cell pressure difference 1 (alarm), 0 (normal) alarm // Bit 8 Overtemperature alarm in the battery box 1 (alarm), 0 (normal) alarm // Bit 9 Battery low temperature 1 (alarm), 0 (normal) alarm // Bit 10 Cell overvoltage 1 (alarm), 0 (normal) alarm // Bit 11 Cell undervoltage 1 (alarm), 0 (normal) alarm // Bit 12 309_A protection 1 (alarm), 0 (normal) alarm // Bit 13 309_A protection 1 (alarm), 0 (normal) alarm // Bit 14 Reserved // Bit 15 Reserved // // 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) { uint16_t jk_errormask = id(errors_bitmask).state; 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) = "OVP"; ESP_LOGI("main", "Hight.Voltage.Alarm JK bit 2,7,10 - can_msg[0] : %x", can_mesg[0]); } 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) = "UVP"; ESP_LOGI("main", "Low.Voltage.Alarm JK bit 3,11 - can_msg[0] : %x", can_mesg[0]); } 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) = "OTP"; ESP_LOGI("main", "Hight.Temp.Alarm JK bit 1,4,8 - can_msg[0] : %x", can_mesg[0]); } 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) = "UTP"; ESP_LOGI("main", "Low.Temp.Alarm JK bit 9 - can_msg[0] : %x", can_mesg[0]); } 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) = "DOCP"; ESP_LOGI("main", "Discharge.Over.Current JK bit 6 - can_msg[0] : %x", can_mesg[0]); } if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) id(alarm_status) = "COCP"; ESP_LOGI("main", "Charge.Over.Current JK bit 5 - can_msg[1] : %x", can_mesg[1]); } 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) = "BMS"; ESP_LOGI("main", "BMS internal error JK bit 12,13 - can_msg[1] : %x", can_mesg[1]); } if ((jk_errormask & 0x80)) { // Cell Imbalance JK bit 7 can_mesg[1] = can_mesg[1] | 0x10; // byte1_bit4 (0x10 = bin 10000) ESP_LOGI("main", "Cell Imbalance JK bit 7 - can_msg[1] : %x", can_mesg[1]); } } // No Alarm else id(alarm_status) = "NoAlarm"; // +---------------------------+ // | 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; // byte4 - Module in parallel can_mesg[5] = 0x00; // byte5 can_mesg[6] = 0x00; // byte6 can_mesg[7] = 0x00; // byte7 - DIP switches 1,3 10000100 0x84 ESP_LOGI("main", "send can id: 0x359 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return ((id(can_msg_counter) == 1) & ((${can_protocol} == 3) | (${can_protocol} == 4))); then: canbus.send: # Protection Alarms and Warning ( SMA / Victron ) 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) { uint16_t jk_errormask = id(errors_bitmask).state; 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) = "OVP"; ESP_LOGI("main", "Hight.Voltage.Alarm JK bit 2,7,10 - can_msg[0] : %x", can_mesg[0]); } 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) = "UVP"; ESP_LOGI("main", "Low.Voltage.Alarm JK bit 3,11 - can_msg[0] : %x", can_mesg[0]); } 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) = "OTP"; ESP_LOGI("main", "Hight.Temp.Alarm JK bit 1,4,8 - can_msg[0] : %x", can_mesg[0]); } if ((jk_errormask & 0x200)) { // Low.Temp.Alarm JK bit 9 can_mesg[1] = 0x01; // byte1_bit0 (0x01 = bin 1) id(alarm_status) = "UTP"; ESP_LOGI("main", "Low.Temp.Alarm JK bit 9 - can_msg[1] : %x", can_mesg[1]); } 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) = "DOCP"; ESP_LOGI("main", "Discharge.Over.Current JK bit 6 - can_msg[1] : %x", can_mesg[1]); } if ((jk_errormask & 0x20)) { // Charge.Over.Current JK bit 5 can_mesg[2] = 0x01; // byte2_bit0 (0x01 = bin 1) id(alarm_status) = "COCP"; ESP_LOGI("main", "Charge.Over.Current JK bit 5 - can_msg[2] : %x", can_mesg[2]); } 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) = "BMS"; ESP_LOGI("main", "BMS internal error JK bit 12,13 - can_msg[2] : %x", can_mesg[2]); } if ((jk_errormask & 0x80)) { // Cell.Imbalance JK bit 7 can_mesg[3] = 0x01; // byte3_bit0 (0x01 = bin 1) ESP_LOGI("main", "Cell Imbalance JK bit 7 - can_msg[3] : %x", can_mesg[3]); } } // No Alarm else id(alarm_status) = "NoAlarm"; ESP_LOGI("main", "send can id: 0x35A hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return id(can_msg_counter) == 2; then: canbus.send: # BMS instruction : Charge Volts, Charge Amps, Discharge Amps, Min voltage 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) { id(charge_status) = "Alarm"; } // No Alarm => Wait else if ((id(errors_bitmask).state < 2) & (id(charge_status) == "Alarm")) { id(charge_status) = "Wait"; } // Disabled : charging disabled if BMS or ESP32 switch is OFF else if ((!id(charging_switch).state) | (!id(switch_charging).state)) { id(charge_status) = "Disabled"; } // No Disabled => Wait else if ((id(charging_switch).state) & (id(switch_charging).state) & (id(charge_status) == "Disabled")) { id(charge_status) = "Wait"; } // Bulk Manually : after switch ON 'Charging manually (top bal)' else if (id(switch_chg_bulk).state) { id(charge_status) = "Bulk Manually"; } // No Bulk Manually => Wait else if ((!id(switch_chg_bulk).state) & (id(charge_status) == "Bulk Manually")) { id(charge_status) = "Wait"; } // 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)) { id(charge_status) = "Bulk"; 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) == "Bulk") & (id(total_voltage).state >= (id(bulk_voltage).state - id(absorption_offset).state))) { id(charge_status) = "Absorption"; if (!id(absorption_script).is_running()) id(absorption_script).execute(); // 10 % from top start absorption timer } // 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) == "Wait")) { id(charge_status) = "Float"; } // No Float => Wait else if ((!id(switch_chg_float).state) & (id(charge_status) == "Float")) { id(charge_status) = "Wait"; } // +--------------------------------------+ // | Charge values | // +--------------------------------------+ // Bulk Charge if ((id(charge_status) == "Bulk") | (id(charge_status) == "Bulk Manually") | (id(charge_status) == "Absorption")) { id(charging_v) = id(bulk_voltage).state; id(charging_a) = id(charging_current).state; } // Float Charge else if (id(charge_status) == "Float") { id(charging_v) = id(float_voltage).state; id(charging_a) = id(charging_current).state; } // Disabled or Wait : Stop Charging else if ((id(charge_status) == "Disabled") | (id(charge_status) == "Wait")) { id(charging_v) = round(id(total_voltage).state * 10)/10; // Actual battery voltage id(charging_a) = 0; } // +--------------------------------------+ // | 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("main", "Alarm Status : %s", id(alarm_status).c_str()); // Alarm : Stop Charging and Discharging if ((id(alarm_status) == "OTP") | (id(alarm_status) == "BMS")){ id(charging_v) = 51.2; id(charging_a) = 0; id(discharging_a) = 0; } // Alarm : Stop Charging else if ((id(alarm_status) == "OVP") | (id(alarm_status) == "UTP") | (id(alarm_status) == "COCP")){ id(charging_v) = 51.2; id(charging_a) = 0; } // Alarm : Stop Discharging else if ((id(alarm_status) == "UVP") | (id(alarm_status) == "DOCP")){ id(discharging_a) = 0; } // +--------------------------------------+ // | 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("main", "send can id: 0x351 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); ESP_LOGI("main", "Charge Status : %s", id(charge_status).c_str()); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return id(can_msg_counter) == 3; then: canbus.send: # Actual State of Charge (SOC) / State of Health (SOH) can_id: 0x355 data: !lambda |- int soh = round(((id(charging_cycles).state/${max_cycles})-1)*-100); 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("main", "send can id: 0x355 hex: %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3]}; - if: condition: lambda: return id(can_msg_counter) == 4; then: canbus.send: # Actual Voltage / Current / Temperature (Deye 0x305 ACK) 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, id(temperature_sensor_2).state)* 10) & 0xff; can_mesg[5] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; ESP_LOGI("main", "send can id: 0x356 hex: %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5]}; - if: condition: lambda: return ((id(can_msg_counter) == 5) & ((${can_protocol} == 1) | (${can_protocol} == 2))); then: canbus.send: # Request flag to Enable/Disable: Charge, Discharge ( Pylontech / Goodwe / Seplos ) 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)) can_mesg[0] = 0x80; // Bit 6 : Discharge enable if ((id(discharging_switch).state) & (id(switch_discharging).state)) can_mesg[0] = can_mesg[0] | 0x40; ESP_LOGI("main", "send can id: 0x35C hex: %x %x", can_mesg[0], can_mesg[1]); return {can_mesg[0], can_mesg[1]}; - if: condition: lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); then: canbus.send: # Actual Max Cell Temp, Min Cell Temp, Max Cell V, Min Cell V ( Pylontech / Goodwe / Seplos ) 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, id(temperature_sensor_2).state)* 10) & 0xff; can_mesg[1] = int16_t(max(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; can_mesg[2] = int16_t(min(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) & 0xff; can_mesg[3] = int16_t(min(id(temperature_sensor_1).state, id(temperature_sensor_2).state)* 10) >> 8 & 0xff; 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("main", "send can id: 0x70 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return ((id(can_msg_counter) == 6) & (${can_protocol} == 2)); then: - canbus.send: # Actual Max Cell Temp ID, Min Cell Temp ID, Max Cell V ID, Min Cell ID ( Pylontech / Goodwe / Seplos ) 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("main", "send can id: 0x371 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return ((id(can_msg_counter) == 7) & (${can_protocol} == 4)); then: - canbus.send: # Battery modules information ( Victron ) 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("main", "send can id: 0x372 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); then: canbus.send: # Actual Min Cell V, Max Cell V, Min Cell Temp (Kelvin), Max Cell Temp (Kelvin) ( Victron ) 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, id(temperature_sensor_2).state) + 273.15; int max_temp_kelvin = max(id(temperature_sensor_1).state, id(temperature_sensor_2).state) + 273.15; 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("main", "send can id: 0x373 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); then: - canbus.send: # Min cell voltage ID [ASCII] ( Victron ) can_id: 0x374 data: !lambda |- int cell_id = id(min_voltage_cell).state; ESP_LOGI("main", "send can id: 0x374 [ASCII] Min cell voltage ID : %i", cell_id); 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: # Max cell voltage ID [ASCII] ( Victron ) can_id: 0x375 data: !lambda |- int cell_id = id(max_voltage_cell).state; ESP_LOGI("main", "send can id: 0x375 [ASCII] Max cell voltage ID : %i", cell_id); 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: # Min cell temperature ID [ASCII] ( Victron ) can_id: 0x376 data: !lambda |- // Min Temp. Sensor ID ? if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ ESP_LOGI("main", "send can id: 0x376 [ASCII] Min Temp. Sensor ID : 2"); return {0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; } else { ESP_LOGI("main", "send can id: 0x376 [ASCII] Min Temp. Sensor ID : 1"); return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; } - if: condition: lambda: return ((id(can_msg_counter) == 8) & (${can_protocol} == 4)); then: - canbus.send: # Max cell temperature ID [ASCII] ( Victron ) can_id: 0x377 data: !lambda |- // Max Temp. Sensor ID ? if (id(temperature_sensor_1).state >= id(temperature_sensor_2).state){ ESP_LOGI("main", "send can id: 0x377 [ASCII] Max Temp. Sensor ID : 1"); return {0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; } else { ESP_LOGI("main", "send can id: 0x377 [ASCII] Max Temp. Sensor ID : 2"); 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: # Battery Installed Capacity Ah ( Victron, Sol-Ark, Luxpower ) 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("main", "send can id: 0x379 hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return ((id(can_msg_counter) == 10) & (${can_protocol} == 4)); then: - canbus.send: # Product identification [ASCII] ( Victron ) can_id: 0x382 data: !lambda |- ESP_LOGI("main", "send can id: 0x382 [ASCII] Product : JK-BMS"); 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: # Battery information ( SMA, Victron ) can_id: 0x35F data: !lambda |- // SMA Victron // Byte [00:01] : Bat-Type Product ID // Byte [02:03] : BMS Version Firmware version (1.16 => HEX [01:10]) // Byte [04:05] : Bat-Capacity Available Capacity Ah // Byte [06:07] : Manufacturer ID Hardware version 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("main", "send can id: 0x35F hex: %x %x %x %x %x %x %x %x", can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]); return {can_mesg[0], can_mesg[1], can_mesg[2], can_mesg[3], can_mesg[4], can_mesg[5], can_mesg[6], can_mesg[7]}; - if: condition: lambda: return id(can_msg_counter) == 12; then: - canbus.send: can_id: 0x35E # Manufacturer name data: !lambda |- if (${can_bms_name} == 1){ ESP_LOGI("main", "send can id: 0x35E ASCII : PYLON"); return {0x50, 0x59, 0x4C, 0x4F, 0x4E, 0x20, 0x20, 0x20}; // PYLON ( recognized by Deye, display PYLON name and SOH ) } else if (${can_bms_name} == 2){ ESP_LOGI("main", "send can id: 0x35E ASCII : GOODWE"); return {0x47, 0x4F, 0x4F, 0x44, 0x57, 0x45, 0x20, 0x20}; // GOODWE } else if (${can_bms_name} == 3){ ESP_LOGI("main", "send can id: 0x35E ASCII : SHEnergy"); return {0x53, 0x48, 0x45, 0x6E, 0x65, 0x72, 0x67, 0x79}; // SHEnergy (SEPLOS) } # Reset counter - lambda: id(can_msg_counter) = 0;