物联网 · 盛世宏博 · RS485总线 · 批量接入 · 恒湿净化 · Modbus RTU
关键词:RS485、Modbus RTU、恒湿净化一体机、批量接入、档案库房、多设备通信、协议网关、轮询调度 标签:#物联网 #Modbus #RS485 #TCP/IP #腾讯云 #Wireshark #Python #以太网温湿度传感器 #网口温湿度变送器 #机房监控

恒湿净化一体机属于"功能多、单价高、换机周期长"的设备。市面上主流品牌(含部分进口机型)的控制板几乎清一色是RS485 + Modbus RTU,以太网口要么是选配、要么要额外加钱。而档案库房这类项目,设备数量通常是5~20台,单机距离从20米到80米不等,走线集中在同一库房内。
这个场景恰恰是RS485的舒适区:
维度 | RS485总线 | 逐台网口接入 |
|---|---|---|
布线 | 手拉手菊花链,1根总线走到底 | 每台独立网线回到交换机 |
设备成本 | 一体机便宜(无网口模块) | 带网口机型贵10%~20% |
协议 | Modbus RTU,标准寄存器 | 各家私有/需SDK |
数量 | 1条总线挂8~32台 | 交换机端口消耗大 |
调试 | Modbus Poll + 手拉手排查 | 需IP规划、VLAN |
不足 | 半双工、轮询、总线故障影响整条链 | — |
结论:库房内一体机优先RS485,把整条总线收敛到1~2台串口服务器上转TCP,再进入平台。关键是把"总线"当成一个整体来设计,而不是当成32个独立设备来管。
┌──────────────┐
平台 ◄──TCP──► 串口服务器 ├──┐
└──────────────┘ │
▼
A ──── B ──── C ──── D ──── E ← 手拉手
[1#] [2#] [3#] [4#] [5#]
终端电阻120Ω只挂在链路两端(A和E)规则 | 要求 |
|---|---|
拓扑 | 必须手拉手(daisy chain),禁止星型分支 |
分支 | 单点分支≤0.3m,否则改用485集线器 |
终端电阻 | 只在链路两端各1个120Ω,中间节点全部不接 |
极性 | A对A、B对B,全程不能反;建议全库房统一颜色 |
屏蔽 | 双绞屏蔽线,屏蔽层单点接地(在串口服务器端) |
线径 | ≥0.5mm²(24AWG),长距离用0.75mm² |
接地 | 多台设备共地,避免地环路(浮地设备用隔离型串口服务器) |
90%的总线故障来自终端电阻接错和极性接反——进场施工前用万用表量一遍A/B通断和首尾电阻,比上线后抓包排查省十倍时间。
理论:最大32个单元负载,最大1200米(9600bps)
实际建议(档案库房项目经验值):
设备数量 ≤ 16台 → 1200米内无压力,9600bps足够
设备数量 17~24台 → 需确认每台是1/4或1/8负载,否则加485中继器
设备数量 > 24台 → 拆分为2条总线(如按区域或楼层)
波特率选择:
9600 → 默认,1200米内稳定
19200 → 50台以内、距离<400米可考虑
>19200 → 不建议,抗干扰能力差,调试痛苦各家一体机的寄存器表是"同一套Modbus,不同方言"。平台接入前必须做一次寄存器归一化,否则后续联动逻辑会被设备差异拖死。
功能 | 常见地址范围 | 归一化后平台定义 | 缩放/单位 |
|---|---|---|---|
设备状态 | 0x0001 / 10001H | device_status | 位标志 |
运行模式 | 0x0002 / 10002H | run_mode | 0待机1自动2除湿3加湿4净化5手动 |
回风温度 | 0x0010 起 | temp_return | ×0.1 ℃ |
回风湿度 | 0x0011 起 | hum_return | ×0.1 %RH |
设定湿度 | 0x0100 起 | hum_setpoint | ×0.1 %RH |
设定温度 | 0x0101 起 | temp_setpoint | ×0.1 ℃ |
风机转速 | 0x0020 起 | fan_speed | % |
故障码 | 0x0030 起 | fault_code | 位标志 |
累计时长 | 0x0040 起 | run_hours | h |
关键动作:每家品牌出一份《寄存器映射对照表》,把地址、字节序(AB/CD vs CD/AB)、缩放系数全部固化。平台只读"归一化表",不读原厂表。
规则:每条总线内地址唯一,1~247
建议:地址 = 区域号×10 + 序号
1#库房:11, 12, 13 ...
2#库房:21, 22, 23 ...
⚠️ 现场常踩的坑:
□ 多台设备出厂默认地址都是1 → 总线全是错帧
□ 地址拨码开关是二进制但读反了
□ 改完地址没断电重启,实际未生效
→ 规范:上电前用Modbus Poll逐台改地址,改完断电再级联# rs485_bus.py - RS485总线管理器(精简版)
import socket
import struct
import time
import threading
from typing import Dict, Optional
def crc16_rtu(data: bytes) -> bytes:
crc = 0xFFFF
for b in data:
crc ^= b
for _ in range(8):
crc = (crc >> 1) ^ 0xA001 if crc & 1 else crc >> 1
return struct.pack('<H', crc)
class RS485Bus:
"""一条RS485总线 = 1个TCP连接(串口服务器透明模式)"""
def __init__(self, host, port, timeout=1.5, inter_frame=0.02):
self.host, self.port = host, port
self.timeout = timeout
self.inter_frame = inter_frame # 帧间隔,防粘包
self.sock: Optional[socket.socket] = None
self._lock = threading.Lock()
def connect(self):
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.sock.settimeout(self.timeout)
self.sock.connect((self.host, self.port))
def close(self):
if self.sock:
self.sock.close()
self.sock = None
def _request(self, slave, fc, addr, count=1, value=None) -> Optional[bytes]:
with self._lock: # 整条总线串行访问
if not self.sock:
self.connect()
if fc == 0x03:
body = struct.pack('>HHBHH', 0, 0, 6, slave, fc, addr, count)
elif fc == 0x06:
body = struct.pack('>HHHHBHH', 0, 0, 6, slave, fc, addr, value)
else:
return None
frame = body + crc16_rtu(body)
self.sock.send(frame)
time.sleep(self.inter_frame)
# RTU靠时间判定帧尾,收不够就超时
buf = b''
deadline = time.time() + self.timeout
while time.time() < deadline:
try:
chunk = self.sock.recv(256)
if not chunk:
break
buf += chunk
if len(buf) >= 5 and len(buf) >= (5 + buf[2]):
break
except socket.timeout:
break
if len(buf) < 5:
return None
if buf[-2:] != crc16_rtu(buf[:-2]):
return None
return buf
def read_holding(self, slave, addr, count=1):
return self._request(slave, 0x03, addr, count)
def write_single(self, slave, addr, value):
return self._request(slave, 0x06, addr, value=value) is not None# polling_scheduler.py - 批量轮询(关键在调度策略)
import asyncio
from dataclasses import dataclass, field
@dataclass
class RTUDevice:
slave_id: int
room: str
model: str
reg_map: dict = field(default_factory=dict)
online: bool = True
last_seen: float = 0.0
fail_count: int = 0
class PollingScheduler:
"""多总线、多设备批量轮询,带失败重试与离线判定"""
def __init__(self, buses: Dict[str, 'RS485Bus'], devices: list):
self.buses = buses # bus_id -> RS485Bus
self.devices: Dict[str, RTUDevice] = {d.slave_id: d for d in devices}
self.offline_threshold = 3 # 连续3次失败判离线
self.scan_interval = 10 # 全量扫描周期(秒)
async def poll_device(self, bus_id: str, dev: RTUDevice):
bus = self.buses[bus_id]
try:
resp = bus.read_holding(dev.slave_id, dev.reg_map['hum_return'], 2)
if resp:
dev.online = True
dev.fail_count = 0
dev.last_seen = time.time()
# 解析:return (value, )
return self._parse(resp, dev.reg_map)
else:
raise IOError("no response")
except Exception:
dev.fail_count += 1
if dev.fail_count >= self.offline_threshold:
dev.online = False
return None
async def run(self):
"""全量轮询循环;设备多时分片扫描避免单次周期过长"""
while True:
tasks = []
for bus_id, bus in self.buses.items():
for dev in self._devices_on_bus(bus_id):
tasks.append(self.poll_device(bus_id, dev))
await asyncio.gather(*tasks, return_exceptions=True)
await asyncio.sleep(self.scan_interval)
def _devices_on_bus(self, bus_id):
return [d for d in self.devices.values() if d.bus_id == bus_id]
def _parse(self, resp, reg_map):
# 简化:按寄存器表解析,实际需处理字节序
byte_count = resp[2]
return struct.unpack(f'>{byte_count//2}H', resp[3:3+byte_count])调度要点:
选型要素 | 建议 |
|---|---|
工作模式 | TCP Client + 串口服务器模式(平台做TCP Server)或 TCP Server 透明传输 |
端口隔离 | 每条总线1个独立串口,避免共用 |
隔离保护 | 光电隔离 + TVS,库房潮湿环境必备 |
波特率/校验 | 9600 / 8N1(默认值,改了必须全链路一致) |
打包参数 | 串口打包时间设2~5ms、打包长度64字节 |
心跳 | 开启TCP KeepAlive,断线自动重连 |
网管 | 支持SNMP或Web管理,便于批量运维 |
透明模式 vs 内置Modbus:串口服务器若支持内置Modbus TCP转RTU网关(如MOXA MGate、有人USR-TCP232),平台可直接发Modbus TCP、由网关转RTU帧,省掉自研协议层。但注意网关的从站映射表容量限制。
Step 1 单台验证(脱离总线)
用Modbus Poll直连单台设备,核对全部寄存器读/写
目的:确认寄存器表正确、设备本身正常
Step 2 地址规划与改址
逐台上电改从站地址,改完断电、记录地址贴标签
目的:杜绝地址冲突
Step 3 总线级联
从首端一台一台往下挂,每挂一台用Modbus Poll扫描全部
目的:实时发现接反、终端电阻错误
Step 4 平台联调
跑全量轮询,核对数据一致性、写入回读、告警联动
目的:验证批量接入逻辑项目 | 标准 |
|---|---|
数据采集 | 全部设备连续72小时无中断 |
单帧响应 | ≤100ms(9600bps) |
全量扫描 | 周期≤30秒 |
离线检测 | 3次失败内判定并告警 |
写入验证 | 写后回读100%一致 |
总线故障隔离 | 单点故障不影响其他总线 |
档案库房多台恒湿净化一体机走RS485批量接入,本质是把"多设备通信"收敛为"多总线管理"。三条主线:拓扑上守死手拉手+两端终端电阻,协议上做寄存器归一化消除厂家方言,调度上串行访问+失败降频。最省事的做法是选带内置Modbus网关的串口服务器,平台直接发Modbus TCP;需要定制联动逻辑的,就用自研轮询层。调试按"单台→改址→级联→平台"四步走,可把总线类问题在进场阶段消灭掉。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。