# modbus-rtu-jSerialComm **Repository Path**: junwen_admin/modbus-rtu-j-serial-comm ## Basic Information - **Project Name**: modbus-rtu-jSerialComm - **Description**: 基于 Spring Boot、jSerialComm、mica-mqtt 的 Modbus RTU 主站:按配置或前端参数打开串口,完成寄存器/线圈读写;在 REST 响应中返回每次事务的 txHex / rxHex(发送前/收包后由服务内字段记录,无独立抓包代理)。可选 MQTT 定时采集与 Web 调试页。组帧、CRC 与解析见 ModbusRtuUtil,不依赖 modbus4j。 - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 1 - **Created**: 2026-05-21 - **Last Updated**: 2026-05-21 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Modbus RTU 数据采集与 MQTT 推送 基于 **Spring Boot**、**jSerialComm**、**mica-mqtt** 的 Modbus RTU **主站**:按配置或前端参数打开串口,完成寄存器/线圈读写;在 REST 响应中返回每次事务的 **`txHex` / `rxHex`**(发送前/收包后由服务内字段记录,无独立抓包代理)。可选 **MQTT 定时采集**与 **Web 调试页**。组帧、CRC 与解析见 `ModbusRtuUtil`,**不依赖 modbus4j**。 **连接策略一览** | `modbus.rtu.auto-connect` | 启动时 | 读写时未连接 / 掉线 | |---------------------------|--------|----------------------| | `true` | `ApplicationReady` 时调用无参 `connect()`,**仅使用配置文件** `modbus.rtu.port` / `baud-rate` / `data-bits` / `stop-bits` / `parity` / `unit-id` 建连 | 按**会话目标**(最后一次成功 `POST /connect` 写入的 `lastPort` 等,或启动建连成功时写入的目标)**自动重连**;**不会**再读 YAML 里其它地址去重连 | | `false` | 不打开串口 | 不自动建连/重连,须 `POST /api/modbus/connect` 或 Web「连接」 | **说明**:整笔 RTU 事务在配置的重试次数内仍失败(无应答、写出失败、CRC/帧头不匹配、I/O 异常等)时,会**关闭串口并标记未连接**,HTTP 中的 **`connection.connected`** 与错误提示一致;`auto-connect=true` 时**下一次**读写仍会按上述会话目标尝试重新打开串口。 --- ## 目录 - [功能特性](#功能特性) - [技术栈](#技术栈) - [项目结构](#项目结构) - [快速开始](#快速开始) - [配置说明](#配置说明) - [HTTP API](#http-api) - [MQTT 与定时采集](#mqtt-与定时采集) - [Web 界面](#web-界面) - [使用示例](#使用示例) - [原始报文如何产生](#原始报文如何产生) - [常见问题](#常见问题) - [应用场景](#应用场景) - [贡献](#贡献) - [许可证](#许可证) - [侵权与第三方声明](#侵权与第三方声明) - [支持作者](#支持作者) - [技术支持](#技术支持) --- ## 功能特性 - **功能码**:FC01 / 02 / 03 / 04 / 05 / 06 / 15 / 16 - **线程安全**:`ModbusRtuServiceImpl` 使用 `modbusLock` 串行化建连、读写与自动重连,避免多线程交错访问同一 COM 口 - **原始帧**:读写成功时响应常含 **`txHex` / `rxHex`**(空格分隔十六进制,**RTU 纯 PDU+CRC**,无 MBAP) - **连接快照**:多数接口 JSON 含 **`connection`**(`port`、`baudRate`、`dataBits`、`stopBits`、`parity`、`unitId`);读写接口的快照在 **`modbusLock` 释放前**记录,与**本次**读写结果一致,便于前端同步状态 - **帧间静默**:可配置 `inter-frame-gap-ms`;为 `0` 时按波特率估算,减轻连续 HTTP 轮询时从站/USB 转串口偶发无应答 - **可选 MQTT**:`mqtt.client.enabled=true` 时定时读保持寄存器并发布(默认约 5 秒) - **调试页**:`static/index.html`(含报文历史本地缓存;**无**独立 `modbus-guide.html`,可与姊妹 TCP 项目文档对照) --- ## 技术栈 | 技术 | 版本 | 说明 | |------|------|------| | JDK | 1.8+ | 运行环境 | | Spring Boot | 2.6.6 | Web 与配置 | | jSerialComm | 2.9.2 | 串口读写 | | mica-mqtt | 2.4.2 | MQTT 客户端(Dromara) | | Lombok | 1.18.30 | 样板代码简化 | | Hutool | 5.8.16 | 工具库 | | 自研 RTU | — | `ModbusRtuUtil` 组帧 / CRC16 / 解析 | --- ## 项目结构 ``` modbus-rtu-jSerialComm/ ├── src/main/java/com/device/modbus/ │ ├── ModbusRtuMasterApplication.java │ ├── config/ModbusRtuConfig.java │ ├── controller/ModbusController.java │ ├── dto/ # ModbusResponse、连接状态、报文 DTO、SerialPortInfo │ ├── listener/ # 启动自动连 Modbus、MQTT 监听 │ ├── service/ # ModbusRtuService、MQTT;impl 含总线锁与快照 │ ├── task/ScheduledTasks.java │ └── util/ModbusRtuUtil.java ├── src/main/resources/ │ ├── application.yml │ ├── application-dev.yml / application-prod.yml │ └── static/ # index.html、css、js(无 modbus-guide.html) ├── docs/ # README 用图:界面截图、打赏二维码 │ ├── img.png │ ├── wechat.jpg │ └── alipay.jpg ├── pom.xml └── README.md ``` --- ## 快速开始 ### 环境 - JDK 1.8+、Maven 3.6+ - USB 转 RS485/RS232 或板载串口,确认系统内端口名(如 `COM3`、`/dev/ttyUSB0`) - 若 `mqtt.client.enabled=true`,需可达的 MQTT Broker ### 构建与运行 ```bash git clone <你的仓库地址> cd modbus-rtu-jSerialComm mvn clean package ``` 默认 `spring.profiles.active` 多为 **`dev`**(见 `application.yml`)。默认 **HTTP 端口为 `8081`**(与 TCP 示例的 8080 不同,请注意)。 ```bash java -jar target/modbus-rtu-jSerialComm.jar --spring.profiles.active=dev java -jar target/modbus-rtu-jSerialComm.jar --spring.profiles.active=prod ``` ### 页面 | 说明 | 地址 | |------|------| | 调试主页 | http://localhost:8081 |  ### 配置摘录(`application-dev.yml`) ```yaml modbus: rtu: port: COM1 baud-rate: 9600 data-bits: 8 stop-bits: 1 parity: none timeout: 0 retries: 1 inter-frame-gap-ms: 0 unit-id: 1 auto-connect: true # 见文首表格;生产可按需 false mqtt: client: enabled: false ``` --- ## 配置说明 ### `modbus.rtu.*` | 键 | 含义 | |----|------| | `port` / `baud-rate` / `data-bits` / `stop-bits` / `parity` / `unit-id` | 串口与从站;**仅**在启动时无参 `connect()`(`auto-connect: true`)使用配置;之后会话目标以 `POST /connect` 或启动成功结果为准 | | `timeout` | **本帧内**收包:串口 `read` 的字节间静默上限 (ms);`0` 时按波特率自动估算 | | `retries` | 失败重试次数(总尝试次数 = `retries + 1`) | | `inter-frame-gap-ms` | **两笔事务之间**:在发出下一帧请求**之前**对总线插入的静默 (ms)。满足 RTU 规范帧间至少约 3.5 个字符时间,减轻连发/HTTP 快轮询时从站或 USB 转串口偶发无应答。`0` 时按当前波特率取约 `2×3.5` 个字符时间,并夹在 **5~50** ms;大于 `0` 时按你设置的毫秒数等待 | | `auto-connect` | 见文首表格 | **`timeout` 与 `inter-frame-gap-ms`**:前者只影响**一次读写过程中**如何判定一帧收完;后者在**每次新请求发出前**执行 `Thread.sleep`,与单帧内读超时无关。 ### `mqtt.client.*` | 键 | 含义 | |----|------| | `enabled` | 为 `true` 才加载 MQTT 与定时采集 | | `ip` / `port` / `client-id` / `username` / `password` | Broker 与认证 | | `topic-prefix` | 发布所用 Topic 前缀(默认 `modbus/rtu/data/`) | | 其它 | 见 `application-dev.yml` 注释 | 定时任务仅在 Modbus **已连接**时读寄存器;`auto-connect: false` 时需先通过 Web 或 API 建连。 --- ## HTTP API ### 连接 | 方法 | 路径 | 说明 | |------|------|------| | GET | `/api/modbus/serial-ports` | 枚举本机串口 | | GET | `/api/modbus/default-config` | 返回与 YAML 对齐的 JSON:`port`、`baudRate`、`dataBits`、`stopBits`、`parity`、`timeout`、`retries`、**`interFrameGapMs`**(即 `inter-frame-gap-ms`)、`unitId`、`autoConnect` | | POST | `/api/modbus/connect` | 参数:`port`、`baudRate`、`dataBits`、`stopBits`、`parity`、`unitId` | | GET | `/api/modbus/status` | 当前是否连接及会话串口参数 | | POST | `/api/modbus/disconnect` | 关闭串口;`auto-connect=true` 时后续读写仍可按会话目标自动重开 | ### 读 / 写 读:`GET` … `/holding-registers`、`/input-registers`、`/coils`、`/discrete-inputs`(`address`、`quantity`)。 写:`POST` … `/coil/{address}`、`/register/{address}`、`/registers/{address}`、`/coils/{address}`(见下文 curl)。 ### 响应字段 - **`connection`**:`{ "connected", "port", "baudRate", "dataBits", "stopBits", "parity", "unitId" }`(未连接时除 `connected` 外字段常为 `null`)。连接类与读写类接口均可能返回,**Web 页根据该字段刷新指示灯与表单锁定**。 - **`txHex` / `rxHex`**:读写成功且捕获到帧时常有;失败时通常无接收帧或整笔失败已关串口。 - 姊妹项目 **modbus-tcp-modbus4j** 的路径与字段命名一致,便于同一套前端逻辑切换 TCP/RTU(仅连接参数与默认端口不同)。 --- ## MQTT 与定时采集 - 条件:`mqtt.client.enabled=true`(`@ConditionalOnProperty`)。 - 周期:默认 Cron `0/5 * * * * ?`(约 5 秒)。 - 逻辑:若已连接,FC03 读保持寄存器 `0` 起共 `10` 个,序列化为 `value0`…`value9` 后 `publish` 到 `topic-prefix`。 - 修改频率或地址:编辑 `ScheduledTasks.java`。 ```mermaid flowchart LR T[ScheduledTasks] --> M[ModbusRtuService] T --> P[MqttClientPublishService] P --> B[MQTT Broker] ``` --- ## Web 界面 - **index.html**:串口选择、连接参数、`connection` 驱动的状态同步、读写、日志、原始报文(本地缓存)、写入结果区。 - 本仓库**未**附带 `modbus-guide.html`;协议说明可参考姊妹仓库 **modbus-tcp-modbus4j** 中的指南页。 - 界面效果见上文 [快速开始 → 页面](#页面) 中的截图 `docs/img.png`。 --- ## 使用示例 以下默认 **8081**;请按 `server.port` 调整。 ### 连接 ```bash curl -X POST "http://localhost:8081/api/modbus/connect?port=COM1&baudRate=9600&dataBits=8&stopBits=1&parity=none&unitId=1" ``` ### 读保持寄存器 ```bash curl "http://localhost:8081/api/modbus/holding-registers?address=0&quantity=10" ``` 响应示例(`connection` 随实际会话变化;十六进制为示意): ```json { "code": 200, "message": "读取成功", "data": [100, 200, 300, 400, 500, 0, 0, 0, 0, 0], "txHex": "01 03 00 00 00 0A C5 CD", "rxHex": "01 03 14 ...", "connection": { "connected": true, "port": "COM1", "baudRate": 9600, "dataBits": 8, "stopBits": 1, "parity": "none", "unitId": 1 } } ``` ### 写多个寄存器(FC16) ```bash curl -X POST "http://localhost:8081/api/modbus/registers/0" \ -H "Content-Type: application/json" \ -d "[100, 200, 300, 400]" ``` ### 订阅 MQTT Topic 示例:`modbus/rtu/data/#`(随 `topic-prefix` 调整)。Broker、账号与 `application-*.yml` 一致。 --- ## 原始报文如何产生 - **`ModbusRtuServiceImpl#sendRequest`** 在写出前将请求字节转为 **`lastTxHex`**;在收到 CRC 合法且与当前请求头匹配的完整帧后写入 **`lastRxHex`**。 - REST 层使用 **`ModbusRtuPacket(lastTxHex, lastRxHex)`** 填入 **`ModbusResponse`**,**非** modbus4j 的 IoLog 回调。 - RTU 帧为 **地址 + 功能码 + 数据 + CRC16**,**无 MBAP**;请勿与 TCP 项目的十六进制逐字节对比长度。 --- ## 常见问题 1. **定时任务无输出** 确认 `mqtt.client.enabled=true`;且 Modbus 已连接(`auto-connect: false` 时要先手动连接)。 2. **MQTT 频繁断开** 检查 Broker、账号;**`client-id` 唯一**;防火墙放行 MQTT 端口。 3. **Modbus 读超时 / 偶发失败** 检查串口号、波特率/校验、从站地址、RS485 A/B;适当增大 `retries` 或 `inter-frame-gap-ms`;必要时设置 `timeout`。 4. **8081 占用** 修改 `server.port`。 5. **前端状态与读写结果不一致** 以响应体 **`connection`** 为准刷新 UI;读写接口的快照与本次事务在服务端锁内对齐;失败返回若已关串口,应看到 **`connected: false`**。 6. **Modbus Slave 调试** 选 **RTU**、正确 COM 对、波特率与站号一致;TCP 项目中的「端口 502」说明**不适用于 RTU**。 7. **串口被占用** 关闭其它占用该端口的程序;避免多实例同时打开同一 COM。 --- ## 应用场景 - 工业采集与监控、边缘网关、RS485/RS232 设备联调、Modbus RTU 学习与测试 --- ## 贡献 欢迎 Issue 与 Pull Request。 --- ## 许可证 以仓库内 **`LICENSE`** 文件为准;若与 `pom.xml` 描述不一致,以 **`LICENSE`** 为准。 --- ## 侵权与第三方声明 ### 权益反馈 若认为本项目侵犯您的权益,请在仓库 **Issues** 提交身份证明、权属证明与具体位置,维护者将在合理期限内处理。 ### 第三方 | 内容 | 来源 | 许可证 | |------|------|--------| | Modbus 协议 | Modicon, Inc. | 公开标准 | | Spring Boot | spring.io | Apache 2.0 | | jSerialComm | fazecast.github.io | 以官方为准 | | mica-mqtt | dromara.org | Apache 2.0 | | Lombok | projectlombok.org | MIT | | Hutool | hutool.cn | Apache 2.0 | ### 免责声明 按「原样」提供;请在合法合规下使用;生产环境须充分测试。使用所致损失作者不承担责任。 --- ## 支持作者 若本项目对你有帮助,欢迎支持维护。
| 微信 | 支付宝 |
|---|---|
![]() 微信扫一扫 |
![]() 支付宝扫一扫 |