物联网 · 盛世宏博 · RS485调试

一、先定调:RS485 上的恒湿消毒净化一体机到底在传什么
恒湿消毒净化一体机把恒湿、净化、消毒、风机、滤网、故障这几条控制链路揉在一台设备里。RS485 上跑的是 Modbus RTU,平台侧真正要采的通常不是“一个温湿度”,而是:
工程第一原则:先拿到厂家《Modbus 寄存器表》,再谈平台点表。没寄存器表就上平台,后期 90% 时间都在“读出来的值像乱码”这种事上耗。
很多项目“平台采集不到数据”,根因不在协议,而在总线。
项目 | 推荐做法 | 常见错误 |
|---|---|---|
线缆 | 屏蔽双绞线,线径 ≥0.5mm²,特性阻抗 120Ω | 拿普通网线/RVV 代替 |
拓扑 | 手拉手总线 | 星型分支过长 |
终端电阻 | 总线首尾各 120Ω,中间节点不接 | 全接/全不接 |
屏蔽层接地 | 单点接地(主机端/转换器端) | 两端都接形成地环流 |
A/B 极性 | A 接 A,B 接 B | 接反后整条总线不通 |
波特率 | 9600 / 19200 起步 | 盲目上 115200 导致远距离误码 |
从站地址 | 1~247 唯一 | 多台同地址互相抢总线 |
档案库房湿度大,端子氧化是隐性杀手:A-B 静态电压正常应 >200mV,末端设备偶发掉线先量电压、看端子发黑没,不信“设备坏了”。
Modbus RTU 四类区:
0x 线圈 00001~09999 读写位
1x 离散输入 10001~19999 只读位
3x 输入寄存器 30001~39999 只读 16bit
4x 保持寄存器 40001~49999 读写 16bit最经典坑:手册写“40001 当前湿度”,平台/代码里实际发的是地址 0;写“40002”对应地址 1。
公式:
协议地址 = 手册点号 - 区基址
40001 → 0
40002 → 1
30001 → 0功能码:
异常码:01 不支持功能码 / 02 地址不存在 / 03 数据非法 / 04 设备内部错。
下面是一份工程可落地的点表模板,不同厂家地址会平移,但字段几乎都这些:
平台点名 | 手册地址 | 功能码 | 类型 | 读写 | 缩放 | 含义 |
|---|---|---|---|---|---|---|
run_mode | 40001 | 03 | UINT16 | R/W | 1 | 0停机 1自动 2手动加湿 3手动除湿 4通风 |
target_rh | 40002 | 03 | UINT16 | R/W | 0.1 | 目标湿度 %RH |
rh_deadband | 40003 | 03 | UINT16 | R/W | 0.1 | 湿度回差 |
humid_valve | 40004 | 03 | UINT16 | R/W | 1 | 加湿阀开度 0~100% |
compressor_freq | 40005 | 03 | UINT16 | R/W | 1 | 除湿压缩机频率 0~100% |
fan_speed | 40006 | 03 | UINT16 | R/W | 1 | 风机转速 0~100% |
room_rh | 40007 | 03 | UINT16 | R | 0.1 | 当前回风/室内湿度 |
room_temp | 40008 | 03 | INT16 | R | 0.1 | 当前温度,可为负 |
status_word | 40009 | 03 | UINT16 | R | 1 | bit0运行 bit1故障 bit2加湿中 bit3除湿中 bit4净化中 bit5消毒中 |
fault_code | 40010 | 03 | UINT16 | R | 1 | 0无 1高压 2低压 3缺相 … |
run_hours | 40011 | 03 | UINT32 | R | 1 | 累计运行小时,占 2 寄存器 |
filter_remain | 40013 | 03 | UINT16 | R | 1 | 滤网剩余小时 |
filter_alarm | 40014 | 03 | UINT16 | R | 1 | 0正常 1更换 |
remote_enable | 40015 | 06 | UINT16 | W | 1 | 0本地 1远程 |
disinfect_mode | 40016 | 03 | UINT16 | R/W | 1 | 0关 1紫外 2臭氧 3等离子 |
disinfect_run_hours | 40017 | 03 | UINT32 | R | 1 | 消毒模块累计小时 |
⚠️ 厂家 A 可能把 room_rh 放 30001(04 读),厂家 B 放 40007(03 读)。点表必须带“功能码”一列,不能只写地址。
读出 250 ≠ 250℃,可能是 25.0℃(×0.1),也可能是 250.0℃(×1.0)。
规则:点表里必须写 scale,平台采集后统一乘系数,不让前端页面硬除。
温度可能为负,room_temp 用 INT16;湿度用 UINT16。
读出来 65535 别直接显示,先判断是不是 -1 的十六进制补码。
run_hours / 累计电量 / 消毒时长常占 2 个寄存器。
字节序可能是:
大端:reg0 高16位,reg1 低16位
小端:reg0 低16位,reg1 高16位Python 里别裸拼:
import struct
def combine_u32(high: int, low: int, big_endian: bool = True) -> int:
raw = struct.pack('>HH', high, low) if big_endian else struct.pack('<HH', high, low)
return struct.unpack('>I' if big_endian else '<I', raw)[0]恒湿消毒净化一体机(RS485/Modbus RTU)
│
▼
RS485→串口/以太网网关(Modbus RTU→TCP 或 MQTT)
│
▼
边缘采集服务(点表解析 / 字节序 / 缩放 / 合理性校验)
│
▼
动环平台(设备模型 + 实时值 + 告警 + 控制下发)不要让平台直接轮询 30 台一体机原始串口:
RS485 半双工,必须“发一帧—等响应—再发下一帧”。网关做串行调度,平台只消费结果。
轮询节奏(体感比“越快越好”重要):
湿度传感器响应 3~5s,消毒因子更慢,100ms 轮询只是把总线打满。
# hvac_purifier_collector.py
import time
import struct
from typing import Dict, Any
from pymodbus.client import ModbusSerialClient
POINT_TABLE = {
"run_mode": {"addr": 0, "fc": 3, "type": "u16", "scale": 1, "unit": ""},
"target_rh": {"addr": 1, "fc": 3, "type": "u16", "scale": 0.1, "unit": "%RH"},
"room_rh": {"addr": 6, "fc": 3, "type": "u16", "scale": 0.1, "unit": "%RH"},
"room_temp": {"addr": 7, "fc": 3, "type": "i16", "scale": 0.1, "unit": "°C"},
"status_word": {"addr": 8, "fc": 3, "type": "u16", "scale": 1, "unit": ""},
"fault_code": {"addr": 9, "fc": 3, "type": "u16", "scale": 1, "unit": ""},
"disinfect_mode": {"addr": 15, "fc": 3, "type": "u16", "scale": 1, "unit": ""},
}
STATUS_BITS = {
0: "running",
1: "fault",
2: "humidifying",
3: "dehumidifying",
4: "purifying",
5: "disinfecting",
}
class PurifierCollector:
def __init__(self, port: str, slave: int, baudrate: int = 9600,
parity: str = "N", stopbits: int = 1, timeout: float = 1.5):
self.slave = slave
self.client = ModbusSerialClient(
port=port, baudrate=baudrate, parity=parity,
stopbits=stopbits, bytesize=8, timeout=timeout
)
def connect(self) -> bool:
return self.client.connect()
def read_point(self, name: str) -> Any:
cfg = POINT_TABLE[name]
rr = self.client.read_holding_registers(cfg["addr"], count=1, slave=self.slave)
if rr.is_error():
raise RuntimeError(f"{name} read error: {rr}")
raw = rr.registers[0]
if cfg["type"] == "i16" and raw >= 32768:
raw -= 65536
return raw * cfg["scale"]
def read_all(self) -> Dict[str, Any]:
out = {}
for name in POINT_TABLE:
try:
out[name] = self.read_point(name)
except Exception as e:
out[name] = None
out[f"{name}__err"] = str(e)
out["status_bits"] = self.decode_status(out.get("status_word") or 0)
return out
def decode_status(self, word: int) -> list:
return [label for bit, label in STATUS_BITS.items() if word & (1 << bit)]
def set_target_rh(self, rh: float):
"""下发目标湿度:%RH → 0.1 精度整数"""
val = int(rh / 0.1)
rr = self.client.write_register(POINT_TABLE["target_rh"]["addr"], val, slave=self.slave)
if rr.is_error():
raise RuntimeError(f"write target_rh error: {rr}")每条点必须包含:
- name: room_rh
slave: 1
function: 3
address: 6 # 0-based
register_type: holding
data_type: u16
scale: 0.1
byte_order: AB
word_order: ABCD # 32位才用
unit: '%RH'
min: 0
max: 100
valid_range: [0, 100]
poll_interval: 2
alarm:
warning_high: 60
warning_low: 45
critical_high: 65
critical_low: 40平台侧别只存“值”,要存:原始值、缩放后值、采样时间、通讯状态、数据有效期(TTL)。
某寄存器 30s 不刷新就标“数据陈旧”,比等 5 分钟再报离线更实用。
现象 | 根因 | 处理 |
|---|---|---|
完全无响应 | 波特率/校验/停止位不一致 | 先锁 9600-N-8-1 |
偶发超时 | 终端电阻缺失/屏蔽层断 | 首尾加 120Ω,查穿墙处屏蔽层 |
地址通但值乱跳 | 字节序/缩放错 | 比对手持表,修正 scale/endian |
写不进去 | 本地模式/通讯锁 | 先写 remote_enable=1 |
末端一台老掉线 | 总线反射/端子氧化 | 终端电阻+镀镍端子+缩短分支 |
消毒状态不准 | 状态字 bit 定义版本不同 | 拿厂家固件版本协议对 |
平台显示离线设备正常 | 网关轮询阻塞 | 网关串行调度+超时重试隔离 |
关键词:恒湿消毒净化一体机,RS485,Modbus RTU,寄存器映射,点表,缩放因子,字节序,终端电阻,动环平台,数据采集
标签:#恒湿消毒净化一体机 #RS485 #ModbusRTU #寄存器映射 #点表配置 #RS485调试 #动环平台 #数据采集 #终端电阻 #边缘网关
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。