设备通知
设备状态上报与通知处理
概述
点对点设备通过 BLE Notify 特征主动上报数据,包括认证信息、电量、电机状态等。不同协议的通知格式各异,系统通过统一的通知处理机制自动路由和解析。
通知订阅
Web Bluetooth 订阅
// WebBleManager 中的通知订阅
private async enableNotifications(): Promise<void> {
this.notifyCharacteristic.addEventListener(
'characteristicvaluechanged',
(event: Event) => {
const characteristic = event.target as BluetoothRemoteGATTCharacteristic
const value = new Uint8Array(characteristic.value!.buffer)
// 调用通知回调
if (this.notifyCallback) {
this.notifyCallback(value)
}
if (this.notifyListener) {
this.notifyListener.onData(value)
}
}
)
await this.notifyCharacteristic.startNotifications()
}Native 订阅
// NativeBleManager 中的通知订阅
await BleClient.startNotifications(
deviceId,
serviceUUID,
notifyCharacteristicUUID,
(data: DataView) => {
const value = new Uint8Array(data.buffer)
if (this.notifyCallback) {
this.notifyCallback(value)
}
}
)PrivateProtocol 通知
PrivateProtocol 的通知数据经过 FrameCodec 编码,接收后需要先解码再解析。
通知帧结构
所有通知帧以 0xBA 开头:
[0xBA] [通知类型] [数据...]认证通知 (类型 0x00)
设备连接后首先发送认证通知。详见 设备认证。
偏移 长度 说明
0 1 0xBA -- 帧标记
1 1 0x00 -- 认证类型
2-3 2 客户编号 (ClientID)
4-5 2 硬件版本
6-11 6 软件版本
12 1 电池电量 (0-100)解析后的数据结构:
interface AuthNotification {
type: 'auth'
clientId: number // 客户编号
hardwareVersion: string // 格式: "MAT3_V5.6"
softwareVersion: string // 格式: "3.1.240115"
battery: number // 电量 (0-100)
rawData: Uint8Array // 原始数据
}状态通知 (类型 0x01)
设备在运行期间周期性上报运行状态:
偏移 长度 说明
0 1 0xBA -- 帧标记
1 1 0x01 -- 状态类型
2 1 电池电量 (0-100)
3 1 Motor1 强度 (0-10)
4 1 Motor2 强度 (0-10)
5 1 Motor3 强度 (0-10)解析后的数据结构:
interface StatusNotification {
type: 'status'
battery: number // 电量 (0-100)
motors: number[] // [Motor1, Motor2, Motor3],各 0-10
}PrivateProtocol 解析代码
public static parseDeviceNotification(data: Uint8Array): DeviceNotification | null {
if (!data || data.length < 2) return null
const frame = data[0] & 0xFF
const type = data[1] & 0xFF
// 认证通知: 0xBA 0x00
if (frame === 0xBA && type === 0x00) {
const clientId = (data[2] << 8) | data[3]
// 硬件版本解析
const hwVersionNum = (data[4] << 8) | data[5]
const boardType = Math.floor(hwVersionNum / 100)
const hwVer = hwVersionNum % 100
const major = Math.floor(hwVer / 10)
const minor = hwVer % 10
const hardwareVersion = `MAT${boardType}_V${major}.${minor}`
// 软件版本解析
const swBoardType = (data[6] << 8) | data[7]
const swNumber = data[8]
const year = data[9].toString().padStart(2, '0')
const month = data[10].toString().padStart(2, '0')
const day = data[11].toString().padStart(2, '0')
const softwareVersion = `${swBoardType}.${swNumber}.${year}${month}${day}`
const battery = data[12]
return {
type: 'auth',
clientId,
hardwareVersion,
softwareVersion,
battery,
rawData: data
}
}
// 状态通知: 0xBA 0x01
if (frame === 0xBA && type === 0x01) {
return {
type: 'status',
battery: data[2],
motors: [data[3], data[4], data[5]]
}
}
return { type: 'unknown', frame, typeCode: type, rawData: data }
}VxMi Protocol 通知
VxMi 设备返回 JSON 格式的状态数据,封装在 A5 5A 帧中。
通知帧结构
A5 5A [Length] ... [Payload] ... [CRC16]解析流程
- 验证帧头
A5 5A - 检查响应类型标记(偏移 8-10 包含
02) - 提取有效载荷(跳过帧头 10 字节和尾部 CRC 4 字节)
- 将 Hex 载荷转为 UTF-8 字符串
- 解析 JSON
返回数据
interface VxMiNotification {
type: 'status'
battery: number // 电量百分比 (0-100)
voltage: number // 电压值
firmwareVersion: string // 固件版本
mcu1Firmware: string // MCU1 固件版本
mcu2Firmware: string // MCU2 固件版本
mtu: number // MTU 值
rawData: Uint8Array // 原始数据
}电量计算
VxMi 设备根据电压值线性插值计算电量:
function voltageToPercent(voltage: number): number {
const MIN_VOLTAGE = 3.0 // 对应 0%
const MAX_VOLTAGE = 4.2 // 对应 100%
if (voltage <= MIN_VOLTAGE) return 0
if (voltage >= MAX_VOLTAGE) return 100
return Math.round(
((voltage - MIN_VOLTAGE) / (MAX_VOLTAGE - MIN_VOLTAGE)) * 100
)
}统一通知处理
系统通过 parseDeviceNotification 函数根据设备类型自动路由通知解析:
// deviceModes.ts
export const parseDeviceNotification = (
modelName: string,
isPrivate: any,
data: any
): any => {
if (isPrivate) {
// 私有协议设备:先解码再解析
let payload: Uint8Array
try {
payload = FrameCodec.decode(data)
} catch {
// 解码失败,使用原始数据
payload = data
}
return PrivateProtocol.parseDeviceNotification(payload)
}
// 其他协议(VxMi、EL 等)返回空对象
return {}
}通知处理流程
BLE Notify 特征收到数据
|
v
判断设备协议类型
|
+-- PrivateProtocol --+
| |
| v
| FrameCodec.decode(data)
| |
| v
| PrivateProtocol.parseDeviceNotification(payload)
| |
| +-- type='auth' --> 处理认证
| |
| +-- type='status' --> 更新状态
|
+-- VxMi Protocol ----+
| |
| v
| 解析 A5 5A 帧
| 提取 JSON 数据
| 计算电量
|
+-- EL Protocol ------+
|
v
协议特定解析通知监听完整示例
import { unifiedBluetoothManager } from '@/utils/bluetooth/UnifiedBluetoothManager'
import { parseDeviceNotification } from '@/utils/deviceModes'
// 设置通知监听
unifiedBluetoothManager.setNotifyListener({
onData: (data: Uint8Array) => {
const connectedDevice = useDeviceStore.getState().connectedDevice
const modelName = connectedDevice?.deviceModel?.modelName
const isPrivate = connectedDevice?.deviceModel?.isPrivate
// 统一解析通知
const notification = parseDeviceNotification(modelName, isPrivate, data)
if (!notification) return
switch (notification.type) {
case 'auth':
console.log('认证信息:', {
clientId: notification.clientId,
hardware: notification.hardwareVersion,
software: notification.softwareVersion,
battery: notification.battery
})
// 自动处理认证握手(由 connectAuth 内部完成)
break
case 'status':
console.log('设备状态:', {
battery: notification.battery,
motors: notification.motors
})
// 更新 UI 显示
updateBatteryDisplay(notification.battery)
updateMotorDisplay(notification.motors)
break
default:
console.log('未知通知类型:', notification)
}
}
})