广播协议详情
BLE 广播数据包的编码格式与结构
广播数据载荷
广播指令以 Hex 字符串形式存储在 deviceModes.ts 中,发送时需要转为字节数组并编码到 BLE Advertisement 数据包中。Android 和 iOS 使用不同的编码方式(详见平台差异)。
Android — Manufacturer Specific Data
Android 使用 BLE Advertisement 的 Manufacturer Specific Data(类型 0xFF)字段携带指令数据:
Advertisement Data:
┌───────────────────────────────────────────┐
│ AD Structure │
├────────────┬────────────┬─────────────────┤
│ Length (1B)│ Type (1B) │ Data │
│ │ 0xFF │ │
│ │ ├────────┬────────┤
│ │ │ MfgID │ Payload│
│ │ │ (2B LE)│ (N B) │
│ │ │ 0xFF00 │ 命令 │
└────────────┴────────────┴────────┴────────┘- AD Type:
0xFF(Manufacturer Specific Data) - Manufacturer ID:
255(0x00FF,Little-Endian 存储为0xFF 0x00) - Payload: Hex 命令字符串转换后的字节数组
广播参数
| 参数 | 值 | 说明 |
|---|---|---|
| Manufacturer ID | 255 (0x00FF) | 固定制造商标识 |
| Instance ID | 1 | 固定实例 ID,避免资源泄漏 |
| Connectable | true | 广播可连接 |
| Timeout | 0 | 由 App 控制停止(800ms 自动停止) |
| Mode | LOW_LATENCY | 高功率低延迟模式 |
| TX Power | HIGH | 最大发射功率 |
Service UUID
Android 广播额外携带一个固定的 Service UUID,用于标识广播来源:
0000FADE-0000-1000-8000-00805F9B34FB此 UUID 不携带指令数据,仅作为广播身份标识。
Hex 字符串转字节数组
BluetoothService.hexStringToBytes() 负责将 Hex 字符串转为字节数组:
public static hexStringToBytes(hexString: string): Uint8Array {
// 1. 移除空格并转为小写
const cleanHex = hexString.replace(/\s/g, '').toLowerCase()
// 2. 确保长度为偶数(奇数时前补 0)
const normalizedHex = cleanHex.length % 2 === 0 ? cleanHex : `0${cleanHex}`
// 3. 每 2 个字符解析为 1 个字节
const bytes = new Uint8Array(normalizedHex.length / 2)
for (let i = 0; i < normalizedHex.length; i += 2) {
bytes[i / 2] = Number.parseInt(normalizedHex.substring(i, i + 2), 16)
}
return bytes
}编码示例
发送 Default 设备模式 1 指令 6db643ce97fe427ce49c6c:
Hex 字符串: 6db643ce97fe427ce49c6c
字节数组: [0x6D, 0xB6, 0x43, 0xCE, 0x97, 0xFE,
0x42, 0x7C, 0xE4, 0x9C, 0x6C]
载荷长度: 11 字节广播指令发送流程
// 1. 清理 Hex 字符串
const cleanedCommand = command.replace(/\s/g, '')
// 2. 转为字节数组
const dataBytes = BluetoothService.hexStringToBytes(cleanedCommand)
// 3. 转为数字数组(BLE Advertiser 要求)
const dataArray = Array.from(dataBytes)
// 4. 调用原生广播 API
await bleAdvertiser.startAdvertising({
manufacturerId: 255,
data: dataArray,
instanceId: 1
})命令到电机强度映射
bleCommandModes 定义了广播命令与电机强度的映射关系。当设备使用点对点连接时,此映射用于将广播命令转换为 PrivateProtocol 的电机控制参数。
type BleCommandMode = {
command: string // 广播命令 (Hex)
motors: number[] // 电机强度 [电机1, 电机2, 电机3],0=停止,1-9=强度
}Default 设备命令映射
export const bleCommandModes = {
'default': [
// 停止命令
{ command: '6db643ce97fe427ce5157d', motors: [0, 0, 0] },
// 长指令(modes): 模式 1-9
{ command: '6db643ce97fe427ce49c6c', motors: [1, 1, 1] },
{ command: '6db643ce97fe427ce7075e', motors: [2, 2, 2] },
// ... 档位 3-9
// 短指令(func): 档位 1-9
{ command: '6db643ce97fe427cf41d7c', motors: [1, 1, 1] },
{ command: '6db643ce97fe427cf7864e', motors: [2, 2, 2] },
// ... 档位 3-9
]
}getBlePayloadCommand
将高层命令参数转为 PrivateProtocol 电机控制帧:
export const getBlePayloadCommand = (
modelName: string,
data: { amplitude: number, vibration: number, i3: number }
): Uint8Array => {
return PrivateProtocol.makeMotorControlCombined(
data.amplitude, // 电机 1 强度
data.vibration, // 电机 2 强度
data.i3 // 电机 3 强度
)
}getBlePayloadCommandByCode
将广播命令字符串转为可通过蓝牙发送的 Uint8Array。该函数同时支持广播模式和点对点模式的命令转换:
export const getBlePayloadCommandByCode = (
modelName: string,
isPrivate: any,
command: string
): Uint8Array => {
// 1. 确定协议类型
let model: string
if (isPrivate) {
model = 'default'
} else {
// 根据 modelName 匹配 bleCommandModes 的键
model = Object.keys(bleCommandModes).find(m =>
modelName.toLowerCase().includes(m.toLowerCase())
)
}
// 2. 在映射表中查找命令
const commands = model ? bleCommandModes[model] : undefined
const startCommand = commands?.find(
m => m.command.toLowerCase() === command.toLowerCase()
)
// 3a. 找到映射 → 通过 PrivateProtocol 生成控制帧
if (commands && startCommand) {
const [amplitude, vibration, i3] = startCommand.motors
return getBlePayloadCommand(modelName, { amplitude, vibration, i3 })
}
// 3b. 未找到映射 → 直接将 Hex 字符串转为字节数组
const hexStr = command.replace(/\s/g, '')
const bytes = new Uint8Array(hexStr.length / 2)
for (let i = 0; i < bytes.length; i++) {
bytes[i] = parseInt(hexStr.slice(i * 2, i * 2 + 2), 16)
}
return bytes
}命令转换流程图
输入: modelName, isPrivate, command (字符串)
│
▼
判断协议类型
│
├── isPrivate → model = 'default'
│
└── 非 Private → 根据 modelName 匹配
│
▼
在 bleCommandModes[model] 中查找 command
│
├── 找到 → 提取 motors 数组
│ → PrivateProtocol.makeMotorControlCombined()
│ → 输出 Uint8Array
│
└── 未找到 → 直接 Hex 转 Uint8Array
→ 输出 Uint8Array不同设备类型的数据编码
| 设备类型 | 命令格式 | 编码方式 | 载荷大小 |
|---|---|---|---|
| Default | 22 字符 Hex (6db6...) | Manufacturer Data 字节数组 | 11 字节 |
| NNG / NONO | 22 字符 Hex (6DB6...) | Manufacturer Data 字节数组 | 11 字节 |
| MY | 10 字符 Hex (AA0B...) | Manufacturer Data 字节数组 | 5 字节 |
| PrivateProtocol 设备 | 命令映射 → 电机参数 | PrivateProtocol.makeMotorControlCombined() | 协议帧大小 |