Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decode intra pack communication frame (0x5A) #117

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions components/seplos_bms/seplos_bms.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,16 @@ void SeplosBms::on_seplos_modbus_data(const std::vector<uint8_t> &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());
}
Expand Down Expand Up @@ -152,6 +162,84 @@ void SeplosBms::on_telemetry_data_(const std::vector<uint8_t> &data) {
// 79 0x00 0x00 Reserved
}

void SeplosBms::on_intra_pack_data_(const std::vector<uint8_t> &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_);
Expand Down Expand Up @@ -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
2 changes: 2 additions & 0 deletions components/seplos_bms/seplos_bms.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<uint8_t> &data);
void on_intra_pack_data_(const std::vector<uint8_t> &data);
};

} // namespace seplos_bms
Expand Down
21 changes: 21 additions & 0 deletions esp8266-intra-communication-example-debug.yaml
Original file line number Diff line number Diff line change
@@ -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);
30 changes: 30 additions & 0 deletions esp8266-intra-communication-example-faker.yaml
Original file line number Diff line number Diff line change
@@ -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
});
104 changes: 104 additions & 0 deletions esp8266-intra-communication-example.yaml
Original file line number Diff line number Diff line change
@@ -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"
Loading