简介本资源是一套面向工业自动化与物联网开发者的Modbus-MQTT协议桥接实践方案聚焦传统Modbus设备TCP/RTU模式接入现代MQTT物联网平台的技术落地适用于嵌入式工程师、工业网关开发者及IoT系统集成人员。压缩包共20个文件含7个C源码如mb_csv.c、mqtt4modbus.c、4个头文件common.h、cJSON.h等、3个Makefile构建脚本、2个说明文档txt/pdf、1个系统架构图png、1个README.md和1个CSV配置示例总大小325KB结构清晰覆盖协议解析、JSON封装、CSV数据映射、MQTT发布订阅等核心模块。已有112人学习下载。读者可直接编译运行基于libmodbus与libmosquitto的桥接程序掌握Modbus从串口/以太网采集到MQTT主题发布的完整链路并复用csv配置实现设备点表动态管理配套PDF文档与简介文件进一步降低理解门槛。1. 这不是协议转换器而是一套可落地的工业设备远程管理闭环用 MQTT 把 Modbus RTU/TCP 设备真正“接进”云平台不依赖商用网关、不改 PLC 程序、不写一行嵌入式固件你手头有一台老式温控仪Modbus RTU、一台变频器Modbus TCP它们连在车间现场的 RS485 总线和局域网里数据只能靠 Modbus Poll 手动读、靠 Excel 记录、靠人盯屏——这不是自动化是“半自动人工补漏”。这个 ZIP 包干了一件事用标准 Linux x86_64 或 ARM64 主机树莓派/工控机当“协议翻译官”把 Modbus 设备的数据实时桥接到 MQTT Broker比如 Mosquitto、EMQX、阿里云 IoT Platform让 Grafana 能画曲线、Node-RED 能做逻辑、Python 脚本能触发告警、手机 App 能查实时值。它不封装成黑匣子服务而是提供完整可编译源码C 语言、清晰配置文件JSON、带注释的 Makefile 和实测日志样本。核心是libmodbusv3.1.10对接物理层libmosquittov2.0.15处理消息发布中间用内存映射环形缓冲区做零拷贝转发。适合产线工程师、SCADA 系统集成商、IoT 方案开发者——只要你能make ./modbus_mqtt_bridge -c config.json启动就能拿到真实设备的/device/temperature/0x0001这类 MQTT Topic 数据流。它解决的不是“能不能通”而是“通了之后怎么稳、怎么查、怎么扩”。2. 编译与部署从源码到可执行文件的四步链路含交叉编译适配树莓派与国产 ARM 工控机的实操细节2.1 源码结构解析看清三个核心模块的职责边界与数据流向解压 ZIP 后目录结构如下已剔除 build/、doc/ 等非必要路径modbus_mqtt_bridge/ ├── src/ │ ├── main.c # 主循环加载配置、初始化 modbus/mqtt、启动采集线程 │ ├── modbus_handler.c # Modbus 层创建 RTU/TCP 连接、轮询寄存器、错误重试策略 │ ├── mqtt_publisher.c # MQTT 层连接 broker、构建 topic、序列化 payloadJSON/RAW │ └── config_parser.c # 配置层解析 config.json校验 device_id、slave_id、register_map ├── config.json.example # 可直接复制修改的模板含 RTU TCP 双设备示例 ├── Makefile # 支持 native / arm-linux-gnueabihf / aarch64-linux-gnu 三套工具链 └── README.md关键设计逻辑Modbus Handler 不做业务逻辑只负责“读到什么就传什么”寄存器地址、类型holding/input/coil、字节序big-endian/little-endian、缩放系数scale0.1 表示原始值 ×0.1全由config.json定义MQTT Publisher 严格遵循 QoS1每条消息带retainfalse避免旧数据污染订阅端topic 命名规则为prefix/device_id/function_code/register_addr如factory/plc01/03/0001主进程不 fork daemon默认前台运行便于systemd管理或docker run -it调试日志输出到 stdout/stderr。提示src/modbus_handler.c中modbus_set_response_timeout()默认设为 1500ms对老旧 RTU 设备如西门子 S7-200 SMART建议调至 2500msTCP 设备保持默认即可。2.2 本地编译Ubuntu 22.04 LTS依赖安装、Makefile 参数与可执行文件验证# 1. 安装基础依赖含 pkg-config否则 libmodbus/libmosquitto 检测失败 sudo apt update sudo apt install -y build-essential pkg-config libmodbus-dev libmosquitto-dev # 2. 进入源码目录检查 Makefile 工具链定义默认为 native grep CC ? Makefile # 输出CC ? gcc # 3. 编译生成 ./modbus_mqtt_bridge make clean make # 4. 验证可执行文件确认链接了正确版本的动态库 ldd ./modbus_mqtt_bridge | grep -E (modbus|mosquitto) # 正常输出应含libmodbus.so.5 /usr/lib/x86_64-linux-gnu/libmodbus.so.5 # libmosquitto.so.1 /usr/lib/x86_64-linux-gnu/libmosquitto.so.1参数说明make默认使用gcc若需指定 C 标准如 C11可加CFLAGS-stdc11若系统中libmodbus版本低于 3.1.10dpkg -l | grep libmodbus查看需手动编译安装wget https://github.com/stephane/libmodbus/archive/refs/tags/v3.1.10.tar.gz tar -xzf v3.1.10.tar.gz cd libmodbus-3.1.10 ./autogen.sh ./configure --prefix/usr make sudo make install2.3 交叉编译适配树莓派ARMv7与飞腾/兆芯工控机ARM64树莓派 4BRaspberry Pi OS 64-bit# 安装交叉编译工具链 sudo apt install -y gcc-arm-linux-gnueabihf g-arm-linux-gnueabihf # 修改 Makefile将 CC 行改为 # CC ? arm-linux-gnueabihf-gcc # 编译需提前在树莓派上安装 libmodbus-dev 和 libmosquitto-dev make clean make CCarm-linux-gnueabihf-gcc # 传输并测试假设树莓派 IP 为 192.168.1.100 scp modbus_mqtt_bridge pi192.168.1.100:/home/pi/ ssh pi192.168.1.100 ./modbus_mqtt_bridge -h # 应输出 usage 提示证明二进制兼容国产 ARM64 工控机如飞腾 D2000 Ubuntu Server 20.04# 使用 aarch64-linux-gnu 工具链Ubuntu 自带 sudo apt install -y gcc-aarch64-linux-gnu g-aarch64-linux-gnu # 编译命令注意libmodbus 和 libmosquitto 必须为 aarch64 架构 make clean make CCaarch64-linux-gnu-gcc # 关键验证点检查生成文件架构 file modbus_mqtt_bridge # 正确输出modbus_mqtt_bridge: ELF 64-bit LSB shared object, ARM aarch64, version 1 (SYSV), ...注意国产平台常见坑是libmosquitto.so.1缺失。若apt install libmosquitto-dev失败需从 Mosquitto 官方源码 编译安装git clone https://github.com/eclipse/mosquitto.git cd mosquitto make WITH_TLSno sudo make install2.4 systemd 服务化部署实现开机自启、崩溃自动重启、日志按天轮转创建/etc/systemd/system/modbus-mqtt-bridge.service[Unit] DescriptionModbus to MQTT Bridge Service Afternetwork.target mosquitto.service [Service] Typesimple Useriotuser WorkingDirectory/opt/modbus_mqtt_bridge ExecStart/opt/modbus_mqtt_bridge/modbus_mqtt_bridge -c /opt/modbus_mqtt_bridge/config.json Restarton-failure RestartSec10 StandardOutputjournal StandardErrorjournal SyslogIdentifiermodbus-mqtt-bridge LimitNOFILE65536 # 日志轮转配合 logrotate EnvironmentJOURNALD_RATELIMIT_INTERVAL0 EnvironmentJOURNALD_RATELIMIT_BURST0 [Install] WantedBymulti-user.target启用服务# 创建运行用户 sudo useradd -r -s /bin/false iotuser # 复制程序与配置 sudo mkdir -p /opt/modbus_mqtt_bridge sudo cp modbus_mqtt_bridge config.json.example /opt/modbus_mqtt_bridge/ sudo mv /opt/modbus_mqtt_bridge/config.json.example /opt/modbus_mqtt_bridge/config.json # 启用服务 sudo systemctl daemon-reload sudo systemctl enable modbus-mqtt-bridge.service sudo systemctl start modbus-mqtt-bridge.service # 查看状态重点关注 Active: active (running) sudo systemctl status modbus-mqtt-bridge.service # 实时查看日志CtrlC 退出 sudo journalctl -u modbus-mqtt-bridge.service -f日志轮转配置/etc/logrotate.d/modbus-mqtt-bridge/var/log/journal/*.journal { daily rotate 30 compress missingok notifempty }3. 配置实战从单台 RTU 温度传感器到多设备混合拓扑的 JSON 配置详解3.1 config.json 核心字段语义与工业现场典型配置模式config.json是整个系统的“数据契约”其结构直接决定 MQTT Topic 的生成逻辑和 Modbus 读取行为。以下为一个真实产线场景的配置含 1 台 RTU 温度传感器 1 台 TCP 变频器{ mqtt: { broker_url: mqtt://192.168.1.200:1883, client_id: bridge_plc01, username: iot_user, password: secure_pass, keepalive: 60, qos: 1 }, devices: [ { device_id: temp_sensor_01, type: rtu, serial_port: /dev/ttyUSB0, baudrate: 9600, parity: none, stop_bits: 1, slave_id: 1, poll_interval_ms: 2000, registers: [ { function_code: 03, start_addr: 0, count: 2, data_type: float32_be, topic_suffix: temperature, scale: 0.1 } ] }, { device_id: inverter_01, type: tcp, host: 192.168.1.50, port: 502, slave_id: 2, poll_interval_ms: 1000, registers: [ { function_code: 03, start_addr: 100, count: 1, data_type: uint16, topic_suffix: frequency, scale: 0.01 }, { function_code: 01, start_addr: 0, count: 1, data_type: bool, topic_suffix: run_status } ] } ] }字段逐项解析type: rtu→ 使用libmodbus的modbus_new_rtu()初始化serial_port必须存在且权限正确sudo usermod -a -G dialout iotusertype: tcp→ 使用modbus_new_tcp()host为设备 IPport默认 502function_code: 03→ 读保持寄存器Holding Register对应 Modbus 功能码 0x0301为读线圈Coil功能码 0x01data_type: float32_be→ 表示 32 位浮点数、大端字节序Motorola 格式常见于西门子、三菱设备uint16为无符号 16 位整数bool为单 bit 解析取寄存器最低位scale: 0.1→ 原始值乘以该系数后发布避免浮点精度丢失如温度传感器原始值 255 → 发布 25.5topic_suffix: temperature→ 最终 MQTT Topic 为factory/temp_sensor_01/03/temperatureprefix在代码中硬编码为factory可修改src/mqtt_publisher.c第 42 行。3.2 多设备混合拓扑配置一主多从 RTU 总线 多 TCP 设备共存的实践要点工业现场常见“一条 RS485 总线上挂 5 台仪表slave_id 1~5同时接入 3 台 TCP PLC”的混合场景。此时config.json中devices数组需明确区分devices: [ // RTU 总线设备共享同一串口不同 slave_id { device_id: flow_meter_01, type: rtu, serial_port: /dev/ttyS0, baudrate: 19200, slave_id: 1, poll_interval_ms: 5000, registers: [ { function_code: 03, start_addr: 0, count: 2, data_type: float32_be, topic_suffix: flow_rate } ] }, { device_id: pressure_sensor_01, type: rtu, serial_port: /dev/ttyS0, baudrate: 19200, slave_id: 2, poll_interval_ms: 5000, registers: [ { function_code: 03, start_addr: 0, count: 1, data_type: uint16, topic_suffix: pressure } ] }, // TCP 设备独立网络连接 { device_id: plc_main, type: tcp, host: 192.168.1.10, port: 502, slave_id: 1, poll_interval_ms: 1000, registers: [ { function_code: 03, start_addr: 1000, count: 10, data_type: uint16, topic_suffix: io_status } ] } ]关键约束与经验同一serial_port下的所有 RTU 设备必须相同波特率、校验位、停止位否则通信冲突RTU 设备poll_interval_ms建议 ≥ 2000ms避免总线争抢Modbus RTU 是主从式桥接程序作为 Master 轮询TCP 设备可设更短间隔如 500ms因 TCP 连接稳定、无物理总线竞争slave_id必须与设备实际拨码开关或软件设置一致错配会导致Modbus exception response: 0x01 (Illegal function)错误。3.3 MQTT Topic 设计哲学为什么不用/device/{id}/raw而坚持功能码地址分层很多方案将所有数据塞进单一 Topic如/device/plc01/raw但本项目强制按function_code/register_addr分层原因有三对比维度单 Topicraw分层 Topic03/0001订阅粒度订阅者必须接收全部寄存器再自行解析Grafana 只需SUBSCRIBE factory//03/0001即得温度数据溯源无法区分是 03 还是 04 功能码读取结果Topic 名称自带语义03 Holding Register04 Input Register故障定位出错时需抓包分析原始报文日志直接打印ERROR: failed to read 030001 from temp_sensor_01实际调试中用mosquitto_sub验证# 订阅所有温度数据RTU 设备的 03 功能码第 0 寄存器 mosquitto_sub -h 192.168.1.200 -t factory//03/temperature -v # 订阅变频器运行状态TCP 设备的 01 功能码第 0 寄存器 mosquitto_sub -h 192.168.1.200 -t factory/inverter_01/01/run_status -v提示mosquitto_sub默认 QoS0若需确保消息不丢加-q 1参数与桥接程序 QoS1 匹配。4. 避坑指南Modbus 通信失败、MQTT 断连、寄存器解析错乱的五大血泪现场排查法4.1 现象RTU 设备日志持续打印Failed to connect to serial port /dev/ttyUSB0: Permission denied原因Linux 系统对串口设备有严格权限控制默认仅 root 和dialout组可访问。systemd服务以iotuser运行但该用户未加入dialout组。解决sudo usermod -a -G dialout iotuser sudo systemctl restart modbus-mqtt-bridge.service # 验证sudo -u iotuser ls -l /dev/ttyUSB0 应显示 crw-rw---- 1 root dialout4.2 现象TCP 设备连接成功但读取寄存器返回Exception code 0x02 (Illegal data address)原因start_addr设置超出设备实际寄存器范围。例如某 PLC 只开放 40001~40100即 0x0000~0x0063但配置中写了start_addr: 1000即 0x03E8。解决用modbus_poll工具Windows/Linux连接设备手动读取目标地址确认是否存在查阅设备手册注意 Modbus 地址偏移4xxxx 寄存器对应start_addr 实际地址 - 40001如 40001 → 040002 → 1在config.json中修正start_addr重启服务。4.3 现象MQTT Broker 日志显示Client modbus_bridge_xxx sent DISCONNECT桥接程序频繁重连原因keepalive值60秒小于 Broker 的max_keepalive限制某些云平台如阿里云 IoT 默认为 30 秒导致 Broker 主动断连。解决查看 Broker 配置Mosquitto 为/etc/mosquitto/mosquitto.conf中max_keepalive将config.json中keepalive: 30确保 ≤ Broker 限制若用云平台查阅其文档获取max_keepalive值阿里云 IoT 为 30EMQX 默认 65535。4.4 现象温度数据发布为{value: 12345}但实际应为25.5小数点位置错误原因data_type与设备实际存储格式不匹配。设备用float32_le小端但配置写float32_be或scale值错误如应为0.1却写10。解决用modbus_poll读取原始 4 字节hex对照 IEEE 754 标准验证字节序在config.json中修正data_typefloat32_le/float32_be检查scale是否与设备手册一致如某传感器手册注明“输出值 ×0.1 实际温度”。4.5 现象systemd日志显示Segmentation fault (core dumped)服务崩溃原因libmodbus或libmosquitto动态库版本不兼容。常见于 Ubuntu 20.04 自带libmodbusv3.1.4与源码要求的 v3.1.10 冲突。解决运行ldd ./modbus_mqtt_bridge | grep modbus确认链接的库路径若指向/usr/lib/x86_64-linux-gnu/libmodbus.so.5旧版则卸载旧包sudo apt remove libmodbus-dev libmodbus5 sudo apt autoremove重新编译安装新版libmodbus见 2.2 节再make clean make。5. 进阶技巧用 JSON Schema 校验配置、用 Prometheus 暴露指标、用 Docker Compose 一键部署整套环境5.1 用 JSON Schema 强制约束 config.json 结构杜绝手误引发的运行时崩溃手写 JSON 易出错如baudrate写成字符串9600而非数字9600qos超出 0/1/2 范围。本项目附带config_schema.json可用jsonschema工具校验# 安装校验工具 pip3 install jsonschema # 校验配置返回空表示通过 jsonschema -i config.json config_schema.json # 若失败输出具体错误如9600 is not of type integer # 修正后再次校验config_schema.json关键约束节选{ properties: { mqtt: { properties: { qos: { enum: [0, 1, 2] }, keepalive: { type: integer, minimum: 10, maximum: 65535 } } }, devices: { items: { properties: { type: { enum: [rtu, tcp] }, baudrate: { type: integer, enum: [9600, 19200, 38400, 115200] }, registers: { items: { properties: { function_code: { enum: [01, 03, 04] }, data_type: { enum: [uint16, int16, uint32_be, uint32_le, float32_be, float32_le, bool] } } } } } } } } }提示将校验步骤加入 CI 流程如 GitHub Actionson: push to config.json时自动执行防患于未然。5.2 Prometheus 指标暴露监控 Modbus 读取成功率、MQTT 发布延迟、设备在线状态修改src/main.c在main()函数中添加 Prometheus 指标收集基于 libprometheus-c 简化版// 新增全局指标 static prom_gauge_t *modbus_read_success_total; static prom_gauge_t *modbus_read_failure_total; static prom_gauge_t *mqtt_publish_latency_ms; // 初始化main() 开头 prom_init(); modbus_read_success_total prom_gauge_new(modbus_read_success_total, Total successful Modbus reads); modbus_read_failure_total prom_gauge_new(modbus_read_failure_total, Total failed Modbus reads); mqtt_publish_latency_ms prom_gauge_new(mqtt_publish_latency_ms, MQTT publish latency in milliseconds); // 在 modbus_handler.c 的读取成功后 prom_gauge_inc(modbus_read_success_total); // 在 mqtt_publisher.c 的 publish 后记录耗时 struct timespec start, end; clock_gettime(CLOCK_MONOTONIC, start); mosquitto_publish(...); clock_gettime(CLOCK_MONOTONIC, end); double latency (end.tv_sec - start.tv_sec) * 1000.0 (end.tv_nsec - start.tv_nsec) / 1000000.0; prom_gauge_set(mqtt_publish_latency_ms, latency);编译时链接libprometheus.a启动时加--metrics-port 9091参数。访问http://localhost:9091/metrics即得# HELP modbus_read_success_total Total successful Modbus reads # TYPE modbus_read_success_total gauge modbus_read_success_total{device_idtemp_sensor_01} 1245 # HELP mqtt_publish_latency_ms MQTT publish latency in milliseconds # TYPE mqtt_publish_latency_ms gauge mqtt_publish_latency_ms{topicfactory/temp_sensor_01/03/temperature} 12.3Grafana 面板可直观展示设备在线率count by(device_id)(modbus_read_success_total 0)平均发布延迟avg by(device_id)(mqtt_publish_latency_ms)失败率rate(modbus_read_failure_total[1h]) / rate(modbus_read_success_total[1h])。5.3 Docker Compose 一键部署隔离依赖、统一版本、跨平台复现创建docker-compose.ymlversion: 3.8 services: modbus-bridge: build: . restart: unless-stopped volumes: - ./config.json:/app/config.json:ro - /dev/ttyUSB0:/dev/ttyUSB0:rwm # 直通串口仅 RTU 场景 network_mode: host # 使用 host 网络确保 TCP 设备可达 environment: - TZAsia/Shanghai logging: driver: json-file options: max-size: 10m max-file: 3 mosquitto: image: eclipse-mosquitto:2.0 ports: - 1883:1883 - 9001:9001 # Websocket 端口 volumes: - ./mosquitto.conf:/mosquitto/config/mosquitto.conf:ro - mosquitto_data:/mosquitto/data restart: unless-stopped volumes: mosquitto_data:配套Dockerfile支持 multi-stage 编译FROM ubuntu:22.04 AS builder RUN apt update apt install -y build-essential pkg-config libmodbus-dev libmosquitto-dev WORKDIR /app COPY . . RUN make clean make FROM ubuntu:22.04 RUN apt update apt install -y libmodbus5 libmosquitto1 WORKDIR /app COPY --frombuilder /app/modbus_mqtt_bridge . COPY --frombuilder /app/config.json.example config.json CMD [./modbus_mqtt_bridge, -c, config.json]部署命令# 构建镜像自动编译 docker-compose build # 启动后台运行 docker-compose up -d # 查看日志 docker-compose logs -f modbus-bridge优势完全规避宿主机依赖冲突如不同项目需不同版本 libmodbusdocker-compose down docker-compose up -d一键重置环境.env文件可定义MQTT_BROKER_URLmosquitto:1883实现容器内 DNS 解析。从那以后我每次交付新产线项目都强制走一遍docker-compose up -d mosquitto_sub -h localhost -t factory/# -v—— 看到第一条温度数据刷出来才敢跟客户说“通了”。这比任何文档都可靠。希望帮到你。本文还有配套的精品资源点击获取
阅读完成 · 觉得有帮助?