diff --git a/components/seplos_bms/seplos_bms.cpp b/components/seplos_bms/seplos_bms.cpp index 2ba80c8..fa0a9e5 100644 --- a/components/seplos_bms/seplos_bms.cpp +++ b/components/seplos_bms/seplos_bms.cpp @@ -18,6 +18,16 @@ void SeplosBms::on_seplos_modbus_data(const std::vector &data) { return; } + if (data.size() == 6 && data[3] == 0x5A && data[5] == 0x00) { + ESP_LOGVV(TAG, "Intra pack communication request received"); + return; + } + + if (data.size() == 38 && data[3] == 0x5A && data[5] == 0x40) { + this->on_intra_pack_data_(data); + return; + } + ESP_LOGW(TAG, "Unhandled data received (data_len: 0x%02X): %s", data[5], format_hex_pretty(&data.front(), data.size()).c_str()); } @@ -152,6 +162,84 @@ void SeplosBms::on_telemetry_data_(const std::vector &data) { // 79 0x00 0x00 Reserved } +void SeplosBms::on_intra_pack_data_(const std::vector &data) { + auto seplos_get_16bit = [&](size_t i) -> uint16_t { + return (uint16_t(data[i + 0]) << 8) | (uint16_t(data[i + 1]) << 0); + }; + + ESP_LOGI(TAG, "Intra pack communication frame (%d bytes) received", data.size()); + ESP_LOGVV(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str()); + + if(!this->update_interval_never_()) { + ESP_LOGW(TAG, "Decoding of the intra pack communication discarded"); + return; + } + + // <<< ~2001465A0000FD9D\r + // >>> ~2001465AC04000010CE10CDE0B5C0B53FFFE14983FC26D600246149A00000000000003028010EFE8\r + // + // >>> ~2002465A0000FD9C\r + // <<< ~2002465AC04000010CE00CDD0B620B5A0001149741236D600253149700000000000003028010F072\r + // + // *Data* + // + // Byte Address Content: Description Decoded content Coeff./Unit + // 0 0x20 Protocol version VER 2.0 + // 1 0x01 Device address ADR + // 2 0x46 Device type CID1 Lithium iron phosphate battery BMS + // 3 0x5A Function code CID2 0x00: Normal, 0x01 VER error, 0x02 Chksum error, ... + // 4 0xC0 Data length checksum LCHKSUM + // 5 0x40 Data length LENID 150 / 2 = 75 + // 6 0x00 Data flag + // 7 0x01 Command group + // 8 0x0C 0xE1 Maximum Cell Voltage + ESP_LOGD(TAG, "Maximum Cell Voltage: %.3f V", seplos_get_16bit(8) * 0.001f); + + // 10 0x0C 0xDE Minimum Cell Voltage + ESP_LOGD(TAG, "Minimum Cell Voltage: %.3f V", seplos_get_16bit(10) * 0.001f); + + // 12 0x0B 0x5C Temperature 1 + ESP_LOGD(TAG, "Temperature 1: %.1f °C", (seplos_get_16bit(12) - 2731) * 0.1f); + + // 14 0x0B 0x53 Temperature 2 + ESP_LOGD(TAG, "Temperature 2: %.1f °C", (seplos_get_16bit(14) - 2731) * 0.1f); + + // 16 0xFF 0xFE Current + float current = ((int16_t) seplos_get_16bit(16)) * 0.01f; + ESP_LOGD(TAG, "Current: %.2f A", current); + + // 18 0x14 0x98 Total voltage + float total_voltage = seplos_get_16bit(18) * 0.01f; + ESP_LOGD(TAG, "Total voltage: %.2f V", total_voltage); + + // 20 0x3F 0xC2 Residual capacity + ESP_LOGD(TAG, "Residual capacity: %.2f Ah", seplos_get_16bit(20) * 0.01f); + + // 22 0x6D 0x60 Battery capacity + ESP_LOGD(TAG, "Battery capacity: %.2f Ah", seplos_get_16bit(22) * 0.01f); + + // 24 0x02 0x46 State of charge + ESP_LOGD(TAG, "State of charge: %.1f %%", seplos_get_16bit(24) * 0.1f); + + // 26 0x14 0x9A Port voltage + ESP_LOGD(TAG, "Port voltage: %.2f V", seplos_get_16bit(26) * 0.01f); + + // 28 0x00 0x00 + ESP_LOGD(TAG, "Unknown28: 0x%02X 0x%02X (%f)", data[28], data[29], seplos_get_16bit(28) * 1.0f); + + // 30 0x00 0x00 + ESP_LOGD(TAG, "Unknown28: 0x%02X 0x%02X (%f)", data[30], data[31], seplos_get_16bit(30) * 1.0f); + + // 32 0x00 0x00 + ESP_LOGD(TAG, "Unknown28: 0x%02X 0x%02X (%f)", data[32], data[33], seplos_get_16bit(32) * 1.0f); + + // 34 0x03 0x02 + ESP_LOGD(TAG, "Unknown28: 0x%02X 0x%02X (%f)", data[34], data[35], seplos_get_16bit(34) * 1.0f); + + // 36 0x80 0x10 + ESP_LOGD(TAG, "Unknown28: 0x%02X 0x%02X (%f)", data[36], data[37], seplos_get_16bit(36) * 1.0f); +} + void SeplosBms::dump_config() { ESP_LOGCONFIG(TAG, "SeplosBms:"); LOG_SENSOR("", "Minimum Cell Voltage", this->min_cell_voltage_sensor_); @@ -224,5 +312,9 @@ void SeplosBms::publish_state_(text_sensor::TextSensor *text_sensor, const std:: text_sensor->publish_state(state); } +bool SeplosBms::update_interval_never_() { + return this->get_update_interval() == SCHEDULER_DONT_RUN; +} + } // namespace seplos_bms } // namespace esphome diff --git a/components/seplos_bms/seplos_bms.h b/components/seplos_bms/seplos_bms.h index 487d7b8..6b94111 100644 --- a/components/seplos_bms/seplos_bms.h +++ b/components/seplos_bms/seplos_bms.h @@ -112,10 +112,12 @@ class SeplosBms : public PollingComponent, public seplos_modbus::SeplosModbusDev uint8_t override_cell_count_{0}; + bool update_interval_never_(); void publish_state_(binary_sensor::BinarySensor *binary_sensor, const bool &state); void publish_state_(sensor::Sensor *sensor, float value); void publish_state_(text_sensor::TextSensor *text_sensor, const std::string &state); void on_telemetry_data_(const std::vector &data); + void on_intra_pack_data_(const std::vector &data); }; } // namespace seplos_bms diff --git a/esp8266-intra-communication-example-debug.yaml b/esp8266-intra-communication-example-debug.yaml new file mode 100644 index 0000000..b243066 --- /dev/null +++ b/esp8266-intra-communication-example-debug.yaml @@ -0,0 +1,21 @@ +<<: !include esp8266-intra-communication-example.yaml + +logger: + level: DEBUG + +uart: + id: uart_0 + # Please set the default baudrate of your Seplos BMS model here. It's sometimes 19200 baud instead of 9600. + baud_rate: 9600 + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + # The increased RX buffer size is important because + # the full BMS response must fit into the buffer + rx_buffer_size: 384 + debug: + dummy_receiver: false + direction: BOTH + # after: + # delimiter: "\r" + # sequence: + # - lambda: UARTDebug::log_string(direction, bytes); diff --git a/esp8266-intra-communication-example-faker.yaml b/esp8266-intra-communication-example-faker.yaml new file mode 100644 index 0000000..43fb109 --- /dev/null +++ b/esp8266-intra-communication-example-faker.yaml @@ -0,0 +1,30 @@ +<<: !include esp8266-intra-communication-example-debug.yaml + +interval: + - interval: 4s + then: + # >>> ~2001465A0000FD9D\r + # <<< ~2001465AC04000010CE10CDE0B5C0B53FFFE14983FC26D600246149A00000000000003028010EFE8\r + - lambda: |- + id(battery_bank0).on_seplos_modbus_data({ + 0x20, 0x01, 0x46, 0x5A, 0x00, 0x00 + }); + id(battery_bank0).on_seplos_modbus_data({ + 0x20, 0x01, 0x46, 0x5A, 0xC0, 0x40, 0x00, 0x01, 0x0C, 0xE1, 0x0C, 0xDE, 0x0B, 0x5C, + 0x0B, 0x53, 0xFF, 0xFE, 0x14, 0x98, 0x3F, 0xC2, 0x6D, 0x60, 0x02, 0x46, 0x14, 0x9A, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x80, 0x10 + }); + + - delay: 2s + + # >>> ~2002465A0000FD9C\r + # <<< ~2002465AC04000010CE00CDD0B620B5A0001149741236D600253149700000000000003028010F072\r + - lambda: |- + id(battery_bank1).on_seplos_modbus_data({ + 0x20, 0x02, 0x46, 0x5A, 0x00, 0x00 + }); + id(battery_bank1).on_seplos_modbus_data({ + 0x20, 0x02, 0x46, 0x5A, 0xC0, 0x40, 0x00, 0x01, 0x0C, 0xE0, 0x0C, 0xDD, 0x0B, 0x62, + 0x0B, 0x5A, 0x00, 0x01, 0x14, 0x97, 0x41, 0x23, 0x6D, 0x60, 0x02, 0x53, 0x14, 0x97, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x02, 0x80, 0x10 + }); diff --git a/esp8266-intra-communication-example.yaml b/esp8266-intra-communication-example.yaml new file mode 100644 index 0000000..2787842 --- /dev/null +++ b/esp8266-intra-communication-example.yaml @@ -0,0 +1,104 @@ +substitutions: + name: seplos-bms + battery_bank0: "${name} bank 0" + battery_bank1: "${name} bank 1" + battery_bank2: "${name} bank 2" + device_description: "Monitor a Seplos BMS via RS485" + external_components_source: github://syssi/esphome-seplos-bms@main + tx_pin: GPIO4 + rx_pin: GPIO5 + +esphome: + name: ${name} + comment: ${device_description} + min_version: 2024.6.0 + project: + name: "syssi.esphome-seplos-bms" + version: 1.2.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: + level: 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 + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + # The increased RX buffer size is important because + # the full BMS response must fit into the buffer + rx_buffer_size: 384 + +seplos_modbus: + id: modbus0 + uart_id: uart_0 + +seplos_bms: + - id: battery_bank0 + # Dip switch configuration of the first pack / address 0x01 + # 8 7 6 5 4 3 2 1 + # off, off, off, off, off, off, off, on + address: 0x01 + # Known protocol versions: 0x20 (Seplos), 0x26 (Boqiang) + protocol_version: 0x20 + seplos_modbus_id: modbus0 + update_interval: never + - id: battery_bank1 + # Dip switch configuration of the second pack / address 0x02 + # 8 7 6 5 4 3 2 1 + # off, off, off, off, off, off, on, off + address: 0x02 + # Known protocol versions: 0x20 (Seplos), 0x26 (Boqiang) + protocol_version: 0x20 + seplos_modbus_id: modbus0 + update_interval: never + +sensor: + - platform: seplos_bms + seplos_bms_id: battery_bank0 + min_cell_voltage: + name: "${battery_bank0} min cell voltage" + max_cell_voltage: + name: "${battery_bank0} max cell voltage" + total_voltage: + name: "${battery_bank0} total voltage" + current: + name: "${battery_bank0} current" + state_of_charge: + name: "${battery_bank0} state of charge" + + - platform: seplos_bms + seplos_bms_id: battery_bank1 + min_cell_voltage: + name: "${battery_bank1} min cell voltage" + max_cell_voltage: + name: "${battery_bank1} max cell voltage" + total_voltage: + name: "${battery_bank1} total voltage" + current: + name: "${battery_bank1} current" + state_of_charge: + name: "${battery_bank1} state of charge"