diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 6e891c47..3aef7a8e 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -168,7 +168,7 @@ jobs: - name: Dont move consts run: | - sed -i 's#if len(uses) < 3:#if len(uses) < 5:#' script/ci-custom.py + sed -i 's#if len(uses) < 3:#if len(uses) < 6:#' script/ci-custom.py git update-index --assume-unchanged script/ci-custom.py working-directory: ${{ env.esphome_directory }} @@ -203,24 +203,6 @@ jobs: esphome -s external_components_source components config $YAML >/dev/null done - - name: Write yaml-snippets/secrets.yaml - shell: bash - run: 'echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > yaml-snippets/secrets.yaml' - - name: Validate YAML snippets - run: | - for YAML in yaml-snippets/esp*.yaml; do - esphome -s external_components_source ../components config $YAML >/dev/null - done - - - name: Write tests/secrets.yaml - shell: bash - run: 'echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > tests/secrets.yaml' - - name: Validate test configurations - run: | - for YAML in tests/esp*.yaml; do - esphome -s external_components_source ../components config $YAML >/dev/null - done - esphome-compile: runs-on: ubuntu-latest needs: [esphome-config] @@ -256,32 +238,5 @@ jobs: - name: Write secrets.yaml shell: bash run: 'echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > secrets.yaml' - - name: Compile esp32 (heltec_balancer_ble) example configurations - run: | - esphome -s external_components_source components compile esp32-heltec-balancer-ble-example-faker.yaml - # esphome -s external_components_source components compile esp32-heltec-balancer-ble-example-multiple-devices.yaml - - name: Compile esp8266 (jk_bms) example configurations - run: | - esphome -s external_components_source components compile esp8266-example-faker.yaml - - name: Compile esp32 (jk_bms) example configurations - run: | - esphome -s external_components_source components compile esp32-example-faker.yaml - - name: Compile esp32 (jk_bms_ble) example configurations - run: | - esphome -s external_components_source components -s name jk-bms-jk04 compile esp32-ble-jk04-example-faker.yaml - esphome -s external_components_source components -s name jk-bms-jk02-24s compile esp32-ble-example-faker.yaml - esphome -s external_components_source components -s name jk-bms-jk02-32s compile esp32-ble-v14-example-faker.yaml - - name: Compile esp32 (modbus) example configurations - run: | - esphome -s name jk-bms-pb compile esp32-jk-pb-modbus-example.yaml - - name: Compile display port example configurations - run: | - esphome -s external_components_source components compile esp32-display-example.yaml - # esphome -s external_components_source components compile esp8266-display-example.yaml - - - name: Write tests/secrets.yaml - shell: bash - run: 'echo -e "wifi_ssid: ssid\nwifi_password: password\nmqtt_host: host\nmqtt_username: username\nmqtt_password: password" > tests/secrets.yaml' - - name: Compile test configurations - run: | - esphome -s external_components_source ../components compile tests/esp32c6-compatibility-test.yaml + - run: | + esphome -s external_components_source components compile esp32-active-balancer-example.yaml diff --git a/components/jk_balancer/__init__.py b/components/jk_balancer/__init__.py new file mode 100644 index 00000000..6ff0a785 --- /dev/null +++ b/components/jk_balancer/__init__.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +from esphome.components import jk_balancer_modbus +import esphome.config_validation as cv +from esphome.const import CONF_ID + +AUTO_LOAD = ["jk_balancer_modbus", "binary_sensor", "sensor", "switch", "text_sensor"] +CODEOWNERS = ["@syssi"] +MULTI_CONF = True + +CONF_JK_BALANCER_ID = "jk_balancer_id" + +jk_balancer_ns = cg.esphome_ns.namespace("jk_balancer") +JkBalancer = jk_balancer_ns.class_( + "JkBalancer", cg.PollingComponent, jk_balancer_modbus.JkBalancerModbusDevice +) + +JK_BALANCER_COMPONENT_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_JK_BALANCER_ID): cv.use_id(JkBalancer), + } +) + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(JkBalancer), + } + ) + .extend(cv.polling_component_schema("5s")) + .extend(jk_balancer_modbus.jk_balancer_modbus_device_schema(0x01)) +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await jk_balancer_modbus.register_jk_balancer_modbus_device(var, config) diff --git a/components/jk_balancer/binary_sensor.py b/components/jk_balancer/binary_sensor.py new file mode 100644 index 00000000..506c4416 --- /dev/null +++ b/components/jk_balancer/binary_sensor.py @@ -0,0 +1,81 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import CONF_ID, DEVICE_CLASS_CONNECTIVITY, ENTITY_CATEGORY_DIAGNOSTIC + +from . import CONF_JK_BALANCER_ID, JK_BALANCER_COMPONENT_SCHEMA +from .const import CONF_CHARGING, CONF_DISCHARGING + +DEPENDENCIES = ["jk_balancer"] + +CODEOWNERS = ["@syssi"] + +CONF_CHARGING_SWITCH = ( + "charging_switch" # @DEPRECATED and superseded by switch.charging +) +CONF_DISCHARGING_SWITCH = ( + "discharging_switch" # @DEPRECATED and superseded by switch.discharging +) +CONF_BALANCING = "balancing" +CONF_BALANCING_SWITCH = "balancing_switch" +CONF_DEDICATED_CHARGER_SWITCH = "dedicated_charger_switch" +CONF_ONLINE_STATUS = "online_status" + +ICON_CHARGING = "mdi:battery-charging" +ICON_CHARGING_SWITCH = "mdi:battery-charging" +ICON_DISCHARGING = "mdi:power-plug" +ICON_DISCHARGING_SWITCH = "mdi:power-plug" +ICON_BALANCING = "mdi:battery-heart-variant" +ICON_BALANCING_SWITCH = "mdi:battery-heart-variant" +ICON_DEDICATED_CHARGER_SWITCH = "mdi:battery-charging" + +BINARY_SENSORS = [ + CONF_CHARGING, + CONF_CHARGING_SWITCH, + CONF_DISCHARGING, + CONF_DISCHARGING_SWITCH, + CONF_BALANCING, + CONF_BALANCING_SWITCH, + CONF_DEDICATED_CHARGER_SWITCH, + CONF_ONLINE_STATUS, +] + +CONFIG_SCHEMA = JK_BALANCER_COMPONENT_SCHEMA.extend( + { + cv.Optional(CONF_CHARGING): binary_sensor.binary_sensor_schema( + icon=ICON_CHARGING + ), + cv.Optional(CONF_CHARGING_SWITCH): binary_sensor.binary_sensor_schema( + icon=ICON_CHARGING_SWITCH + ), + cv.Optional(CONF_DISCHARGING): binary_sensor.binary_sensor_schema( + icon=ICON_DISCHARGING + ), + cv.Optional(CONF_DISCHARGING_SWITCH): binary_sensor.binary_sensor_schema( + icon=ICON_DISCHARGING_SWITCH + ), + cv.Optional(CONF_BALANCING): binary_sensor.binary_sensor_schema( + icon=ICON_BALANCING + ), + cv.Optional(CONF_BALANCING_SWITCH): binary_sensor.binary_sensor_schema( + icon=ICON_BALANCING_SWITCH + ), + cv.Optional(CONF_DEDICATED_CHARGER_SWITCH): binary_sensor.binary_sensor_schema( + icon=ICON_DEDICATED_CHARGER_SWITCH + ), + cv.Optional(CONF_ONLINE_STATUS): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_CONNECTIVITY, + entity_category=ENTITY_CATEGORY_DIAGNOSTIC, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_JK_BALANCER_ID]) + for key in BINARY_SENSORS: + if key in config: + conf = config[key] + sens = cg.new_Pvariable(conf[CONF_ID]) + await binary_sensor.register_binary_sensor(sens, conf) + cg.add(getattr(hub, f"set_{key}_binary_sensor")(sens)) diff --git a/components/jk_balancer/const.py b/components/jk_balancer/const.py new file mode 100644 index 00000000..eba2c904 --- /dev/null +++ b/components/jk_balancer/const.py @@ -0,0 +1,5 @@ +"""Constants for the jk bms component.""" + +CONF_BALANCER = "balancer" +CONF_CHARGING = "charging" +CONF_DISCHARGING = "discharging" diff --git a/components/jk_balancer/jk_balancer.cpp b/components/jk_balancer/jk_balancer.cpp new file mode 100644 index 00000000..3566bd32 --- /dev/null +++ b/components/jk_balancer/jk_balancer.cpp @@ -0,0 +1,439 @@ +#include "jk_balancer.h" +#include "esphome/core/log.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace jk_balancer { + +static const char *const TAG = "jk_balancer"; + +static const uint8_t MAX_NO_RESPONSE_COUNT = 5; + +static const uint8_t FUNCTION_READ_ALL = 0xFF; +static const uint8_t FUNCTION_WRITE_REGISTER = 0x02; + +static const uint8_t ERRORS_SIZE = 14; +static const char *const ERRORS[ERRORS_SIZE] = { + "Low capacity", // Byte 0.0, warning + "Power tube overtemperature", // Byte 0.1, alarm + "Charging overvoltage", // Byte 0.2, alarm + "Discharging undervoltage", // Byte 0.3, alarm + "Battery over temperature", // Byte 0.4, alarm + "Charging overcurrent", // Byte 0.5, alarm + "Discharging overcurrent", // Byte 0.6, alarm + "Cell pressure difference", // Byte 0.7, alarm + "Overtemperature alarm in the battery box", // Byte 1.0, alarm + "Battery low temperature", // Byte 1.1, alarm + "Cell overvoltage", // Byte 1.2, alarm + "Cell undervoltage", // Byte 1.3, alarm + "309_A protection", // Byte 1.4, alarm + "309_A protection", // Byte 1.5, alarm +}; + +static const uint8_t OPERATION_MODES_SIZE = 4; +static const char *const OPERATION_MODES[OPERATION_MODES_SIZE] = { + "Charging enabled", // 0x00 + "Discharging enabled", // 0x01 + "Balancer enabled", // 0x02 + "Battery dropped", // 0x03 +}; + +static const uint8_t BATTERY_TYPES_SIZE = 3; +static const char *const BATTERY_TYPES[BATTERY_TYPES_SIZE] = { + "Lithium Iron Phosphate", // 0x00 + "Ternary Lithium", // 0x01 + "Lithium Titanate", // 0x02 +}; + +void JkBalancer::on_jk_balancer_modbus_data(const uint8_t &function, const std::vector &data) { + this->reset_online_status_tracker_(); + + if (function == FUNCTION_READ_ALL) { + this->on_status_data_(data); + return; + } + + ESP_LOGW(TAG, "Unhandled response (%zu bytes) received: %s", data.size(), + format_hex_pretty(&data.front(), data.size()).c_str()); +} + +void JkBalancer::on_status_data_(const std::vector &data) { + auto jk_get_16bit = [&](size_t i) -> uint16_t { return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0); }; + // auto jk_get_32bit = [&](size_t i) -> uint32_t { + // return (uint32_t(jk_get_16bit(i + 0)) << 16) | (uint32_t(jk_get_16bit(i + 2)) << 0); + // }; + + ESP_LOGI(TAG, "Status frame received"); + + // Status request + // -> 0x55 0xAA 0x01 0xFF 0x00 0x00 0xFF + // + // Status response + // <- 0xEB, 0x90, 0x01, 0xFF, + // *Data* + // 0x1E, 0xD3, 0x0F, 0x69, 0x14, 0x13, 0x02, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x05, 0x03, + // 0xE8, 0x01, 0x14, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, + // 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, + // 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x0F, 0x69, 0x00, 0x16, 0x6F, + // + // *Data* + // + // Address Content: Description Decoded content Coeff./Unit + // 0x1E 0xD3: total battery voltage 0.001 V + // 0x0F 0x69: average cell voltage 0.001 V + // 0x14 : identified Cell count 20 = 20 cells identified + // 0x13 : highest voltage cellnumber + // 0x02 : lowest voltage cellnumber -> balancing from 19 -> 02 + // 0x00 : equilibrium - 0 = do nothing / 1 = balanced cell = charging / 2 = balanced cell = discharging + // 0x00 : alarm status - bit0 = cellcount incorrectly set + // bit1 = wire resistance to large + // bit2 = battery overvoltage + // 0x00 0x07: maximum cell difference 7 * 0.001V = 7mV 0.001 V + // 0x00 0x00: balancer current 0.001 mA (?) + // 0x00 0x05: balancer trigger voltage 5 * 0.001V = 5mV 0.001 V (?) + // 0x03 0xE8: max. balancer current 1000 * 0.001 = 1000mA 0.001 mA (?) + // 0x01 : Balance switch 1 = ON + // 0x14 : cell count set 20 = 20 cells set + // Individual Cell voltage + // 0x0F 0x69: Cell 1 3945 * 0.001 = 3.945V 0.001 V + // 0x0F 0x69: Cell 2 3945 * 0.001 = 3.945V 0.001 V + // 0x0F 0x69: Cell 3 ... 0.001 V + // 0x0F 0x69: Cell 4 ... 0.001 V + // 0x0F 0x69: Cell 5 ... 0.001 V + // 0x0F 0x69: Cell 6 ... 0.001 V + // 0x0F 0x69: Cell 7 ... 0.001 V + // 0x0F 0x69: Cell 8 ... 0.001 V + // 0x0F 0x69: Cell 9 ... 0.001 V + // 0x0F 0x69: Cell 10 ... 0.001 V + // ... + // 0x0F 0x69: Cell 20 3945 * 0.001 = 3.945V 0.001 V + // 0x00 0x00: Cell 21 0000 * 0.001 = 0.000V 0.001 V + // 0x00 0x00: Cell 22 0000 * 0.001 = 0.000V 0.001 V + // 0x00 0x00: Cell 23 0000 * 0.001 = 0.000V 0.001 V + // 0x00 0x00: Cell 24 0000 * 0.001 = 0.000V 0.001 V + // 0x00 0xA6: temperature 166 * 0.1°C = 16.6°C 0.1°C + // 0x6F : chksum + // uint8_t cells = data[1] / 3; + uint8_t cells = data[8]; // identified cell count + + // Total battery voltage 5359 * 0.01 = 53.59V 0.01 V + this->publish_state_(this->total_voltage_sensor_, (float) jk_get_16bit(0 + 4) * 0.01f); + + // average cell voltage + + // highest voltage cellnumber + + // lowest voltage cellnumber + + // equilibrium state + + // alarm status + // 0x0001 = 00000001: cellcount incorrectly set + // 0x0002 = 00000010: wire resistance to large + // 0x0003 = 00000100: battery overvoltage + // uint16_t raw_errors_bitmask = jk_get_16bit(offset + 6 + 3 * 8); + // this->publish_state_(this->errors_bitmask_sensor_, (float) raw_errors_bitmask); + // this->publish_state_(this->errors_text_sensor_, this->error_bits_to_string_(raw_errors_bitmask)); + + // maximum cell difference + + // balancer current + this->publish_state_(this->current_sensor_, (float) jk_get_16bit(12 + 4) * 0.001f); + + // balancer triffer voltage + + // maximal balance current + + // balance switch + this->publish_state_(this->balancing_switch_binary_sensor_, (bool) data[18 + 4]); + + float min_cell_voltage = 100.0f; + float max_cell_voltage = -100.0f; + uint8_t min_voltage_cell = 0; + uint8_t max_voltage_cell = 0; + for (uint8_t i = 0; i < cells; i++) { + float cell_voltage = (float) jk_get_16bit(i * 2 + 19 + 4) * 0.001f; + if (cell_voltage < min_cell_voltage) { + min_cell_voltage = cell_voltage; + min_voltage_cell = i + 1; + } + if (cell_voltage > max_cell_voltage) { + max_cell_voltage = cell_voltage; + max_voltage_cell = i + 1; + } + this->publish_state_(this->cells_[i].cell_voltage_sensor_, cell_voltage); + } + + this->publish_state_(this->min_cell_voltage_sensor_, min_cell_voltage); + this->publish_state_(this->max_cell_voltage_sensor_, max_cell_voltage); + this->publish_state_(this->max_voltage_cell_sensor_, (float) max_voltage_cell); + this->publish_state_(this->min_voltage_cell_sensor_, (float) min_voltage_cell); + this->publish_state_(this->delta_cell_voltage_sensor_, max_cell_voltage - min_cell_voltage); + + // Read battery temperature 28°C 0.1 °C + this->publish_state_(this->temperature_sensor_1_sensor_, (float) jk_get_16bit(24 * 2 + 19 + 4) * 0.1f); + + // : End of frame +} + +void JkBalancer::update() { + this->track_online_status_(); + this->query_balancer_status(); +} + +void JkBalancer::track_online_status_() { + if (this->no_response_count_ < MAX_NO_RESPONSE_COUNT) { + this->no_response_count_++; + } + if (this->no_response_count_ == MAX_NO_RESPONSE_COUNT) { + this->publish_device_unavailable_(); + this->no_response_count_++; + } +} + +void JkBalancer::reset_online_status_tracker_() { + this->no_response_count_ = 0; + this->publish_state_(this->online_status_binary_sensor_, true); +} + +void JkBalancer::publish_device_unavailable_() { + this->publish_state_(this->online_status_binary_sensor_, false); + this->publish_state_(this->errors_text_sensor_, "Offline"); + + this->publish_state_(min_cell_voltage_sensor_, NAN); + this->publish_state_(max_cell_voltage_sensor_, NAN); + this->publish_state_(min_voltage_cell_sensor_, NAN); + this->publish_state_(max_voltage_cell_sensor_, NAN); + this->publish_state_(delta_cell_voltage_sensor_, NAN); + this->publish_state_(average_cell_voltage_sensor_, NAN); + this->publish_state_(power_tube_temperature_sensor_, NAN); + this->publish_state_(temperature_sensor_1_sensor_, NAN); + this->publish_state_(temperature_sensor_2_sensor_, NAN); + this->publish_state_(total_voltage_sensor_, NAN); + this->publish_state_(current_sensor_, NAN); + this->publish_state_(power_sensor_, NAN); + this->publish_state_(charging_power_sensor_, NAN); + this->publish_state_(discharging_power_sensor_, NAN); + this->publish_state_(capacity_remaining_sensor_, NAN); + this->publish_state_(capacity_remaining_derived_sensor_, NAN); + this->publish_state_(temperature_sensors_sensor_, NAN); + this->publish_state_(charging_cycles_sensor_, NAN); + this->publish_state_(total_charging_cycle_capacity_sensor_, NAN); + this->publish_state_(battery_strings_sensor_, NAN); + this->publish_state_(errors_bitmask_sensor_, NAN); + this->publish_state_(operation_mode_bitmask_sensor_, NAN); + this->publish_state_(total_voltage_overvoltage_protection_sensor_, NAN); + this->publish_state_(total_voltage_undervoltage_protection_sensor_, NAN); + this->publish_state_(cell_voltage_overvoltage_protection_sensor_, NAN); + this->publish_state_(cell_voltage_overvoltage_recovery_sensor_, NAN); + this->publish_state_(cell_voltage_overvoltage_delay_sensor_, NAN); + this->publish_state_(cell_voltage_undervoltage_protection_sensor_, NAN); + this->publish_state_(cell_voltage_undervoltage_recovery_sensor_, NAN); + this->publish_state_(cell_voltage_undervoltage_delay_sensor_, NAN); + this->publish_state_(cell_pressure_difference_protection_sensor_, NAN); + this->publish_state_(discharging_overcurrent_protection_sensor_, NAN); + this->publish_state_(discharging_overcurrent_delay_sensor_, NAN); + this->publish_state_(charging_overcurrent_protection_sensor_, NAN); + this->publish_state_(charging_overcurrent_delay_sensor_, NAN); + this->publish_state_(balance_starting_voltage_sensor_, NAN); + this->publish_state_(balance_opening_pressure_difference_sensor_, NAN); + this->publish_state_(power_tube_temperature_protection_sensor_, NAN); + this->publish_state_(power_tube_temperature_recovery_sensor_, NAN); + this->publish_state_(temperature_sensor_temperature_protection_sensor_, NAN); + this->publish_state_(temperature_sensor_temperature_recovery_sensor_, NAN); + this->publish_state_(temperature_sensor_temperature_difference_protection_sensor_, NAN); + this->publish_state_(charging_high_temperature_protection_sensor_, NAN); + this->publish_state_(discharging_high_temperature_protection_sensor_, NAN); + this->publish_state_(charging_low_temperature_protection_sensor_, NAN); + this->publish_state_(charging_low_temperature_recovery_sensor_, NAN); + this->publish_state_(discharging_low_temperature_protection_sensor_, NAN); + this->publish_state_(discharging_low_temperature_recovery_sensor_, NAN); + this->publish_state_(total_battery_capacity_setting_sensor_, NAN); + this->publish_state_(charging_sensor_, NAN); + this->publish_state_(discharging_sensor_, NAN); + this->publish_state_(current_calibration_sensor_, NAN); + this->publish_state_(device_address_sensor_, NAN); + this->publish_state_(sleep_wait_time_sensor_, NAN); + this->publish_state_(alarm_low_volume_sensor_, NAN); + this->publish_state_(password_sensor_, NAN); + this->publish_state_(manufacturing_date_sensor_, NAN); + this->publish_state_(total_runtime_sensor_, NAN); + this->publish_state_(start_current_calibration_sensor_, NAN); + this->publish_state_(actual_battery_capacity_sensor_, NAN); + this->publish_state_(protocol_version_sensor_, NAN); + + for (auto &cell : this->cells_) { + this->publish_state_(cell.cell_voltage_sensor_, NAN); + } +} + +void JkBalancer::publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state) { + if (binary_sensor == nullptr) + return; + + binary_sensor->publish_state(state); +} + +void JkBalancer::publish_state_(sensor::Sensor *sensor, float value) { + if (sensor == nullptr) + return; + + sensor->publish_state(value); +} + +void JkBalancer::publish_state_(switch_::Switch *obj, const bool &state) { + if (obj == nullptr) + return; + + obj->publish_state(state); +} + +void JkBalancer::publish_state_(text_sensor::TextSensor *text_sensor, const std::string &state) { + if (text_sensor == nullptr) + return; + + text_sensor->publish_state(state); +} + +std::string JkBalancer::error_bits_to_string_(const uint16_t mask) { + bool first = true; + std::string errors_list = ""; + + if (mask) { + for (int i = 0; i < ERRORS_SIZE; i++) { + if (mask & (1 << i)) { + if (first) { + first = false; + } else { + errors_list.append(";"); + } + errors_list.append(ERRORS[i]); + } + } + } + + return errors_list; +} + +std::string JkBalancer::mode_bits_to_string_(const uint16_t mask) { + bool first = true; + std::string modes_list = ""; + + if (mask) { + for (int i = 0; i < OPERATION_MODES_SIZE; i++) { + if (mask & (1 << i)) { + if (first) { + first = false; + } else { + modes_list.append(";"); + } + modes_list.append(OPERATION_MODES[i]); + } + } + } + + return modes_list; +} + +void JkBalancer::dump_config() { // NOLINT(google-readability-function-size,readability-function-size) + ESP_LOGCONFIG(TAG, "JkBalancer:"); + ESP_LOGCONFIG(TAG, " Address: 0x%02X", this->address_); + LOG_SENSOR("", "Minimum Cell Voltage", this->min_cell_voltage_sensor_); + LOG_SENSOR("", "Maximum Cell Voltage", this->max_cell_voltage_sensor_); + LOG_SENSOR("", "Minimum Voltage Cell", this->min_voltage_cell_sensor_); + LOG_SENSOR("", "Maximum Voltage Cell", this->max_voltage_cell_sensor_); + LOG_SENSOR("", "Delta Cell Voltage", this->delta_cell_voltage_sensor_); + LOG_SENSOR("", "Average Cell Voltage", this->average_cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 1", this->cells_[0].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 2", this->cells_[1].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 3", this->cells_[2].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 4", this->cells_[3].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 5", this->cells_[4].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 6", this->cells_[5].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 7", this->cells_[6].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 8", this->cells_[7].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 9", this->cells_[8].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 10", this->cells_[9].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 11", this->cells_[10].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 12", this->cells_[11].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 13", this->cells_[12].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 14", this->cells_[13].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 15", this->cells_[14].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 16", this->cells_[15].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 17", this->cells_[16].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 18", this->cells_[17].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 19", this->cells_[18].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 20", this->cells_[19].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 21", this->cells_[20].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 22", this->cells_[21].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 23", this->cells_[22].cell_voltage_sensor_); + LOG_SENSOR("", "Cell Voltage 24", this->cells_[23].cell_voltage_sensor_); + LOG_SENSOR("", "Power Tube Temperature", this->power_tube_temperature_sensor_); + LOG_SENSOR("", "Temperature Sensor 1", this->temperature_sensor_1_sensor_); + LOG_SENSOR("", "Temperature Sensor 2", this->temperature_sensor_2_sensor_); + LOG_SENSOR("", "Total Voltage", this->total_voltage_sensor_); + LOG_SENSOR("", "Current", this->current_sensor_); + LOG_SENSOR("", "Power", this->power_sensor_); + LOG_SENSOR("", "Charging Power", this->charging_power_sensor_); + LOG_SENSOR("", "Discharging Power", this->discharging_power_sensor_); + LOG_SENSOR("", "Capacity Remaining", this->capacity_remaining_sensor_); + LOG_SENSOR("", "Capacity Remaining Derived", this->capacity_remaining_derived_sensor_); + LOG_SENSOR("", "Temperature Sensors", this->temperature_sensors_sensor_); + LOG_SENSOR("", "Charging Cycles", this->charging_cycles_sensor_); + LOG_SENSOR("", "Total Charging Cycle Capacity", this->total_charging_cycle_capacity_sensor_); + LOG_SENSOR("", "Battery Strings", this->battery_strings_sensor_); + LOG_SENSOR("", "Errors Bitmask", this->errors_bitmask_sensor_); + LOG_SENSOR("", "Operation Mode Bitmask", this->operation_mode_bitmask_sensor_); + LOG_SENSOR("", "Total Voltage Overvoltage Protection", this->total_voltage_overvoltage_protection_sensor_); + LOG_SENSOR("", "Total Voltage Undervoltage Protection", this->total_voltage_undervoltage_protection_sensor_); + LOG_SENSOR("", "Cell Voltage Overvoltage Protection", this->cell_voltage_overvoltage_protection_sensor_); + LOG_SENSOR("", "Cell Voltage Overvoltage Recovery", this->cell_voltage_overvoltage_recovery_sensor_); + LOG_SENSOR("", "Cell Voltage Overvoltage Delay", this->cell_voltage_overvoltage_delay_sensor_); + LOG_SENSOR("", "Cell Voltage Undervoltage Protection", this->cell_voltage_undervoltage_protection_sensor_); + LOG_SENSOR("", "Cell Voltage Undervoltage Recovery", this->cell_voltage_undervoltage_recovery_sensor_); + LOG_SENSOR("", "Cell Voltage Undervoltage Delay", this->cell_voltage_undervoltage_delay_sensor_); + LOG_SENSOR("", "Cell Pressure Difference Protection", this->cell_pressure_difference_protection_sensor_); + LOG_SENSOR("", "Discharging Overcurrent Protection", this->discharging_overcurrent_protection_sensor_); + LOG_SENSOR("", "Discharging Overcurrent Delay", this->discharging_overcurrent_delay_sensor_); + LOG_SENSOR("", "Charging Overcurrent Protection", this->charging_overcurrent_protection_sensor_); + LOG_SENSOR("", "Charging Overcurrent Delay", this->charging_overcurrent_delay_sensor_); + LOG_SENSOR("", "Balance Starting Voltage", this->balance_starting_voltage_sensor_); + LOG_SENSOR("", "Balance Opening Pressure Difference", this->balance_opening_pressure_difference_sensor_); + LOG_SENSOR("", "Power Tube Temperature Protection", this->power_tube_temperature_protection_sensor_); + LOG_SENSOR("", "Power Tube Temperature Recovery", this->power_tube_temperature_recovery_sensor_); + LOG_SENSOR("", "Temperature Sensor Temperature Protection", this->temperature_sensor_temperature_protection_sensor_); + LOG_SENSOR("", "Temperature Sensor Temperature Recovery", this->temperature_sensor_temperature_recovery_sensor_); + LOG_SENSOR("", "Temperature Sensor Temperature Difference Protection", + this->temperature_sensor_temperature_difference_protection_sensor_); + LOG_SENSOR("", "Charging High Temperature Protection", this->charging_high_temperature_protection_sensor_); + LOG_SENSOR("", "Discharging High Temperature Protection", this->discharging_high_temperature_protection_sensor_); + LOG_SENSOR("", "Charging Low Temperature Protection", this->charging_low_temperature_protection_sensor_); + LOG_SENSOR("", "Charging Low Temperature Recovery", this->charging_low_temperature_recovery_sensor_); + LOG_SENSOR("", "Discharging Low Temperature Protection", this->discharging_low_temperature_protection_sensor_); + LOG_SENSOR("", "Discharging Low Temperature Recovery", this->discharging_low_temperature_recovery_sensor_); + LOG_SENSOR("", "Total Battery Capacity Setting", this->total_battery_capacity_setting_sensor_); + LOG_SENSOR("", "Current Calibration", this->current_calibration_sensor_); + LOG_SENSOR("", "Device Address", this->device_address_sensor_); + LOG_TEXT_SENSOR("", "Battery Type", this->battery_type_text_sensor_); + LOG_SENSOR("", "Sleep Wait Time", this->sleep_wait_time_sensor_); + LOG_SENSOR("", "Alarm Low Volume", this->alarm_low_volume_sensor_); + LOG_TEXT_SENSOR("", "Password", this->password_text_sensor_); + LOG_TEXT_SENSOR("", "Device Type", this->device_type_text_sensor_); + LOG_SENSOR("", "Manufacturing Date", this->manufacturing_date_sensor_); + LOG_SENSOR("", "Total Runtime", this->total_runtime_sensor_); + LOG_TEXT_SENSOR("", "Software Version", this->software_version_text_sensor_); + LOG_SENSOR("", "Start Current Calibration", this->start_current_calibration_sensor_); + LOG_TEXT_SENSOR("", "Manufacturer", this->manufacturer_text_sensor_); + LOG_SENSOR("", "Protocol Version", this->protocol_version_sensor_); + LOG_BINARY_SENSOR("", "Balancing", this->balancing_binary_sensor_); + LOG_BINARY_SENSOR("", "Balancing Switch", this->balancing_switch_binary_sensor_); + LOG_BINARY_SENSOR("", "Charging", this->charging_binary_sensor_); + LOG_BINARY_SENSOR("", "Charging Switch", this->charging_switch_binary_sensor_); + LOG_BINARY_SENSOR("", "Discharging", this->discharging_binary_sensor_); + LOG_BINARY_SENSOR("", "Discharging Switch", this->discharging_switch_binary_sensor_); + LOG_BINARY_SENSOR("", "Dedicated Charger Switch", this->dedicated_charger_switch_binary_sensor_); + LOG_TEXT_SENSOR("", "Total Runtime Formatted", this->total_runtime_formatted_text_sensor_); +} + +} // namespace jk_balancer +} // namespace esphome diff --git a/components/jk_balancer/jk_balancer.h b/components/jk_balancer/jk_balancer.h new file mode 100644 index 00000000..d2a2212a --- /dev/null +++ b/components/jk_balancer/jk_balancer.h @@ -0,0 +1,387 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/binary_sensor/binary_sensor.h" +#include "esphome/components/sensor/sensor.h" +#include "esphome/components/text_sensor/text_sensor.h" +#include "esphome/components/switch/switch.h" +#include "esphome/components/jk_balancer_modbus/jk_balancer_modbus.h" + +namespace esphome { +namespace jk_balancer { + +class JkBalancer : public PollingComponent, public jk_balancer_modbus::JkBalancerModbusDevice { + public: + void set_balancing_binary_sensor(binary_sensor::BinarySensor *balancing_binary_sensor) { + balancing_binary_sensor_ = balancing_binary_sensor; + } + void set_balancing_switch_binary_sensor(binary_sensor::BinarySensor *balancing_switch_binary_sensor) { + balancing_switch_binary_sensor_ = balancing_switch_binary_sensor; + } + void set_charging_binary_sensor(binary_sensor::BinarySensor *charging_binary_sensor) { + charging_binary_sensor_ = charging_binary_sensor; + } + void set_charging_switch_binary_sensor(binary_sensor::BinarySensor *charging_switch_binary_sensor) { + charging_switch_binary_sensor_ = charging_switch_binary_sensor; + } + void set_discharging_binary_sensor(binary_sensor::BinarySensor *discharging_binary_sensor) { + discharging_binary_sensor_ = discharging_binary_sensor; + } + void set_discharging_switch_binary_sensor(binary_sensor::BinarySensor *discharging_switch_binary_sensor) { + discharging_switch_binary_sensor_ = discharging_switch_binary_sensor; + } + void set_dedicated_charger_switch_binary_sensor(binary_sensor::BinarySensor *dedicated_charger_switch_binary_sensor) { + dedicated_charger_switch_binary_sensor_ = dedicated_charger_switch_binary_sensor; + } + void set_online_status_binary_sensor(binary_sensor::BinarySensor *online_status_binary_sensor) { + online_status_binary_sensor_ = online_status_binary_sensor; + } + + void set_min_cell_voltage_sensor(sensor::Sensor *min_cell_voltage_sensor) { + min_cell_voltage_sensor_ = min_cell_voltage_sensor; + } + void set_max_cell_voltage_sensor(sensor::Sensor *max_cell_voltage_sensor) { + max_cell_voltage_sensor_ = max_cell_voltage_sensor; + } + void set_min_voltage_cell_sensor(sensor::Sensor *min_voltage_cell_sensor) { + min_voltage_cell_sensor_ = min_voltage_cell_sensor; + } + void set_max_voltage_cell_sensor(sensor::Sensor *max_voltage_cell_sensor) { + max_voltage_cell_sensor_ = max_voltage_cell_sensor; + } + void set_delta_cell_voltage_sensor(sensor::Sensor *delta_cell_voltage_sensor) { + delta_cell_voltage_sensor_ = delta_cell_voltage_sensor; + } + void set_average_cell_voltage_sensor(sensor::Sensor *average_cell_voltage_sensor) { + average_cell_voltage_sensor_ = average_cell_voltage_sensor; + } + void set_cell_voltage_sensor(uint8_t cell, sensor::Sensor *cell_voltage_sensor) { + this->cells_[cell].cell_voltage_sensor_ = cell_voltage_sensor; + } + void set_power_tube_temperature_sensor(sensor::Sensor *power_tube_temperature_sensor) { + power_tube_temperature_sensor_ = power_tube_temperature_sensor; + } + void set_temperature_sensor_1_sensor(sensor::Sensor *temperature_sensor_1_sensor) { + temperature_sensor_1_sensor_ = temperature_sensor_1_sensor; + } + void set_temperature_sensor_2_sensor(sensor::Sensor *temperature_sensor_2_sensor) { + temperature_sensor_2_sensor_ = temperature_sensor_2_sensor; + } + void set_total_voltage_sensor(sensor::Sensor *total_voltage_sensor) { total_voltage_sensor_ = total_voltage_sensor; } + void set_current_sensor(sensor::Sensor *current_sensor) { current_sensor_ = current_sensor; } + void set_power_sensor(sensor::Sensor *power_sensor) { power_sensor_ = power_sensor; } + void set_charging_power_sensor(sensor::Sensor *charging_power_sensor) { + charging_power_sensor_ = charging_power_sensor; + } + void set_discharging_power_sensor(sensor::Sensor *discharging_power_sensor) { + discharging_power_sensor_ = discharging_power_sensor; + } + void set_capacity_remaining_sensor(sensor::Sensor *capacity_remaining_sensor) { + capacity_remaining_sensor_ = capacity_remaining_sensor; + } + void set_capacity_remaining_derived_sensor(sensor::Sensor *capacity_remaining_derived_sensor) { + capacity_remaining_derived_sensor_ = capacity_remaining_derived_sensor; + } + void set_temperature_sensors_sensor(sensor::Sensor *temperature_sensors_sensor) { + temperature_sensors_sensor_ = temperature_sensors_sensor; + } + void set_charging_cycles_sensor(sensor::Sensor *charging_cycles_sensor) { + charging_cycles_sensor_ = charging_cycles_sensor; + } + void set_total_charging_cycle_capacity_sensor(sensor::Sensor *total_charging_cycle_capacity_sensor) { + total_charging_cycle_capacity_sensor_ = total_charging_cycle_capacity_sensor; + } + void set_battery_strings_sensor(sensor::Sensor *battery_strings_sensor) { + battery_strings_sensor_ = battery_strings_sensor; + } + void set_errors_bitmask_sensor(sensor::Sensor *errors_bitmask_sensor) { + errors_bitmask_sensor_ = errors_bitmask_sensor; + } + void set_operation_mode_bitmask_sensor(sensor::Sensor *operation_mode_bitmask_sensor) { + operation_mode_bitmask_sensor_ = operation_mode_bitmask_sensor; + } + void set_total_voltage_overvoltage_protection_sensor(sensor::Sensor *total_voltage_overvoltage_protection_sensor) { + total_voltage_overvoltage_protection_sensor_ = total_voltage_overvoltage_protection_sensor; + } + void set_total_voltage_undervoltage_protection_sensor(sensor::Sensor *total_voltage_undervoltage_protection_sensor) { + total_voltage_undervoltage_protection_sensor_ = total_voltage_undervoltage_protection_sensor; + } + void set_cell_voltage_overvoltage_protection_sensor(sensor::Sensor *cell_voltage_overvoltage_protection_sensor) { + cell_voltage_overvoltage_protection_sensor_ = cell_voltage_overvoltage_protection_sensor; + } + void set_cell_voltage_overvoltage_recovery_sensor(sensor::Sensor *cell_voltage_overvoltage_recovery_sensor) { + cell_voltage_overvoltage_recovery_sensor_ = cell_voltage_overvoltage_recovery_sensor; + } + void set_cell_voltage_overvoltage_delay_sensor(sensor::Sensor *cell_voltage_overvoltage_delay_sensor) { + cell_voltage_overvoltage_delay_sensor_ = cell_voltage_overvoltage_delay_sensor; + } + void set_cell_voltage_undervoltage_protection_sensor(sensor::Sensor *cell_voltage_undervoltage_protection_sensor) { + cell_voltage_undervoltage_protection_sensor_ = cell_voltage_undervoltage_protection_sensor; + } + void set_cell_voltage_undervoltage_recovery_sensor(sensor::Sensor *cell_voltage_undervoltage_recovery_sensor) { + cell_voltage_undervoltage_recovery_sensor_ = cell_voltage_undervoltage_recovery_sensor; + } + void set_cell_voltage_undervoltage_delay_sensor(sensor::Sensor *cell_voltage_undervoltage_delay_sensor) { + cell_voltage_undervoltage_delay_sensor_ = cell_voltage_undervoltage_delay_sensor; + } + void set_cell_pressure_difference_protection_sensor(sensor::Sensor *cell_pressure_difference_protection_sensor) { + cell_pressure_difference_protection_sensor_ = cell_pressure_difference_protection_sensor; + } + void set_discharging_overcurrent_protection_sensor(sensor::Sensor *discharging_overcurrent_protection_sensor) { + discharging_overcurrent_protection_sensor_ = discharging_overcurrent_protection_sensor; + } + void set_discharging_overcurrent_delay_sensor(sensor::Sensor *discharging_overcurrent_delay_sensor) { + discharging_overcurrent_delay_sensor_ = discharging_overcurrent_delay_sensor; + } + void set_charging_overcurrent_protection_sensor(sensor::Sensor *charging_overcurrent_protection_sensor) { + charging_overcurrent_protection_sensor_ = charging_overcurrent_protection_sensor; + } + void set_charging_overcurrent_delay_sensor(sensor::Sensor *charging_overcurrent_delay_sensor) { + charging_overcurrent_delay_sensor_ = charging_overcurrent_delay_sensor; + } + void set_balance_starting_voltage_sensor(sensor::Sensor *balance_starting_voltage_sensor) { + balance_starting_voltage_sensor_ = balance_starting_voltage_sensor; + } + void set_balance_opening_pressure_difference_sensor(sensor::Sensor *balance_opening_pressure_difference_sensor) { + balance_opening_pressure_difference_sensor_ = balance_opening_pressure_difference_sensor; + } + void set_power_tube_temperature_protection_sensor(sensor::Sensor *power_tube_temperature_protection_sensor) { + power_tube_temperature_protection_sensor_ = power_tube_temperature_protection_sensor; + } + void set_power_tube_temperature_recovery_sensor(sensor::Sensor *power_tube_temperature_recovery_sensor) { + power_tube_temperature_recovery_sensor_ = power_tube_temperature_recovery_sensor; + } + void set_temperature_sensor_temperature_protection_sensor( + sensor::Sensor *temperature_sensor_temperature_protection_sensor) { + temperature_sensor_temperature_protection_sensor_ = temperature_sensor_temperature_protection_sensor; + } + void set_temperature_sensor_temperature_recovery_sensor( + sensor::Sensor *temperature_sensor_temperature_recovery_sensor) { + temperature_sensor_temperature_recovery_sensor_ = temperature_sensor_temperature_recovery_sensor; + } + void set_temperature_sensor_temperature_difference_protection_sensor( + sensor::Sensor *temperature_sensor_temperature_difference_protection_sensor) { + temperature_sensor_temperature_difference_protection_sensor_ = + temperature_sensor_temperature_difference_protection_sensor; + } + void set_charging_high_temperature_protection_sensor(sensor::Sensor *charging_high_temperature_protection_sensor) { + charging_high_temperature_protection_sensor_ = charging_high_temperature_protection_sensor; + } + void set_discharging_high_temperature_protection_sensor( + sensor::Sensor *discharging_high_temperature_protection_sensor) { + discharging_high_temperature_protection_sensor_ = discharging_high_temperature_protection_sensor; + } + void set_charging_low_temperature_protection_sensor(sensor::Sensor *charging_low_temperature_protection_sensor) { + charging_low_temperature_protection_sensor_ = charging_low_temperature_protection_sensor; + } + void set_charging_low_temperature_recovery_sensor(sensor::Sensor *charging_low_temperature_recovery_sensor) { + charging_low_temperature_recovery_sensor_ = charging_low_temperature_recovery_sensor; + } + void set_discharging_low_temperature_protection_sensor( + sensor::Sensor *discharging_low_temperature_protection_sensor) { + discharging_low_temperature_protection_sensor_ = discharging_low_temperature_protection_sensor; + } + void set_discharging_low_temperature_recovery_sensor(sensor::Sensor *discharging_low_temperature_recovery_sensor) { + discharging_low_temperature_recovery_sensor_ = discharging_low_temperature_recovery_sensor; + } + void set_total_battery_capacity_setting_sensor(sensor::Sensor *total_battery_capacity_setting_sensor) { + total_battery_capacity_setting_sensor_ = total_battery_capacity_setting_sensor; + } + void set_current_calibration_sensor(sensor::Sensor *current_calibration_sensor) { + current_calibration_sensor_ = current_calibration_sensor; + } + void set_device_address_sensor(sensor::Sensor *device_address_sensor) { + device_address_sensor_ = device_address_sensor; + } + void set_sleep_wait_time_sensor(sensor::Sensor *sleep_wait_time_sensor) { + sleep_wait_time_sensor_ = sleep_wait_time_sensor; + } + void set_alarm_low_volume_sensor(sensor::Sensor *alarm_low_volume_sensor) { + alarm_low_volume_sensor_ = alarm_low_volume_sensor; + } + void set_manufacturing_date_sensor(sensor::Sensor *manufacturing_date_sensor) { + manufacturing_date_sensor_ = manufacturing_date_sensor; + } + void set_total_runtime_sensor(sensor::Sensor *total_runtime_sensor) { total_runtime_sensor_ = total_runtime_sensor; } + void set_start_current_calibration_sensor(sensor::Sensor *start_current_calibration_sensor) { + start_current_calibration_sensor_ = start_current_calibration_sensor; + } + void set_actual_battery_capacity_sensor(sensor::Sensor *actual_battery_capacity_sensor) { + actual_battery_capacity_sensor_ = actual_battery_capacity_sensor; + } + void set_protocol_version_sensor(sensor::Sensor *protocol_version_sensor) { + protocol_version_sensor_ = protocol_version_sensor; + } + + void set_charging_switch(switch_::Switch *charging_switch) { charging_switch_ = charging_switch; } + void set_discharging_switch(switch_::Switch *discharging_switch) { discharging_switch_ = discharging_switch; } + void set_balancer_switch(switch_::Switch *balancer_switch) { balancer_switch_ = balancer_switch; } + + void set_errors_text_sensor(text_sensor::TextSensor *errors_text_sensor) { errors_text_sensor_ = errors_text_sensor; } + void set_operation_mode_text_sensor(text_sensor::TextSensor *operation_mode_text_sensor) { + operation_mode_text_sensor_ = operation_mode_text_sensor; + } + void set_battery_type_text_sensor(text_sensor::TextSensor *battery_type_text_sensor) { + battery_type_text_sensor_ = battery_type_text_sensor; + } + void set_password_text_sensor(text_sensor::TextSensor *password_text_sensor) { + password_text_sensor_ = password_text_sensor; + } + void set_device_type_text_sensor(text_sensor::TextSensor *device_type_text_sensor) { + device_type_text_sensor_ = device_type_text_sensor; + } + void set_software_version_text_sensor(text_sensor::TextSensor *software_version_text_sensor) { + software_version_text_sensor_ = software_version_text_sensor; + } + void set_manufacturer_text_sensor(text_sensor::TextSensor *manufacturer_text_sensor) { + manufacturer_text_sensor_ = manufacturer_text_sensor; + } + void set_total_runtime_formatted_text_sensor(text_sensor::TextSensor *total_runtime_formatted_text_sensor) { + total_runtime_formatted_text_sensor_ = total_runtime_formatted_text_sensor; + } + + void dump_config() override; + + void on_jk_balancer_modbus_data(const uint8_t &function, const std::vector &data) override; + + void update() override; + + protected: + binary_sensor::BinarySensor *balancing_binary_sensor_; + binary_sensor::BinarySensor *balancing_switch_binary_sensor_; + binary_sensor::BinarySensor *charging_binary_sensor_; + binary_sensor::BinarySensor *charging_switch_binary_sensor_; + binary_sensor::BinarySensor *discharging_binary_sensor_; + binary_sensor::BinarySensor *discharging_switch_binary_sensor_; + binary_sensor::BinarySensor *dedicated_charger_switch_binary_sensor_; + binary_sensor::BinarySensor *online_status_binary_sensor_; + + sensor::Sensor *min_cell_voltage_sensor_; + sensor::Sensor *max_cell_voltage_sensor_; + sensor::Sensor *min_voltage_cell_sensor_; + sensor::Sensor *max_voltage_cell_sensor_; + sensor::Sensor *delta_cell_voltage_sensor_; + sensor::Sensor *average_cell_voltage_sensor_; + sensor::Sensor *power_tube_temperature_sensor_; + sensor::Sensor *temperature_sensor_1_sensor_; + sensor::Sensor *temperature_sensor_2_sensor_; + sensor::Sensor *total_voltage_sensor_; + sensor::Sensor *current_sensor_; + sensor::Sensor *power_sensor_; + sensor::Sensor *charging_power_sensor_; + sensor::Sensor *discharging_power_sensor_; + sensor::Sensor *capacity_remaining_sensor_; + sensor::Sensor *capacity_remaining_derived_sensor_; + sensor::Sensor *temperature_sensors_sensor_; + sensor::Sensor *charging_cycles_sensor_; + sensor::Sensor *total_charging_cycle_capacity_sensor_; + sensor::Sensor *battery_strings_sensor_; + sensor::Sensor *errors_bitmask_sensor_; + sensor::Sensor *operation_mode_bitmask_sensor_; + sensor::Sensor *total_voltage_overvoltage_protection_sensor_; + sensor::Sensor *total_voltage_undervoltage_protection_sensor_; + sensor::Sensor *cell_voltage_overvoltage_protection_sensor_; + sensor::Sensor *cell_voltage_overvoltage_recovery_sensor_; + sensor::Sensor *cell_voltage_overvoltage_delay_sensor_; + sensor::Sensor *cell_voltage_undervoltage_protection_sensor_; + sensor::Sensor *cell_voltage_undervoltage_recovery_sensor_; + sensor::Sensor *cell_voltage_undervoltage_delay_sensor_; + sensor::Sensor *cell_pressure_difference_protection_sensor_; + sensor::Sensor *discharging_overcurrent_protection_sensor_; + sensor::Sensor *discharging_overcurrent_delay_sensor_; + sensor::Sensor *charging_overcurrent_protection_sensor_; + sensor::Sensor *charging_overcurrent_delay_sensor_; + sensor::Sensor *balance_starting_voltage_sensor_; + sensor::Sensor *balance_opening_pressure_difference_sensor_; + sensor::Sensor *power_tube_temperature_protection_sensor_; + sensor::Sensor *power_tube_temperature_recovery_sensor_; + sensor::Sensor *temperature_sensor_temperature_protection_sensor_; + sensor::Sensor *temperature_sensor_temperature_recovery_sensor_; + sensor::Sensor *temperature_sensor_temperature_difference_protection_sensor_; + sensor::Sensor *charging_high_temperature_protection_sensor_; + sensor::Sensor *discharging_high_temperature_protection_sensor_; + sensor::Sensor *charging_low_temperature_protection_sensor_; + sensor::Sensor *charging_low_temperature_recovery_sensor_; + sensor::Sensor *discharging_low_temperature_protection_sensor_; + sensor::Sensor *discharging_low_temperature_recovery_sensor_; + sensor::Sensor *total_battery_capacity_setting_sensor_; + sensor::Sensor *charging_sensor_; + sensor::Sensor *discharging_sensor_; + sensor::Sensor *current_calibration_sensor_; + sensor::Sensor *device_address_sensor_; + sensor::Sensor *sleep_wait_time_sensor_; + sensor::Sensor *alarm_low_volume_sensor_; + sensor::Sensor *password_sensor_; + sensor::Sensor *manufacturing_date_sensor_; + sensor::Sensor *total_runtime_sensor_; + sensor::Sensor *start_current_calibration_sensor_; + sensor::Sensor *actual_battery_capacity_sensor_; + sensor::Sensor *protocol_version_sensor_; + + switch_::Switch *charging_switch_; + switch_::Switch *discharging_switch_; + switch_::Switch *balancer_switch_; + + text_sensor::TextSensor *errors_text_sensor_; + text_sensor::TextSensor *operation_mode_text_sensor_; + text_sensor::TextSensor *battery_type_text_sensor_; + text_sensor::TextSensor *password_text_sensor_; + text_sensor::TextSensor *device_type_text_sensor_; + text_sensor::TextSensor *software_version_text_sensor_; + text_sensor::TextSensor *manufacturer_text_sensor_; + text_sensor::TextSensor *total_runtime_formatted_text_sensor_; + + struct Cell { + sensor::Sensor *cell_voltage_sensor_{nullptr}; + } cells_[24]; + + uint8_t no_response_count_{0}; + + void on_status_data_(const std::vector &data); + void publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state); + void publish_state_(sensor::Sensor *sensor, float value); + void publish_state_(switch_::Switch *obj, const bool &state); + void publish_state_(text_sensor::TextSensor *text_sensor, const std::string &state); + void publish_device_unavailable_(); + void reset_online_status_tracker_(); + void track_online_status_(); + + std::string error_bits_to_string_(uint16_t bitmask); + std::string mode_bits_to_string_(uint16_t bitmask); + + float get_temperature_(const uint16_t value) { + if (value > 100) + return (float) (100 - (int16_t) value); + + return (float) value; + }; + + float get_current_(const uint16_t value, const uint8_t protocol_version) { + float current = 0.0f; + if (protocol_version == 0x01) { + if ((value & 0x8000) == 0x8000) { + current = (float) (value & 0x7FFF); + } else { + current = (float) (value & 0x7FFF) * -1; + } + } + + return current; + }; + + std::string format_total_runtime_(const uint32_t value) { + int seconds = (int) value; + int years = seconds / (24 * 3600 * 365); + seconds = seconds % (24 * 3600 * 365); + int days = seconds / (24 * 3600); + seconds = seconds % (24 * 3600); + int hours = seconds / 3600; + return (years ? to_string(years) + "y " : "") + (days ? to_string(days) + "d " : "") + + (hours ? to_string(hours) + "h" : ""); + } + + bool check_bit_(uint16_t mask, uint16_t flag) { return (mask & flag) == flag; } +}; + +} // namespace jk_balancer +} // namespace esphome diff --git a/components/jk_balancer/sensor.py b/components/jk_balancer/sensor.py new file mode 100644 index 00000000..050ccc1c --- /dev/null +++ b/components/jk_balancer/sensor.py @@ -0,0 +1,836 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_CURRENT, + CONF_POWER, + DEVICE_CLASS_BATTERY, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_EMPTY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_TEMPERATURE, + DEVICE_CLASS_VOLTAGE, + ICON_COUNTER, + ICON_EMPTY, + ICON_TIMELAPSE, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_CELSIUS, + UNIT_EMPTY, + UNIT_PERCENT, + UNIT_VOLT, + UNIT_WATT, +) + +from . import CONF_JK_BALANCER_ID, JK_BALANCER_COMPONENT_SCHEMA + +DEPENDENCIES = ["jk_balancer"] + +CODEOWNERS = ["@syssi"] + +CONF_MIN_CELL_VOLTAGE = "min_cell_voltage" +CONF_MAX_CELL_VOLTAGE = "max_cell_voltage" +CONF_MIN_VOLTAGE_CELL = "min_voltage_cell" +CONF_MAX_VOLTAGE_CELL = "max_voltage_cell" +CONF_DELTA_CELL_VOLTAGE = "delta_cell_voltage" +CONF_AVERAGE_CELL_VOLTAGE = "average_cell_voltage" +CONF_CELL_VOLTAGE_1 = "cell_voltage_1" +CONF_CELL_VOLTAGE_2 = "cell_voltage_2" +CONF_CELL_VOLTAGE_3 = "cell_voltage_3" +CONF_CELL_VOLTAGE_4 = "cell_voltage_4" +CONF_CELL_VOLTAGE_5 = "cell_voltage_5" +CONF_CELL_VOLTAGE_6 = "cell_voltage_6" +CONF_CELL_VOLTAGE_7 = "cell_voltage_7" +CONF_CELL_VOLTAGE_8 = "cell_voltage_8" +CONF_CELL_VOLTAGE_9 = "cell_voltage_9" +CONF_CELL_VOLTAGE_10 = "cell_voltage_10" +CONF_CELL_VOLTAGE_11 = "cell_voltage_11" +CONF_CELL_VOLTAGE_12 = "cell_voltage_12" +CONF_CELL_VOLTAGE_13 = "cell_voltage_13" +CONF_CELL_VOLTAGE_14 = "cell_voltage_14" +CONF_CELL_VOLTAGE_15 = "cell_voltage_15" +CONF_CELL_VOLTAGE_16 = "cell_voltage_16" +CONF_CELL_VOLTAGE_17 = "cell_voltage_17" +CONF_CELL_VOLTAGE_18 = "cell_voltage_18" +CONF_CELL_VOLTAGE_19 = "cell_voltage_19" +CONF_CELL_VOLTAGE_20 = "cell_voltage_20" +CONF_CELL_VOLTAGE_21 = "cell_voltage_21" +CONF_CELL_VOLTAGE_22 = "cell_voltage_22" +CONF_CELL_VOLTAGE_23 = "cell_voltage_23" +CONF_CELL_VOLTAGE_24 = "cell_voltage_24" + +CONF_POWER_TUBE_TEMPERATURE = "power_tube_temperature" +CONF_TEMPERATURE_SENSOR_1 = "temperature_sensor_1" +CONF_TEMPERATURE_SENSOR_2 = "temperature_sensor_2" +CONF_TOTAL_VOLTAGE = "total_voltage" +CONF_CHARGING_POWER = "charging_power" +CONF_DISCHARGING_POWER = "discharging_power" +CONF_CAPACITY_REMAINING = "capacity_remaining" +CONF_CAPACITY_REMAINING_DERIVED = "capacity_remaining_derived" +CONF_TEMPERATURE_SENSORS = "temperature_sensors" +CONF_CHARGING_CYCLES = "charging_cycles" +CONF_TOTAL_CHARGING_CYCLE_CAPACITY = "total_charging_cycle_capacity" +CONF_BATTERY_STRINGS = "battery_strings" + +CONF_ERRORS_BITMASK = "errors_bitmask" +CONF_OPERATION_MODE_BITMASK = "operation_mode_bitmask" + +CONF_TOTAL_VOLTAGE_OVERVOLTAGE_PROTECTION = "total_voltage_overvoltage_protection" +CONF_TOTAL_VOLTAGE_UNDERVOLTAGE_PROTECTION = "total_voltage_undervoltage_protection" + +CONF_CELL_VOLTAGE_OVERVOLTAGE_PROTECTION = "cell_voltage_overvoltage_protection" +CONF_CELL_VOLTAGE_OVERVOLTAGE_RECOVERY = "cell_voltage_overvoltage_recovery" +CONF_CELL_VOLTAGE_OVERVOLTAGE_DELAY = "cell_voltage_overvoltage_delay" + +CONF_CELL_VOLTAGE_UNDERVOLTAGE_PROTECTION = "cell_voltage_undervoltage_protection" +CONF_CELL_VOLTAGE_UNDERVOLTAGE_RECOVERY = "cell_voltage_undervoltage_recovery" +CONF_CELL_VOLTAGE_UNDERVOLTAGE_DELAY = "cell_voltage_undervoltage_delay" + +CONF_CELL_PRESSURE_DIFFERENCE_PROTECTION = "cell_pressure_difference_protection" + +CONF_DISCHARGING_OVERCURRENT_PROTECTION = "discharging_overcurrent_protection" +CONF_DISCHARGING_OVERCURRENT_DELAY = "discharging_overcurrent_delay" + +CONF_CHARGING_OVERCURRENT_PROTECTION = "charging_overcurrent_protection" +CONF_CHARGING_OVERCURRENT_DELAY = "charging_overcurrent_delay" + +CONF_BALANCE_STARTING_VOLTAGE = "balance_starting_voltage" +CONF_BALANCE_OPENING_PRESSURE_DIFFERENCE = "balance_opening_pressure_difference" + +CONF_POWER_TUBE_TEMPERATURE_PROTECTION = "power_tube_temperature_protection" +CONF_POWER_TUBE_TEMPERATURE_RECOVERY = "power_tube_temperature_recovery" + +CONF_TEMPERATURE_SENSOR_TEMPERATURE_PROTECTION = ( + "temperature_sensor_temperature_protection" +) +CONF_TEMPERATURE_SENSOR_TEMPERATURE_RECOVERY = "temperature_sensor_temperature_recovery" +CONF_TEMPERATURE_SENSOR_TEMPERATURE_DIFFERENCE_PROTECTION = ( + "temperature_sensor_temperature_difference_protection" +) + +CONF_CHARGING_HIGH_TEMPERATURE_PROTECTION = "charging_high_temperature_protection" +CONF_DISCHARGING_HIGH_TEMPERATURE_PROTECTION = "discharging_high_temperature_protection" + +CONF_CHARGING_LOW_TEMPERATURE_PROTECTION = "charging_low_temperature_protection" +CONF_CHARGING_LOW_TEMPERATURE_RECOVERY = "charging_low_temperature_recovery" +CONF_DISCHARGING_LOW_TEMPERATURE_PROTECTION = "discharging_low_temperature_protection" +CONF_DISCHARGING_LOW_TEMPERATURE_RECOVERY = "discharging_low_temperature_recovery" + +# r/w +# CONF_BATTERY_STRINGS = "battery_strings" +CONF_TOTAL_BATTERY_CAPACITY_SETTING = "total_battery_capacity_setting" + +CONF_CURRENT_CALIBRATION = "current_calibration" +CONF_DEVICE_ADDRESS = "device_address" +CONF_SLEEP_WAIT_TIME = "sleep_wait_time" +CONF_ALARM_LOW_VOLUME = "alarm_low_volume" +CONF_MANUFACTURING_DATE = "manufacturing_date" +CONF_TOTAL_RUNTIME = "total_runtime" +CONF_START_CURRENT_CALIBRATION = "start_current_calibration" +CONF_ACTUAL_BATTERY_CAPACITY = "actual_battery_capacity" +CONF_PROTOCOL_VERSION = "protocol_version" + +ICON_CURRENT_DC = "mdi:current-dc" +ICON_MIN_VOLTAGE_CELL = "mdi:battery-minus-outline" +ICON_MAX_VOLTAGE_CELL = "mdi:battery-plus-outline" + +ICON_BATTERY_STRINGS = "mdi:car-battery" +ICON_CAPACITY_REMAINING_DERIVED = "mdi:battery-50" +ICON_ACTUAL_BATTERY_CAPACITY = "mdi:battery-50" +ICON_TOTAL_BATTERY_CAPACITY_SETTING = "mdi:battery-sync" + +ICON_DEVICE_ADDRESS = "mdi:identifier" +ICON_ERRORS_BITMASK = "mdi:alert-circle-outline" +ICON_OPERATION_MODE_BITMASK = "mdi:heart-pulse" +ICON_CHARGING_CYCLES = "mdi:battery-sync" +ICON_ALARM_LOW_VOLUME = "mdi:volume-high" + +UNIT_SECONDS = "s" +UNIT_HOURS = "h" +UNIT_AMPERE_HOURS = "Ah" + +CELLS = [ + CONF_CELL_VOLTAGE_1, + CONF_CELL_VOLTAGE_2, + CONF_CELL_VOLTAGE_3, + CONF_CELL_VOLTAGE_4, + CONF_CELL_VOLTAGE_5, + CONF_CELL_VOLTAGE_6, + CONF_CELL_VOLTAGE_7, + CONF_CELL_VOLTAGE_8, + CONF_CELL_VOLTAGE_9, + CONF_CELL_VOLTAGE_10, + CONF_CELL_VOLTAGE_11, + CONF_CELL_VOLTAGE_12, + CONF_CELL_VOLTAGE_13, + CONF_CELL_VOLTAGE_14, + CONF_CELL_VOLTAGE_15, + CONF_CELL_VOLTAGE_16, + CONF_CELL_VOLTAGE_17, + CONF_CELL_VOLTAGE_18, + CONF_CELL_VOLTAGE_19, + CONF_CELL_VOLTAGE_20, + CONF_CELL_VOLTAGE_21, + CONF_CELL_VOLTAGE_22, + CONF_CELL_VOLTAGE_23, + CONF_CELL_VOLTAGE_24, +] + +SENSORS = [ + CONF_MIN_CELL_VOLTAGE, + CONF_MAX_CELL_VOLTAGE, + CONF_MIN_VOLTAGE_CELL, + CONF_MAX_VOLTAGE_CELL, + CONF_DELTA_CELL_VOLTAGE, + CONF_AVERAGE_CELL_VOLTAGE, + CONF_POWER_TUBE_TEMPERATURE, + CONF_TEMPERATURE_SENSOR_1, + CONF_TEMPERATURE_SENSOR_2, + CONF_TOTAL_VOLTAGE, + CONF_CURRENT, + CONF_POWER, + CONF_CHARGING_POWER, + CONF_DISCHARGING_POWER, + CONF_CAPACITY_REMAINING, + CONF_CAPACITY_REMAINING_DERIVED, + CONF_TEMPERATURE_SENSORS, + CONF_CHARGING_CYCLES, + CONF_TOTAL_CHARGING_CYCLE_CAPACITY, + CONF_BATTERY_STRINGS, + CONF_ERRORS_BITMASK, + CONF_OPERATION_MODE_BITMASK, + CONF_TOTAL_VOLTAGE_OVERVOLTAGE_PROTECTION, + CONF_TOTAL_VOLTAGE_UNDERVOLTAGE_PROTECTION, + CONF_CELL_VOLTAGE_OVERVOLTAGE_PROTECTION, + CONF_CELL_VOLTAGE_OVERVOLTAGE_RECOVERY, + CONF_CELL_VOLTAGE_OVERVOLTAGE_DELAY, + CONF_CELL_VOLTAGE_UNDERVOLTAGE_PROTECTION, + CONF_CELL_VOLTAGE_UNDERVOLTAGE_RECOVERY, + CONF_CELL_VOLTAGE_UNDERVOLTAGE_DELAY, + CONF_CELL_PRESSURE_DIFFERENCE_PROTECTION, + CONF_DISCHARGING_OVERCURRENT_PROTECTION, + CONF_DISCHARGING_OVERCURRENT_DELAY, + CONF_CHARGING_OVERCURRENT_PROTECTION, + CONF_CHARGING_OVERCURRENT_DELAY, + CONF_BALANCE_STARTING_VOLTAGE, + CONF_BALANCE_OPENING_PRESSURE_DIFFERENCE, + CONF_POWER_TUBE_TEMPERATURE_PROTECTION, + CONF_POWER_TUBE_TEMPERATURE_RECOVERY, + CONF_TEMPERATURE_SENSOR_TEMPERATURE_PROTECTION, + CONF_TEMPERATURE_SENSOR_TEMPERATURE_RECOVERY, + CONF_TEMPERATURE_SENSOR_TEMPERATURE_DIFFERENCE_PROTECTION, + CONF_CHARGING_HIGH_TEMPERATURE_PROTECTION, + CONF_DISCHARGING_HIGH_TEMPERATURE_PROTECTION, + CONF_CHARGING_LOW_TEMPERATURE_PROTECTION, + CONF_CHARGING_LOW_TEMPERATURE_RECOVERY, + CONF_DISCHARGING_LOW_TEMPERATURE_PROTECTION, + CONF_DISCHARGING_LOW_TEMPERATURE_RECOVERY, + CONF_TOTAL_BATTERY_CAPACITY_SETTING, + CONF_CURRENT_CALIBRATION, + CONF_DEVICE_ADDRESS, + CONF_SLEEP_WAIT_TIME, + CONF_ALARM_LOW_VOLUME, + CONF_MANUFACTURING_DATE, + CONF_TOTAL_RUNTIME, + CONF_START_CURRENT_CALIBRATION, + CONF_ACTUAL_BATTERY_CAPACITY, + CONF_PROTOCOL_VERSION, +] + +# pylint: disable=too-many-function-args +CONFIG_SCHEMA = JK_BALANCER_COMPONENT_SCHEMA.extend( + { + cv.Optional(CONF_MIN_CELL_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_CELL_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MIN_VOLTAGE_CELL): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_MIN_VOLTAGE_CELL, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MAX_VOLTAGE_CELL): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_MAX_VOLTAGE_CELL, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DELTA_CELL_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_AVERAGE_CELL_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_1): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_2): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_3): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_4): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_5): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_6): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_7): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_8): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_9): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_10): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_11): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_12): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_13): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_14): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_15): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_16): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_17): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_18): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_19): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_20): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_21): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_22): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_23): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_24): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_TUBE_TEMPERATURE): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_SENSOR_1): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_SENSOR_2): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_DC, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_EMPTY, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHARGING_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_EMPTY, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISCHARGING_POWER): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + icon=ICON_EMPTY, + accuracy_decimals=2, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CAPACITY_REMAINING): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_BATTERY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CAPACITY_REMAINING_DERIVED): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE_HOURS, + icon=ICON_CAPACITY_REMAINING_DERIVED, + accuracy_decimals=1, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_SENSORS): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHARGING_CYCLES): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_CHARGING_CYCLES, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_CHARGING_CYCLE_CAPACITY): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE_HOURS, + icon=ICON_COUNTER, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BATTERY_STRINGS): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_BATTERY_STRINGS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ERRORS_BITMASK): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_ERRORS_BITMASK, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_OPERATION_MODE_BITMASK): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_OPERATION_MODE_BITMASK, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_VOLTAGE_OVERVOLTAGE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_VOLTAGE_UNDERVOLTAGE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=2, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_OVERVOLTAGE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_OVERVOLTAGE_RECOVERY): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_OVERVOLTAGE_DELAY): sensor.sensor_schema( + unit_of_measurement=UNIT_SECONDS, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_UNDERVOLTAGE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_UNDERVOLTAGE_RECOVERY): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_VOLTAGE_UNDERVOLTAGE_DELAY): sensor.sensor_schema( + unit_of_measurement=UNIT_SECONDS, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CELL_PRESSURE_DIFFERENCE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISCHARGING_OVERCURRENT_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_DC, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISCHARGING_OVERCURRENT_DELAY): sensor.sensor_schema( + unit_of_measurement=UNIT_SECONDS, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHARGING_OVERCURRENT_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_DC, + accuracy_decimals=0, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHARGING_OVERCURRENT_DELAY): sensor.sensor_schema( + unit_of_measurement=UNIT_SECONDS, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BALANCE_STARTING_VOLTAGE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_BALANCE_OPENING_PRESSURE_DIFFERENCE): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + icon=ICON_EMPTY, + accuracy_decimals=3, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_TUBE_TEMPERATURE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_POWER_TUBE_TEMPERATURE_RECOVERY): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + CONF_TEMPERATURE_SENSOR_TEMPERATURE_PROTECTION + ): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TEMPERATURE_SENSOR_TEMPERATURE_RECOVERY): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional( + CONF_TEMPERATURE_SENSOR_TEMPERATURE_DIFFERENCE_PROTECTION + ): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHARGING_HIGH_TEMPERATURE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISCHARGING_HIGH_TEMPERATURE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHARGING_LOW_TEMPERATURE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CHARGING_LOW_TEMPERATURE_RECOVERY): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISCHARGING_LOW_TEMPERATURE_PROTECTION): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DISCHARGING_LOW_TEMPERATURE_RECOVERY): sensor.sensor_schema( + unit_of_measurement=UNIT_CELSIUS, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_TEMPERATURE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_BATTERY_CAPACITY_SETTING): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE_HOURS, + icon=ICON_TOTAL_BATTERY_CAPACITY_SETTING, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_CURRENT_CALIBRATION): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + icon=ICON_CURRENT_DC, + accuracy_decimals=3, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_DEVICE_ADDRESS): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_DEVICE_ADDRESS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_SLEEP_WAIT_TIME): sensor.sensor_schema( + unit_of_measurement=UNIT_SECONDS, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ALARM_LOW_VOLUME): sensor.sensor_schema( + unit_of_measurement=UNIT_PERCENT, + icon=ICON_ALARM_LOW_VOLUME, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_MANUFACTURING_DATE): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_TOTAL_RUNTIME): sensor.sensor_schema( + unit_of_measurement=UNIT_HOURS, + icon=ICON_TIMELAPSE, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional(CONF_START_CURRENT_CALIBRATION): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_ACTUAL_BATTERY_CAPACITY): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE_HOURS, + icon=ICON_ACTUAL_BATTERY_CAPACITY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional(CONF_PROTOCOL_VERSION): sensor.sensor_schema( + unit_of_measurement=UNIT_EMPTY, + icon=ICON_EMPTY, + accuracy_decimals=0, + device_class=DEVICE_CLASS_EMPTY, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_JK_BALANCER_ID]) + for i, key in enumerate(CELLS): + if key in config: + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(hub.set_cell_voltage_sensor(i, sens)) + for key in SENSORS: + if key in config: + conf = config[key] + sens = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}_sensor")(sens)) diff --git a/components/jk_balancer/switch/__init__.py b/components/jk_balancer/switch/__init__.py new file mode 100644 index 00000000..f4a5890d --- /dev/null +++ b/components/jk_balancer/switch/__init__.py @@ -0,0 +1,60 @@ +import esphome.codegen as cg +from esphome.components import switch +import esphome.config_validation as cv +from esphome.const import CONF_ICON, CONF_ID + +from .. import CONF_JK_BALANCER_ID, JK_BALANCER_COMPONENT_SCHEMA, jk_balancer_ns +from ..const import CONF_BALANCER, CONF_CHARGING, CONF_DISCHARGING + +DEPENDENCIES = ["jk_balancer"] + +CODEOWNERS = ["@syssi"] + +ICON_CHARGING = "mdi:battery-charging-50" +ICON_DISCHARGING = "mdi:battery-charging-50" +ICON_BALANCER = "mdi:seesaw" + +SWITCHES = { + CONF_CHARGING: 0xAB, + CONF_DISCHARGING: 0xAC, + # The BMS (v11) doesn't accept updates of register 0x9D at the moment + CONF_BALANCER: 0x9D, +} + +JkSwitch = jk_balancer_ns.class_("JkSwitch", switch.Switch, cg.Component) + +CONFIG_SCHEMA = JK_BALANCER_COMPONENT_SCHEMA.extend( + { + cv.Optional(CONF_CHARGING): switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(JkSwitch), + cv.Optional(CONF_ICON, default=ICON_CHARGING): cv.icon, + } + ).extend(cv.COMPONENT_SCHEMA), + cv.Optional(CONF_DISCHARGING): switch.SWITCH_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(JkSwitch), + cv.Optional(CONF_ICON, default=ICON_DISCHARGING): cv.icon, + } + ).extend(cv.COMPONENT_SCHEMA), + # cv.Optional(CONF_BALANCER): switch.SWITCH_SCHEMA.extend( + # { + # cv.GenerateID(): cv.declare_id(JkSwitch), + # cv.Optional(CONF_ICON, default=ICON_BALANCER): cv.icon, + # } + # ).extend(cv.COMPONENT_SCHEMA), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_JK_BALANCER_ID]) + for key, address in SWITCHES.items(): + if key in config: + conf = config[key] + var = cg.new_Pvariable(conf[CONF_ID]) + await cg.register_component(var, conf) + await switch.register_switch(var, conf) + cg.add(getattr(hub, f"set_{key}_switch")(var)) + cg.add(var.set_parent(hub)) + cg.add(var.set_holding_register(address)) diff --git a/components/jk_balancer/switch/jk_switch.cpp b/components/jk_balancer/switch/jk_switch.cpp new file mode 100644 index 00000000..fadee510 --- /dev/null +++ b/components/jk_balancer/switch/jk_switch.cpp @@ -0,0 +1,17 @@ +#include "jk_switch.h" +#include "esphome/core/log.h" +#include "esphome/core/application.h" + +namespace esphome { +namespace jk_balancer { + +static const char *const TAG = "jk_balancer.switch"; + +void JkSwitch::dump_config() { LOG_SWITCH("", "JkBalancer Switch", this); } +void JkSwitch::write_state(bool state) { + this->parent_->write_register(this->holding_register_, (uint8_t) state); + this->publish_state(state); +} + +} // namespace jk_balancer +} // namespace esphome diff --git a/components/jk_balancer/switch/jk_switch.h b/components/jk_balancer/switch/jk_switch.h new file mode 100644 index 00000000..b1560ad5 --- /dev/null +++ b/components/jk_balancer/switch/jk_switch.h @@ -0,0 +1,26 @@ +#pragma once + +#include "../jk_balancer.h" +#include "esphome/core/component.h" +#include "esphome/components/switch/switch.h" + +namespace esphome { +namespace jk_balancer { + +class JkBalancer; +class JkSwitch : public switch_::Switch, public Component { + public: + void set_parent(JkBalancer *parent) { this->parent_ = parent; }; + void set_holding_register(uint8_t holding_register) { this->holding_register_ = holding_register; }; + void dump_config() override; + void loop() override {} + float get_setup_priority() const override { return setup_priority::DATA; } + + protected: + void write_state(bool state) override; + JkBalancer *parent_; + uint8_t holding_register_; +}; + +} // namespace jk_balancer +} // namespace esphome diff --git a/components/jk_balancer/text_sensor.py b/components/jk_balancer/text_sensor.py new file mode 100644 index 00000000..51bd5355 --- /dev/null +++ b/components/jk_balancer/text_sensor.py @@ -0,0 +1,99 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import CONF_ICON, CONF_ID, CONF_PASSWORD, ICON_EMPTY, ICON_TIMELAPSE + +from . import CONF_JK_BALANCER_ID, JK_BALANCER_COMPONENT_SCHEMA + +DEPENDENCIES = ["jk_balancer"] + +CODEOWNERS = ["@syssi"] + +CONF_BATTERY_TYPE = "battery_type" +CONF_ERRORS = "errors" +CONF_OPERATION_MODE = "operation_mode" +CONF_DEVICE_TYPE = "device_type" +CONF_SOFTWARE_VERSION = "software_version" +CONF_MANUFACTURER = "manufacturer" +CONF_TOTAL_RUNTIME_FORMATTED = "total_runtime_formatted" + +ICON_BATTERY_TYPE = "mdi:car-battery" +ICON_ERRORS = "mdi:alert-circle-outline" +ICON_OPERATION_MODE = "mdi:heart-pulse" +ICON_PASSWORD = "mdi:lock-outline" + +TEXT_SENSORS = [ + CONF_OPERATION_MODE, + CONF_ERRORS, + CONF_BATTERY_TYPE, + CONF_PASSWORD, + CONF_DEVICE_TYPE, + CONF_SOFTWARE_VERSION, + CONF_MANUFACTURER, + CONF_TOTAL_RUNTIME_FORMATTED, +] + +CONFIG_SCHEMA = JK_BALANCER_COMPONENT_SCHEMA.extend( + { + cv.Optional(CONF_OPERATION_MODE): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_OPERATION_MODE): cv.icon, + } + ), + cv.Optional(CONF_ERRORS): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_ERRORS): cv.icon, + } + ), + cv.Optional(CONF_BATTERY_TYPE): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_BATTERY_TYPE): cv.icon, + } + ), + cv.Optional(CONF_PASSWORD): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_PASSWORD): cv.icon, + } + ), + cv.Optional(CONF_DEVICE_TYPE): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_EMPTY): cv.icon, + } + ), + cv.Optional(CONF_SOFTWARE_VERSION): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_EMPTY): cv.icon, + } + ), + cv.Optional(CONF_MANUFACTURER): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_EMPTY): cv.icon, + } + ), + cv.Optional( + CONF_TOTAL_RUNTIME_FORMATTED + ): text_sensor.TEXT_SENSOR_SCHEMA.extend( + { + cv.GenerateID(): cv.declare_id(text_sensor.TextSensor), + cv.Optional(CONF_ICON, default=ICON_TIMELAPSE): cv.icon, + } + ), + } +) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_JK_BALANCER_ID]) + for key in TEXT_SENSORS: + if key in config: + conf = config[key] + sens = cg.new_Pvariable(conf[CONF_ID]) + await text_sensor.register_text_sensor(sens, conf) + cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) diff --git a/components/jk_balancer_modbus/__init__.py b/components/jk_balancer_modbus/__init__.py new file mode 100644 index 00000000..d7191db0 --- /dev/null +++ b/components/jk_balancer_modbus/__init__.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ADDRESS, CONF_ID + +DEPENDENCIES = ["uart"] +MULTI_CONF = True + +jk_balancer_modbus_ns = cg.esphome_ns.namespace("jk_balancer_modbus") +JkBalancerModbus = jk_balancer_modbus_ns.class_( + "JkBalancerModbus", cg.Component, uart.UARTDevice +) +JkBalancerModbusDevice = jk_balancer_modbus_ns.class_("JkBalancerModbusDevice") + +CONF_JK_BALANCER_MODBUS_ID = "jk_balancer_modbus_id" +CONF_RX_TIMEOUT = "rx_timeout" + +CONFIG_SCHEMA = ( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(JkBalancerModbus), + cv.Optional( + CONF_RX_TIMEOUT, default="50ms" + ): cv.positive_time_period_milliseconds, + } + ) + .extend(cv.COMPONENT_SCHEMA) + .extend(uart.UART_DEVICE_SCHEMA) +) + + +async def to_code(config): + cg.add_global(jk_balancer_modbus_ns.using) + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + + await uart.register_uart_device(var, config) + + cg.add(var.set_rx_timeout(config[CONF_RX_TIMEOUT])) + + +def jk_balancer_modbus_device_schema(default_address): + schema = { + cv.GenerateID(CONF_JK_BALANCER_MODBUS_ID): cv.use_id(JkBalancerModbus), + } + if default_address is None: + schema[cv.Required(CONF_ADDRESS)] = cv.hex_uint8_t + else: + schema[cv.Optional(CONF_ADDRESS, default=default_address)] = cv.hex_uint8_t + return cv.Schema(schema) + + +async def register_jk_balancer_modbus_device(var, config): + parent = await cg.get_variable(config[CONF_JK_BALANCER_MODBUS_ID]) + cg.add(var.set_parent(parent)) + cg.add(var.set_address(config[CONF_ADDRESS])) + cg.add(parent.register_device(var)) diff --git a/components/jk_balancer_modbus/jk_balancer_modbus.cpp b/components/jk_balancer_modbus/jk_balancer_modbus.cpp new file mode 100644 index 00000000..902eb863 --- /dev/null +++ b/components/jk_balancer_modbus/jk_balancer_modbus.cpp @@ -0,0 +1,139 @@ +#include "jk_balancer_modbus.h" +#include "esphome/core/log.h" +#include "esphome/core/hal.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace jk_balancer_modbus { + +static const char *const TAG = "jk_balancer_modbus"; + +static const uint8_t ADDRESS_READ_ALL = 0xFF; + +void JkBalancerModbus::loop() { + const uint32_t now = millis(); + if (now - this->last_jk_balancer_modbus_byte_ > this->rx_timeout_) { + ESP_LOGVV(TAG, "Buffer cleared due to timeout: %s", + format_hex_pretty(&this->rx_buffer_.front(), this->rx_buffer_.size()).c_str()); + this->rx_buffer_.clear(); + this->last_jk_balancer_modbus_byte_ = now; + } + + while (this->available()) { + uint8_t byte; + this->read_byte(&byte); + if (this->parse_jk_balancer_modbus_byte_(byte)) { + this->last_jk_balancer_modbus_byte_ = now; + } else { + ESP_LOGVV(TAG, "Buffer cleared due to reset: %s", + format_hex_pretty(&this->rx_buffer_.front(), this->rx_buffer_.size()).c_str()); + this->rx_buffer_.clear(); + } + } +} + +uint8_t chksum(const uint8_t data[], const uint8_t len) { + uint8_t checksum = 0; + for (uint8_t i = 0; i < len; i++) { + checksum = checksum + data[i]; + } + return checksum; +} + +bool JkBalancerModbus::parse_jk_balancer_modbus_byte_(uint8_t byte) { + size_t at = this->rx_buffer_.size(); + this->rx_buffer_.push_back(byte); + const uint8_t *raw = &this->rx_buffer_[0]; + + // Byte 0: Start sequence (0xEB) + if (at == 0) { + // return false to reset buffer + return raw[0] == 0xEB; + } + + // Byte 1: Start sequence (0x57) + if (at == 1) { + if (raw[0] != 0xEB || raw[1] != 0x90) { + ESP_LOGW(TAG, "Invalid header: 0x%02X 0x%02X", raw[0], raw[1]); + + // return false to reset buffer + return false; + } + + return true; + } + + // Byte 2: Device address + if (at == 2) + return true; + + uint8_t address = raw[2]; + + // Byte 3: Function + if (at == 3) + return true; + + uint8_t function = raw[3]; + uint8_t frame_len = 72; + + if (at <= frame_len) + return true; + + // data_len+1: CRC_HI (over all bytes) + uint8_t computed_crc = chksum(raw, frame_len); + uint8_t remote_crc = raw[frame_len + 1]; + if (computed_crc != remote_crc) { + ESP_LOGW(TAG, "CRC check failed! 0x%04X != 0x%04X", computed_crc, remote_crc); + // return false; + } + + std::vector data(this->rx_buffer_.begin() + 0, this->rx_buffer_.begin() + frame_len - 0); + + bool found = false; + for (auto *device : this->devices_) { + if (device->address_ == address) { + device->on_jk_balancer_modbus_data(function, data); + found = true; + } + } + if (!found) { + ESP_LOGW(TAG, "Got JkBalancerModbus frame from unknown address 0x%02X!", address); + } + + // return false to reset buffer + return false; +} + +void JkBalancerModbus::dump_config() { + ESP_LOGCONFIG(TAG, "JkBalancerModbus:"); + ESP_LOGCONFIG(TAG, " RX timeout: %d ms", this->rx_timeout_); +} +float JkBalancerModbus::get_setup_priority() const { + // After UART bus + return setup_priority::BUS - 1.0f; +} + +void JkBalancerModbus::send(uint8_t function, uint8_t address, uint8_t value) { + // uint8_t frame[22]; + // this->write_array(frame, 22); + this->flush(); +} + +void JkBalancerModbus::write_register(uint8_t address, uint8_t value) { this->send(0x00, address, value); } + +void JkBalancerModbus::query_balancer_status() { + uint8_t frame[7]; + frame[0] = 0x55; + frame[1] = 0xAA; + frame[2] = 0x01; + frame[3] = 0xFF; + frame[4] = 0x00; + frame[5] = 0x00; + frame[6] = 0xFF; + + this->write_array(frame, 7); + this->flush(); +} + +} // namespace jk_balancer_modbus +} // namespace esphome diff --git a/components/jk_balancer_modbus/jk_balancer_modbus.h b/components/jk_balancer_modbus/jk_balancer_modbus.h new file mode 100644 index 00000000..d7e4d72b --- /dev/null +++ b/components/jk_balancer_modbus/jk_balancer_modbus.h @@ -0,0 +1,57 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/components/uart/uart.h" + +namespace esphome { +namespace jk_balancer_modbus { + +class JkBalancerModbusDevice; + +class JkBalancerModbus : public uart::UARTDevice, public Component { + public: + JkBalancerModbus() = default; + + void loop() override; + + void dump_config() override; + + void register_device(JkBalancerModbusDevice *device) { this->devices_.push_back(device); } + + float get_setup_priority() const override; + + void send(uint8_t function, uint8_t address, uint8_t value); + void write_register(uint8_t address, uint8_t value); + void read_registers(); + void query_balancer_status(); + void set_rx_timeout(uint16_t rx_timeout) { rx_timeout_ = rx_timeout; } + + protected: + bool parse_jk_balancer_modbus_byte_(uint8_t byte); + + std::vector rx_buffer_; + uint16_t rx_timeout_{50}; + uint32_t last_jk_balancer_modbus_byte_{0}; + std::vector devices_; +}; + +class JkBalancerModbusDevice { + public: + void set_parent(JkBalancerModbus *parent) { parent_ = parent; } + void set_address(uint8_t address) { address_ = address; } + virtual void on_jk_balancer_modbus_data(const uint8_t &function, const std::vector &data) = 0; + + void send(int8_t function, uint8_t address, uint8_t value) { this->parent_->send(function, address, value); } + void write_register(uint8_t address, uint8_t value) { this->parent_->write_register(address, value); } + void read_registers() { this->parent_->read_registers(); } + void query_balancer_status() { this->parent_->query_balancer_status(); } + + protected: + friend JkBalancerModbus; + + JkBalancerModbus *parent_; + uint8_t address_; +}; + +} // namespace jk_balancer_modbus +} // namespace esphome diff --git a/docs/balancer-b1a24s/pdus-17cells.txt b/docs/balancer-b1a24s/pdus-17cells.txt new file mode 100644 index 00000000..94fb6f60 --- /dev/null +++ b/docs/balancer-b1a24s/pdus-17cells.txt @@ -0,0 +1,4 @@ +>>> 55:AA:01:FF:00:00:FF +<<< EB:90:01:FF:16:11:0C:FB:11:0E:00:00:01:00:03:00:00:00:0B:07:D0:01:11:0C:F9:0C:FB:0C:FB:0C:FB:0C:FB:0C:FC:0C:FB:0C:FB:0C:FB:0C:FC:0C:FB:0C:FB:0C:FB:0C:FB:0C:FC:0C:FB:0C:FB:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:A3:DB +>>> 55:AA:01:FF:00:00:FF +<<< EB:90:01:FF:16:11:0C:FB:11:01:00:00:01:00:02:00:00:00:0B:07:D0:01:11:0C:F9:0C:FB:0C:FB:0C:FB:0C:FB:0C:FC:0C:FB:0C:FB:0C:FB:0C:FB:0C:FB:0C:FB:0C:FB:0C:FB:0C:FB:0C:FB:0C:FB:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:A3:CB diff --git a/docs/balancer-b1a24s/pdus-20cells.txt b/docs/balancer-b1a24s/pdus-20cells.txt new file mode 100644 index 00000000..eb7f052d --- /dev/null +++ b/docs/balancer-b1a24s/pdus-20cells.txt @@ -0,0 +1,4 @@ +>>> 55:AA:01:FF:00:00:FF +<<< EB:90:01:FF:19:F7:0C:FC:14:00:13:00:01:00:03:00:00:00:0B:07:D0:01:14:0C:FC:0C:FC:0C:FC:0C:FB:0C:FB:0C:FC:0C:FB:0C:FB:0C:FC:0C:FC:0C:FC:0C:FC:0C:FC:0C:FC:0C:FB:0C:FC:0C:FB:0C:FC:0C:FC:0C:F9:00:00:00:00:00:00:00:00:00:A3:EF +>>> 55:AA:01:FF:00:00:FF +<<< EB:90:01:FF:19:F7:0C:FC:14:00:13:00:01:00:03:00:00:00:0B:07:D0:01:14:0C:FC:0C:FC:0C:FC:0C:FB:0C:FC:0C:FC:0C:FC:0C:FB:0C:FC:0C:FB:0C:FB:0C:FB:0C:FB:0C:FC:0C:FC:0C:FB:0C:FC:0C:FC:0C:FB:0C:F9:00:00:00:00:00:00:00:00:00:A3:ED diff --git a/esp32-active-balancer-example.yaml b/esp32-active-balancer-example.yaml new file mode 100644 index 00000000..366ddb64 --- /dev/null +++ b/esp32-active-balancer-example.yaml @@ -0,0 +1,98 @@ +substitutions: + name: jk-bms + device_description: "Monitor a JK-Balancer using RS485" + external_components_source: github://syssi/esphome-jk-bms@add-jk-balancer-support + tx_pin: GPIO16 + rx_pin: GPIO17 + +esphome: + name: ${name} + comment: ${device_description} + min_version: 2024.6.0 + project: + name: "syssi.esphome-jk-bms" + version: 2.1.0 + +esp32: + board: wemos_d1_mini32 + framework: + type: esp-idf + +external_components: + - source: ${external_components_source} + refresh: 0s + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +ota: + platform: esphome + +logger: + level: DEBUG + logs: + api.service: WARN + ota: WARN + wifi: WARN + sensor: DEBUG + +# If you use Home Assistant please remove this `mqtt` section and uncomment the `api` component! +# The native API has many advantages over MQTT: https://esphome.io/components/api.html#advantages-over-mqtt +mqtt: + broker: !secret mqtt_host + username: !secret mqtt_username + password: !secret mqtt_password + id: mqtt_client + +# api: + +uart: + - id: uart_0 + baud_rate: 9600 + rx_buffer_size: 384 + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + debug: + direction: BOTH + +jk_balancer_modbus: + - id: modbus0 + uart_id: uart_0 + rx_timeout: 50ms + +jk_balancer: + - id: balancer0 + jk_balancer_modbus_id: modbus0 + address: 0x01 + update_interval: 5s + +binary_sensor: + - platform: jk_balancer + jk_balancer_id: balancer0 + balancing: + name: "${name} balancing" + balancing_switch: + name: "${name} balancing switch" + charging: + name: "${name} charging" + discharging: + name: "${name} discharging" + dedicated_charger_switch: + name: "${name} dedicated charger switch" + online_status: + name: "${name} online status" + +sensor: + - platform: jk_balancer + jk_balancer_id: balancer0 + min_cell_voltage: + name: "${name} min cell voltage" + max_cell_voltage: + name: "${name} max cell voltage" + min_voltage_cell: + name: "${name} min voltage cell" + max_voltage_cell: + name: "${name} max voltage cell" + delta_cell_voltage: + name: "${name} delta cell voltage" diff --git a/esp8266-active-balancer-example.yaml b/esp8266-active-balancer-example.yaml new file mode 100644 index 00000000..34e7cefb --- /dev/null +++ b/esp8266-active-balancer-example.yaml @@ -0,0 +1,97 @@ +substitutions: + name: jk-bms + device_description: "Monitor a JK-Balancer using RS485" + external_components_source: github://syssi/esphome-jk-bms@add-jk-balancer-support + tx_pin: GPIO4 + rx_pin: GPIO5 + +esphome: + name: ${name} + comment: ${device_description} + min_version: 2024.6.0 + project: + name: "syssi.esphome-jk-bms" + version: 2.1.0 + +esp8266: + board: d1_mini + +external_components: + - source: ${external_components_source} + refresh: 0s + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +ota: + platform: esphome + +logger: + baud_rate: 0 + level: DEBUG + logs: + api.service: WARN + ota: WARN + wifi: WARN + sensor: DEBUG + +# If you use Home Assistant please remove this `mqtt` section and uncomment the `api` component! +# The native API has many advantages over MQTT: https://esphome.io/components/api.html#advantages-over-mqtt +mqtt: + broker: !secret mqtt_host + username: !secret mqtt_username + password: !secret mqtt_password + id: mqtt_client + +# api: + +uart: + - id: uart_0 + baud_rate: 9600 + rx_buffer_size: 384 + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + debug: + direction: BOTH + +jk_balancer_modbus: + - id: modbus0 + uart_id: uart_0 + rx_timeout: 50ms + +jk_balancer: + - id: balancer0 + jk_balancer_modbus_id: modbus0 + address: 0x01 + update_interval: 5s + +binary_sensor: + - platform: jk_balancer + jk_balancer_id: balancer0 + balancing: + name: "${name} balancing" + balancing_switch: + name: "${name} balancing switch" + charging: + name: "${name} charging" + discharging: + name: "${name} discharging" + dedicated_charger_switch: + name: "${name} dedicated charger switch" + online_status: + name: "${name} online status" + +sensor: + - platform: jk_balancer + jk_balancer_id: balancer0 + min_cell_voltage: + name: "${name} min cell voltage" + max_cell_voltage: + name: "${name} max cell voltage" + min_voltage_cell: + name: "${name} min voltage cell" + max_voltage_cell: + name: "${name} max voltage cell" + delta_cell_voltage: + name: "${name} delta cell voltage" diff --git a/tests/esp8266-fake-active-balancer.yaml b/tests/esp8266-fake-active-balancer.yaml new file mode 100644 index 00000000..83ae7ba6 --- /dev/null +++ b/tests/esp8266-fake-active-balancer.yaml @@ -0,0 +1,47 @@ +substitutions: + name: jk-balancer-fake + tx_pin: GPIO4 + rx_pin: GPIO5 + +esphome: + name: ${name} + min_version: 2024.6.0 + +esp8266: + board: d1_mini + +wifi: + ssid: !secret wifi_ssid + password: !secret wifi_password + +ota: + platform: esphome + +logger: + level: DEBUG + +api: + reboot_timeout: 0s + +uart: + baud_rate: 9600 + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + debug: + direction: BOTH + dummy_receiver: true + +interval: + - interval: 16s + then: + - uart.write: [0xEB, 0x90, 0x01, 0xFF, 0x16, 0x11, 0x0C, 0xFB, 0x11, 0x0E, 0x00, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x07, 0xD0, 0x01, 0x11, 0x0C, 0xF9, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0xDB] + - delay: 2s + + - uart.write: [0xEB, 0x90, 0x01, 0xFF, 0x16, 0x11, 0x0C, 0xFB, 0x11, 0x01, 0x00, 0x00, 0x01, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0B, 0x07, 0xD0, 0x01, 0x11, 0x0C, 0xF9, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0xCB] + - delay: 2s + + - uart.write: [0xEB, 0x90, 0x01, 0xFF, 0x19, 0xF7, 0x0C, 0xFC, 0x14, 0x00, 0x13, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x07, 0xD0, 0x01, 0x14, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0xEF] + - delay: 2s + + - uart.write: [0xEB, 0x90, 0x01, 0xFF, 0x19, 0xF7, 0x0C, 0xFC, 0x14, 0x00, 0x13, 0x00, 0x01, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0B, 0x07, 0xD0, 0x01, 0x14, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xFC, 0x0C, 0xFC, 0x0C, 0xFB, 0x0C, 0xF9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xA3, 0xED] + - delay: 2s