更新记录

1.0.0(2026-07-20)

新增

  • 首次发布
  • sendHex(options) - 发送 HEX 指令到 PLC,返回 HEX 响应字符串
  • testConnection(host, port, timeout) - 测试 PLC TCP 连通性
  • Android 端基于 java.net.Socket 的短连接实现
  • 网络操作在 IO 线程执行,不阻塞 UI 线程
  • HEX 字符串与 ByteArray 互转工具函数
  • 支持 uni-app (vue3) 和 uni-app x 的 Android 平台

平台兼容性

uni-app(3.8.0)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android iOS 鸿蒙
- √ - - - - 5.0 - -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(3.91)

Chrome Safari Android iOS 鸿蒙 微信小程序
- - 5.0 - - -

其他

多语言 暗黑模式 宽屏模式 蒸汽模式
× × × √

modbus-tcp

Android 端 Modbus TCP 直连通信 UTS 插件。

通过 java.net.Socket 实现 Modbus TCP 短连接通信,App 无需代理服务器即可直接连接 PLC。

特性

  • 纯原生 TCP Socket 通信,不依赖 HTTP 代理
  • 短连接模式:每次请求新建 Socket,发送报文 → 读取响应 → 关闭连接
  • 支持发送任意 HEX 格式的 Modbus TCP 指令
  • 网络操作在 IO 线程执行,不阻塞 UI
  • 返回 HEX 格式响应字符串,便于业务层解析

支持平台

平台 支持情况
uni-app App-Android (vue3) ✓(minSdk 21 / Android 5.0+)
uni-app App-iOS ✗
uni-app H5 ✗(浏览器不支持原生 TCP)
uni-app 小程序 ✗
uni-app x App-Android ✓

注意:UTS 插件需要打自定义基座才能运行,标准基座下不生效。

目录结构

uni_modules/modbus-tcp/
├── package.json
├── README.md
├── CHANGELOG.md
└── utssdk/
    ├── interface.uts              # 接口类型声明
    └── app-android/
        ├── index.uts              # Android 原生实现
        └── config.json            # 权限与依赖配置

API

SendHexOptions

发送 HEX 指令的参数类型。

属性 类型 必填 说明
host string 是 PLC 的 IP 地址
port number 是 PLC 的端口号(Modbus TCP 默认 502)
hex string 是 HEX 指令字符串,可含空格,如 "00 01 00 00 00 06 01 06 00 00 00 01"
timeout number 是 超时时间(毫秒)

sendHex(options)

发送 HEX 指令到 PLC,返回 PLC 的 HEX 响应字符串。

function sendHex(options: SendHexOptions): Promise<string>
  • 成功:resolve(HEX响应字符串),格式为大写空格分隔,如 "00 01 00 00 00 06 01 06 00 00 00 01"
  • 失败:reject(Error),如连接超时、PLC 无响应等

testConnection(host, port, timeout)

测试 PLC 的 TCP 连通性(建立连接后立即关闭,不发送数据)。

function testConnection(host: string, port: number, timeout: number): Promise<boolean>
  • 成功:resolve(true)
  • 失败:resolve(false)(注意:不会 reject,失败时返回 false)

使用示例

1. 导入插件

// #ifdef APP-PLUS
import { sendHex, testConnection } from '@/uni_modules/modbus-tcp'
// #endif

2. 测试 PLC 连通性

// #ifdef APP-PLUS
const reachable = await testConnection('192.168.0.100', 502, 3000)
console.log('PLC连接:', reachable ? '成功' : '失败')
// #endif

3. 发送指令 - 写单个寄存器(功能码 06)

Modbus TCP 写单个寄存器指令格式:

事务ID(2) + 协议ID(2) + 长度(2) + 从站地址(1) + 功能码(1) + 寄存器地址(2) + 写入值(2)
// #ifdef APP-PLUS
// 向地址 0x0000 写入值 0x0001(触发继电器1)
const writeCmd = '00 01 00 00 00 06 01 06 00 00 00 01'
const response = await sendHex({
  host: '192.168.0.100',
  port: 502,
  hex: writeCmd,
  timeout: 5000
})
console.log('PLC响应:', response)
// 响应: 00 01 00 00 00 06 01 06 00 00 00 01(原样回显表示写入成功)
// #endif

4. 发送指令 - 读离散输入(功能码 02)

读取 PLC 输入端子状态,常用于检测按钮、传感器信号。

// #ifdef APP-PLUS
// 读取第13脚输入(地址 0x0D = 13,施耐德 M221 的 I13)
// 事务ID: 00 03 | 协议: 00 00 | 长度: 00 06 | 从站: 01 | 功能码: 02 | 起始地址: 00 0D | 数量: 00 01
const readCmd = '00 03 00 00 00 06 01 02 00 0D 00 01'
const response = await sendHex({
  host: '192.168.0.100',
  port: 502,
  hex: readCmd,
  timeout: 3000
})
console.log('PLC响应:', response)

// 解析响应(功能码02,读取1个离散输入)
// 响应格式: 事务ID(2) + 协议(2) + 长度(2) + 从站(1) + 功能码(1) + 字节数(1) + 数据(1)
// 示例: 00 03 00 00 00 04 01 02 01 01
//                                             ↑ parts[9] = 数据字节
const parts = response.split(' ')
if (parts.length >= 10) {
  const dataByte = parseInt(parts[9], 16)
  const isActive = (dataByte & 0x01) !== 0  // 最低位即输入状态
  console.log('I13输入状态:', isActive ? '高电平' : '低电平')
}
// #endif

5. 发送指令 - 读输入寄存器(功能码 04)

// #ifdef APP-PLUS
// 读取地址 0x0000 的 1 个输入寄存器
const readCmd = '00 04 00 00 00 06 01 04 00 00 00 01'
const response = await sendHex({
  host: '192.168.0.100',
  port: 502,
  hex: readCmd,
  timeout: 3000
})
console.log('PLC响应:', response)

// 解析响应(功能码04,读取1个寄存器)
// 响应格式: 事务ID(2) + 协议(2) + 长度(2) + 从站(1) + 功能码(1) + 字节数(1) + 数据(2)
// 示例: 00 04 00 00 00 05 01 04 02 00 01
//                                           ↑↑ parts[9-10] = 寄存器值(大端序)
const parts = response.split(' ')
if (parts.length >= 11) {
  const hi = parseInt(parts[9], 16)
  const lo = parseInt(parts[10], 16)
  const value = hi * 256 + lo
  console.log('寄存器值:', value)
}
// #endif

6. 封装工具类(推荐用法)

参考实际项目中的封装方式,将插件调用封装为通用工具类:

/**
 * Modbus TCP 工具类
 */
// #ifdef APP-PLUS
import { sendHex, testConnection } from '@/uni_modules/modbus-tcp'
// #endif

let plcHost = '192.168.0.100'
let plcPort = 502

/** 设置 PLC 地址 */
export function setPlcAddress(host, port) {
  plcHost = host
  plcPort = port
}

/**
 * 发送 HEX 指令
 * @param {string} hexCommand - HEX 指令字符串
 * @param {number} timeout - 超时时间(ms)
 * @returns {Promise<string>} PLC 响应的 HEX 字符串
 */
export function sendRawHexCommand(hexCommand, timeout = 5000) {
  // #ifdef APP-PLUS
  return sendHex({
    host: plcHost,
    port: plcPort,
    hex: hexCommand,
    timeout: timeout
  })
  // #endif

  // #ifndef APP-PLUS
  return Promise.reject(new Error('当前平台不支持 Modbus TCP 直连'))
  // #endif
}

/**
 * 检查 PLC 是否可连接
 * @returns {Promise<boolean>}
 */
export function checkConnection() {
  // #ifdef APP-PLUS
  return testConnection(plcHost, Number(plcPort), 3000)
  // #endif

  // #ifndef APP-PLUS
  return Promise.resolve(false)
  // #endif
}

/**
 * 读取 PLC 离散输入状态(功能码 02)
 * @param {number} inputNo - 输入路号(从1开始)
 * @returns {Promise<boolean>} 输入是否为高电平
 */
export async function readPlcInput(inputNo) {
  const addr = inputNo - 1
  const addrHi = (addr >> 8).toString(16).padStart(2, '0')
  const addrLo = (addr & 0xFF).toString(16).padStart(2, '0')
  const hexCmd = `00 03 00 00 00 06 01 02 ${addrHi} ${addrLo} 00 01`

  const response = await sendRawHexCommand(hexCmd)

  const parts = response.split(' ')
  if (parts.length >= 10) {
    const dataByte = parseInt(parts[9], 16)
    return (dataByte & 0x01) !== 0
  }
  return false
}

7. 轮询读取 PLC 输入(唤醒场景示例)

import { setPlcAddress, readPlcInput } from './modbusUtil'

let pollTimer = null
let polling = false

/** 开始轮询 PLC 输入 */
function startPolling(plcHost, plcPort, inputNo, onActive) {
  setPlcAddress(plcHost, plcPort)

  pollTimer = setInterval(async () => {
    if (polling) return
    polling = true
    try {
      const isActive = await readPlcInput(inputNo)
      if (isActive) {
        onActive()  // 输入为高电平,执行回调
      }
    } catch (e) {
      console.log('读取失败:', e)
    } finally {
      polling = false
    }
  }, 500)
}

/** 停止轮询 */
function stopPolling() {
  if (pollTimer) {
    clearInterval(pollTimer)
    pollTimer = null
  }
}

// 使用:每隔 500ms 读取 I13,高电平时触发回调
startPolling('192.168.0.100', 502, 14, () => {
  console.log('I13 触发!')
  stopPolling()
})

Modbus TCP 指令格式速查

Modbus TCP 报文 = MBAP 头(7字节) + PDU

┌─────────┬─────────┬─────────┬──────────┬──────────┐
│ 事务ID  │ 协议ID  │  长度   │ 从站地址 │   PDU    │
│ 2字节   │ 2字节   │ 2字节   │ 1字节    │ N字节    │
│ (任意)  │(00 00)  │         │ (通常01) │功能码+...│
└─────────┴─────────┴─────────┴──────────┴──────────┘

常用功能码指令模板

功能码 操作 指令模板(HEX) 说明
06 写单个寄存器 XX XX 00 00 00 06 01 06 AA AA VV VV AA=地址, VV=值
02 读离散输入 XX XX 00 00 00 06 01 02 AA AA NN NN AA=起始地址, NN=数量
04 读输入寄存器 XX XX 00 00 00 06 01 04 AA AA NN NN AA=起始地址, NN=数量
03 读保持寄存器 XX XX 00 00 00 06 01 03 AA AA NN NN AA=起始地址, NN=数量
05 写单个线圈 XX XX 00 00 00 06 01 05 AA AA FF 00 AA=地址, FF00=ON/0000=OFF

事务ID(XX XX)可任意取值,PLC 会原样回显。

已验证 PLC 型号

品牌 型号 说明
施耐德 TM221CE24R I0~I13 对应 Modbus 离散输入地址 0~13
更多 - 欢迎补充

注意事项

  1. 自定义基座:UTS 插件在标准基座下不生效,需打自定义基座运行。
  2. Android 最低版本:需 Android 5.0(API 21)及以上。
  3. 网络权限:插件已声明 INTERNET 权限,无需额外配置。
  4. 明文流量:原生 TCP Socket 不受 usesCleartextTraffic 限制。
  5. H5 不支持:浏览器环境无法建立 TCP 连接,请使用条件编译 #ifdef APP-PLUS 隔离。
  6. 短连接模式:每次 sendHex 调用都会新建 TCP 连接,适用于低频控制场景。如需高频通信,请评估连接开销。
  7. 超时设置:建议读取操作超时设 3000ms,写入操作设 5000ms。超时过短可能导致 PLC 响应不完整。

隐私、权限声明

1. 本插件需要申请的系统权限列表:

需要网络权限(android.permission.INTERNET)用于建立TCP连接与PLC通信。

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件仅通过网络发送用户构建的Modbus TCP报文到用户指定的PLC设备,不收集、不上传任何用户数据。

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率:

无