设备认证
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(原始) => 发送到设备