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:03] – [Software] 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 ===== |