找回密码
 立即注册

微信扫码登录

搜索
查看: 189|回复: 2

[基础教程] 拓邦保护板通过esp32接入ha

[复制链接]

3

主题

51

回帖

645

积分

高级会员

积分
645
金钱
591
HASS币
0
发表于 5 天前 | 显示全部楼层 |阅读模式
买了个大刀370,卖家送了拓邦200a的保护板,我想接入ha,发现没有任何教程,但是又看到不少人能接入,都是不公开,估计这个协议还是可以有收益的。直接通过ai折腾出来,通过485获取数据,但是只能看参数。不能调整设置,需要调整的请用通用上位机。想发bin文件,好像发不了

这个是是在ha的显示。 image.png ,这个是web显示 image.png 。这个是用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");
          }

评分

参与人数 1金钱 +12 收起 理由
隔壁的王叔叔 + 12 感谢楼主分享!

查看全部评分

回复

使用道具 举报

xjol 手机认证

0

主题

91

回帖

839

积分

高级会员

积分
839
金钱
748
HASS币
0
发表于 4 天前 | 显示全部楼层
不错,有协议丢给AI 自己生成的吗?
回复

使用道具 举报

3

主题

51

回帖

645

积分

高级会员

积分
645
金钱
591
HASS币
0
 楼主| 发表于 4 天前 | 显示全部楼层
xjol 发表于 2025-11-12 13:32
不错,有协议丢给AI 自己生成的吗?

理论是这样,但是还是得你自己调试一下。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Hassbian ( 晋ICP备17001384号-1 )

GMT+8, 2025-11-16 14:21 , Processed in 0.490119 second(s), 10 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2025 Discuz! Team.

快速回复 返回顶部 返回列表