Modbus TCP / UDP 驱动
概述
基于 Modbus 工业标准 (Modbus-IDA / IEC 61158-6-12) 的 PLC 通信。
| 项目 | 值 |
|---|---|
opc_type | MODBUS (TCP) / MODBUSUDP (UDP) |
| 实现类 | plantpulse.driver.protocol.modbus.ModbusTCPDriver / ModbusUDPDriver |
| 库 | 自研原生 (plantpulse-plc-protocol 的 ModbusTcp / ModbusUdp) |
| read | ✅ |
| write | ✅ (coil / holding-register) |
| 安全 | 无 (Modbus 标准所限) |
Modbus TCP/UDP 均已移除旧的 PLC4j 委托,改为使用 plantpulse-plc-protocol 的原生 ModbusTcp / ModbusUdp(公共基类 AbstractNativeModbusDriver)重写 —— 与 FINS / MELSEC / AB-ETH 采用相同模式。UDP 为 MBAP-over-UDP(单个数据报 = 单个 ADU),其地址、格式与 write 层与 TCP 完全一致。地址语法(holding-register:N[:TYPE])原样保留 PLC4X modbus4x 表示法。
OPC 注册表单
| 字段 | 含义 | 示例 |
|---|---|---|
opc_agent_ip | Modbus 从站 IP | 192.168.0.50 |
opc_agent_port | Modbus 端口 | 502 (TCP 默认) |
timecycle | 轮询周期 (ms) | 1000 |
options.request-timeout | 请求超时 (ms) | 5000 |
Modbus TCP 通常以 IP+端口区分从站,因此 unit-identifier(别名 unit-id) 默认为 1。
当串口-TCP 网关后面挂接多个从站时,指定为 1~247。
标签 plc_address 格式
地址表示 (<area>:<address> + :<datatype> + [<count>]) —— 原样保留 PLC4X modbus4x 语法。
地址为 1-based(在 wire 上转换为 0-based,holding-register:5 → wire 寄存器 4)。
| 表示 | 含义 |
|---|---|
holding-register:1 | Holding Register 1 (16-bit) |
holding-register:1:UINT | 16-bit unsigned |
holding-register:1:DINT | 32-bit signed (2 个寄存器) |
holding-register:1:REAL | 32-bit float (2 个寄存器) |
holding-register:100:STRING[10] | 100~109 的 ASCII 字符串 |
coil:1 | 线圈 1 位 |
discrete-input:1 | discrete input 1 位 |
input-register:1 | input register 16-bit |
详细表示规则参见 PLC4j Modbus 文档。
data_type / format 映射
Edge 的 format 会直接映射为 PLC4j 的 type suffix。
edge data_type | edge format | PLC4j addr 映射 | 备注 |
|---|---|---|---|
| Boolean | (empty) / BOOL | coil:N 或 :BOOL | 1 bit |
| Integer / Short | (empty) | :INT | 16-bit signed |
| UInteger | UI / UWORD | :UINT | 16-bit unsigned |
| Integer (32) | DW | :DINT | 32-bit signed (2 reg) |
| UInteger (32) | UDW | :UDINT | 32-bit unsigned |
| Float | REAL | :REAL | 32-bit IEEE 754 |
| Double | LREAL | :LREAL | 64-bit IEEE 754 |
| String | STR[N] | :STRING[N] | N 个字符 |
若 format 以 STR[N]、REAL[N]、UDINT[N] 开头,则按数组处理 (readWordValues)。
Write (原生 —— TCP/UDP 通用)
ModbusTCPDriver / ModbusUDPDriver 支持 write(isWriteSupported()=true):
coil:N→ single coil write (Boolean)。holding-register:N[:TYPE]→ 按 datatype 执行 register write。REAL/DINT为 MSWord-first 的 2 个寄存器,其余为单个 16-bit。值由调用方以ProtocolAddress.value传入。discrete-input/input-register在协议上为 read-only —— 无法 write。
常见错误与解决
| 消息 / 现象 | 原因 | 解决 |
|---|---|---|
PLC_READ_TIMEOUT_EXCEPTION | 从站响应延迟或断开 | 检查线缆/防火墙/端口(502)。增大 request-timeout |
PLC_READ_RUNTIME_EXCEPTION: Invalid PLC4j address | 地址表示拼写错误 | 校验 holding-register:N 格式,注意勿遗漏 : |
| 仅返回 0 值 | 寄存器种类不匹配 (input vs holding) | 在从站手册中确认属于 fc 03/04/01/02 中的哪一种 |
| 32-bit 值 swap 异常 | byte/word order | 若从站为 little-endian,则将 format 改为 :UDINT_LSWORD_FIRST 等 (PLC4j 选项) |
curl 注册示例 (Modbus TCP)
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_MB_HVAC",
"opc_type": "MODBUS",
"opc_name": "HVAC Slave",
"opc_agent_ip": "192.168.0.50",
"opc_agent_port": "502",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": { "request-timeout": "5000" },
"tag_list": [
{
"tag_id": "OPC_MB_HVAC_TAG_00001",
"tag_name": "Temperature",
"plc_address": "holding-register:1:REAL",
"data_type": "Float",
"format": "REAL"
},
{
"tag_id": "OPC_MB_HVAC_TAG_00002",
"tag_name": "Status",
"plc_address": "coil:1",
"data_type": "Boolean"
}
]
}'
UDP 时改为 opc_type: "MODBUSUDP",端口通常为 502 或依从站配置而定。
读取值:
curl -s http://<edge-host>/api/v1/tag/OPC_MB_HVAC_TAG_00001/value | jq
示例集 (按数据类型)
线圈 / 离散量 (Boolean)
data_type | format | plc_address 示例 | 备注 |
|---|---|---|---|
Boolean | (empty) | coil:1 | FC 01 (Read Coils) |
Boolean | (empty) | discrete-input:1 | FC 02 (Read Discrete Inputs) |
Boolean | BIN[3] | holding-register:1 | 读取字后提取 bit 3 |
16-bit 整数
data_type | format | plc_address 示例 | 备注 |
|---|---|---|---|
Integer | (empty) | holding-register:1 | signed INT 16 |
Integer | (empty) | holding-register:1:INT | 相同 (PLC4j 表示) |
Integer | UI | holding-register:1:UINT | unsigned 16 |
Integer | (empty) | input-register:1 | input register (FC 04) |
32-bit 整数
data_type | format | plc_address 示例 | 备注 |
|---|---|---|---|
Integer | DW | holding-register:1:DINT | signed 32 (2 个寄存器) |
Integer | UDW | holding-register:1:UDINT | unsigned 32 |
Integer | UDINT | holding-register:1 | 通过显式 format 指定为 32-bit |
浮点数
data_type | format | plc_address 示例 | 备注 |
|---|---|---|---|
Float | REAL | holding-register:1 | 32-bit float (2 个寄存器) |
Float | REAL | holding-register:1:REAL | 相同表示 |
Double | LREAL | holding-register:1:LREAL | 64-bit double (4 个寄存器) |
字符串
data_type | format | plc_address 示例 | 备注 |
|---|---|---|---|
String | STR[5] | holding-register:10 | 5 word = 10 byte ASCII |
String | STR[16] | holding-register:100:STRING[16] | PLC4j 表示 |
Formula 应用
| 用途 | data_type | fomula | 备注 |
|---|---|---|---|
| 整数 raw → 小数 | Float | ${VALUE}*0.1 | 压力/温度 raw 缩放 |
| Word swap 校正 | Float | ${VALUE}*1.0 | 适用于 32-bit float byte order 不同的从站。替代方案::REAL_LSWORD_FIRST |
| RH/Temp combo (一个字两个值) | Float | ${VALUE}/256 | 仅高 8 bit |
curl 综合示例
curl -X POST http://<edge-host>/api/v1/opc \
-H "Content-Type: application/json" \
-d '{
"opc_id": "OPC_MB_FULL",
"opc_type": "MODBUS",
"opc_name": "Modbus Full",
"opc_agent_ip": "192.168.0.50",
"opc_agent_port": "502",
"site_id": "SITE_00001",
"auto_collect": true,
"timecycle": 1000,
"options": { "request-timeout": "5000" },
"tag_list": [
{"tag_id":"OPC_MB_FULL_T01", "tag_name":"Coil1", "plc_address":"coil:1", "data_type":"Boolean"},
{"tag_id":"OPC_MB_FULL_T02", "tag_name":"DI1", "plc_address":"discrete-input:1", "data_type":"Boolean"},
{"tag_id":"OPC_MB_FULL_T03", "tag_name":"HRSigned", "plc_address":"holding-register:1", "data_type":"Integer"},
{"tag_id":"OPC_MB_FULL_T04", "tag_name":"HRUnsigned", "plc_address":"holding-register:2:UINT", "data_type":"Integer", "format":"UI"},
{"tag_id":"OPC_MB_FULL_T05", "tag_name":"DInt", "plc_address":"holding-register:3:DINT", "data_type":"Integer", "format":"DW"},
{"tag_id":"OPC_MB_FULL_T06", "tag_name":"Temp", "plc_address":"holding-register:5:REAL", "data_type":"Float", "format":"REAL"},
{"tag_id":"OPC_MB_FULL_T07", "tag_name":"BatchName", "plc_address":"holding-register:10:STRING[10]", "data_type":"String", "format":"STR[10]"},
{"tag_id":"OPC_MB_FULL_T08", "tag_name":"PressScale", "plc_address":"holding-register:7:REAL", "data_type":"Float", "format":"REAL", "fomula":"${VALUE}*0.1"}
]
}'