ModbusTCP 通信工具 (SL-WX-ModbusTcp.js)
插件市场 操作视频
基于 HTML5+ plus.android 调用 java.net.Socket 实现的 ModbusTCP 二进制协议通信工具,仅支持 Android App(5+) 环境。非 Android 环境调用任何方法会自动 reject 并弹窗提示。
⚠️ 关于 plus.net 的说明:HTML5+ 的 plus.net 模块仅提供 XMLHttpRequest(HTTP 请求),并不提供原生 TCP Socket API。ModbusTCP 是基于 TCP 的二进制协议,必须使用 Socket。因此本工具通过 plus.android.importClass('java.net.Socket') 调用 Java 原生 Socket 实现,仅 Android 可用;iOS 需要另行开发 uni 原生插件(Objective-C / Swift)。
目录结构
components/SL-WX-ModbusTcp
└── SL-WX-ModbusTcp.js
快速开始
import modbus from '@/components/SL-WX-ModbusTcp/SL-WX-ModbusTcp.js'
// 1. 连接 PLC
await modbus.connect('192.168.1.10', 502, 3000)
// 2. 读 BOOL(线圈,功能码 1)—— 起始地址 1,数量 1
const bools = await modbus.readBool(1, 1, 1, 1)
// → [true]
// 3. 读 INT16(保持寄存器,功能码 3)—— 起始地址 1000,数量 4
const ints = await modbus.readInt(1000, 4, 1, 3)
// → [1234, -5678, 90, 100]
// 4. 读 INT32(占 2 寄存器)—— 地址 1000,字节序 CDAB
const i32 = await modbus.readInt32(1000, 1, 'CDAB', 3)
// 5. 读 FLOAT(占 2 寄存器)—— 地址 2000,字节序 CDAB
const f = await modbus.readFloat(2000, 1, 'CDAB', 3)
// → 3.14
// 6. 写 BOOL(线圈,功能码 5)
await modbus.writeBool(1, true, 1)
// 7. 写 INT16(保持寄存器,功能码 6)
await modbus.writeInt(1000, 12345, 1)
// 8. 写 INT32(占 2 寄存器,功能码 16)
await modbus.writeInt32(1000, 123456789, 'ABCD', 1)
// 9. 写 FLOAT(占 2 寄存器,功能码 16)
await modbus.writeFloat(2000, 3.14159, 'ABCD', 1)
// 10. 断开
await modbus.disconnect()
环境检测
if (!modbus.isApp() || !modbus.isAndroid()) {
const platform = modbus.getPlatformName() // 'H5' | '微信小程序' | …
uni.showModal({ title: '提示', content: `当前为${platform},无法使用 ModbusTCP` })
return
}
核心特性
- 完整 ModbusTCP 协议栈:自行构建/解析 MBAP Header(7 字节)+ PDU,支持功能码 1/2/3/4/5/6/16,覆盖线圈、离散输入、保持寄存器、输入寄存器的读写。
- BOOL / INT16 / INT32 / FLOAT 四种数据类型:读写齐全,INT32 与 FLOAT 自动处理双寄存器拼接。
- 4 种字节序:ABCD(大端)/ DCBA(小端)/ BADC / CDAB(字交换),适配不同 PLC 厂商的 32 位数据存储顺序。
- 从站号可配:每次读写可指定 unitId(1~247,默认 1),适配多从站现场。
- 浮点精度截断:readFloat 返回值自动 toFixed(6) 截断 IEEE 754 float32 精度噪声,无需手动处理。
- 异常响应解析:自动识别 Modbus 异常响应(功能码最高位 = 1),抛出含异常码与中文说明的错误。
- Transaction ID 校验:每帧递增并校验响应帧的事务标识,防止串帧。
- 粘包处理:按功能码与请求参数计算预期响应长度,精确读取,无需额外处理粘包。
- 连接自动清理:connect 前自动关闭旧 Socket / InputStream / OutputStream,可安全重复调用。
- Promise 化 API:所有读写方法返回 Promise,配合 async/await 流畅书写。
- 平台自动检测:非 Android App 环境调用自动 reject 并弹窗提示,附带 isApp() / isAndroid() / getPlatformName() 环境检测工具。
API 列表
连接 / 断开
connect(host, port, timeout) → Promise<true>
建立到 PLC 的 TCP 连接。连接前会自动清理旧连接。
| host | String | — | PLC 的 IP 地址,如 '192.168.1.10' |
| port | Number | 502 | ModbusTCP 标准端口 502 |
| timeout | Number | 3000 | 连接超时与读取超时(毫秒),超时后抛出 SocketTimeoutException |
disconnect() → Promise<true>
断开连接,依次关闭 InputStream、OutputStream、Socket,释放 Java 侧资源。
isConnected() → Boolean
同步方法,返回当前是否处于已连接状态。
读取
readBool(addr, count, unitId, funcCode) → Promise<Boolean[]>
读取线圈或离散输入,返回布尔值数组。
| addr | Number | — | 起始地址(0-based),如 1 表示从第 1 个线圈开始 |
| count | Number | 1 | 读取数量,1~2000 |
| unitId | Number | 1 | 从站号(1~247) |
| funcCode | Number | 1 | 功能码:1 = 读线圈(可读可写区域),2 = 读离散输入(只读区域) |
readInt(addr, count, unitId, funcCode) → Promise<Number[]>
读取保持寄存器或输入寄存器,返回有符号 16 位整数数组(-32768~32767)。
| addr | Number | — | 起始寄存器地址(0-based),如 1000 |
| count | Number | 1 | 读取寄存器数量,1~125 |
| unitId | Number | 1 | 从站号(1~247) |
| funcCode | Number | 3 | 功能码:3 = 读保持寄存器(可读可写区域),4 = 读输入寄存器(只读区域) |
readInt32(addr, unitId, wordOrder, funcCode) → Promise<Number>
读取 32 位有符号整数,占 2 个连续寄存器。
| addr | Number | — | 起始寄存器地址(0-based) |
| unitId | Number | 1 | 从站号(1~247) |
| wordOrder | String | 'ABCD' | 字节序,见「字节序说明」章节。可选:ABCD / DCBA / BADC / CDAB |
| funcCode | Number | 3 | 功能码:3 = 保持寄存器,4 = 输入寄存器 |
readFloat(addr, unitId, wordOrder, funcCode) → Promise<Number>
读取 32 位浮点数,占 2 个连续寄存器。返回值已做 toFixed(6) 精度截断。
| addr | Number | — | 起始寄存器地址(0-based) |
| unitId | Number | 1 | 从站号(1~247) |
| wordOrder | String | 'ABCD' | 字节序,见「字节序说明」章节。可选:ABCD / DCBA / BADC / CDAB |
| funcCode | Number | 3 | 功能码:3 = 保持寄存器,4 = 输入寄存器 |
写入
writeBool(addr, value, unitId) → Promise<true>
写单个线圈(功能码 5)。
| addr | Number | — | 线圈地址(0-based) |
| value | Boolean | — | true = ON(发送 0xFF00),false = OFF(发送 0x0000) |
| unitId | Number | 1 | 从站号(1~247) |
writeInt(addr, value, unitId) → Promise<true>
写单个保持寄存器(功能码 6)。
| addr | Number | — | 寄存器地址(0-based) |
| value | Number | — | 有符号 16 位整数,范围 -32768~32767 |
| unitId | Number | 1 | 从站号(1~247) |
writeInt32(addr, value, wordOrder, unitId) → Promise<true>
写 32 位有符号整数,占 2 个连续寄存器(功能码 16)。
| addr | Number | — | 起始寄存器地址(0-based) |
| value | Number | — | 有符号 32 位整数,范围 -2147483648~2147483647 |
| wordOrder | String | 'ABCD' | 字节序,见「字节序说明」章节。可选:ABCD / DCBA / BADC / CDAB |
| unitId | Number | 1 | 从站号(1~247) |
writeFloat(addr, value, wordOrder, unitId) → Promise<true>
写 32 位浮点数,占 2 个连续寄存器(功能码 16)。
| addr | Number | — | 起始寄存器地址(0-based) |
| value | Number | — | 浮点数,如 3.14 |
| wordOrder | String | 'ABCD' | 字节序,见「字节序说明」章节。可选:ABCD / DCBA / BADC / CDAB |
| unitId | Number | 1 | 从站号(1~247) |
环境检测
isApp() → Boolean
同步方法,返回当前是否运行在 App 环境(APP-PLUS 编译条件)。
isAndroid() → Boolean
同步方法,返回当前是否为 Android 平台。非 Android 的 App 环境(如 iOS)返回 false。
getPlatformName() → String
同步方法,返回当前平台名称字符串,用于提示文案。可能的返回值:'H5' / '微信小程序' / '支付宝小程序' / '当前平台'。
功能码说明
| 1 | Read Coils | BOOL | 只读 | readBool(addr, count, unitId, 1) |
| 2 | Read Discrete Inputs | BOOL | 只读 | readBool(addr, count, unitId, 2) |
| 3 | Read Holding Registers | INT16 | 读写 | readInt / readInt32 / readFloat (默认 funcCode) |
| 4 | Read Input Registers | INT16 | 只读 | readInt(addr, count, unitId, 4) 等 |
| 5 | Write Single Coil | BOOL | 只写 | writeBool(addr, value, unitId) |
| 6 | Write Single Register | INT16 | 只写 | writeInt(addr, value, unitId) |
| 16 | Write Multiple Registers | INT16 | 只写 | writeInt32 / writeFloat(内部使用) |
字节序说明
32 位数据(INT32 / FLOAT)占用 2 个连续寄存器,共 4 字节。不同 PLC 的字节排列顺序不同,本工具支持 4 种字节序:
| ABCD | 大端(高字在前,高字节在前) | [A B] [C D] | 多数 PLC 默认 / 莫迪康 |
| DCBA | 小端(低字在前,低字节在前) | [D C] [B A] | 部分西门子 |
| BADC | 字交换 + 字节交换 | [B A] [D C] | 部分施耐德 |
| CDAB | 字交换(高字在后) | [C D] [A B] | 部分台达 / 信捷 |
大多数 PLC 默认 ABCD,调用时可不传 wordOrder 参数(自动取默认值)。具体字节序请参考设备厂商文档。
使用示例
示例 1:读线圈状态(BOOL)
// 读地址 1~8 的 8 个线圈
const bools = await modbus.readBool(1, 8, 1, 1)
// bools = [true, false, true, true, false, false, true, false]
示例 2:读保持寄存器(INT16)
// 读地址 1000~1003 的 4 个保持寄存器
const ints = await modbus.readInt(1000, 4, 1, 3)
// ints = [1234, -5678, 90, 100]
示例 3:读浮点数(FLOAT,32 位)
// 读地址 2000 的 float(占 D2000~D2001 两个寄存器),字节序 ABCD
const f = await modbus.readFloat(2000, 1, 'ABCD', 3)
// f = 3.14
示例 4:写线圈(BOOL)
// 写线圈地址 1 为 ON
await modbus.writeBool(1, true, 1)
// 写线圈地址 2 为 OFF
await modbus.writeBool(2, false, 1)
示例 5:写浮点数(FLOAT,32 位)
// 写 float 到地址 2000(占 D2000~D2001),字节序 ABCD
await modbus.writeFloat(2000, 3.14159, 'ABCD', 1)
示例 6:批量轮询示例
async function poll() {
try {
const [bools, ints, f] = await Promise.all([
modbus.readBool(1, 8, 1, 1), // 线圈状态
modbus.readInt(1000, 4, 1, 3), // 寄存器值
modbus.readFloat(2000, 1, 'ABCD', 3) // 浮点数
])
console.log('bools:', bools)
console.log('ints:', ints)
console.log('float:', f)
} catch (e) {
console.error('轮询失败:', e.message)
await modbus.disconnect()
}
}
// 每 1 秒轮询一次
setInterval(poll, 1000)
ModbusTCP 帧结构
MBAP Header(7 字节)
| 0 | 2 | Transaction ID | 事务标识,递增;响应帧回传相同值 |
| 2 | 2 | Protocol ID | 协议标识,Modbus 固定为 0x0000 |
| 4 | 2 | Length | 后续字节数(Unit ID + PDU) |
| 6 | 1 | Unit ID | 从站号(1~247) |
PDU
| 7 | 1 | Func Code | 功能码 |
| 8+ | N | Data | 数据(请求/响应内容) |
异常响应
| 7 | 1 | Exception Func | 功能码 + 0x80(最高位为 1 标识异常) |
| 8 | 1 | Exception Code | 异常码(1=非法功能码,2=非法地址 等) |
注意事项
仅 Android:本工具基于 plus.android.importClass('java.net.Socket'),iOS 不适用。iOS 需要开发 uni 原生插件(Objective-C / Swift 实现 BSD Socket)。
网络权限:App 需要在 manifest.json 中声明 android.permission.INTERNET 权限(HBuilderX 默认已包含)。
局域网要求:手机与 PLC 必须在同一局域网,且 PLC 已开启 ModbusTCP 服务(默认端口 502,具体启用方式请参考设备厂商文档)。
超时设置:connect 时设置 timeout(默认 3000ms),同时作为 setSoTimeout 读超时。读不到数据会在 timeout 后抛出 java.net.SocketTimeoutException。
数据范围:
- readBool 单次最多 2000 个
- readInt 单次最多 125 个寄存器
- readInt32 / readFloat 实际读 2 个寄存器
- writeInt 范围 -32768~32767
- writeInt32 范围 -2147483648~2147483647
字节序选择:不同 PLC 厂商对 32 位数据在寄存器中的存储顺序不同。多数 PLC 默认 ABCD,若读出的数据明显异常(如很大的随机数),尝试切换为 CDAB 或 DCBA。
连接复用:connect 前会自动清理旧连接,可重复调用。但建议调用 disconnect 后再 connect,避免资源泄漏。
异常处理:所有方法 reject 时返回的是原始 Java 异常对象或包装的 Error,可通过 e.message 获取中文错误说明。Modbus 异常响应(如非法地址)会被解析为 'Modbus 异常响应:功能码 0x3,异常码 0x2(非法数据地址)'。
粘包处理:ModbusTCP 响应长度可预期(根据功能码与请求参数可计算),本工具按预期长度读取,无需处理粘包。但若网络异常导致响应残缺,_readBytes 会在读到 EOF(返回 -1)时抛出 '连接已关闭' 错误。
离开页面:建议在页面 onUnload 生命周期中调用 disconnect(),避免 Socket 资源泄漏。
浮点精度:readFloat 返回值已做 toFixed(6) 精度截断,避免 IEEE 754 float32 的精度噪声(如 3.140000104904175 → 3.14)。如需更高精度可自行处理原始寄存器值。
预览界面说明
预览页面 pages/modbustcp/modbustcp.vue 提供完整的交互式调试面板,包含以下区域:
| 连接配置 | — | IP / 端口 / 从站号 / 字节序(ABCD / DCBA / BADC / CDAB) |
| 读 BOOL | 1 | 功能码 1(线圈)/ 2(离散输入),支持批量读取,结果以 ON/OFF 网格展示 |
| 写 BOOL | 1 | 功能码 5,ON / OFF 切换写入 |
| 读 INT | 1000 | 功能码 3(保持寄存器)/ 4(输入寄存器),支持批量读取 |
| 写 INT | 1000 | 功能码 6(INT16)/ 16(INT32),可切换类型 |
| 读 FLOAT | 2000 | 功能码 3,占 2 个寄存器,结果自动截断精度噪声 |
| 写 FLOAT | 2000 | 功能码 16,占 2 个寄存器 |
| 操作日志 | — | 记录最近 50 条操作结果,支持清空 |



