Skip to Content
硬件接入概念底层传输插件

底层传输插件(Low-level Transport Plugin)

仅在 WebUSB / React Native BLE 无法满足需求时使用。你需要自管设备生命周期和消息帧处理,维护成本更高。

我们的蓝牙 SDK 基于 React Native。如果你使用 Swift/Kotlin/Flutter 等非 RN 技术栈,引入 RN 依赖可能不合适。可通过 LowlevelTransportSharedPlugin 自行适配底层传输,负责”连接/断开/收发数据”协议。

快速参考

OneKey BLE UUID

用途UUID
服务00000001-0000-1000-8000-00805f9b34fb
写入特征00000002-0000-1000-8000-00805f9b34fb
通知特征00000003-0000-1000-8000-00805f9b34fb

BLE 头部数据示例

收到的第一个 BLE 数据包(头部包)示例:

原始数据: 3F 23 23 00 02 00 00 00 0A 12 08 ... │ │ │ │ │ │ │ └─ Protobuf 载荷开始 │ │ │ │ │ └────────┴─ 载荷长度: 0x0000000A = 10 字节 │ │ │ └──┴─ 消息类型: 0x0002 └──┴──┴─ Magic: 0x3F 0x23 0x23 (固定标识)

检测头部包:前 3 字节为 3F 23 23

原生示例(均基于低层插件):

LowlevelTransportSharedPlugin 接口

export type LowLevelDevice = { id: string; name: string }; export type LowlevelTransportSharedPlugin = { enumerate: () => Promise<LowLevelDevice[]>; send: (uuid: string, data: string) => Promise<void>; receive: () => Promise<string>; connect: (uuid: string) => Promise<void>; disconnect: (uuid: string) => Promise<void>; init: () => Promise<void>; version: string; };

WebUSB vs BLE:协议差异

重要: WebUSB(桌面端)和 BLE(移动端)的传输协议不同:

WebUSB (桌面端)BLE (原生移动端)
数据包大小固定 64 字节可变长度 (取决于 MTU)
每个包前缀所有包都有 0x3F仅头部包有 0x3F 0x23 0x23
填充零填充到 64 字节无需填充
receive() 返回每次一个 64 字节帧完整重组后的消息

WebUSB 协议(桌面端)

传输使用固定 64 字节帧,SDK 负责消息组装。

Magic bytes 说明: 0x3F 0x23 0x23 是协议固定的标识字节,用于识别数据包的起始位置。

首帧:

OffsetLengthContent
03Magic: 0x3F 0x23 0x23
32消息类型 (大端 u16)
54载荷长度 (大端 u32)
955前 55 字节 protobuf(零填充)

后续帧:

OffsetLengthContent
01Magic: 0x3F
163剩余 protobuf 字节(零填充)

BLE 协议(原生移动端)

BLE 使用可变长度块,插件必须自行重组完整消息

数据包格式

头部块(消息第一个包):

┌─────────┬──────────────┬──────────┬──────────────┬─────────────┐ │ Byte 0 │ Byte 1-2 │ Byte 3-4 │ Byte 5-8 │ Byte 9+ │ ├─────────┼──────────────┼──────────┼──────────────┼─────────────┤ │ 0x3F │ 0x23 0x23 │ Type │ Length │ Payload... │ │ (Magic) │ (Magic) │ (大端u16)│ (大端u32) │ │ └─────────┴──────────────┴──────────┴──────────────┴─────────────┘

后续块: 仅包含原始载荷数据,无任何前缀。

头部检测

function isHeaderChunk(data: Uint8Array): boolean { return data.length >= 9 && data[0] === 0x3F && // '?' data[1] === 0x23 && // '#' data[2] === 0x23 // '#' }

读取载荷长度(大端序)

function readUint32BE(buffer: Uint8Array, offset: number): number { return ( (buffer[offset] << 24) | (buffer[offset + 1] << 16) | (buffer[offset + 2] << 8) | buffer[offset + 3] ) >>> 0 } // 用法: payloadLength = readUint32BE(data, 5)

消息重组示例

class BleMessageAssembler { private buffer: number[] = [] private expectedLength = 0 private resolve: ((hex: string) => void) | null = null onNotification(data: Uint8Array): void { if (this.isHeader(data)) { this.expectedLength = this.readUint32BE(data, 5) this.buffer = [...data.subarray(3)] // 跳过 3F 23 23 } else { this.buffer.push(...data) } // 检查完成: buffer = Type(2) + Length(4) + Payload if (this.buffer.length - 6 >= this.expectedLength) { const hex = this.toHex(new Uint8Array(this.buffer)) this.buffer = [] this.expectedLength = 0 if (this.resolve) { this.resolve(hex) this.resolve = null } } } receive(): Promise<string> { return new Promise(resolve => { this.resolve = resolve }) } private isHeader(d: Uint8Array): boolean { return d.length >= 9 && d[0] === 0x3F && d[1] === 0x23 && d[2] === 0x23 } private readUint32BE(b: Uint8Array, o: number): number { return ((b[o] << 24) | (b[o+1] << 16) | (b[o+2] << 8) | b[o+3]) >>> 0 } private toHex(arr: Uint8Array): string { return Array.from(arr).map(b => b.toString(16).padStart(2, '0')).join('') } }

常见错误

// ❌ 错误: 重新分片成 64 字节包 receive() { return this.messageQueue.shift() } // ✅ 正确: 返回完整重组后的消息 receive() { return this.completeMessagePromise }
// ❌ 低效: 字符串拼接 let buffer = '' buffer += dataViewToHex(chunk) // ✅ 更好: 使用数组 let buffer: number[] = [] buffer.push(...chunk)

初始化示例

import HardwareSDK from '@onekeyfe/hd-common-connect-sdk' const plugin = { enumerate: () => Promise.resolve([{ id: 'foo', name: 'bar' }]), send: (uuid, data) => { /* 发送 hex 数据 */ }, receive: () => { /* WebUSB: 64字节帧 / BLE: 完整消息 */ }, connect: (uuid) => { /* BLE Android: 先配对! */ }, disconnect: (uuid) => { /* 清理连接 */ }, init: () => { /* 初始化 BLE 栈 */ }, version: 'OneKey-1.0' } HardwareSDK.init({ env: 'lowlevel', debug: true }, undefined, plugin)

下一步

Last updated on