蓝牙设备对接文档

设备认证

CRC8 握手认证流程详解

认证概述

当设备接口返回的 isAuth === 1 时,设备在 GATT 连接建立后需要完成 CRC8 握手认证。认证通过后设备才会正常响应控制指令。

认证超时时间为 15 秒,超时未完成将断开连接并提示用户。

认证时序图

        App                          设备
         |                            |
         |---- GATT 连接建立 -------->|
         |                            |
         |<--- 认证请求帧 ------------|
         |    0xBA 0x00               |
         |    [ClientID(2)]           |
         |    [HW Ver(2)]            |
         |    [SW Ver(6)]            |
         |    [Battery(1)]           |
         |                            |
         |    计算 CRC8               |
         |    (对 rawData)            |
         |                            |
         |---- 认证响应帧 ----------->|
         |    0xAB 0x00               |
         |    [CRC8]                  |
         |    0xFF 0xFF               |
         |                            |
         |    认证成功                 |
         |<--- 正常通信开始 ----------|

认证流程详解

步骤 1:接收设备认证帧

设备连接后主动发送认证通知帧(帧标记 0xBA,类型 0x00):

字节位置    长度    说明
[0]        1       0xBA -- 帧标记(设备 -> App)
[1]        1       0x00 -- 认证通知类型
[2-3]      2       客户编号 (ClientID)
[4-5]      2       蓝牙硬件版本(5位数:前3位板型编号,后2位硬件版本)
[6-11]     6       软件版本号(板型2字节 + 编号1字节 + 日期3字节)
[12]       1       电量数据 (0-100)

硬件版本解析

硬件版本占 2 字节,合并为 16-bit 整数后按十进制拆分:

// 硬件版本解析
const hwVersionNum = (data[4] << 8) | data[5]  // 两字节合并
const boardType = Math.floor(hwVersionNum / 100) // 前3位:板型编号
const hwVer = hwVersionNum % 100                 // 后2位:硬件版本
const major = Math.floor(hwVer / 10)
const minor = hwVer % 10
const hardwareVersion = `MAT${boardType}_V${major}.${minor}`

// 示例: [0x01, 0x64] => 0x0164 = 356
// boardType = 3, hwVer = 56
// 格式: MAT3_V5.6

软件版本解析

软件版本占 6 字节:

// 软件版本解析
const boardType = (data[6] << 8) | data[7]  // 板型 2字节
const number = data[8]                        // 编号 1字节
const year = data[9]                          // 年份 1字节
const month = data[10]                        // 月份 1字节
const day = data[11]                          // 日期 1字节

const yearStr = year.toString().padStart(2, '0')
const monthStr = month.toString().padStart(2, '0')
const dayStr = day.toString().padStart(2, '0')

const softwareVersion = `${boardType}.${number}.${yearStr}${monthStr}${dayStr}`

// 示例: [0x00, 0x03, 0x01, 0x18, 0x01, 0x15]
// boardType = 3, number = 1, date = 24/01/15
// 格式: 3.1.240115

步骤 2:计算 CRC8

App 收到设备认证帧的原始数据(rawData,即完整的 Uint8Array)后,使用 CRC8 算法计算校验值:

// CRC8 计算,多项式 0x1D
const crc = FrameCodec.crc8Poly1D(notification.rawData)

步骤 3:发送认证响应帧

构造认证响应帧并经过 FrameCodec 编码后发送:

字节位置    长度    说明
[0]        1       0xAB -- 帧标记(App -> 设备)
[1]        1       0x00 -- 认证类型
[2]        1       CRC8 -- 校验值(对设备发送的 rawData 计算得出)
[3]        1       0xFF -- 填充
[4]        1       0xFF -- 填充
// 构造认证响应帧
const authPayload = PrivateProtocol.makeAuth(crc)
// 结果: Uint8Array([0xAB, 0x00, crc, 0xFF, 0xFF])

// 经过 FrameCodec 编码后发送
const encodedAuth = FrameCodec.encode(authPayload)
await unifiedBluetoothManager.write(encodedAuth, connectedDevice)

CRC8 算法

参数

参数
多项式0x1D
初始值0xFF
最终异或0xFF
位序MSB-first

完整实现

以下为 FrameCodec.crc8Poly1D 的完整实现代码:

/**
 * CRC-8 计算,使用多项式 0x1D
 * init=0xFF, polynomial=0x1D (MSB-first), final xor=0xFF
 */
public static crc8Poly1D(
  data: Uint8Array,
  offset: number = 0,
  length?: number
): number {
  const len = length ?? (data.length - offset)
  let crc = 0xFF       // 初始值
  const poly = 0x1D    // 多项式

  for (let i = 0; i < len; i++) {
    crc ^= (data[offset + i] & 0xFF)
    for (let j = 0; j < 8; j++) {
      if ((crc & 0x80) !== 0) {
        crc = ((crc << 1) & 0xFF) ^ poly
      } else {
        crc = (crc << 1) & 0xFF
      }
    }
  }

  crc ^= 0xFF          // 最终异或
  return crc & 0xFF
}

计算输入

CRC8 的输入为设备认证帧的 完整原始数据rawData),即从 0xBA 0x00 开始到电量字节结束的全部 13 字节。

认证处理代码

系统在 UnifiedBluetoothManager.handleDeviceNotification 中自动处理认证流程:

private handleDeviceNotification(
  notification: DeviceNotification,
  authCallback: (data: any) => void,
  connectedDevice: any
): void {
  switch (notification.type) {
    case 'auth':
      // 1. 计算 CRC8
      const crc = FrameCodec.crc8Poly1D(notification.rawData)

      // 2. 构造认证响应帧
      const authPayload = PrivateProtocol.makeAuth(crc)

      // 3. 编码并发送
      const encodedAuth = FrameCodec.encode(authPayload)
      this.write(encodedAuth, connectedDevice).then((success) => {
        if (success) {
          // 4. 认证成功,通知上层
          authCallback(notification)
        }
      })
      break
  }
}

认证数据结构

认证成功后,authCallback 接收的通知对象结构为:

interface AuthNotification {
  type: 'auth'
  clientId: number           // 客户编号
  hardwareVersion: string    // 硬件版本,格式: "MAT100_V1.0"
  softwareVersion: string    // 软件版本,格式: "101.1.251106"
  battery: number            // 电量 (0-100)
  rawData: Uint8Array        // 原始数据(用于 CRC8 计算)
}

认证示例

设备发送认证请求:
  BA 00 01 02 01 64 00 03 01 18 01 15 4B

解析:
  帧标记:    0xBA
  类型:      0x00(认证)
  ClientID:  0x01 + 0x02 = 3
  硬件版本:  0x0164 = 356 => MAT3_V5.6
  软件版本:  板型=3, 编号=1, 日期=24/01/21 => 3.1.240121
  电量:      75% (0x4B)

App 计算 CRC8:
  crc = crc8Poly1D([BA 00 01 02 01 64 00 03 01 18 01 15 4B])
  crc = <计算结果>

App 回复认证响应:
  原始:  AB 00 [CRC8] FF FF
  编码:  FrameCodec.encode(原始) => 发送到设备