蓝牙设备对接文档

平台差异

Android 与 iOS 平台的 BLE 广播实现差异

概述

由于 iOS CoreBluetooth 框架的限制,Android 和 iOS 平台使用截然不同的数据编码方式发送 BLE 广播指令。

平台编码方式数据格式限制
AndroidManufacturer Specific Data原始字节数组无特殊限制
iOSService UUID 列表13 个 16-bit UUID 编码不支持自定义 Manufacturer Data

Android 编码

Android 直接使用 BluetoothLeAdvertiser 的 Manufacturer Specific Data 携带命令字节,编码简单直接。

编码流程

Hex 命令字符串


hexStringToBytes() 转为字节数组


写入 Manufacturer Data (ID=255)


BLE Advertisement 发送

代码实现

// Android 原生层实现
AdvertiseData data = new AdvertiseData.Builder()
    .addManufacturerSpecificData(255, commandBytes)
    .addServiceUuid(new ParcelUuid(SERVICE_UUID_FADE))
    .build();

广播参数

参数说明
Manufacturer ID255 (0x00FF)命令数据载荷标识
Connectabletrue可连接广播
Timeout0无限广播,由 App 控制停止
ModeLOW_LATENCY高功率低延迟
TX PowerHIGH最大发射功率
Service UUID0000FADE-...广播来源标识

iOS 编码

iOS 的 CBPeripheralManager 不支持自定义 Manufacturer Data 广播。系统使用算法编码方案,将命令数据映射到 13 个 16-bit Service UUID 中。

为什么需要不同的方案

iOS CoreBluetooth 框架对 Peripheral 广播有严格限制:

  1. 不支持 Manufacturer DataCBPeripheralManagerstartAdvertising 方法仅支持 CBAdvertisementDataLocalNameKeyCBAdvertisementDataServiceUUIDsKey
  2. 设备名称长度受限 — 不适合编码命令数据
  3. Service UUID 列表可用 — 可以携带多个 16-bit UUID,用于编码命令

因此,iOS 端将命令数据通过算法编码到 Service UUID 列表中,设备端同时支持解析 Manufacturer Data(Android)和 Service UUID 列表(iOS)。

UUID 列表结构

总计 13 个 16-bit UUID:
┌────────────────────────────┐
│ 固定前缀(4 个 UUID)       │  08F9, 2349, CBAE, D1C1
├────────────────────────────┤
│ 可变 UUID 1 (uuid5)        │  编码后的数据
├────────────────────────────┤
│ 可变 UUID 2 (uuid6)        │  编码后的数据
├────────────────────────────┤
│ 固定后缀(7 个 UUID)       │  0D0C, 0F0E, 1110, 1312,
│                            │  1514, 1716, 1918
└────────────────────────────┘
  • 固定前缀(4 个):用于设备端识别广播来源
  • 可变 UUID(2 个):编码了实际的命令数据
  • 固定后缀(7 个):填充结构,保持一致性

Default 设备编码算法

从命令字节数组的最后 3 个字节(A, B, C)计算两个可变 UUID。

输入

命令 Hex 字符串,例如: 6db643ce97fe427ce49c6c
最后 3 字节: A = 0xE4, B = 0x9C, C = 0x6C

可变 UUID 2 (uuid6)

uuid6 = 0x0B00 | (C XOR 0x40)

可变 UUID 1 (uuid5)

C = 命令最后一个字节
B = 命令倒数第二个字节
A = 命令倒数第三个字节

Hn = C >> 4           // C 的高 4 位
L  = C & 0x0F         // C 的低 4 位
A_high = A >> 4       // A 的高 4 位

// 模式判断
if A_high == 0xE → E-mode
if A_high == 0xF → F-mode

// E-mode 查找表
d_e = {
  0x0: 0,  0x1: 0,  0x2: 1,  0x3: 15,
  0x4: 14, 0x5: 14, 0x6: 3,  0x7: 1,
  0xE: 7,  0xF: 5
}

// F-mode 查找表
d_f = {
  0x1: 0,  0x3: 1,  0x5: 2,
  0xE: 5,  0xF: 7
}

// 计算 UUID 字节
highByte = B XOR 0x89
lowByte  = base | ((L + d(Hn)) % 16)

// E-mode: base = 0x60
// F-mode: base = 0x70

uuid5 = (highByte << 8) | lowByte

完整示例

发送 Default 设备模式 1 指令 6db643ce97fe427ce49c6c

最后 3 字节: A = 0xE4, B = 0x9C, C = 0x6C

--- uuid6 ---
uuid6 = 0x0B00 | (0x6C XOR 0x40)
      = 0x0B00 | 0x2C
      = 0x0B2C

--- uuid5 ---
C = 0x6C
Hn = 0x6C >> 4 = 0x6
L  = 0x6C & 0x0F = 0xC
A_high = 0xE4 >> 4 = 0xE → E-mode

d_e[0x6] = 3

highByte = 0x9C XOR 0x89 = 0x15
lowByte  = 0x60 | ((0xC + 3) % 16)
         = 0x60 | 0xF
         = 0x6F

uuid5 = (0x15 << 8) | 0x6F = 0x156F

--- 最终 UUID 列表 ---
08F9, 2349, CBAE, D1C1,
156F,                       ← 可变 UUID 1
0B2C,                       ← 可变 UUID 2
0D0C, 0F0E, 1110, 1312, 1514, 1716, 1918

NNG 设备编码算法

NNG 设备使用不同的固定 UUID 前缀和简化的可变计算:

固定前缀 (4 个): 08F9, C949, A8C1, D1C1
固定后缀 (7 个): 0D0C, 0F0E, 1110, 1312, 1514, 1716, 1918

可变 UUID(从命令末尾字节 b1, b2, b3 计算):
  uuid5 = ((b2 XOR 0x89) << 8) | (b1 XOR 0x8B)
  uuid6 = 0x0B00 | (b3 XOR 0x40)

权限要求

Android (API 31+)

<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />

Android 12(API 31)及以上版本需要在运行时动态请求上述权限。低于 API 31 的版本使用传统的 BLUETOOTHBLUETOOTH_ADMIN 权限。

iOS

<!-- Info.plist -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>需要蓝牙权限以控制设备</string>

使用 CoreBluetooth 框架的 CBPeripheralManager 进行广播。iOS 系统在首次使用蓝牙时会弹出权限请求弹窗。

平台差异总结

维度AndroidiOS
编码方式Manufacturer Data (ID=255)Service UUID 列表 (13 个)
数据容量直接携带原始字节编码到 2 个可变 UUID 中
广播模式LOW_LATENCY(可配置)系统控制(不可配置)
TX 功率HIGH(可配置)系统控制(不可配置)
Connectabletruetrue
超时控制由 App 控制(设为 0)由 App 控制(设为 0)
权限BLUETOOTH_ADVERTISE (运行时)NSBluetoothAlwaysUsageDescription
原生 APIBluetoothLeAdvertiserCBPeripheralManager
设备端解析读取 Manufacturer Data 字段读取 Service UUID 列表并反向解码