更新记录

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. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: