当前位置: 首页 > news >正文

HCMS IOT 框架 — Modbus TCP 使用指南

HCMS IOT 框架 — Modbus TCP 使用指南

本文档面向需要使用个人开发的 IOT 框架,对接 Modbus TCP 、S7、TCP等设备的开发者 以箱式物流 WCS 系统为例,完整演示从数据库配置到业务代码的全流程


一、整体流程

  1. 在数据库 hcms_plc 表中配置 PLC 设备信息
  2. 在数据库 hcms_plc_signal 表中配置信号点位
  3. 创建 @DeviceControl 类,用 @Named 注解注入 Device 和 Tag
  4. 通过 Tag 对象调用 readShort()writeShort() 等方法读写 PLC
  5. @Loop 注解实现定时轮询

二、数据库配置

2.1 配置 PLC 设备(hcms_plc 表)

INSERT INTO hcms_plc (id, del_flag, biz_system, name, ip, port, protocol, is_active, slave_id, plc_code, byte_order,connect_timeout, retry_interval, read_timeout,create_by, create_time, update_by, update_time
) VALUES ('你的UUID',          -- id: 主键0,                   -- del_flag: 0=正常1,                   -- biz_system: 1=箱式'WCS箱式PLC',        -- name: 设备名称'192.168.1.100',     -- ip: PLC的IP地址502,                 -- port: Modbus TCP 默认端口 502'modbus-tcp',        -- protocol: 必须填 modbus-tcp1,                   -- is_active: 1=启用1,                   -- slave_id: Modbus从站地址(1-247)'WCS_PLC_01',        -- plc_code: 重要!用于 @Named 注解注入 Device'CDAB',              -- byte_order: 字节序(根据PLC品牌设置)5000,                -- connect_timeout: 连接超时(毫秒)3000,                -- retry_interval: 重试间隔(毫秒)3000,                -- read_timeout: 读取超时(毫秒)'admin', NOW(), 'admin', NOW()
);

关键字段说明:

  • plc_code:这个值会用在 Java 代码的 @Named("WCS_PLC_01") 注解中,是设备的唯一标识
  • protocol:必须填 modbus-tcp,框架根据这个值选择 Modbus 协议处理器
  • slave_id:Modbus 从站地址,一般是 1
  • byte_order:字节序,常见值 ABCD(大端)、CDAB(中端交换),根据 PLC 品牌确定

2.2 配置信号点位(hcms_plc_signal 表)

以你提供的数据为例,Modbus 地址 %MW40400 对应的 address 就是 40400

INT 类型信号(对应 data_type=1,short)

-- Wcs_SYSSTATUS: 系统启停控制,写1启动,写0停止
INSERT INTO hcms_plc_signal (id, del_flag, plc_id, signal_name, signal_code, symbol, node_id, db_number, address, addr_offset,data_type, signal_level, subscription, is_active,period, length, region,create_by, create_time, update_time
) VALUES ('你的UUID', 0, '上面PLC的id', '系统启停',              -- signal_name: 信号逻辑名称'sys_status',           -- signal_code: 重要!用于 @Named("WCS_PLC_01:sys_status")'Wcs_SYSSTATUS',        -- symbol: PLC中的符号名'ns=0;s=Wcs_SYSSTATUS', -- node_id: 随便填,Modbus不用0,                      -- db_number: Modbus不用,填040401,                  -- address: Modbus寄存器地址0,                      -- addr_offset: 位偏移,INT类型填01,                      -- data_type: 1=short(Modbus的INT是2字节=Java的short)1,                      -- signal_level: 信号级别0,                      -- subscription: 0=不订阅1,                      -- is_active: 1=启用0,                      -- period: 0=不自动轮询(手动读写)1,                      -- length: 读写数量,1个寄存器'holdregistor',         -- region: Modbus保持寄存器'admin', NOW(), NOW()
);

BOOL 类型信号(对应 data_type=0)

-- R6_Clear: 拿走货物清除状态,地址 %MX40707.12
INSERT INTO hcms_plc_signal (id, del_flag, plc_id, signal_name, signal_code,symbol, node_id, db_number, address, addr_offset,data_type, signal_level, subscription, is_active,period, length, region,create_by, create_time, update_time
) VALUES ('你的UUID', 0, '上面PLC的id','R6清除状态','r6_clear',             -- signal_code'R6_Clear',             -- symbol'ns=0;s=R6_Clear',      -- node_id0,                      -- db_number40707,                  -- address: 寄存器地址12,                     -- addr_offset: 位偏移=12(对应 .12)0,                      -- data_type: 0=boolean1, 0, 1,0, 1, 'holdregistor','admin', NOW(), NOW()
);

需要自动轮询的信号

如果某个信号需要框架自动定时读取(比如每 2 秒读一次状态),把 period 设为大于 0 的值:

-- 例如每 2000 毫秒自动读取一次 Port1 状态
period = 2000  -- 单位:毫秒

2.3 你提供的数据完整 SQL

-- ========== PLC 设备 ==========
-- 请替换 id 和实际 IP/端口-- ========== INT 类型信号 ==========
-- Wcs_POWER        %MW40400  INT  未使用
-- Wcs_SYSSTATUS    %MW40401  INT  写1启动,写0停止
-- Wcs_FAULTRESET   %MW40402  INT  写1复位,PLC清0
-- Wcs_DATARESET    %MW40403  INT  写1复位,PLC清0
-- Wcs_SPRY         %MW40404  INT
-- Wcs_SPRY1        %MW40405  INT
-- Wcs_SPRY2        %MW40406  INT
-- Wcs_SPRY3        %MW40407  INT
-- Wcs_SPRY4        %MW40408  INT
-- Wcs_KYJ_Control  %MW40409  INT  保持写1开启,保持写0停止
-- Wcs_Port1        %MW40410  INT  0空闲/1入库/2出库/3故障
-- Wcs_Port2        %MW40411  INT  0空闲/1入库/2出库/3故障
-- Wcs_R1_Target    %MW40510  INT  目标位置
-- Wcs_R1_BarCode   %MW40512  INT  箱号-- 以上全部:
--   data_type = 1 (short)
--   addr_offset = 0
--   region = 'holdregistor'
--   length = 1-- ========== BOOL 类型信号 ==========
-- R6_Clear  %MX40707.12  BOOL  拿走货物清除状态
--   data_type = 0 (boolean)
--   address = 40707
--   addr_offset = 12
--   region = 'holdregistor'
--   length = 1

三、Java 业务代码

3.1 创建 @DeviceControl 类

这是核心步骤。创建一个类,加上 @DeviceControl 注解,框架会自动扫描并注入。

package com.slhc.hcms.module.bcss.plc;import com.google.inject.Inject;
import com.google.inject.name.Named;
import com.slhc.hcms.iot.annotation.DeviceControl;
import com.slhc.hcms.iot.annotation.Loop;
import com.slhc.hcms.iot.model.Device;
import com.slhc.hcms.iot.model.Tag;
import lombok.extern.slf4j.Slf4j;@Slf4j
@DeviceControl
public class WcsPLCService {// ========== 1. 注入设备 ==========// @Named 的值 = 数据库 hcms_plc 表的 plc_code 字段@Inject@Named("WCS_PLC_01")private Device device;// ========== 2. 注入 Tag(信号点位) ==========// @Named 的值 = "plc_code:signal_code"// 格式:设备编码 + 冒号 + 信号编码@Inject@Named("WCS_PLC_01:sys_status")private Tag sysStatusTag;          // 系统启停 %MW40401@Inject@Named("WCS_PLC_01:fault_reset")private Tag faultResetTag;         // 故障复位 %MW40402@Inject@Named("WCS_PLC_01:kyj_control")private Tag kyjControlTag;         // 空压机控制 %MW40409@Inject@Named("WCS_PLC_01:port1")private Tag port1Tag;              // Port1状态 %MW40410@Inject@Named("WCS_PLC_01:port2")private Tag port2Tag;              // Port2状态 %MW40411@Inject@Named("WCS_PLC_01:r1_target")private Tag r1TargetTag;           // R1目标位置 %MW40510@Inject@Named("WCS_PLC_01:r1_barcode")private Tag r1BarcodeTag;          // R1箱号 %MW40512@Inject@Named("WCS_PLC_01:r6_clear")private Tag r6ClearTag;            // R6清除状态 %MX40707.12// ========== 3. 读取操作 ==========/*** 读取 Port1 状态* 返回值:0=空闲, 1=入库中, 2=出库中, 3=故障*/public short readPort1Status() {short value = port1Tag.readShort();log.debug("Port1 状态: {}", value);return value;}/*** 读取 R6 清除状态(BOOL 类型)*/public boolean readR6Clear() {boolean value = r6ClearTag.readBool();log.debug("R6 清除状态: {}", value);return value;}// ========== 4. 写入操作 ==========/*** 系统启动* 写1到 %MW40401*/public void startSystem() {sysStatusTag.writeShort((short) 1);log.info("已发送系统启动指令");}/*** 系统停止* 写0到 %MW40401*/public void stopSystem() {sysStatusTag.writeShort((short) 0);log.info("已发送系统停止指令");}/*** 故障复位* 写1到 %MW40402,PLC收到后会自动清0*/public void faultReset() {faultResetTag.writeShort((short) 1);log.info("已发送故障复位指令");}/*** 空压机开启(保持写1)*/public void startCompressor() {kyjControlTag.writeShort((short) 1);log.info("空压机已开启");}/*** 空压机关闭(保持写0)*/public void stopCompressor() {kyjControlTag.writeShort((short) 0);log.info("空压机已关闭");}/*** 设置 Port1 状态* @param status 0=空闲, 1=入库闪绿灯, 2=出库闪红灯, 3=故障常亮红灯*/public void setPort1Status(int status) {port1Tag.writeShort((short) status);log.info("Port1 状态已设置为: {}", status);}/*** 写入 R1 目标位置和箱号* @param target 目标位置(如 1001, 1002)* @param barcode 箱号*/public void sendR1Task(int target, int barcode) {r1TargetTag.writeShort((short) target);r1BarcodeTag.writeShort((short) barcode);log.info("R1 任务已下发: 目标={}, 箱号={}", target, barcode);}/*** 写入 R6 清除信号(BOOL 类型)*/public void clearR6() {r6ClearTag.writeBool(true);log.info("R6 清除信号已发送");}// ========== 5. @Loop 自动轮询 ==========/*** 自动轮询 Port1 状态* * @Loop 注解说明:* - device: 对应 plc_code* - id: 对应 signal_code* - 轮询周期由数据库 period 字段决定(毫秒)* - period=0 表示不轮询,period=2000 表示每2秒读一次* * 框架会自动按 period 间隔调用这个方法,tag 参数里已经包含了最新读到的值*/@Loop(device = "WCS_PLC_01", id = "port1")public void onPort1Change(Tag tag) {short status = tag.readShort();log.debug("Port1 轮询: 状态={}", status);// 在这里写你的业务逻辑// 比如状态变化时推送消息、更新数据库等switch (status) {case 0:// 空闲break;case 1:log.info("Port1: 入库中");break;case 2:log.info("Port1: 出库中");break;case 3:log.warn("Port1: 故障!");break;}}// ========== 6. 设备连接状态 ==========public boolean isConnected() {return device != null && device.isConnected();}
}

3.2 创建 Spring 桥接层

@DeviceControl 类由 Guice 管理,Spring 的 Controller 无法直接 @Autowired 注入。需要一个桥接类:

package com.slhc.hcms.module.bcss.service.impl;import com.slhc.hcms.iot.Iot;
import com.slhc.hcms.module.bcss.plc.WcsPLCService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;@Slf4j
@Service
public class WcsServiceImpl {@Autowiredprivate Iot iot;private WcsPLCService plcService;private WcsPLCService getPlcService() {if (plcService == null) {synchronized (this) {if (plcService == null) {plcService = iot.getDeviceControl(WcsPLCService.class);}}}return plcService;}// 然后在这里包装业务方法,供 Controller 调用public short readPort1Status() {return getPlcService().readPort1Status();}public void startSystem() {getPlcService().startSystem();}// ... 其他方法同理
}

3.3 Controller 调用

@RestController
@RequestMapping("/wcs")
public class WcsController {@Autowiredprivate WcsServiceImpl wcsService;@GetMapping("/port1/status")public Result<Short> getPort1Status() {return Result.OK(wcsService.readPort1Status());}@PostMapping("/system/start")public Result<String> startSystem() {wcsService.startSystem();return Result.OK("系统启动指令已发送");}
}

四、Tag API 速查表

方法说明对应 data_type
tag.readShort() 读取一个 short 值 1 (short/INT)
tag.writeShort((short) 1) 写入一个 short 值 1
tag.readShorts() 批量读取 short 数组 1
tag.writeShorts(new short[]{1, 2}) 批量写入 short 数组 1
tag.readInt() 读取一个 int 值(4字节) 2 (int)
tag.writeInt(100) 写入一个 int 值 2
tag.readFloat() 读取一个 float 值 3 (float)
tag.writeFloat(3.14f) 写入一个 float 值 3
tag.readBool() 读取一个布尔值(寄存器中的某一位) 0 (boolean)
tag.writeBool(true) 写入一个布尔值 0
tag.readString() 读取字符串 4 (string)
tag.writeString("hello") 写入字符串 4

五、Modbus 地址映射规则

PLC 地址格式数据库字段说明
%MW40401 address=40401, addr_offset=0 整字寄存器
%MX40707.12 address=40707, addr_offset=12 寄存器中的第12位
  • %MW = Memory Word,整字(16位/2字节),对应 Java 的 short
  • %MX = Memory Bit,位地址,格式为 寄存器地址.位偏移
  • Modbus 的 INT 是 2 字节,对应 Java 的 short(不是 Java 的 int)

六、常见问题

Q: 为什么用 writeShort 而不是 writeInt A: Modbus 的 INT 是 2 字节(1个寄存器),对应 Java 的 short。Java 的 int 是 4 字节(2个寄存器)。你的数据都是 %MW(单寄存器),所以用 readShort() / writeShort()

Q: @Loop 方法什么时候被调用? A: 框架根据数据库 period 字段的值自动定时调用。period=2000 表示每 2 秒调用一次。period=0 表示不自动轮询。

Q: 写入后 PLC 会自动清 0 的信号怎么处理? A: 直接 writeShort((short) 1) 就行,PLC 端会自己清 0,你不需要再写 0。

Q: @Named 的值怎么确定? A: Device 用 @Named("plc_code"),Tag 用 @Named("plc_code:signal_code")。这两个值来自数据库。

http://www.jsqmd.com/news/414104/

相关文章:

  • 2026年度NMN排行榜,盼生派登顶职场精英、用脑人士补能首选NMN品牌 - 资讯焦点
  • 农业物流仓储系统微信小程序的设计与实现
  • 2026年深度解析四川托普学院:聚焦人才培养模式的创新实践与成效 - 十大品牌推荐
  • 2026年评价高的餐饮磁吸门帘公司推荐:商场棉门帘、商店磁吸门帘、店铺棉门帘、店铺磁吸门帘、磁吸门帘板材选择指南 - 优质品牌商家
  • 2026年商场磁吸门帘厂家权威推荐榜:商店磁吸门帘/店铺棉门帘/店铺磁吸门帘/西安磁吸门帘/超市棉门帘/选择指南 - 优质品牌商家
  • 说说北京宏恩英语教学机构靠谱吗?在英语教学老牌公司中排名怎样 - 工业品牌热点
  • 431芯片基准电压为什么是2.5V? 怎么来的?
  • 2026年常州ERP公司哪家比较好?本地企业选择参考 - 品牌排行榜
  • 2026年磁吸门帘板材厂家推荐:超市磁吸门帘/陕西磁吸门帘/餐饮店棉门帘/餐饮磁吸门帘/医院棉门帘/商场棉门帘/选择指南 - 优质品牌商家
  • 2026年四川单招学校推荐:职教趋势融合评价,涵盖城乡考生多元场景择校痛点 - 十大品牌推荐
  • [AI提效-60]-
  • 格式总出错?AI论文工具 千笔·专业论文写作工具 VS 云笔AI 专科生必备
  • 喜粤管业在全国的排名如何,不锈钢管值得推荐吗 - myqiye
  • 2026年深度解析四川托普学院:一所信息技术职业院校的区位与产教融合优势剖析 - 十大品牌推荐
  • 河北景达环保是靠谱的污水净化项目品牌机构吗 - 工业品网
  • SRC 挖漏洞入门教程:从 0 到 1 学会合法挖洞、拿赏金、上榜单(如何挖src漏洞_src漏洞_src漏洞平台_src漏洞挖掘_src漏洞新手怎么入门_src漏洞挖掘网站_src漏洞挖掘真实现状)
  • 实测对比后!千笔,备受追捧的降AI率网站
  • 横向对比3类厂家|SLC转运体最优秀的+ABC囊泡好的,性价比拉满 - 品牌推荐大师1
  • 总结昆明不错的家居软装公司如何选择? - 工业推荐榜
  • 2026年四川单招学校推荐:职教趋势与校园生活综合评价,涵盖择校与未来规划多元场景 - 十大品牌推荐
  • 真心不骗你!自考必备的AI论文平台,千笔AI VS 文途AI
  • 吐血推荐!千笔·降AI率助手,本科生论文降重首选
  • 解读2026年农业机械展会,江西AME中部农机展专业可靠权威 - 工业设备
  • 盘点国内细胞储存机构:2026 最新优选攻略,为健康投资选对保障 - 速递信息
  • vue+nodejs+ElementUi的高校教材征订系统的设计与实现
  • 2026年评价高的商店磁吸门帘公司推荐:陕西磁吸门帘、餐饮店棉门帘、医院棉门帘、商场棉门帘、店铺棉门帘选择指南 - 优质品牌商家
  • 好写作AI | 突破思维瓶颈:好写作AI如何用一句话点亮你的整篇文章
  • vue+nodejs+ElementUi的高校教师进修培训管理系统 职称晋升
  • 2026年气体透过率测试仪选购指南:盘点口碑与性能俱佳的十大品牌 - 品牌推荐大师1
  • 教育平台网页编辑器如何解析Word图片?