用 Spring Boot 项目给工厂装“遥控器”:一行 API 控制现场设备!

"不用去车间,浏览器里点一下,就让温控器升温!"

本文属于专栏 《Java × 工业智能》第 5 篇 | GitHub 源码:github.com/iweidujiang/java-industrial-smart

一、引言:工业控制的"最后一公里"难题

在现代化工厂中,PLC、温控器、变频器等设备普遍采用 Modbus RTU 协议 通过 RS485 总线通信。

这种协议简单、稳定、成本低,已沿用数十年。然而,它的封闭性也带来了显著痛点:

  • 操作依赖专用软件:工程师必须安装厂商提供的 HMI 或 SCADA 客户端;
  • 无法远程干预:设备异常时,必须派人到现场按按钮或改参数;
  • 难以集成新系统:想通过 Web 端点点按钮控制风机?想用 Python 脚本批量调整温度?几乎不可能。

这些问题的本质,是 OT(运营技术)与 IT(信息技术)之间的协议鸿沟

而我们的目标,就是用 Java 构建一座桥梁------一个轻量级的 Modbus 控制网关 ,它能将 Modbus 的底层指令,转化为标准的 HTTP REST API。从此,任何能发 HTTP 请求的系统(Web、App、脚本、云平台)都能安全、可靠地控制现场设备。

二、整体设计

下图展示了本方案的核心架构:

该架构具有以下优势:

  1. 解耦:上层应用无需关心 Modbus 帧格式、CRC、串口细节;
  2. 抽象 :提供语义化接口(如 /temperature),而非裸寄存器地址;
  3. 可扩展:未来可轻松加入认证、日志、限流等企业级功能;
  4. 轻量:仅需一个 Spring Boot 应用 + 串口转 USB 线,即可部署在边缘设备。

三、核心实现原理

1. Modbus RTU 写操作详解

Modbus RTU 支持多种"写"功能码,最常用的是:

  • 功能码 06(Write Single Register):写单个 16 位寄存器
  • 功能码 16(Write Multiple Registers):写多个连续寄存器

以功能码 06 为例,一帧完整数据如下(十六进制):

字段 长度 示例 说明
设备地址 1 byte 01 从站 ID
功能码 1 byte 06 写单寄存器
寄存器高字节 1 byte 00 地址高位
寄存器低字节 1 byte 01 地址低位(即寄存器 1)
值高字节 1 byte 00 数据高位
值低字节 1 byte FF 数据低位(即值 255)
CRC 低字节 1 byte ... 循环冗余校验
CRC 高字节 1 byte ...

设备收到信息后,会返回相同帧作为确认(除非出错)。

因此,我们的 Java 程序必须:

  • 正确构造上述 8 字节帧;
  • 使用 CRC16-MODBUS 算法计算校验值;
  • 通过串口发送,并等待响应。

2. 串口通信的可靠性处理

RS485 是半双工总线,通信需注意:

  • 波特率匹配:必须与设备一致(常见 9600、19200);
  • 延迟控制:发送后需短暂等待(如 100ms)再读响应;
  • 资源独占:同一时间只能一个线程操作串口。

为此,在 ModbusRtuWriter 中采用 "打开 → 操作 → 关闭" 模式,确保线程安全(虽牺牲性能,但简化设计)。

生产环境可引入连接池优化。

四、关键代码解析

📌 完整可运行代码已开源至 GitHub:github.com/iweidujiang/java-industrial-smart

模块路径:code/05-modbus-rest-control

1. 底层通信:ModbusRtuWriter

java 复制代码
// 构造 Modbus RTU 写帧(功能码 06)
ByteBuffer buffer = ByteBuffer.allocate(6).order(ByteOrder.BIG_ENDIAN);
buffer.put((byte) deviceId)
       .put((byte) 0x06)          // 写单寄存器
       .putShort((short) regAddr)
       .putShort((short) value);

// 计算 CRC16 并附加
int crc = calculateCRC16(frame, 6);
frame[6] = (byte) (crc & 0xFF);
frame[7] = (byte) ((crc >> 8) & 0xFF);

这段代码是整个网关的"引擎",负责与物理设备对话。CRC 算法经过多款国产温控器验证,兼容大多数国产温控器与 PLC。

2. 业务封装:ModbusControlService

java 复制代码
public void setTargetTemperature(double temperature) {
    int rawValue = (int) Math.round(temperature * 10); // 25.5℃ → 255
    ModbusRTUWriter.writeRegister("COM4", 9600, 1, 1, rawValue);
}

这里体现了领域驱动设计思想:将技术细节(寄存器地址、缩放因子)隐藏在服务层,对外暴露业务语义。

3. REST 接口:ModbusController

java 复制代码
@RestController
@RequestMapping("/api/modbus")
public class ModbusController {

    @Autowired
    private ModbusControlService service;

    @PostMapping("/temperature")
    public ResponseEntity<String> setTemperature(@RequestBody Map<String, Double> req) {
        double temp = req.get("temperature");
        service.setTargetTemperature(temp);
        return ResponseEntity.ok("目标温度已设为 " + temp + "℃");
    }

    @PostMapping("/pump")
    public ResponseEntity<String> controlPump(@RequestBody Map<String, Boolean> req) {
        boolean start = req.get("start");
        service.controlPump(start);
        return ResponseEntity.ok("水泵已" + (start ? "启动" : "停止"));
    }
}

支持三种典型操作:

  • /api/modbus/temperature:设置目标温度
  • /api/modbus/pump:启停水泵
  • /api/modbus/write:通用寄存器写入

五、模拟测试

即使没有 RS485 设备,咱们可以通过软件来模拟完成验证功能。

5.1 创建虚拟串口对

由于 Modbus Slave 和 Java 程序不能同时占用同一物理串口,我们需要虚拟串口对 ,使用 Virtuak Serial Port Driver (VSPD)工具模拟。

5.2 使用 Modbus Slave 配置从站参数并绑定到虚拟串口

  1. 点击菜单 Setup → Slave Definition...
  2. 设置如下参数:
    • Slave ID : 1
    • Function : 03 Holding Register (4x)(用于读取保持寄存器)
    • Address mode : Dec(十进制)
    • Address : 0(起始地址)
    • Quantity : 10(共 10 个寄存器,地址 0~9)
  3. 点击 OK
  4. 点击 Connection → Connect
  5. 选择 Serial Port ,端口选 COM3
  6. 波特率设为 9600,数据位 8,停止位 1,无校验

此时,Modbus Slave 会自动创建一个包含 10 个寄存器的表格,初始值如下:

5.3 修改 Java 代码中的串口名

打开 ModbusControlService.java,将串口改为 COM4(与 Slave 配对的另一端):

java 复制代码
private final String SERIAL_PORT = "COM4"; // Windows
// private final String SERIAL_PORT = "/dev/ttyUSB0"; // Linux

5.4 启动 Spring Boot 应用并调用 API 观察结果

5.4.1 启动
powershell 复制代码
# 进入到项目根目录 05-modbus-rest-control ,执行
mvn spring-boot:run

或者任一方便的方式启动Spring Boot应用即可(比如IDE工具上点击一下按钮)。

5.4.2 测试 1:设置目标温度为 26.5℃

假设我们希望将温度值写入 寄存器 1,但由于初始值为 0,写入后将会覆盖它。

然后我们看下 Modbus Slave 上的值是否改过来了:

成功写入数据:寄存器 1 的值应变为 265(因为 26.5 × 10 = 265)

5.4.3 测试 2:启动水泵

代码中,将水泵启停状态写入 寄存器 10 :

java 复制代码
ModbusRtuWriter.writeRegister(SERIAL_PORT, BAUD_RATE, DEVICE_ID, 10, start ? 1 : 0);

所以,在 Modbus Slave 中设置的寄存器得有寄存器10:

调用接口 /api/modbus/pump:

观察 Modbus Slave :寄存器 10 的值应变为 1

六、生产环境增强建议

6.1 配置外部化

当前实现为最小可用版本。在实际部署中,一般我们会将各种配置放到配置中心,比如 Nacos ,或者在 application.yml 中配置。

以将配置放到 application.yml 文件为例:将串口、设备 ID、寄存器映射等参数配置到 application.yml,支持多设备管理。

yaml 复制代码
modbus:
  devices:
    - id: 1
      port: COM4
      baud: 9600
      registers:
        temperature: 1
        pump: 10

6.2 操作审计

记录每次写操作的:

  • 时间戳
  • 操作人(IP 或 Token)
  • 寄存器地址与旧/新值

6.3 异常处理

  • 串口打不开?返回 503 Service Unavailable
  • CRC 校验失败?重试 2 次
  • 设备无响应?触发邮件/短信告警

6.3 安全防护

  • 添加 JWT 认证,防止未授权控制
  • 对关键寄存器(如设备 ID)设置写保护

这些功能均可基于 Spring Security、AOP、Spring Boot Actuator 实现。

七、小结

通过本文,我们完成了一项看似简单却极具价值的工作:

将封闭的 Modbus 协议,转化为开放的 Web 服务

这不仅解决了"远程控制"的刚需,更打开了工业设备与现代 IT 系统融合的大门。

完整源码已开源

🔗 GitHub 仓库:github.com/iweidujiang/java-industrial-smart

📁 模块路径:code/05-modbus-rest-control

欢迎 Star、Fork、提交 Issue!


本文属于专栏 《Java × 工业智能》第 5 篇

如果你对这个系列感兴趣,记得关注我哦!

相关推荐
Dream_sky分享1 小时前
IDEA 2025中TODO找不到
java·ide·intellij-idea
沫儿笙1 小时前
安川机器人气保焊省气方案
人工智能·机器人
xiamin1 小时前
(第57册)人工智能通识教程 夏敏捷、张书钦、周雪燕
人工智能
伊甸31 小时前
基于LangChain4j从0到1搭建自己的的AI智能体并部署上线-1
java·langchain·prompt
我待_JAVA_如初恋1 小时前
重装系统后,idea被拦截,突然无法运行
java·ide·intellij-idea
东东5161 小时前
校园短期闲置资源置换平台 ssm+vue
java·前端·javascript·vue.js·毕业设计·毕设
像少年啦飞驰点、1 小时前
零基础入门 Spring Boot:从“Hello World”到独立可运行 Web 应用的完整学习闭环
java·spring boot·web开发·编程入门·后端开发
IT 行者1 小时前
Spring MVC 慎用@InitBinder,谨防内存泄漏
java·spring·mvc
程途拾光1581 小时前
算法公平性:消除偏见与歧视的技术探索
大数据·人工智能·算法