Skip to content

Commit

Permalink
Decode protocol version 2.5 of the TDT-6022 BMS
Browse files Browse the repository at this point in the history
Kudos to @wojtbar
  • Loading branch information
syssi committed Jun 3, 2024
1 parent ceaf3bb commit a1b73e1
Show file tree
Hide file tree
Showing 6 changed files with 203 additions and 59 deletions.
2 changes: 2 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ jobs:
esphome -s external_components_source components config esp32-boqiang-bms001-example-faker.yaml
esphome -s external_components_source components config esp32-seplos-v3-example.yaml
esphome -s external_components_source components config esp32-seplos-v3-example-multiple-battery-banks.yaml
esphome -s external_components_source components config esp32-tdt-example.yaml
- name: Validate test configurations
run: |
esphome -s external_components_source ../components config tests/esp8266-boqiang-emulator.yaml
Expand Down Expand Up @@ -273,3 +274,4 @@ jobs:
esphome -s external_components_source components compile esp32-example-faker.yaml
esphome -s external_components_source components compile esp32-boqiang-example-faker.yaml
esphome -s external_components_source components compile esp32-seplos-v3-example.yaml
esphome -s external_components_source components compile esp32-tdt-example.yaml
2 changes: 1 addition & 1 deletion components/seplos_bms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
CONF_SEPLOS_BMS_ID = "seplos_bms_id"
CONF_OVERRIDE_CELL_COUNT = "override_cell_count"

DEFAULT_PROTOCOL_VERSION = 0x20
DEFAULT_PROTOCOL_VERSION = 0x25
DEFAULT_ADDRESS = 0x00

seplos_bms_ns = cg.esphome_ns.namespace("seplos_bms")
Expand Down
14 changes: 7 additions & 7 deletions components/seplos_bms/sensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
CONF_BATTERY_CAPACITY = "battery_capacity"
CONF_RATED_CAPACITY = "rated_capacity"
CONF_CHARGING_CYCLES = "charging_cycles"
CONF_STATE_OF_HEALTH = "state_of_health"
CONF_SAMPLING_VOLTAGE = "sampling_voltage"
CONF_PORT_VOLTAGE = "port_voltage"

CONF_CELL_VOLTAGE_1 = "cell_voltage_1"
Expand Down Expand Up @@ -127,7 +127,7 @@
CONF_BATTERY_CAPACITY,
CONF_RATED_CAPACITY,
CONF_CHARGING_CYCLES,
CONF_STATE_OF_HEALTH,
CONF_SAMPLING_VOLTAGE,
CONF_PORT_VOLTAGE,
]

Expand Down Expand Up @@ -400,11 +400,11 @@
device_class=DEVICE_CLASS_EMPTY,
state_class=STATE_CLASS_MEASUREMENT,
),
cv.Optional(CONF_STATE_OF_HEALTH): sensor.sensor_schema(
unit_of_measurement=UNIT_PERCENT,
icon=ICON_STATE_OF_HEALTH,
accuracy_decimals=1,
device_class=DEVICE_CLASS_EMPTY,
cv.Optional(CONF_SAMPLING_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_PORT_VOLTAGE): sensor.sensor_schema(
Expand Down
93 changes: 45 additions & 48 deletions components/seplos_bms/seplos_bms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ static const char *const TAG = "seplos_bms";

void SeplosBms::on_seplos_modbus_data(const std::vector<uint8_t> &data) {
// num_of_cells frame_size data_len
// 8 65 118 (0x76) guessed
// 14 77 142 (0x8E)
// 15 79 146 (0x92)
// 16 81 150 (0x96)
// 8 58 104 (0x68) 65 118 (0x76) guessed
// 14 70 128 (0x80) 77 142 (0x8E)
// 15 72 132 (0x84) 79 146 (0x92)
// 16 74 136 (0x88) 81 150 (0x96)
if (data.size() >= 44 && data[8] >= 8 && data[8] <= 16) {
this->on_telemetry_data_(data);
return;
Expand All @@ -31,29 +31,32 @@ void SeplosBms::on_telemetry_data_(const std::vector<uint8_t> &data) {
ESP_LOGVV(TAG, " %s", format_hex_pretty(&data.front(), data.size()).c_str());

// ->
// 0x2000460010960001100CD70CE90CF40CD60CEF0CE50CE10CDC0CE90CF00CE80CEF0CEA0CDA0CDE0CD8060BA60BA00B970BA60BA50BA2FD5C14A0344E0A426803134650004603E8149F0000000000000000
// 0x26004600307600011000000000000000000000000000000000000000000000000000000000000000000608530853085308530BAC0B9000000000002D0213880001E6B8
//
// 0x20004600109600 01 10 0CD7 0CE9 0CF4 0CD6 0CEF 0CE5 0CE1 0CDC 0CE9 0CF0 0CE8 0CEF 0CEA 0CDA 0CDE 0CD8 06 0BA6 0BA0 0B97 0BA6 0BA5 0BA2 FD5C 14A0 344E 0A 4268 0313 4650 0046 03E8 149F 0000 0000 0000 0000
// 0 1 2 3 4 5 6 7 8 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 42 44 46 48 50 52 54 56 58 60 61 63 65 67 69 71 73 75 77 79
// 0x25014F42808000 01 0E 0DD2 0DD2 0DD3 0DD3 0DCC 0DCC 0DCD 0DCD 0DCE 0DCF 0DCD 0DCE 0DCE 0DCE 06 0BE2 0BB1 0B9A 0B9A 0B9A 0BA2 0000 1354 395A 00 5B68 0000 5B68 133A 09BE 3F 0409
// 0 1 2 3 4 5 6 7 8 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 38 40 42 44 46 48 50 52 54 56 57 59 61 63 65 67 68
// 37 38 40 42 44 46 48 50 52 54 56 57 59 61 63 65 67 68 razem 70 byte
// 1 2 3 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 33 35 37 39 41 43 45 47 49 51 52 54 56 58 60 62 6364
// *Data*
//
// Byte Address Content: Description Decoded content Coeff./Unit
// 0 0x20 Protocol version VER 2.0
// 1 0x00 Device address ADR
// 2 0x46 Device type CID1 Lithium iron phosphate battery BMS
// 3 0x00 Function code CID2 0x00: Normal, 0x01 VER error, 0x02 Chksum error, ...
// 4 0x10 Data length checksum LCHKSUM
// 5 0x96 Data length LENID 150 / 2 = 75
// 0 0x25 Protocol version VER 2.5
// 1 0x01 Device address ADR
// 2 0x4F Device type CID1 Lithium iron phosphate battery BMS
// 3 0x42 Function code CID2 0x00: Normal, 0x01 VER error, 0x02 Chksum error, ...
// 4 0x80 Data length checksum LCHKSUM
// 5 0x80 Data length LENID 128 / 2 = 64
// 6 0x00 Data flag
// 7 0x01 Command group
ESP_LOGV(TAG, "Command group: %d", data[7]);
// 8 0x10 Number of cells 16
// 8 0x0E Number of cells 14
uint8_t cells = (this->override_cell_count_) ? this->override_cell_count_ : data[8];

ESP_LOGV(TAG, "Number of cells: %d", cells);
// 9 0x0C 0xD7 Cell voltage 1 3287 * 0.001f = 3.287 V
// 11 0x0C 0xE9 Cell voltage 2 3305 * 0.001f = 3.305 V
// 9 0x0D 0xD2 Cell voltage 1 3538 * 0.001f = 3.538 V
// 11 0x0D 0xD2 Cell voltage 2 3538 * 0.001f = 3.538 V
// ... ... ...
// 39 0x0C 0xD8 Cell voltage 16 V
// 39 0x0D 0xCE Cell voltage 14 V
float min_cell_voltage = 100.0f;
float max_cell_voltage = -100.0f;
float average_cell_voltage = 0.0f;
Expand Down Expand Up @@ -82,28 +85,28 @@ void SeplosBms::on_telemetry_data_(const std::vector<uint8_t> &data) {
this->publish_state_(this->average_cell_voltage_sensor_, average_cell_voltage);

uint8_t offset = 9 + (cells * 2);

// 41 0x06 Number of temperatures 6 V
// 9+28=37
// 37 41 0x06 Number of temperatures 6 V
uint8_t temperature_sensors = data[offset];
ESP_LOGV(TAG, "Number of temperature sensors: %d", temperature_sensors);

// 42 0x0B 0xA6 Temperature sensor 1 (2982 - 2731) * 0.1f = 25.1 °C
// 44 0x0B 0xA0 Temperature sensor 2 (2976 - 2731) * 0.1f = 24.5 °C
// 46 0x0B 0x97 Temperature sensor 3 (2967 - 2731) * 0.1f = 23.6 °C
// 48 0x0B 0xA6 Temperature sensor 4 (2982 - 2731) * 0.1f = 25.1 °C
// 50 0x0B 0xA5 Environment temperature (2981 - 2731) * 0.1f = 25.0 °C
// 52 0x0B 0xA2 Mosfet temperature (2978 - 2731) * 0.1f = 24.7 °C
// 38 42 0x0B 0xE2 Environment temperature (3042 - 2731) * 0.1f = 31.1 °C
// 40 44 0x0B 0xB1 Mosfet temperature (2993 - 2731) * 0.1f = 26.2 °C
// 42 46 0x0B 0x9A Temperature sensor 1 (2970 - 2731) * 0.1f = 23.9 °C
// 44 48 0x0B 0x9A Temperature sensor 2 (2970 - 2731) * 0.1f = 23.9 °C
// 46 50 0x0B 0x9A Temperature sensor 3 (2970 - 2731) * 0.1f = 23.9 °C
// 48 52 0x0B 0xA2 Temperature sensor 4 (2978 - 2731) * 0.1f = 24.7 °C
for (uint8_t i = 0; i < std::min((uint8_t) 6, temperature_sensors); i++) {
float raw_temperature = (float) seplos_get_16bit(offset + 1 + (i * 2));
this->publish_state_(this->temperatures_[i].temperature_sensor_, (raw_temperature - 2731.0f) * 0.1f);
}
offset = offset + 1 + (temperature_sensors * 2);

// 54 0xFD 0x5C Charge/discharge current signed int? A
// 50 54 0x00 0x00 Charge/discharge current signed int? A
float current = (float) ((int16_t) seplos_get_16bit(offset)) * 0.01f;
this->publish_state_(this->current_sensor_, current);

// 56 0x14 0xA0 Total battery voltage 5280 * 0.01f = 52.80 V
// 52 56 0x13 0x54 Total battery voltage 4948 * 0.01f = 49.48 V
float total_voltage = (float) seplos_get_16bit(offset + 2) * 0.01f;
this->publish_state_(this->total_voltage_sensor_, total_voltage);

Expand All @@ -112,44 +115,38 @@ void SeplosBms::on_telemetry_data_(const std::vector<uint8_t> &data) {
this->publish_state_(this->charging_power_sensor_, std::max(0.0f, power)); // 500W vs 0W -> 500W
this->publish_state_(this->discharging_power_sensor_, std::abs(std::min(0.0f, power))); // -500W vs 0W -> 500W

// 58 0x34 0x4E Residual capacity 13390 * 0.01f = 133.90 Ah
// 54 58 0x39 0x5A Residual capacity 14682 * 0.01f = 146.82 Ah
this->publish_state_(this->residual_capacity_sensor_, (float) seplos_get_16bit(offset + 4) * 0.01f);

// 60 0x0A Custom number 10
// 61 0x42 0x68 Battery capacity 17000 * 0.01f = 170.00 Ah
// 56 60 0x0A Custom number 10
// 57 61 0x5B 0x68 Battery capacity 23400 * 0.01f = 234.00 Ah
this->publish_state_(this->battery_capacity_sensor_, (float) seplos_get_16bit(offset + 7) * 0.01f);

// 63 0x03 0x13 Stage of charge 787 * 0.1f = 78.7 %
this->publish_state_(this->state_of_charge_sensor_, (float) seplos_get_16bit(offset + 9) * 0.1f);
// 59 63 0x00 0x00 Number of cycles 0
this->publish_state_(this->charging_cycles_sensor_, (float) seplos_get_16bit(offset + 9) * 1.0f);

// 65 0x46 0x50 Rated capacity 18000 * 0.01f = 180.00 Ah
// 61 65 0x5B 0x68 Rated capacity 23400 * 0.01f = 234.00 Ah
this->publish_state_(this->rated_capacity_sensor_, (float) seplos_get_16bit(offset + 11) * 0.01f);

if (data.size() < offset + 13 + 2) {
return;
}

// 67 0x00 0x46 Number of cycles 70
this->publish_state_(this->charging_cycles_sensor_, (float) seplos_get_16bit(offset + 13));
// 63 67 0x13 0x3A Port voltage 4922 * 0.01f = 49.22 V
this->publish_state_(this->port_voltage_sensor_, (float) seplos_get_16bit(offset + 13) * 0.01f);

if (data.size() < offset + 15 + 2) {
return;
}

// 69 0x03 0xE8 State of health 1000 * 0.1f = 100.0 %
this->publish_state_(this->state_of_health_sensor_, (float) seplos_get_16bit(offset + 15) * 0.1f);

if (data.size() < offset + 17 + 2) {
return;
}
// 65 69 0x09 0xBE Sampling Voltage 2494 * 0.001f = 2.494 V
this->publish_state_(this->sampling_voltage_sensor_, (float) seplos_get_16bit(offset + 15) * 0.001f);

// 71 0x14 0x9F Port voltage 5279 * 0.01f = 52.79 V
this->publish_state_(this->port_voltage_sensor_, (float) seplos_get_16bit(offset + 17) * 0.01f);
// 67 71 0x3F Stage of charge 63 %
this->publish_state_(this->state_of_charge_sensor_, (float) data[offset + 17] * 1.0f);

// 73 0x00 0x00 Reserved
// 75 0x00 0x00 Reserved
// 77 0x00 0x00 Reserved
// 79 0x00 0x00 Reserved
// 68 0x04 0x09 Hardware version 1033
// this->publish_state_(this->hardware_version_sensor_, (float) seplos_get_16bit(offset + 18) * 0.001f);
}

void SeplosBms::dump_config() {
Expand Down Expand Up @@ -192,7 +189,7 @@ void SeplosBms::dump_config() {
LOG_SENSOR("", "Battery capacity", this->battery_capacity_sensor_);
LOG_SENSOR("", "Rated capacity", this->rated_capacity_sensor_);
LOG_SENSOR("", "Charging cycles", this->charging_cycles_sensor_);
LOG_SENSOR("", "State of health", this->state_of_health_sensor_);
LOG_SENSOR("", "Sampling voltage", this->sampling_voltage_sensor_);
LOG_SENSOR("", "Port Voltage", this->port_voltage_sensor_);
}

Expand Down
6 changes: 3 additions & 3 deletions components/seplos_bms/seplos_bms.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ class SeplosBms : public PollingComponent, public seplos_modbus::SeplosModbusDev
void set_charging_cycles_sensor(sensor::Sensor *charging_cycles_sensor) {
charging_cycles_sensor_ = charging_cycles_sensor;
}
void set_state_of_health_sensor(sensor::Sensor *state_of_health_sensor) {
state_of_health_sensor_ = state_of_health_sensor;
void set_sampling_voltage_sensor(sensor::Sensor *sampling_voltage_sensor) {
sampling_voltage_sensor_ = sampling_voltage_sensor;
}
void set_port_voltage_sensor(sensor::Sensor *port_voltage_sensor) { port_voltage_sensor_ = port_voltage_sensor; }

Expand Down Expand Up @@ -97,7 +97,7 @@ class SeplosBms : public PollingComponent, public seplos_modbus::SeplosModbusDev
sensor::Sensor *battery_capacity_sensor_;
sensor::Sensor *rated_capacity_sensor_;
sensor::Sensor *charging_cycles_sensor_;
sensor::Sensor *state_of_health_sensor_;
sensor::Sensor *sampling_voltage_sensor_;
sensor::Sensor *port_voltage_sensor_;

text_sensor::TextSensor *errors_text_sensor_;
Expand Down
145 changes: 145 additions & 0 deletions esp32-tdt-example.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
substitutions:
name: seplos-bms
device_description: "Monitor a TDT-BMS via UART-TTL"
external_components_source: github://syssi/esphome-seplos-bms@main
tx_pin: GPIO4
rx_pin: GPIO5

esphome:
name: ${name}
comment: ${device_description}
project:
name: "syssi.esphome-seplos-bms"
version: 1.1.0

esp32:
board: wemos_d1_mini32

external_components:
- source: ${external_components_source}
refresh: 0s

wifi:
ssid: !secret wifi_ssid
password: !secret wifi_password

ota:

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
# Please set the default baudrate of your TDT 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

seplos_modbus:
id: modbus0
uart_id: uart_0
rx_timeout: 150ms

seplos_bms:
- id: bms0
address: 0x01
protocol_version: 0x25
seplos_modbus_id: modbus0
update_interval: 10s

sensor:
- platform: seplos_bms
seplos_bms_id: bms0
min_cell_voltage:
name: "min cell voltage"
max_cell_voltage:
name: "max cell voltage"
min_voltage_cell:
name: "min voltage cell"
max_voltage_cell:
name: "max voltage cell"
delta_cell_voltage:
name: "delta cell voltage"
average_cell_voltage:
name: "average cell voltage"
cell_voltage_1:
name: "cell voltage 1"
cell_voltage_2:
name: "cell voltage 2"
cell_voltage_3:
name: "cell voltage 3"
cell_voltage_4:
name: "cell voltage 4"
cell_voltage_5:
name: "cell voltage 5"
cell_voltage_6:
name: "cell voltage 6"
cell_voltage_7:
name: "cell voltage 7"
cell_voltage_8:
name: "cell voltage 8"
cell_voltage_9:
name: "cell voltage 9"
cell_voltage_10:
name: "cell voltage 10"
cell_voltage_11:
name: "cell voltage 11"
cell_voltage_12:
name: "cell voltage 12"
cell_voltage_13:
name: "cell voltage 13"
cell_voltage_14:
name: "cell voltage 14"
cell_voltage_15:
name: "cell voltage 15"
cell_voltage_16:
name: "cell voltage 16"
temperature_1:
name: "environment temperature"
temperature_2:
name: "mosfet temperature"
temperature_3:
name: "temperature 1"
temperature_4:
name: "temperature 2"
temperature_5:
name: "temperature 3"
temperature_6:
name: "temperature 4"
total_voltage:
name: "total voltage"
current:
name: "current"
power:
name: "power"
charging_power:
name: "charging power"
discharging_power:
name: "discharging power"
residual_capacity:
name: "residual capacity"
battery_capacity:
name: "battery capacity"
rated_capacity:
name: "rated capacity"
state_of_charge:
name: "state of charge"
charging_cycles:
name: "charging cycles"
sampling_voltage:
name: "sampling voltage"
port_voltage:
name: "port voltage"

0 comments on commit a1b73e1

Please sign in to comment.