简介:这份资源面向工业自动化与物联网方向的开发者、运维人员及技术学习者,聚焦将支持Modbus协议的工业设备接入物联网网关,并通过MQTT完成消息发布订阅与数据转换。包内以JavaScript实现为核心,配合配置文件、容器化脚本与说明文档,覆盖串口通信、实时数据采集、设备配置管理及远程监控等典型场景,适合具备一定编程基础、希望快速搭建Modbus转MQTT链路的读者参考。资源共32个文件,包含15个js源码、4个md文档、3个yml与3个json配置、2个txt说明,另有yaml、sh、pdf、dockerfile等,压缩包约273KB,结构紧凑便于按模块查阅。目前已有69人学习下载。通过源码与配置示例,读者可理解Modbus设备接入、MQTT主题发布订阅及数据标准化转换的实现思路,并借助Docker与运行脚本快速部署验证,为工业物联网网关开发提供可复用的参考方案。
1. 工业网关到底在转什么:从 Modbus 寄存器到 MQTT 主题的一条数据链路
车间里一台老 PLC 还在用 Modbus RTU 往外吐数据,上位机却想用 MQTT 订阅;现场有 485 传感器、有 Modbus TCP 的仪表、还有几台只认串口的设备,你不可能给每台设备都写一套采集程序。工业自动化里这类需求非常典型:底层是 Modbus 协议(RTU over RS485、TCP over 以太网),上层是 MQTT 消息队列,中间需要一个物联网网关把串口通信、数据转换、设备配置管理、远程监控串起来。这个标题讲的就是这么一套开源、跨平台的网关方案:把 Modbus 设备接入,做实时数据采集,再通过 MQTT 发布订阅把数据送到平台侧。适合做设备集成、边缘采集、SCADA 替代或自建监控的工程师,也适合想用 STM32 或 Linux 盒子做网关原型的开发者。下面按“协议怎么通、数据怎么转、配置怎么管、坑在哪”一路拆开。
2. Modbus 侧接入:RTU 与 TCP 的采集链路怎么搭
2.1 先分清 Modbus RTU、Modbus TCP 和串口参数
Modbus 本身是主从问答式协议,主站发请求,从站回响应。落到物理层,常见两种形态:Modbus RTU 跑在 RS485/RS232 串口上,靠波特率、数据位、停止位、校验位这组串口参数对齐;Modbus TCP 跑在以太网上,默认 502 端口,报文里多了 MBAP 头。热词里常出现的 modbus rtu、modbus tcp、modbus 报文、modbus 校验码在线计算,本质都是在确认同一件事:请求帧的从站地址、功能码、寄存器地址、数量、CRC 校验要对得上。
功能码决定你读的是什么。读线圈和离散输入用 01/02,读保持寄存器和输入寄存器用 03/04,写单个线圈/寄存器用 05/06,写多个用 15/16。很多新手卡在“地址对不上”,是因为设备手册写的是 1 基地址(比如 40001),而代码库通常用 0 基地址(对应 0)。这个偏移不统一,读出来就是错位或异常码。
串口参数必须和从站完全一致,常见组合是 9600/8/N/1 或 19200/8/E/1。波特率不一致时,现象是超时或乱码;校验位不一致时,可能偶尔能通但 CRC 频繁报错。RS485 半双工还要注意收发方向控制,USB 转 485 模块一般自动切换,但某些廉价模块需要手动 RTS 控制,否则发出去收不回来。
2.2 用 Python 跑通一次 Modbus RTU 采集
最小验证不要一上来就写网关,先用脚本把单台设备读通。下面用 pymodbus 读保持寄存器,串口走 RTU。
# modbus_rtu_probe.py from pymodbus.client import ModbusSerialClient import time # 串口参数必须与从站一致:端口、波特率、数据位、校验、停止位 client = ModbusSerialClient( port="/dev/ttyUSB0", # Windows 下换成 COM3 baudrate=9600, bytesize=8, parity="N", # 无校验;偶校验写 "E" stopbits=1, timeout=1.0, # 单次请求超时,现场建议 0.5~2s ) if not client.connect(): raise SystemExit("串口打开失败,检查端口占用和权限") try: # slave=1 是从站地址;address=0 是 0 基地址,对应手册 40001 rr = client.read_holding_registers(address=0, count=4, slave=1) if rr.isError(): print("读取异常:", rr) else: print("原始寄存器:", rr.registers) # 常见 32 位浮点:两个寄存器拼,注意字序 import struct raw = struct.pack(">HH", rr.registers[0], rr.registers[1]) print("按大端解析 float:", struct.unpack(">f", raw)[0]) finally: client.close()逻辑说明:先建立串口连接,再发一次读保持寄存器请求。address和count决定读哪段、读几个。slave是从站地址,多台 485 设备挂同一条总线时靠它区分。参数上,timeout太短会误判掉线,太长会拖慢轮询;现场我一般先设 1 秒,稳定后再压到 0.5 秒。解析部分要注意字序,很多仪表是 CDAB 而不是 ABCD,浮点数会翻车成完全离谱的值,这时把>HH换成>HH后交换顺序再试。
2.3 Modbus TCP 与多设备轮询的写法
Modbus TCP 把串口参数换成 IP 和端口,其余请求结构类似。多设备场景不要串行死等,按从站分组轮询,组内串行、组间可并发。
# modbus_tcp_poll.py from pymodbus.client import ModbusTcpClient DEVICES = [ {"host": "192.168.1.10", "slave": 1, "addr": 0, "count": 2}, {"host": "192.168.1.11", "slave": 2, "addr": 10, "count": 4}, ] for d in DEVICES: c = ModbusTcpClient(d["host"], port=502, timeout=1.0) if not c.connect(): print(d["host"], "连接失败") continue rr = c.read_holding_registers(address=d["addr"], count=d["count"], slave=d["slave"]) print(d["host"], "OK" if not rr.isError() else rr) c.close()参数说明:port默认 502,有些网关会改成 5020 之类,以设备为准。slave在 TCP 里对应单元标识,部分设备忽略它,但保留能兼容串口转 TCP 的网关。轮询周期要大于单次超时乘以设备数,否则请求会堆积。实时数据采集里,寄存器变化不频繁的点可以降频,关键点单独提频,别一刀切。
3. 数据转换与 MQTT 发布:把寄存器变成可订阅的主题
3.1 数据转换的三层:字节、工程量、主题模型
Modbus 给你的是 16 位寄存器,业务要的是温度、转速、状态。数据转换分三层:第一层字节解析,处理大小端、字序、有无符号、32/64 位拼接;第二层工程量换算,套用系数和偏移,比如实际值 = 原始值 * 0.1 + 0;第三层主题建模,决定 MQTT 主题怎么命名、payload 用什么格式。
主题设计直接影响后面好不好用。常见做法是工厂/车间/设备/测点,例如plant1/line2/plc1/temperature。payload 用 JSON,字段名和测点对应,方便平台侧直接入库。别把主题写成纯数字,后期排查会非常痛苦。热词里的 mqtt 订阅与发布消息、mqtt 协议详解,落到工程就是 QoS 和 retain 怎么选:实时采集用 QoS 0 够快,关键报警用 QoS 1 保达;设备当前状态可以 retain,让新订阅者立刻拿到最后值。
3.2 用 paho-mqtt 把采集结果发出去
下面把上一节的读取结果转成 JSON 并发布,包含断线重连的基本处理。
# gateway_publish.py import json, time, struct import paho.mqtt.client as mqtt from pymodbus.client import ModbusSerialClient BROKER = "192.168.1.100" TOPIC = "plant1/line2/plc1/data" def on_connect(client, userdata, flags, rc): print("MQTT 已连接,rc =", rc) mq = mqtt.Client(client_id="gw-01") mq.on_connect = on_connect mq.connect(BROKER, 1883, 60) mq.loop_start() # 后台线程维持心跳和重连 mb = ModbusSerialClient(port="/dev/ttyUSB0", baudrate=9600, bytesize=8, parity="N", stopbits=1, timeout=1.0) mb.connect() while True: rr = mb.read_holding_registers(address=0, count=2, slave=1) if not rr.isError(): raw = struct.pack(">HH", rr.registers[0], rr.registers[1]) temp = struct.unpack(">f", raw)[0] payload = { "ts": int(time.time() * 1000), "device": "plc1", "temperature": round(temp, 2), } # QoS 0 追求吞吐;关键点可改 1 mq.publish(TOPIC, json.dumps(payload), qos=0) time.sleep(1) # 采集周期,按现场调整逻辑说明:loop_start()让网络循环在后台跑,避免阻塞采集。client_id要唯一,重复会导致互相踢下线。ts用毫秒时间戳,平台侧好做时序对齐。qos=0适合高频实时数据,丢一两帧不影响趋势;报警类另开主题用 QoS 1。参数上,keepalive=60是心跳间隔,网络差可调大,但别超过 broker 允许的两倍。采集周期和发布周期可以解耦,比如 200ms 采、1s 发一次聚合值,减少消息量。
3.3 设备配置管理:把硬编码换成配置文件
设备一多,把 IP、从站地址、寄存器映射写死在代码里就是灾难。常见做法是外置 YAML 或 JSON,网关启动时加载,支持热更新。配置里至少包含:设备标识、协议类型、连接参数、测点列表(地址、类型、系数、偏移、单位、主题后缀)。
# devices.yaml devices: - id: plc1 protocol: modbus_rtu port: /dev/ttyUSB0 baudrate: 9600 parity: N slave: 1 points: - name: temperature addr: 0 type: float32 word_order: big scale: 1.0 offset: 0 unit: C topic: temperature参数说明:type决定解析方式,word_order处理字序,scale/offset做工程量换算,topic拼到设备主题后面。热更新时先校验新配置,再原子替换,失败保留旧配置,避免改错一个字段整个网关停摆。设备配置管理做得好,后面加设备就是改配置而不是改代码。
4. 避坑与排查:现场最容易翻车的 5 个点
4.1 读不到数据,先查物理层还是协议层
现象:脚本报超时或异常码,设备手册明明写着支持。原因:多数是物理层问题——485 A/B 接反、终端电阻没接、波特率或校验不一致、串口被其他程序占用。解决:先用 modbus poll、modbus 调试助手这类工具单独验证,能通再上代码;用示波器或串口助手看有没有回帧,没有回帧就是物理层,有回帧但 CRC 错就是参数或干扰。
4.2 浮点数解析出来是天文数字
现象:温度读出 1.2e38 之类。原因:字序或字节序不对,ABCD、CDAB、BADC、DCBA 四种组合没试全。解决:拿一个已知值反推,比如设备显示 25.0,逐个组合试,找到能还原的那个;配置里把word_order显式写清楚,别靠默认值。
4.3 MQTT 频繁掉线重连
现象:日志里反复连接、断开。原因:client_id重复被踢、keepalive 太小、broker 侧限制、网络抖动。解决:保证每个网关 client_id 唯一;keepalive 设 60 秒左右;开启自动重连并加退避,别一秒重连十次把 broker 打挂;检查 broker 的最大连接数和认证配置。
4.4 轮询周期和超时打架
现象:设备越多越慢,最后全部超时。原因:串行轮询下,单次超时乘以设备数超过轮询周期,请求排队。解决:算清楚最坏耗时,周期留余量;把慢设备和快设备分组;必要时用多串口或多线程,但同一条 485 总线不能并发,否则冲突。
4.5 配置改错导致网关起不来
现象:改完 YAML 重启,网关直接退出。原因:缩进错误、字段拼写错、寄存器地址越界。解决:启动时做 schema 校验,地址范围对照设备手册检查;保留上一版配置,加载失败自动回滚;日志里打印具体哪个设备哪个字段出错,别只报“配置错误”。
5. 进阶:让网关可验证、可扩展的几个具体技巧
网关做完能跑只是第一步,能不能长期稳定、能不能快速定位问题,靠的是可验证性。我一般会加一个“影子模式”:采集到的数据除了发 MQTT,同时写一份到本地环形缓冲或 SQLite,出问题时能回放对比,确认是采集错还是平台侧解析错。这个习惯帮我省过很多扯皮时间。
验证方法上,分三层做。第一层用 modbus poll 或调试助手确认单设备可读;第二层用 mosquitto_sub 订阅主题,看 payload 结构和频率是否符合预期;第三层做端到端对时,设备显示值、网关日志值、平台入库值三者对齐,偏差超过一个采集周期就要查。命令很简单:
# 订阅所有设备主题,观察实时数据 mosquitto_sub -h 192.168.1.100 -t 'plant1/#' -v参数说明:-t支持通配符,#匹配多层,+匹配单层;-v带上主题名,方便区分来源。调试阶段用这个比翻日志快得多。
扩展方向上,两个点最值得投入。一是协议适配层抽象:把 Modbus RTU、Modbus TCP 甚至其他协议统一成“读点”接口,上层数据转换和 MQTT 发布不变,加协议只加适配器。二是断线缓存:网络中断时把数据暂存本地,恢复后补发,避免实时数据采集出现空洞。缓存要有上限和淘汰策略,别把磁盘写满。
跨平台支持方面,Linux 盒子、Windows 工控机、甚至 STM32 物联网网关原型都能跑,区别在串口库和资源限制。STM32 上跑 FreeRTOS 时,Modbus 和 MQTT 要分任务,注意栈大小和优先级,串口中断里别做重活。我自己的习惯是:任何网关上线前,先让它空跑 24 小时,看内存和连接数是否平稳,再接真实设备。这个笨办法比任何压测都管用。希望帮到你。
本文还有配套的精品资源,点击获取