首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >恒湿一体机与监控平台联动控制:RS485 标准协议下发启停指令实操

恒湿一体机与监控平台联动控制:RS485 标准协议下发启停指令实操

原创
作者头像
BJ盛世宏博小程
发布于 2026-10-13 11:09:11
发布于 2026-10-13 11:09:11
120
举报

恒湿一体机与监控平台联动控制:RS485 标准协议下发启停指令实操

物联网 · 盛世宏博 · RS485联动控制


一、联动控制的本质:平台不是“看数据”,是“发指令”

恒湿消毒净化一体机走 Modbus RTU 时,平台角色分两层:

  • 采集端:03 读保持寄存器 / 04 读输入寄存器,拿温湿度、状态字、故障码
  • 控制端:05 写线圈(开关量启停)或 06 写保持寄存器(模式/目标值/启停字),把“人/策略”变成设备动作

核心原则:启停不一定是“写 1/写 0 到某个寄存器”,要看厂家把“开机”做成线圈还是保持寄存器里的运行模式字。


二、两种启停模型(先对号入座)

模型 A:线圈启停(05 功能码)

设备把“主机启停”映射成线圈地址,比如:

  • 线圈 00001 = 一体机总开关
  • 写 FF 00 = ON
  • 写 00 00 = OFF

报文(从站地址 1,线圈地址 0x0000 开机):

代码语言:javascript
复制
01 05 00 00 FF 00 8C 3A

关机:

代码语言:javascript
复制
01 05 00 00 00 00 CD CA

⚠️ 05 功能码里“开”不是 00 01,是 FF 00;“关”是 00 00。发 00 01 很多设备不认。

模型 B:保持寄存器启停(06 功能码)

设备把运行模式放在保持寄存器里,比如:

  • 40001 运行模式:0=停机,1=自动,2=手动加湿,3=手动除湿,4=通风
  • 40002 远程启停:0=停,1=开

开机写 1:

代码语言:javascript
复制
01 06 00 01 00 01 19 CA

解释:

  • 01 从站地址
  • 06 写单个保持寄存器
  • 00 01 寄存器偏移(40002 → 偏移 1)
  • 00 01 写入值 1
  • 后 2 字节 CRC16

设备成功会原样回显这一帧;回显不一致 = 没写进去。


三、平台下发启停的标准动作(不能“发了就算”)

代码语言:javascript
复制
平台点击“启动”
   ↓
1. 白名单校验:该寄存器是否允许写
   ↓
2. 构造 RTU 帧(地址+功能码+地址+值+CRC)
   ↓
3. 网关串口发送(RS485 半双工,发完切接收)
   ↓
4. 等从站回执
   ↓
5. 回执校验:
   - 原样回显 → 写成功
   - 返回 8X + 功能码 → 异常(01不支持/02地址错/03数据非法)
   ↓
6. 回读确认:再用 03 读一次运行模式/状态字
   ↓
7. 平台状态机更新:running / stopped / fault

关键:写完后必须回读。很多一体机“写成功但没动作”——值超范围、本地锁键、湿度已达设定、消毒互锁,都不会真动。


四、平台侧启停指令代码(可直接用)

代码语言:javascript
复制
# hvac_start_stop.py
import time
from pymodbus.client import ModbusSerialClient

class PurifierController:
    def __init__(self, port, slave=1, baudrate=9600, parity="N", timeout=1.5):
        self.slave = slave
        self.client = ModbusSerialClient(
            port=port, baudrate=baudrate, parity=parity,
            stopbits=1, bytesize=8, timeout=timeout
        )
        # 点表:不同厂家改这里
        self.REG = {
            "run_mode": 0,      # 40001
            "remote_enable": 14, # 40015
            "power_ctrl": 1,     # 40002(若厂家用保持寄存器做启停)
        }
        self.COIL = {
            "power_coil": 0,     # 线圈 00001
        }

    def connect(self):
        return self.client.connect()

    # ---- 线圈启停 ----
    def start_by_coil(self):
        rr = self.client.write_coil(self.COIL["power_coil"], True, slave=self.slave)
        if rr.is_error():
            raise RuntimeError(f"coil start error: {rr}")
        return self._confirm_running()

    def stop_by_coil(self):
        rr = self.client.write_coil(self.COIL["power_coil"], False, slave=self.slave)
        if rr.is_error():
            raise RuntimeError(f"coil stop error: {rr}")
        return self._confirm_stopped()

    # ---- 保持寄存器启停 ----
    def start_by_register(self):
        # 先开远程权限
        self.client.write_register(self.REG["remote_enable"], 1, slave=self.slave)
        # 写运行模式=自动
        rr = self.client.write_register(self.REG["run_mode"], 1, slave=self.slave)
        if rr.is_error():
            raise RuntimeError(f"register start error: {rr}")
        return self._confirm_running()

    def stop_by_register(self):
        rr = self.client.write_register(self.REG["run_mode"], 0, slave=self.slave)
        if rr.is_error():
            raise RuntimeError(f"register stop error: {rr}")
        return self._confirm_stopped()

    # ---- 回读确认 ----
    def _confirm_running(self, retry=3):
        for _ in range(retry):
            rr = self.client.read_holding_registers(self.REG["run_mode"], 1, slave=self.slave)
            if not rr.is_error():
                mode = rr.registers[0]
                status = self.read_status_word()
                return {
                    "mode": mode,
                    "running": mode != 0 and "running" in status,
                    "status": status,
                }
            time.sleep(0.5)
        return {"running": False, "status": ["read_back_failed"]}

    def _confirm_stopped(self, retry=3):
        for _ in range(retry):
            rr = self.client.read_holding_registers(self.REG["run_mode"], 1, slave=self.slave)
            if not rr.is_error():
                mode = rr.registers[0]
                return {"mode": mode, "stopped": mode == 0}
            time.sleep(0.5)
        return {"stopped": False, "status": ["read_back_failed"]}

    def read_status_word(self):
        rr = self.client.read_holding_registers(8, 1, slave=self.slave)  # 40009 状态字
        if rr.is_error():
            return ["read_error"]
        w = rr.registers[0]
        bits = {0:"running",1:"fault",2:"humidifying",3:"dehumidifying",
                4:"purifying",5:"disinfecting"}
        return [v for k,v in bits.items() if w & (1<<k)]

五、平台联动规则怎么写(防抖、防震荡、防跳闸)

单纯“湿度>60% 启动除湿”会出事:湿度在 59/61 之间晃,设备一分钟启停七八次。

正确联动三件套:

1. 回差(死区)

代码语言:javascript
复制
湿度 > 60%RH  → 启动除湿
湿度 < 52%RH  → 停止除湿
52~60 之间:保持现状

2. 持续时间

代码语言:javascript
复制
湿度 > 60%RH 且 持续 5 分钟 → 才下发启动
湿度 < 52%RH 且 持续 10 分钟 → 才下发停止

3. 最小运行时长 / 启停间隔

代码语言:javascript
复制
单台最少运行 10 分钟
两次启停间隔 ≥ 3 分钟
多台同时动作 ≤ 3 台,其余按地址错峰 30s

伪代码:

代码语言:javascript
复制
if rh > 60 and since_over_high > 300:
    if now - last_switch > 180:
        controller.start_by_register()
        last_switch = now

if rh < 52 and since_under_low > 600:
    if now - last_switch > 180:
        controller.stop_by_register()
        last_switch = now

档案库房/配电室真实案例:12 台一体机同时收到平台指令同时启动压缩机,配电柜直接跳闸——错峰和写队列是必做项。


六、平台控制安全边界(上线前必须定)

项目

规则

可写点表

只开放 run_mode / target_rh / remote_enable / disinfect_mode 等白名单

只读点

温湿度、故障码、累计时长、滤网剩余——平台禁止写

本地优先

屏上“本地/远程”开关在“本地”时,平台写指令被忽略或回异常

权限分级

操作员可启停,访客只读,平台账号分角色

二次确认

Web 点“停机”弹窗:“确认停止恒湿机?库房将失去湿控”

通信中断

平台 3 次轮询无响应 → 标通信丢失,但不自动下发任何指令,设备按本地设定继续跑

消毒互锁

门磁/烟感/有人态为真时,平台禁止启动 UV/臭氧


七、启停指令调试顺序(现场版)

  1. 串口助手单发 01 05 00 00 FF 00 或 01 06 00 01 00 01,看设备屏是否变“运行”
  2. 看回帧:原样回 = OK,回 81 05 / 81 06 = 异常
  3. 再发读指令 01 03 00 00 00 01 看运行模式寄存器
  4. 平台侧先接 1 台,跑 24h 启停联动
  5. 再加到 5 台 → 12 台,观察总线 CRC 错误率
  6. 最后接门磁/烟感/定时消毒,做“通信中断”“消毒互锁”故障注入

八、高频翻车点

现象

根因

处理

平台显示“启动成功”但机器不动

没回读,实际本地锁键/模式冲突

写后必须 03 回读状态字

写 05 发 00 01 不开机

05 开=FF00,不是 0001

改报文

12 台同时启动跳闸

无错峰

写队列+按地址延迟

湿度 59↔61 设备狂启停

无死区

加回差+最小运行时长

通信断了平台还下发停机

缺通信互锁

丢联时机型进入“本地自治”

消毒时人在库房

没接门磁互锁

平台联动规则加“有人=禁消毒”


九、一句话收住

RS485 下发启停不是“平台点一下”,而是:白名单校验 → 05/06 发帧 → 原样回显判断 → 03 回读确认 → 状态机更新 → 联动规则带死区/错峰/互锁。​

少一步,现场就多一次半夜跳闸或库房湿度失控。


关键词:恒湿一体机,RS485,Modbus RTU,启停指令,05写线圈,06写寄存器,平台联动,回读确认,死区控制,错峰启动

标签:#恒湿一体机 #RS485 #ModbusRTU #启停控制 #05功能码 #06功能码 #平台联动 #回读确认 #死区控制 #动环监控

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 恒湿一体机与监控平台联动控制:RS485 标准协议下发启停指令实操
    • 一、联动控制的本质:平台不是“看数据”,是“发指令”
    • 二、两种启停模型(先对号入座)
      • 模型 A:线圈启停(05 功能码)
      • 模型 B:保持寄存器启停(06 功能码)
    • 三、平台下发启停的标准动作(不能“发了就算”)
    • 四、平台侧启停指令代码(可直接用)
    • 五、平台联动规则怎么写(防抖、防震荡、防跳闸)
      • 1. 回差(死区)
      • 2. 持续时间
      • 3. 最小运行时长 / 启停间隔
    • 六、平台控制安全边界(上线前必须定)
    • 七、启停指令调试顺序(现场版)
    • 八、高频翻车点
    • 九、一句话收住
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档