积分 645
金钱 591
威望 0
贡献 0
HASS币 0
高级会员
积分 645
金钱 591
HASS币 0
买了个大刀370,卖家送了拓邦200a的保护板,我想接入ha,发现没有任何教程,但是又看到不少人能接入,都是不公开,估计这个协议还是可以有收益的。直接通过ai折腾出来,通过485获取数据,但是只能看参数。不能调整设置,需要调整的请用通用上位机。想发bin文件,好像发不了
这个是是在ha的显示。
,这个是web显示
。这个是用esp32 s3版子的yaml。代码如下。
代码:substitutions:
name: tuobang-bms
tx_pin: GPIO16
rx_pin: GPIO18
update_interval: 3s
esphome:
name: ${name}
min_version: 2025.11.11
esp32:
board: esp32-s3-devkitc-1
framework:
type: esp-idf
wifi:
ssid: "你的wifi"
password: "你的wifi密码"
ap:
ssid: "Tower-BMS-S3-Config"
password: "12345678"
logger:
level: DEBUG
hardware_uart: USB_CDC
web_server:
port: 80
api:
uart:
id: uart_bms
tx_pin: ${tx_pin}
rx_pin: ${rx_pin}
baud_rate: 9600
parity: NONE
stop_bits: 1
globals:
- id: last_bms_update
type: long
restore_value: no
initial_value: '0'
- id: frame_count
type: int
restore_value: no
initial_value: '0'
- id: error_count
type: int
restore_value: no
initial_value: '0'
# ---------------- 传感器 ------------------
sensor:
- platform: template
name: "${name} Total Voltage"
unit_of_measurement: "V"
accuracy_decimals: 2
id: total_voltage
device_class: voltage
icon: "mdi:flash"
- platform: template
name: "${name} Current"
unit_of_measurement: "A"
accuracy_decimals: 2
id: bms_current
device_class: current
icon: "mdi:current-ac"
- platform: template
name: "${name} SOC"
unit_of_measurement: "%"
accuracy_decimals: 0
id: bms_soc
device_class: battery
icon: "mdi:battery"
- platform: template
name: "${name} SOH"
unit_of_measurement: "%"
accuracy_decimals: 1
id: bms_soh
icon: "mdi:heart-pulse"
- platform: template
name: "${name} Remaining Capacity"
unit_of_measurement: "Ah"
accuracy_decimals: 2
id: remaining_capacity
icon: "mdi:battery-50"
- platform: template
name: "${name} Total Capacity"
unit_of_measurement: "Ah"
accuracy_decimals: 0
id: total_capacity
icon: "mdi:battery"
- platform: template
name: "${name} Cycle Count"
id: cycle_count
icon: "mdi:counter"
# 16 串电芯电压
- platform: template
name: "${name} Cell 01 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_01
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 02 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_02
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 03 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_03
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 04 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_04
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 05 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_05
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 06 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_06
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 07 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_07
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 08 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_08
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 09 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_09
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 10 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_10
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 11 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_11
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 12 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_12
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 13 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_13
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 14 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_14
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 15 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_15
icon: "mdi:flash-circle"
- platform: template
name: "${name} Cell 16 Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: cell_16
icon: "mdi:flash-circle"
# 7 路温度
- platform: template
name: "${name} Temp 01"
unit_of_measurement: "°C"
accuracy_decimals: 1
id: temp_01
device_class: temperature
icon: "mdi:thermometer"
- platform: template
name: "${name} Temp 02"
unit_of_measurement: "°C"
accuracy_decimals: 1
id: temp_02
device_class: temperature
icon: "mdi:thermometer"
- platform: template
name: "${name} Temp 03"
unit_of_measurement: "°C"
accuracy_decimals: 1
id: temp_03
device_class: temperature
icon: "mdi:thermometer"
- platform: template
name: "${name} Temp 04"
unit_of_measurement: "°C"
accuracy_decimals: 1
id: temp_04
device_class: temperature
icon: "mdi:thermometer"
- platform: template
name: "${name} Temp 05"
unit_of_measurement: "°C"
accuracy_decimals: 1
id: temp_05
device_class: temperature
icon: "mdi:thermometer"
- platform: template
name: "${name} Temp 06"
unit_of_measurement: "°C"
accuracy_decimals: 1
id: temp_06
device_class: temperature
icon: "mdi:thermometer"
- platform: template
name: "${name} Temp 07"
unit_of_measurement: "°C"
accuracy_decimals: 1
id: temp_07
device_class: temperature
icon: "mdi:thermometer"
# 统计值
- platform: template
name: "${name} Max Cell Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: max_cell_voltage
icon: "mdi:arrow-up-bold-circle"
- platform: template
name: "${name} Min Cell Voltage"
unit_of_measurement: "V"
accuracy_decimals: 3
id: min_cell_voltage
icon: "mdi:arrow-down-bold-circle"
- platform: template
name: "${name} Voltage Difference"
unit_of_measurement: "V"
accuracy_decimals: 3
id: voltage_difference
icon: "mdi:delta"
# 调试信息
- platform: template
name: "${name} Frame Count"
id: frame_count_sensor
icon: "mdi:counter"
- platform: template
name: "${name} Error Count"
id: error_count_sensor
icon: "mdi:alert-circle"
# ------------- 文本传感器 ----------------
text_sensor:
- platform: wifi_info
ip_address:
name: "${name} IP Address"
ssid:
name: "${name} Connected SSID"
mac_address:
name: "${name} MAC Address"
- platform: template
name: "${name} Status"
id: bms_status
icon: "mdi:information"
- platform: template
name: "${name} Mode"
id: bms_mode
icon: "mdi:chip"
- platform: template
name: "BMS Raw Data"
id: bms_raw_data
icon: mdi:file-code
# ------------- 二进制传感器 --------------
binary_sensor:
- platform: template
name: "${name} Communication"
id: bms_communication
device_class: connectivity
icon: "mdi:lan"
- platform: template
name: "${name} Charging"
id: bms_charging
device_class: power
icon: "mdi:power-plug"
- platform: template
name: "${name} Discharging"
id: bms_discharging
device_class: power
icon: "mdi:power-plug-off"
- platform: status
name: "${name} Status"
# ------------- 主要逻辑 --------------
interval:
# 每3秒发送查询命令
- interval: 3s
then:
- lambda: |-
// 发送ASCII查询命令
std::vector<uint8_t> cmd = {
0x7E, 0x32, 0x31, 0x30, 0x30, 0x34, 0x36, 0x34, 0x32, 0x45,
0x30, 0x30, 0x32, 0x30, 0x30, 0x46, 0x44, 0x33, 0x36, 0x0D
};
id(uart_bms).write_array(cmd.data(), cmd.size());
ESP_LOGD("bms", "Sent query command");
# 实时解析接收到的数据 (每50ms检查一次)
- interval: 50ms
then:
- lambda: |-
static std::string ascii_buffer;
static const size_t EXPECTED_FRAME_LENGTH = 145;
while (id(uart_bms).available()) {
uint8_t byte;
if (id(uart_bms).read_byte(&byte)) {
// 帧结束符
if (byte == 0x0D) {
id(frame_count)++;
id(frame_count_sensor).publish_state(id(frame_count));
// 严格检查帧格式
bool valid_frame = true;
std::string error_reason;
// 检查1: 帧头必须是'~'
if (ascii_buffer.empty() || ascii_buffer[0] != '~') {
valid_frame = false;
error_reason = "Invalid header";
}
// 检查2: 精确的帧长度
else if (ascii_buffer.length() != EXPECTED_FRAME_LENGTH) {
valid_frame = false;
error_reason = "Invalid length: " + std::to_string(ascii_buffer.length()) + ", expected: " + std::to_string(EXPECTED_FRAME_LENGTH);
}
if (!valid_frame) {
id(error_count)++;
id(error_count_sensor).publish_state(id(error_count));
ESP_LOGW("bms", "Invalid frame: %s, reason: %s", ascii_buffer.c_str(), error_reason.c_str());
ascii_buffer.clear();
continue;
}
// 发布原始数据用于调试
id(bms_raw_data).publish_state(ascii_buffer);
// 解析数据帧
std::string data = ascii_buffer.substr(1, ascii_buffer.size() - 6);
std::string recv_crc = ascii_buffer.substr(ascii_buffer.size() - 5, 4);
// CRC校验
uint16_t calc_crc = 0;
for (size_t i = 0; i < data.size(); i++) {
calc_crc = (calc_crc + (uint8_t)data) & 0xFFFF;
}
calc_crc = (0x10000 - calc_crc) & 0xFFFF;
char calc_crc_str[5];
sprintf(calc_crc_str, "%04X", calc_crc);
bool crc_ok = (recv_crc == calc_crc_str);
if (!crc_ok) {
id(error_count)++;
id(error_count_sensor).publish_state(id(error_count));
ESP_LOGW("bms", "CRC error: received=%s, calculated=%s", recv_crc.c_str(), calc_crc_str);
}
size_t pos = 0;
// 跳过地址和命令字段 (14字节)
pos += 14;
// 解析电芯数量
int cell_count = strtol(data.substr(pos, 2).c_str(), nullptr, 16);
pos += 2;
// 解析16串电芯电压
float max_cell_v = 0.0f;
float min_cell_v = 5.0f;
for (int i = 0; i < 16; i++) {
uint16_t cell_mv = strtol(data.substr(pos, 4).c_str(), nullptr, 16);
float cell_v = cell_mv * 0.001f;
if (cell_v > max_cell_v) max_cell_v = cell_v;
if (cell_v > 0 && cell_v < min_cell_v) min_cell_v = cell_v;
switch(i) {
case 0: id(cell_01).publish_state(cell_v); break;
case 1: id(cell_02).publish_state(cell_v); break;
case 2: id(cell_03).publish_state(cell_v); break;
case 3: id(cell_04).publish_state(cell_v); break;
case 4: id(cell_05).publish_state(cell_v); break;
case 5: id(cell_06).publish_state(cell_v); break;
case 6: id(cell_07).publish_state(cell_v); break;
case 7: id(cell_08).publish_state(cell_v); break;
case 8: id(cell_09).publish_state(cell_v); break;
case 9: id(cell_10).publish_state(cell_v); break;
case 10: id(cell_11).publish_state(cell_v); break;
case 11: id(cell_12).publish_state(cell_v); break;
case 12: id(cell_13).publish_state(cell_v); break;
case 13: id(cell_14).publish_state(cell_v); break;
case 14: id(cell_15).publish_state(cell_v); break;
case 15: id(cell_16).publish_state(cell_v); break;
}
pos += 4;
}
id(max_cell_voltage).publish_state(max_cell_v);
id(min_cell_voltage).publish_state(min_cell_v);
id(voltage_difference).publish_state(max_cell_v - min_cell_v);
// 解析温度数量
int temp_count = strtol(data.substr(pos, 2).c_str(), nullptr, 16);
pos += 2;
// 解析7路温度
for (int i = 0; i < 7 && i < temp_count; i++) {
uint16_t temp_raw = strtol(data.substr(pos, 4).c_str(), nullptr, 16);
float temperature = (temp_raw - 2731) * 0.1f;
switch(i) {
case 0: id(temp_01).publish_state(temperature); break;
case 1: id(temp_02).publish_state(temperature); break;
case 2: id(temp_03).publish_state(temperature); break;
case 3: id(temp_04).publish_state(temperature); break;
case 4: id(temp_05).publish_state(temperature); break;
case 5: id(temp_06).publish_state(temperature); break;
case 6: id(temp_07).publish_state(temperature); break;
}
pos += 4;
}
// 解析电流
int16_t current_raw = (int16_t)strtol(data.substr(pos, 4).c_str(), nullptr, 16);
float current = current_raw * 0.01f;
id(bms_current).publish_state(current);
pos += 4;
// 解析总电压
uint16_t total_voltage_raw = strtol(data.substr(pos, 4).c_str(), nullptr, 16);
float total_voltage_val = total_voltage_raw * 0.01f;
id(total_voltage).publish_state(total_voltage_val);
pos += 4;
// 解析剩余容量
uint16_t remaining_cap_raw = strtol(data.substr(pos, 4).c_str(), nullptr, 16);
float remaining_capacity_val = remaining_cap_raw * 0.01f;
id(remaining_capacity).publish_state(remaining_capacity_val);
pos += 4;
// 跳过2字节标志
pos += 2;
// 解析总容量
uint16_t total_cap_raw = strtol(data.substr(pos, 4).c_str(), nullptr, 16);
float total_capacity_val = total_cap_raw * 0.01f;
id(total_capacity).publish_state(total_capacity_val);
pos += 4;
// 解析循环次数
uint16_t cycles = strtol(data.substr(pos, 4).c_str(), nullptr, 16);
id(cycle_count).publish_state(cycles);
pos += 4;
// 解析SOC
int soc = strtol(data.substr(pos, 2).c_str(), nullptr, 16);
id(bms_soc).publish_state(soc);
pos += 2;
// 解析SOH
int soh = strtol(data.substr(pos, 2).c_str(), nullptr, 16);
id(bms_soh).publish_state(soh);
pos += 2;
// 更新状态
id(bms_status).publish_state(crc_ok ? "Normal" : "CRC Error");
id(bms_mode).publish_state(current > 0.02f ? "Charging" :
(current < -0.02f ? "Discharging" : "Idle"));
id(bms_charging).publish_state(current > 0.02f);
id(bms_discharging).publish_state(current < -0.02f);
id(bms_communication).publish_state(true);
id(last_bms_update) = millis();
ESP_LOGI("bms", "Frame parsed: %.2fV, %.2fA, %d%%, CRC: %s",
total_voltage_val, current, soc, crc_ok ? "OK" : "ERROR");
ascii_buffer.clear();
}
// 处理数据字符
else if (byte >= 0x20 || byte == 0x09 || byte == 0x0A) {
ascii_buffer += (char)byte;
if (ascii_buffer.length() > 200) {
ESP_LOGW("bms", "Buffer overflow, clearing buffer");
ascii_buffer.clear();
}
}
else if (byte != 0x00) {
ESP_LOGD("bms", "Ignored non-printable char: 0x%02X", byte);
}
}
}
# 10秒检查通信状态
- interval: 10s
then:
- lambda: |-
if (millis() - id(last_bms_update) > 30000) {
id(bms_communication).publish_state(false);
id(bms_status).publish_state("Timeout");
}
评分
查看全部评分