本帖最后由 18005246366 于 2025-10-23 21:49 编辑
最近折腾 485 读写逆变器和保护板的数据,碰到不少坑,记录分享下
机器部分
首先得确保你要采集的机器 485 通信口正常,以我这次要采集的晟格锐逆变器和极空 bms 为例
逆变器说明书上标明有两个 rs485 通信口,但是只有 2 可用,1 只能连接厂家上位机,无法直接使用。2 口又用于逆变器跟bms的直接通信,所以想要自己采集必须接 can 口,然后依旧使用 485 通信协议(iotrix 这种远程采集就无法使用了)
bms 带 3 个 485 口,1 口用来与逆变器直接通信,2 口用来采集,3 口用来并联,但是需要注意必须拨码指定 bms 从机地址才可以成功采集。以最简单的从机 1 为例,拨码开关第一个拨到上,其他拨到下即可
硬件部分
解决完上面机器的问题,用什么硬件采集反而很简单。我用 esp8266 和 esp32 都尝试过,连接基本一致,板子加一个 485 转 ttl 的模块即可
485 ttl 转换模块有好几种,都可以走通,但是建议买 3.3v 的,直接插板子 vcc 针脚供电
rx tx 的连接建议使用软串口,也就是在 esphome 的 uart 配置 tx_pin 和 rx_pin,因为 8266 的串口数量有限,debug 时可能存在串口占用的情况,统一用软串口较好
连接的原理就是板子的 rx tx 跟模块的 rx tx 反接,模块的 ab 跟机器的 ab 正接,具体示意图如下(max 485模块以及自动流控的485不太一样,但是类似)
ps: 通信不成功可以尝试不要让模块的共地跟机器连接,极空 bms 连接后无法通信
modbus 协议部分
逆变器和 bms 从网上都可以搜到协议的具体文档,如果你要采集的机器搜不到具体文档建议联系厂家获取
发送和接受的指令都是 16 进制的 hex 字符,比如 01:03:10:00:00:46:C0:F8,01 代表从机地址,03 代表 modbus 功能码(读),10:00:00:46 代表要读取的寄存器地址,最后两位是 CRC 校验码
如果你用 arduino 写代码的话,就需要手动拼接上面的字符串,所以建议还是用 esphome 来配置采集逻辑,相对来说简单很多
采集部分
来到重点了,这里坑尤其多,不过很多功能都有人封装过,比如 esp-jk-bms,但是预期未来有很多类似的采集需求,所以自己折腾下
esphome 的使用
强烈建议使用 esphome 来配置采集逻辑,一个是因为可以 ota,不需要坐在机器边上坐牢,另一个是因为跟 ha 的 api 无缝对接,不需要再转一道 mqtt,很方便
第一次需要先物理连接安装下,先在 ha 的 esphome builder 里创建个空配置,把 wifi 的名称和密码配上,然后点安装选择手动安装
esphome:
name: jk-bms-esp32
friendly_name: 极空保护板
esp32:
board: esp32dev
framework:
type: esp-idf
logger:
api:
encryption:
key: "mlXE354s0IcV6nfgvH+SadorLlX1WP9YjuROcIyT7V0="
ota:
- platform: esphome
password: "b42e4788201912783e40bfd2bc37eb1e"
wifi:
ssid: !secret wifi_ssid # -> 这个是在右上角的 secrets.yaml 中配置
password: !secret wifi_password
构建成功下载 bin 文件后,打开 esphome web 站点,usb 连接板子选择 install,刷入刚刚的 bin 文件(驱动问题可以看下 b 站太极创客的视频,基本就是安装下 ch340 的驱动即可)
传感器的配置
接下来就是脏活累活了,根据 modbus 协议,挨个配置要读写的传感器,比如
比较常用的就是 sensor、binary_sensor、switch、number 这四种组件,用来控制数据的读写,但是直接配在 ha 前台使用体验会很差,后文我会补下一些技巧
uart:
- id: uart_id
baud_rate: 115200
data_bits: 8
stop_bits: 1
parity: NONE
tx_pin: GPIO17
rx_pin: GPIO16
debug:
direction: BOTH
modbus:
id: modbus_id
uart_id: uart_id
modbus_controller:
- id: modbus_controller_id
modbus_id: modbus_id
address: 1
update_interval: never
sensor:
- platform: modbus_controller
name: "第 1 串电压_A_4608"
address: 4608 # -> 这个就是寄存器的地址,16进制或者10进制
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
filters:
- multiply: 0.001
采集的优化和技巧
按照协议文档挨个配置好 90 多个传感器之后,工作确实正常了,但是碰到了严重的性能问题
虽然 esphome 官方文档说连续的寄存器会合并成一个 uart 指令,但是实测不会
所以 5s 一次的采集频率,会顺序发送 90 多条指令,这会让传感器数据的更新不同步,而且占用串口通信通道,导致修改不生效
所以传感器的采集可以手动发送指令,一次指令采集多个寄存器的数据,拿到数据后手动 publish 到指定的传感器
优化之后,一次采集发送指令的数量从 90 到 5,没有任何压力,而且 ha 的前台数据同时更新,没有挨个刷新的情况,写入数据也立即生效,不会存在排队导致写入失败的情况
uart:
- id: uart_id
baud_rate: 115200
data_bits: 8
stop_bits: 1
parity: NONE
tx_pin: GPIO17
rx_pin: GPIO16
debug:
direction: BOTH
sequence:
- lambda: |-
if (bytes.empty() || bytes.size() < 5 || bytes[0] != 0x01 || bytes[1] != 0x03) return;
auto get_int32 = [&](int offset) -> int32_t {
if (offset + 3 >= bytes.size()) return 0;
return (int32_t(bytes[offset]) << 24) | (int32_t(bytes[offset+1]) << 16) |
(int32_t(bytes[offset+2]) << 8) | int32_t(bytes[offset+3]);
};
auto get_uint16 = [&](int offset) -> uint16_t {
if (offset + 1 >= bytes.size()) return 0;
return (uint16_t(bytes[offset]) << 8) | uint16_t(bytes[offset+1]);
};
// 在 uart 的 debug 里监听数据的返回
// 判断返回数据的第三位来确定到底是哪个数据(第三位就是返回的字节数)
switch (bytes[2]) {
case 0x8c:
// 从第 3 个字节开始,解析出 4 个字节的 int32 数据,push 到 A_4096 传感器
id(A_4096).publish_state(get_uint32(3) / 1000.0);
break;
case 0x80:
// 从第 3 个字节开始,解析出 2 个字节的 uint16 数据,push 到 A_4608 传感器
id(A_4608).publish_state(get_uint16(3) / 1000.0);
break;
}
modbus:
id: modbus_id
uart_id: uart_id
modbus_controller:
- id: modbus_controller_id
modbus_id: modbus_id
address: 1
update_interval: never
# 重点,使用 interval 控制发送频率
interval:
- interval: 5s
then:
# 用 lambda 语句手动发送指令
- lambda: |-
auto modbus = id(modbus_id);
// 从 0x1000 开始读取 70 个寄存器的数据
modbus->send(1, 0x03, 0x1000, 70, 0, nullptr);
- delay: 100ms # -> 延迟 100ms 继续读下一个
- lambda: |-
auto modbus = id(modbus_id);
// 从 0x1200 开始读取 64 个寄存器的数据
modbus->send(1, 0x03, 0x1200, 64, 0, nullptr);
- delay: 100ms
- lambda: |-
auto modbus = id(modbus_id);
// 从 0x128a 开始读取 43 个寄存器的数据
modbus->send(1, 0x03, 0x128a, 43, 0, nullptr);
- delay: 100ms
# number 示意
number:
- platform: template # -> 类型就直接使用 template,受控,由 uart 写入
name: "进入休眠电压_A_4096"
id: A_4096
unit_of_measurement: "V"
device_class: voltage
step: 0.001
min_value: 2.5
max_value: 3.6
set_action:
then:
- lambda: |- # -> 这部分控制写入逻辑,同样是手动构造结果,发送写入命令
uint32_t value = uint32_t(x * 1000);
uint8_t data[4] = {
uint8_t((value >> 24) & 0xFF),
uint8_t((value >> 16) & 0xFF),
uint8_t((value >> 8) & 0xFF),
uint8_t(value & 0xFF)
};
auto modbus = id(modbus_id);
modbus->send(1, 0x10, 4096, 2, 4, data);
# sensor 示意
sensor:
- platform: template
name: "第 1 串电压_A_4608"
id: A_4608
unit_of_measurement: "V"
device_class: voltage
state_class: measurement
# switch 示意
switch:
- platform: template
name: "充电开关_S_4208"
id: A_4208
restore_mode: ALWAYS_ON
turn_on_action: # -> 同样的似乎,ha 前台控制之后,构造指令,发送写入命令
then:
- lambda: |-
auto modbus = id(modbus_id);
uint8_t values[4] = {0, 0, 0, 1};
modbus->send(1, 0x10, 4208, 2, 4, values);
turn_off_action:
then:
- lambda: |-
auto modbus = id(modbus_id);
uint8_t values[4] = {0, 0, 0, 0};
modbus->send(1, 0x10, 4208, 2, 4, values);
# ... 剩下的挨个配置即可
|