更新记录

1.2.0(2026-06-14)

新增

  • 帧头+长度域通用分帧startFramedListening 第三参支持 { type: 'header-length', header, lengthOffset, lengthSize, ... } 配置
    • 通用解决绝大多数自定义工业协议粘包问题(帧头搜索 → 长度域解析 → CRC校验 → 逐帧提取)
    • 支持可选的 CRC-16/MODBUS 校验,自动丢弃噪声帧
    • 支持 1/2 字节长度域(大端序),lengthIncludesCRC 控制长度语义
    • 原理:通过帧头逐字节搜索定位帧起始 + 长度域计算帧总长 + while 循环连续提取多帧
    • 高频上报场景下不会粘包(while 循环从 buffer 中连续剥离完整帧)
  • HeaderLengthConfig 类型interface.uts 新增完整类型定义

改进

  • 代码清理:移除 tryExtractFrames() modbus-rtu 分支中的死代码检查(while 条件已保证数据充足)
  • 废弃标记startListening 标注为 @deprecated,引导用户迁移到 startFramedListening(后者即使不传分帧也有空闲超时分帧能力)
  • 文档完善:readme.md 补充 header-length 完整用法、分帧策略对比表、废弃 API 迁移指引

分帧策略全家桶

模式 新增/已有 适用场景
header-length 新增 自定义工业协议(帧头+长度域+CRC)
modbus-rtu 已有 Modbus RTU 设备
fixed 固定帧长 已有 帧长固定的简单协议
custom 自定义函数 已有 复杂/特殊协议
纯超时 已有 低频上报/无帧格式协议

平台兼容性

uni-app(4.87)

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

uni-app x(4.87)

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

其他

多语言 暗黑模式 宽屏模式
× ×

z-serialport - 工业级串口通信插件

基于 Android RandomAccessFile + stty 实现的工业级 RS485/RS232 串口通信插件。

功能

  • 支持 RS485 / RS232 串口通信
  • 基于 RandomAccessFile 文件操作,稳定性高
  • 支持常规波特率(1200 ~ 921600)
  • 支持奇偶校验(none / odd / even)、数据位(7/8)、停止位(1/2)
  • 支持 485 方向控制 GPIO
  • 帧超时监听(空闲超时分帧,自动解决粘包)
  • 支持 Modbus RTU / 固定帧长 / 自定义分帧器 / 帧头+长度域通用分帧
  • 轮询接收模式
  • 精确字节读取
  • 缓冲区管理
  • 多实例支持(同时连接多个串口)
  • 演示页面适配横竖屏(横屏左右双栏 / 竖屏上下滚动)

兼容平台

  • Android ✅(完整实现)
  • uni-app (vue3) ✅

快速开始

1. 引入

import { openPort, listPorts } from '@/uni_modules/z-serialport/utssdk/app-android/index.uts'

2. 扫描设备

const ports = listPorts()
// 返回串口设备列表,如 ['/dev/ttyS0', '/dev/ttyS1', '/dev/ttyUSB0']

3. 打开串口

const port = null

openPort({
  config: {
    devicePath: '/dev/ttyS0',      // [必填] 串口设备路径,如 /dev/ttyS0、/dev/ttyUSB0
    baudRate: 9600,                // [必填] 波特率:1200 ~ 921600
    dataBits: 8,                   // [必填] 数据位:7 或 8
    stopBits: 1,                   // [必填] 停止位:1 或 2
    parity: 'none',                // [必填] 校验位:'none' | 'odd' | 'even'
    bufferSize: 4096,              // [可选] 缓冲区大小(字节),默认 4096
    readTimeout: 100,              // [可选] 读取超时(毫秒),默认 0(不超时)
    directionGpio: -1              // [可选] 485方向控制GPIO编号,默认 -1(不启用)
  },
  success: (res) => {
    port = res.port               // res.port: SerialPort 实例,res.devicePath: 设备路径
    console.log('串口已打开:', res.devicePath)
  },
  fail: (err) => {
    console.error('打开失败', err.errCode, err.errMsg)  // err.errCode: 错误码,err.errMsg: 错误信息
  }
})

4. 接收数据(帧超时监听,推荐)

// 纯空闲超时分帧(通用)
port.startFramedListening((data) => {
  const hex = Array.from(data).map(b => b.toString(16).padStart(2, '0')).join(' ')
  console.log('收到帧:', hex)
}, 10)  // 第2参 [可选] 字节间超时 ms,默认 10

// 停止监听
port.stopFramedListening()

5. 发送数据

port.send({
  hex: '01 03 00 0A 00 01 A5 C9',       // [必填] HEX字符串,空格可选,大小写均可
  success: (res) => {                    // [可选]
    console.log('发送成功:', res.bytesWritten, '字节')  // res.bytesWritten: 实际发送字节数
  },
  fail: (err) => {                       // [可选]
    console.error('发送失败:', err.errMsg)  // err.errCode: 错误码,err.errMsg: 错误信息
  }
})

6. 关闭串口

port.close({
  complete: () => {
    console.log('串口已关闭')
    port = null
  }
})

演示页面

插件内置完整演示页面 uni_modules/z-serialport/example/example.vue,包含:

  • 设备扫描 — 自动扫描所有可用串口设备
  • 连接配置 — 设备路径 / 波特率 / 数据位 / 停止位 / 校验位下拉选择
  • 数据发送 — HEX 格式输入,支持空格分隔
  • 帧超时监听 — 空闲超时自动分帧,可配置超时时间
  • 轮询模式 — 兼容模式,定时读取缓冲
  • 接收数据 — 实时显示最近 5 条 HEX 数据
  • 通信日志 — 时间戳记录所有收发/状态日志

布局适配:

  • 横屏:左右双栏 50/50 平分,左配置/发送,右接收/日志
  • 竖屏:上下堆叠布局,整屏滚动

API 参考

openPort(options)

打开串口,返回 SerialPort 实例

import { openPort } from '@/uni_modules/z-serialport/utssdk/app-android/index.uts'
openPort({
  config: {
    devicePath: '/dev/ttyS0',      // [必填] 串口设备路径,如 /dev/ttyS0、/dev/ttyUSB0
    baudRate: 9600,                // [必填] 波特率:1200 ~ 921600
    dataBits: 8,                   // [必填] 数据位:7 或 8
    stopBits: 1,                   // [必填] 停止位:1 或 2
    parity: 'none',                // [必填] 校验位:'none' | 'odd' | 'even'
    bufferSize: 4096,              // [可选] 缓冲区大小(字节),默认 4096
    readTimeout: 100,              // [可选] 读取超时(毫秒),默认 0(不超时)
    directionGpio: -1              // [可选] 485方向控制GPIO编号,默认 -1(不启用)
  },
  success: (res) => {
    const port = res.port          // res.port: SerialPort 实例,res.devicePath: 设备路径
    // port.send(...) / port.startFramedListening(...) / ...
  },
  fail: (err) => { console.error('打开失败', err.errMsg) }  // err.errCode: 错误码,err.errMsg: 错误信息
})

port.send(options)

发送数据(传入 HEX 字符串,UTS 内部解析为字节数组写入)

port.send({
  hex: '01 03 00 0A',                   // [必填] HEX字符串,空格可选
  success: (res) => { ... },            // [可选]
  fail: (err) => { ... }                // [可选]
})

port.startFramedListening(callback, interByteTimeout?, frameDelimiter?, onError?)

帧超时监听(推荐,自动分帧解决粘包)

双重策略:协议级分帧优先(即时提取完整帧)+ 空闲超时兜底(残留字节超时后整体交付)

// 纯超时分帧(通用,适合 RS232/自定义协议)
port.startFramedListening((data) => {
  console.log('收到:', port.bytesToHex(data))
}, 10)  // 第2参 [可选] 超时 ms,默认 10

// Modbus RTU 协议分帧(自动 CRC-16 校验,丢弃噪声帧)
port.startFramedListening(callback, 10, 'modbus-rtu')  // 第3参 [可选] 'modbus-rtu' | 固定长度数字 | 自定义函数 | HeaderLengthConfig

// 固定帧长度
port.startFramedListening(callback, 10, 8)

// 帧头+长度域 通用分帧(推荐,解决绝大多数自定义工业协议粘包问题)⭐
port.startFramedListening(callback, 10, {
  type: 'header-length',        // 分帧类型
  header: [0xAA, 0xBB],        // [必填] 帧头同步字节数组
  lengthOffset: 2,             // [必填] 长度域在帧内的起始偏移(含帧头,如帧头2字节则填2)
  lengthSize: 1,               // [必填] 长度域占字节数:1(最大255)或 2(最大65535,大端序)
  crcBytes: 2,                 // [可选] 帧尾CRC字节数,0=不校验,默认0
  crcType: 'modbus',           // [可选] CRC类型:'modbus'(CRC-16/MODBUS)| 'none',默认'modbus'
  lengthIncludesCRC: false,    // [可选] 长度域值是否已包含CRC字节,默认false(长度=纯数据字节数)
  minFrameLen: 5,              // [可选] 最小合法帧总长,用于过滤噪声,默认4
  maxFrameLen: 255             // [可选] 最大合法帧总长,防止异常长度,默认256
})

// 自定义分帧函数
port.startFramedListening(callback, 10, (buffer) => {
  // 返回一条完整帧,或 null 表示数据不足
  if (buffer.length < 4) return null
  const len = buffer[2] + 5
  return buffer.length >= len ? buffer.slice(0, len) : null
})

// 传入 onError 捕获监听异常(第4参 [可选])
port.startFramedListening(callback, 10, undefined, (err) => {
  console.error('监听异常:', err?.errMsg)
})

// 停止
port.stopFramedListening()

分帧策略对比

策略 粘包处理 CRC校验 协议依赖 推荐场景
header-length ✅ 帧头搜索+帧长计算 ✅ 可选 需帧头+长度域 自定义工业协议(推荐)
modbus-rtu ✅ 功能码推导帧长 ✅ 自动 Modbus RTU Modbus 设备
fixed 固定帧长 ✅ 等长切割 帧长固定 简单协议
custom 自定义函数 ✅ 自行实现 ✅ 自行实现 自定义 复杂/特殊协议
纯超时 ⚠️ 依赖帧间间隙 低频上报/RS232

startPolling(callback, interval?, onError?)

轮询模式,定时读取缓冲区

port.startPolling((data) => {
  console.log('轮询收到:', bytesToHex(data))
}, 50)  // 第2参 [可选] 轮询间隔 ms,默认 50

port.stopPolling()

同步方法

const status = port.getStatus()  // { isOpen: boolean, devicePath: string }
const health = port.getHealthStatus()  // 连接健康状态(工业长期运行必需)
const count = port.available()   // 可读字节数,-1 表示 IO 异常
port.clearBuffer()               // 清空缓冲区
port.setDirection(true)          // 485 方向控制(true=发送/false=接收)

port.getHealthStatus() - 工业级断线检测

返回串口连接健康快照,用于心跳检测和自动重连判断:

const health = port.getHealthStatus()
// {
//   isOpen: true,           // 串口是否打开
//   lastDataTime: 1234567,  // 上次收到数据的时间戳(ms),0=尚未收到
//   consecutiveErrors: 0,   // 连续读错误次数(收到数据自动归零)
//   idleMs: 520,            // 距上次收到数据的空闲时长(ms),-1=从未收到
//   bufferSize: 0           // 帧缓冲区待处理字节数
// }

典型用法(参考 example.vue 中的完整实现):

// 每 2 秒检查一次
setInterval(() => {
  const h = port.getHealthStatus()
  if (h.consecutiveErrors >= 5) {
    console.warn('连续 IO 错误,设备可能断开')
    reconnect()  // 触发重连逻辑
  }
  if (h.idleMs >= 30000) {
    console.warn('30 秒无数据响应')
    reconnect()
  }
}, 2000)

断线检测原理

  • consecutiveErrors:底层 inputStream.read()available() 抛出 IOException 时递增,成功读取数据时归零。连续 5 次以上通常意味着设备已物理断开
  • idleMs:成功读取数据时更新 lastDataTime,空闲超过阈值说明对端设备可能停止响应

注意available() 方法现在在 IO 异常时返回 -1(之前返回 0),用于区分"无数据"和"流已损坏"。

port.startListening() — 已废弃

startListening 已废弃,请迁移到 startFramedListening。后者即使不传分帧参数,也能通过空闲超时机制自动划分帧边界。旧 API 将每批原始字节直接交付,无帧边界概念。

数据解析方法

串口接收回调拿到的是 number[] 字节数组,需转 HEX 字符串展示或做协议解析:

// 字节数组 → HEX 字符串(回调中最常用)
// 例:[0x01, 0x03, 0x00, 0x0A] → "01 03 00 0A"
port.bytesToHex(bytes)   // 等同 port.bytesToHex(data)

// HEX 字符串 → 字节数组(手动组装时用)
// 例:"01 03 00 0A" → [1, 3, 0, 10]
port.hexToBytes(hex)     // hex 支持空格分隔,大小写均可

close()

关闭串口

port.close({
  complete: () => { console.log('已关闭') }
})

错误码

所有错误遵循 uni 错误规范,errSubject 固定为 "z-serialport",错误码以 90_ 开头。

错误对象结构

{
  errSubject: "z-serialport",
  errCode: 90_10001,           // 错误码(见下表)
  errMsg: "设备不存在,请检查串口路径:/dev/ttyS5"  // 标准消息 + 额外上下文
}

错误码一览

错误码 说明 触发场景 排查建议
90_10001 设备不存在 openPort 时目标 /dev/tty* 路径不存在 ① 调用 listPorts() 确认设备列表 ② 检查物理连接/USB 线 ③ 确认设备文件名(部分板子为 ttyS1/ttyS2)
90_10002 打开串口失败 openPortRandomAccessFile 打开失败或创建 FileInputStream/FileOutputStream 失败 ① 检查 app 是否持有串口读写权限(android.permission) ② 确认设备未被其他进程占用(lsof \| grep tty) ③ SELinux 是否阻止访问
90_10003 配置串口参数失败 openPortstty 命令执行失败(超时/参数不兼容/设备忙) ① 检查波特率/数据位/停止位/校验位组合是否合法 ② 确认内核驱动支持该参数(部分内核不支持 921600) ③ 检查设备是否处于异常状态(回显 stty -F /dev/ttyS0
90_10004 串口未打开 send/receive/close 等操作时串口实例未连接,或直接调用了模块级旧 API(如 send() 而非 port.send() ① 确认已调用 openPort 并成功返回 ② 使用 openPort 返回的 port 实例方法,而非模块级导出函数
90_10005 发送数据失败 HEX 格式错误(长度非偶数/含非法字符)、发送数据为空、outputStream.write() 写入异常 ① 校验 HEX 字符串格式(如 "01 03 00 0A") ② 确认串口仍处于连接状态(port.getStatus()) ③ 检查输出缓冲区是否已满
90_10006 接收数据失败 帧监听/轮询/事件监听的循环读取异常、用户回调函数内部抛错 ① 检查 onData 回调函数逻辑是否有 bug ② 确认设备未被拔出(物理断线会持续触发此错误) ③ 监听循环会自动重试,无需手动重启
90_10007 操作超时 receive() 同步读取在指定时间内未收到足够数据 ① 增大 timeout 参数 ② 检查设备是否正常响应 ③ 降低波特率或增大 readTimeout
90_10008 GPIO 控制失败 RS485 方向脚 GPIO 导出失败、value 文件写入失败、或 GPIO 编号无效 ① 确认 directionGpio 编号与实际硬件一致 ② 检查 /sys/class/gpio/ 下 GPIO 是否已正确导出 ③ 确认 GPIO 未被其他进程占用
90_10009 未知错误 操作中发生预期外的异常(如 UTS 对象创建失败、内部类型转换异常) 查看 errMsg 中的异常详情,反馈给插件维护者

应用层处理示例

import { openPort } from '@/uni_modules/z-serialport/utssdk/app-android/index.uts'

openPort({
  config: { /* ... */ },
  fail: (err) => {
    switch (err.errCode) {
      case 90_10001:
        uni.showToast({ title: '串口设备不存在,请检查连接' })
        break
      case 90_10002:
        uni.showToast({ title: '无权限访问,请检查应用权限' })
        break
      case 90_10003:
        uni.showToast({ title: '参数配置失败: ' + err.errMsg })
        break
      default:
        uni.showToast({ title: '打开失败: ' + err.errMsg })
    }
  }
})

// 监听异常处理(90_10006)
port.startFramedListening(onData, 10, undefined, (err) => {
  if (err.errCode === 90_10006) {
    console.warn('接收异常,监听自动重试中: ' + err.errMsg)
    // 可在此累加计数,超过阈值触发上层重连
  }
})

技术架构

  • Android 层 (index.uts): 核心串口操作,使用 RandomAccessFile + stty 命令
  • 接口层 (interface.uts): 类型定义、回调签名
  • 错误层 (unierror.uts): 错误码定义、UniError 实现

隐私、权限声明

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

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

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

暂无用户评论。