BLE 升级通道
BLE 升级通道是 HarmonyOS-JL_OTA 中基于 Bluetooth Low Energy(BLE)GATT 协议实现固件升级数据收发的完整链路,涵盖设备扫描、连接、MTU 协商、特征值订阅、分包发送以及与杰理 RCSP OTA SDK 的对接,是固件升级能力中默认的无线通信方式。
Purpose and Scope
本文档完整介绍 BLE 升级通道的实现原理与工程结构,内容包括:
- BLE 传输层的 GATT 服务与特征值约定(RCSP UUID)
BleImpl的扫描、连接、发数与事件发布/订阅机制BluetoothManager对 BLE / SPP(EDR)双通道的统一分发BluetoothOTAManager如何通过OTAWrapperOption将 BLE 通道桥接到 RCSP OTA SDK- 升级过程中的"升级模式回连"(设备重启后 BLE 地址变化)机制
以下主题属于兄弟页面,不在本文展开:SPP(经典蓝牙)通道实现(见 bluetooth/spp/ 目录下的 SppImpl)、OTA 升级流程与固件解析逻辑(见 ota/ 目录)、页面 UI 层的升级交互。本文聚焦"数据如何通过 BLE 通道传输"这一能力边界。
Overview
在杰理(Jieli)方案的 HarmonyOS 固件升级 SDK 中,手机 App 与耳机/音箱等设备之间通过 RCSP(Real-time Control and Streaming Protocol)私有协议通信。RCSP 协议帧既可以承载在 BLE GATT 特征值上,也可以承载在经典蓝牙 SPP 通道上。BLE 升级通道就是前者的完整实现:
- 物理层:调用 HarmonyOS
@kit.ConnectivityKit提供的ble、access、connection等能力完成 BLE 广播扫描、GATT 连接与特征值读写。 - 协议层:约定杰理 RCSP 的 GATT 服务 UUID(
0000AE00-...)、写特征值(0000AE01-...)与通知特征值(0000AE02-...),MTU 默认 512,实现大块升级数据的拆分与流控。 - 业务层:
BluetoothOTAManager将扫描、连接、发数等操作包装成 RCSP OTA SDK 要求的回调(OTAWrapperOption),使OTAWrapper/JL_OTA无需感知底层是 BLE 还是 SPP。 - 异常恢复:升级过程中设备会断开并进入升级模式(可能更换 BLE 地址),通道通过广播包中的
D60541544F4C4A魔数识别设备并自动回连,保证升级流程不中断。
默认情况下整个 SDK 的通信方式即为 BLE(BluetoothManager._communicationWay 默认值为 "BLE"),因此 BLE 升级通道是首选的、开箱即用的升级数据通路。
Architecture
下图展示了 BLE 升级通道从 HarmonyOS 系统能力到 RCSP OTA SDK 的完整分层架构,以及各组件间的依赖关系:
flowchart TD
subgraph sg_UI["应用层"]
App["App 页面 / 升级调用方"]
end
subgraph sg_OTA["OTA 业务层"]
OTAWrapper["OTAWrapper (rcsp)"]
JL_OTA["JL_OTA (rcsp SDK)"]
OTAWrapper --> JL_OTA
end
subgraph sg_Bridge["通道桥接层"]
BTOTAManager["BluetoothOTAManager"]
BTManager["BluetoothManager (bluetoothInstance 单例)"]
end
subgraph sg_BLE["BLE 实现层"]
BleImpl["BleImpl"]
SendHandler["BleSendDataHandler"]
BleScanCfg["BleScanSettingConfigure"]
BleConnCfg["BleConnectSettingConfigure"]
BleDevice["BleDevice"]
end
subgraph sg_System["HarmonyOS 系统能力层"]
KitBLE["@kit.ConnectivityKit: ble / access / connection / hid"]
end
subgraph sg_Device["设备端"]
Device["杰理设备 (RCSP GATT 服务)"]
end
App -->|"startOTA / 扫描 / 连接"| BTOTAManager
BTOTAManager -->|"OTAWrapperOption 回调"| OTAWrapper
BTOTAManager -->|"事件订阅 / 指令分发"| BTManager
BTManager -->|"startScan / connect / sendData / disconnect"| BleImpl
BleImpl -->|"分包发送"| SendHandler
BleImpl --> BleScanCfg
BleImpl --> BleConnCfg
BleImpl -->|"创建"| BleDevice
BleImpl -->|"startBLEScan / createGattClientDevice / write / notify"| KitBLE
KitBLE <-->|"BLE 无线链路"| Device
各组件职责如下:
- BluetoothOTAManager:升级编排层。初始化时订阅 BLE/SPP 的事件,并把扫描、连接、断开、发数封装成
OTAWrapperOption交给OTAWrapper;实现升级回连与startOTA入口。 - BluetoothManager:单例门面(
bluetoothInstance),持有BleImpl与SppImpl两个实现,按设备类型(instanceof)或communicationWay分发调用。默认通信方式为 BLE。 - BleImpl:BLE 通道的核心实现类,同时实现
IBleScan与IBleConnect两个接口,管理扫描状态机、已连接设备数组、GATT 特征值缓存与事件回调注册表。 - BleSendDataHandler:实际的写数据处理器,负责把
Uint8Array数据按 MTU 拆分并顺序写入 GATT 写特征值。 - BleConnectSettingConfigure:连接参数配置,默认 MTU=512,并内置杰理 RCSP 的通知/写特征值。
- BleScanSettingConfigure:扫描参数配置(扫描超时、过滤规则等)。
- BleDevice:BLE 设备的领域模型,携带
deviceId(MAC)与广播数据。
核心设计:从系统能力到协议约定的三层抽象
1. RCSP GATT 服务与特征值约定
BLE 升级通道的协议锚点是 BleConnectSettingConfigure.ets 中定义的一组 UUID 常量,这是手机端与杰理设备端之间的唯一约定:
export const RCSP_UUID_SERVICE = "0000AE00-0000-1000-8000-00805F9B34FB";
export const RCSP_UUID_WRITE = "0000AE01-0000-1000-8000-00805F9B34FB";
export const RCSP_UUID_NOTIFY = "0000AE02-0000-1000-8000-00805F9B34FB";
Source: BleConnectSettingConfigure.ets
设计意图:RCSP 协议数据一律通过 服务 AE00 / 写 AE01 / 通知 AE02 三个特征值交互——App 向设备下发升级指令与固件数据走 writeCharacteristicArray(AE01),设备主动上报状态、升级进度与数据应答走 notifyCharacteristicArray(AE02)。necessaryNotifyCharacteristicArray 与 notifyCharacteristicArray 内容相同,表示通知特征值是连接建立的必要条件:如果设备广播的服务中没有 AE02,连接流程应判定失败。
BleConnectSettingConfigure 类的构造函数把上述特征值封装成 HarmonyOS 的 ble.BLECharacteristic 结构(含 serviceUuid、characteristicUuid、characteristicValue、descriptors),并设置默认 MTU 为 512(注释明确 MTU 有效范围 23~512):
export class BleConnectSettingConfigure implements IConnectSettingConfigure {
/**连接超时*/
// timeout?: number
/**mtu 23~512*/
mtu: number = 512
/**使能的Characteristic*/
notifyCharacteristicArray: Array<ble.BLECharacteristic> = new Array()
/**必须使能的Characteristic*/
necessaryNotifyCharacteristicArray: Array<ble.BLECharacteristic> = new Array()
/**发送数据的Characteristic*/
writeCharacteristicArray: Array<ble.BLECharacteristic> = new Array()
constructor() {
const jlRCSPNotifyBLECharacteristic: ble.BLECharacteristic = {
serviceUuid: RCSP_UUID_SERVICE,
characteristicUuid: RCSP_UUID_NOTIFY,
characteristicValue: new Uint8Array(),
descriptors: []
}
this.notifyCharacteristicArray.push(jlRCSPNotifyBLECharacteristic)
this.necessaryNotifyCharacteristicArray.push(jlRCSPNotifyBLECharacteristic)
const jlRCSPWriteBLECharacteristic: ble.BLECharacteristic = {
serviceUuid: RCSP_UUID_SERVICE,
characteristicUuid: RCSP_UUID_WRITE,
characteristicValue: new Uint8Array(),
descriptors: []
}
this.writeCharacteristicArray.push(jlRCSPWriteBLECharacteristic)
}
}
Source: BleConnectSettingConfigure.ets
2. BleImpl:BLE 通道的心脏
BleImpl 同时实现 IBleScan(扫描)与 IBleConnect(连接/发数)两个接口,是 BLE 升级通道中状态最复杂、能力最完整的类。
蓝牙开关状态监听与异常恢复
构造函数中注册了系统蓝牙开关状态监听:当蓝牙被系统关闭(STATE_OFF)时,遍历已连接设备逐个 disconnect,并把尚未断开成功的设备 ID 记录到 waitDisconnectDevices 数组;当蓝牙重新打开(STATE_ON)时,对该数组中的设备逐一重建 ble.GattClientDevice 并执行 disconnect() + close(),清理残留的 GATT 连接,然后清空数组。这是为了避免"系统蓝牙关闭期间 GATT 回调悬挂"导致的脏连接状态:
constructor() {
this.hidProfile = hid.createHidHostProfile()
access.on('stateChange', (data) => {
let btStateMessage = '';
switch (data) {
case access.BluetoothState.STATE_OFF:
btStateMessage += 'STATE_OFF';
this.isAccessOn = false
this._connectedDeviceArray.forEach(device => {
this.disconnect(device)
})
break;
case access.BluetoothState.STATE_ON:
btStateMessage += 'STATE_ON';
this.isAccessOn = true
this.waitDisconnectDevices.forEach(deviceId => {
try {
let device: ble.GattClientDevice = ble.createGattClientDevice(deviceId);
device.disconnect();
device.close();
} catch (err) {
Log.e(TAG, 'errCode: ' + (err as BusinessError).code + ', errMessage: ' +
(err as BusinessError).message);
}
})
this.waitDisconnectDevices = []
break;
}
Log.e(TAG, `bluetooth status:${btStateMessage}`);
})
}
Source: BleImpl.ets
事件发布/订阅模型
BleImpl 使用一个 callbacksMap: Map<string, Array<Callback<never>>> 维护按事件类型分组的回调数组,提供重载的 on() / off() 方法。支持的事件类型包括:
ScanEventType.SCAN_STATE_CHANGE:扫描状态变化,回调携带ScanStateInfoScanEventType.SCAN_DEVICE_FIND:发现设备,回调携带BleDevice[]BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE:连接状态变化,回调携带ConnectStateInfo<BleDevice>BleConnectEventTypeConstant.CONNECT_MTU_CHANGE:MTU 协商结果,回调携带MTUInfoBleConnectEventTypeConstant.CONNECT_BLE_CHARACTERISTIC_CHANGE:特征值变化(设备通知数据),回调携带BLECharacteristicInfo
off() 在未传回调时清除该类型下全部回调,传回调时仅移除指定回调。这一模型使 BluetoothOTAManager 能在初始化时一次性注册升级所需的事件处理器,且多个订阅者互不干扰。
扫描状态机
扫描部分维护 mIsScanning、mScanDevList、mScanTimeoutID(超时定时器)与 mScanSystemConnectedDevInterval(周期检查系统已连接设备的定时器)。startScan(scanTimeOut?) 支持:
- 传入超时参数时先更新
BleScanSettingConfigure.scanTimeOut; - 若正在扫描则调用
refreshScan()清空设备列表并重启定时器(即"重新扫描"); - 否则清空列表、启动定时、调用
_startScan()。
_startScan() 最终调用 HarmonyOS 的 ble.startBLEScan(null, scanOptions),扫描参数为:interval: 500(扫描间隔)、dutyMode: SCAN_MODE_BALANCED(均衡占空比,兼顾功耗与发现速度)、matchMode: MATCH_MODE_AGGRESSIVE(激进匹配,尽量不漏设备):
private _startScan() {
Log.d(TAG, "_startScan")
try {
try {
this._onBluetoothDeviceFound();
} catch (e) {
Log.d(TAG, "catch _onBluetoothDeviceFound")
}
let scanOptions: ble.ScanOptions = {
interval: 500,
dutyMode: ble.ScanDuty.SCAN_MODE_BALANCED,
matchMode: ble.MatchMode.MATCH_MODE_AGGRESSIVE,
};
ble.startBLEScan(null, scanOptions);
this.mIsScanning = true
this._onScanStart()
} ...
}
Source: BleImpl.ets
扫描结果通过 deviceFindCallbackFun → onBLEDeviceFindReceiveEvent 处理,最终以 SCAN_DEVICE_FIND 事件携带 BleDevice[] 发布给上层。
发数入口:sendData
sendData 是 BLE 升级通道的数据出口。它先在已连接设备信息中查找匹配 serviceId + characteristicId 的写特征值,找到后交给 BleSendDataHandler 执行真正的写入,找不到则按"未找到写特征值 / 设备未连接"分别记录错误日志:
sendData(bluetoothDevice: BleDevice, serviceId: string, characteristicId: string, data: Uint8Array) {
const devInfo = this.getConnectedDevInfo(bluetoothDevice)
if (devInfo) {
const characteristic = devInfo.writeCharacteristics.find(item => item.serviceUuid === serviceId &&
item.characteristicUuid === characteristicId)
if (characteristic) {
// Log.i(TAG, "sendData :" + u8ToHexStr(new Uint8Array(data)))
this.bleSendDataHandler.sendData(devInfo, characteristic, data)
} else {
Log.e(TAG, "sendData device is not find write characteristic")
}
} else {
Log.e(TAG, "sendData device is not connected")
}
}
Source: BleImpl.ets
设计意图:sendData 只负责"定位正确的连接与特征值",把 MTU 分包、写入时序等细节下沉到 BleSendDataHandler,使上层(OTA SDK)可以透明地调用,同时通过 getConnectedDevInfo 保证不会向未连接或特征值不匹配的设备写数据。
BluetoothManager:双通道统一门面
BluetoothManager 是 BLE 与 SPP(EDR)两条无线通道的统一门面,文件末尾导出了进程级单例 bluetoothInstance。它持有 _bleImpl 与 _sppImpl 两个实现,并通过设备类型判断(device instanceof SppDevice / instanceof BleDevice)或通信方式字符串("EDR" / "BLE")完成分发:
export class BluetoothManager {
private _bleImpl: BleImpl
private _sppImpl: SppImpl
// 当前通讯方式
private _communicationWay: BluetoothDeviceType = "BLE"
constructor() {
this._bleImpl = new BleImpl()
this._sppImpl = new SppImpl()
}
...
connect(device: BluetoothDevice, success?: Callback<BluetoothDevice>,
fail?: Callback<BusinessError>) {
if (device instanceof SppDevice) { //根据设备类型判断-Spp
this.sppImpl.connect(device, success, fail)
} else if (device instanceof BleDevice) { //根据设备类型判断-Ble
this.bleImpl.connect(device, success, fail)
} else { //无法判断设备类型
}
}
...
}
export const bluetoothInstance = new BluetoothManager()
Source: BluetoothManager.ets
对外暴露的统一操作集合包括:connect / disconnect / getConnectedDevice / isConnecting / isConnected / isScanning / startScan / refreshScan / stopScan。其中未显式传入 type 的调用默认使用 _communicationWay(默认 "BLE"),这也是整个 SDK 默认走 BLE 升级通道的原因。
设计意图:把"通道选择"从业务代码中剥离——升级 SDK 的业务层只需要持有 BluetoothDevice 并调用门面方法,具体走 BLE 还是 SPP 由设备对象类型决定;而扫描等无设备对象的操作则由全局 communicationWay 决定。这为将来增加新通道(如 NFC、Wi-Fi)预留了扩展点。
BluetoothOTAManager:BLE 通道与 RCSP OTA SDK 的桥
BluetoothOTAManager 是 BLE 升级通道与杰理 RCSP OTA SDK(rcsp 包中的 OTAWrapper / JL_OTA)之间的桥接层。其 init() 分两步:initBluetooth() 订阅底层事件,initRcsp() 构造 OTAWrapperOption 并创建 OTAWrapper。
事件订阅(initBluetooth)
private initBluetooth() {
this.bluetoothInstance.bleImpl.on(ScanEventType.SCAN_STATE_CHANGE, this.scanStateCallbackFun)
this.bluetoothInstance.bleImpl.on(ScanEventType.SCAN_DEVICE_FIND, this.deviceFindCallbackFun)
this.bluetoothInstance.bleImpl.on(BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE, this.connectStateCallbackFun)
this.bluetoothInstance.bleImpl.on(BleConnectEventTypeConstant.CONNECT_BLE_CHARACTERISTIC_CHANGE,
this.characteristicInfoCallbackFun)
this.bluetoothInstance.sppImpl.on(BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE, this.connectStateCallbackFun)
this.bluetoothInstance.sppImpl.on(SppConnectEventTypeConstant.CONNECT_DATA_READ_CHANGE,
this.sppDataReadInfoCallbackFun)
}
Source: BluetoothOTAManager.ets
这里把 BLE 的扫描、发现、连接状态、特征值通知四个事件流统一转译给 OTA 管理层;同时订阅 SPP 的数据读取事件,使上层对两条通道的事件呈现一致。
RCSP 桥接回调(initRcsp)
OTAWrapperOption 是 OTAWrapper 依赖的通道抽象,BluetoothOTAManager 用 BLE 实现填充它:
private initRcsp() {
this.otaWrapperOption = {
/**是否需要认证**/
isUseAuth: (): boolean => {
return true
},
isInnerReconnect: () => {
return true
},
/**扫描设备**/
sanDevice: () => {
//升级成功-扫描的是平时设备。升级中 -扫描的是BLE设备
this.bluetoothInstance.startScan(10 * 1000, "BLE")
if (this.bluetoothInstance.communicationWay == "EDR") {
this.bluetoothInstance.startScan(10 * 1000, "EDR")
}
},
/**连接设备**/
connectDevice: (device: OTADevice) => {
//目前回连只有BLE设备
const isBle = true
const deviceId = device.deviceId
if (isBle) {
this.bluetoothInstance.connect(new BleDevice(deviceId))
} else {
this.bluetoothInstance.connect(new SppDevice(deviceId))
}
},
/**断开设备**/
disconnectDevice: (device: OTADevice) => {
// 此处需判断设备类型
const isBle =
this.bluetoothInstance.bleImpl.getConnectedDevice().findIndex(item => item.deviceId == device.deviceId) != -1
const deviceId = device.deviceId
if (isBle) {
this.bluetoothInstance.disconnect(new BleDevice(deviceId))
} else {
this.bluetoothInstance.disconnect(new SppDevice(deviceId))
}
},
/**发送数据**/
sendData: (device: OTADevice, data: Uint8Array) => {
// 此处需判断设备类型
const isBle =
this.bluetoothInstance.bleImpl.getConnectedDevice().findIndex(item => item.deviceId == device.deviceId) != -1
const deviceId = device.deviceId
if (isBle) {
this.bluetoothInstance.bleImpl.sendData(new BleDevice(deviceId), RCSP_UUID_SERVICE, RCSP_UUID_WRITE, data)
} else {
this.bluetoothInstance.sppImpl.sendData(new SppDevice(deviceId), RCSP_SOCKET_UUID, data)
}
}
}
this.otaWrapper = new OTAWrapper(this.otaWrapperOption)
}
Source: BluetoothOTAManager.ets
关键点解读:
isUseAuth恒为true:升级会话需要 RCSP 认证握手,防止未授权设备发起升级。sanDevice的注释揭示了升级通道的两阶段语义:升级成功前扫描到的是普通 BLE 设备,升级过程中设备已切换到升级模式、以升级广播出现。扫描超时固定为 10 秒。sendData中 BLE 分支固定使用RCSP_UUID_SERVICE+RCSP_UUID_WRITE,即所有 RCSP 帧(含固件数据)都写入 AE01 特征值;SPP 分支则写入RCSP_SOCKET_UUID。connectDevice当前硬编码isBle = true——因为升级回连场景中设备只以 BLE 升级模式出现,经典蓝牙回连暂不适用。
升级入口与回连机制
startOTA 入口
startOTA(deviceId, otaCallback) 创建 JL_OTA.OtaConfig 并设置 isSupportNewRebootWay = true(支持新版回连方式),同时包装 OTAUpgradeCallback 把 SDK 回调透传给页面层。其中 onNeedReconnect 是关键:RCSP SDK 在设备断开进入升级模式后调用它,携带 ReconnectInfo(含 deviceBleMac 与 isSupportNewReconnectADV 标志)和回连结果回调 reconnectCallback。
新旧两种回连判定
BluetoothOTAManager 提供自定义回连的参考实现(ReconnectOp)。当 isInnerReconnect() 为 false 时走自定义逻辑,核心是 isReconnectDevice 对扫描到的每个设备的广播数据进行匹配:
- 新回连方式(
isSupportNewReconnectADV为真):设备的新 BLE 地址藏在广播包的D60541544F4C4A魔数之后。代码在广播数据十六进制串中查找该魔数下标,然后从(index/2)+8起取 6 字节并reverse()得到设备的新 MAC,与 RCSP 协议上报的旧 MAC 比对:
if (reConnectMsg.isSupportNewReconnectADV) { //使用新回连方式,需要通过rcsp协议获取到设备的ble地址
if (oldDeviceMac != undefined && oldDeviceMac !== "") {
const advertiseStr = (JL_OTA.toHexString(scanDevice.advertiseData) as string).toUpperCase();
const index = advertiseStr.indexOf("D60541544F4C4A");
if (index != -1 && scanDevice.advertiseData) {
const unit8Array = new Uint8Array(scanDevice.advertiseData);
const macArray = unit8Array.slice((index / 2) + 8, (index / 2) + 14).reverse();
result = oldDeviceMac == JL_OTA.toHexString(macArray).toUpperCase();
}
}
} else { //旧回连方式,deviceId相同即可
if (deviceId != undefined) {
if (deviceId == scanDevice.deviceId) {
result = true;
}
const oldDeviceDeviceIdPrefix = deviceId.substring(0, 10);
if (oldDeviceDeviceIdPrefix != undefined &&
scanDevice.deviceId.toUpperCase().includes(oldDeviceDeviceIdPrefix)) { //模糊匹配(mac地址中部分相似)
...
Source: BluetoothOTAManager.ets
- 旧回连方式(设备 BLE 地址不变):直接用
deviceId == scanDevice.deviceId精确匹配,并辅以前缀模糊匹配兜底。
设计意图:升级过程中设备会重启进升级模式,此时其 BLE 地址往往发生变化,单纯按 MAC 精确匹配必然失败。新回连方式利用杰理设备在升级广播中嵌入 D60541544F4C4A 魔数 + 新 MAC 的约定,让手机端能够"识别换了地址的老设备",从而在 10 秒扫描窗口内完成回连、续传固件,实现真正无感的 OTA 升级。
Core Flow:一次完整的 BLE 固件升级数据链路
下面的时序图展示了从用户发起升级到固件数据经 BLE 通道写入设备的完整流程,包含升级模式回连环节:
sequenceDiagram
participant App as App 页面
participant OTA as BluetoothOTAManager
participant BM as BluetoothManager
participant BLE as BleImpl
participant SYS as HarmonyOS BLE 系统能力
participant DEV as 杰理设备 (RCSP)
App->>OTA: startOTA(deviceId, otaCallback)
OTA->>OTA: 创建 OtaConfig(isSupportNewRebootWay=true)
OTA->>BM: startScan(10s, "BLE")
BM->>BLE: startScan(10s)
BLE->>SYS: ble.startBLEScan(interval=500, BALANCED, AGGRESSIVE)
SYS-->>BLE: ScanResult (发现设备)
BLE-->>OTA: SCAN_DEVICE_FIND 事件 (BleDevice[])
App->>OTA: 用户选择设备,调用连接
OTA->>BM: connect(new BleDevice(deviceId))
BM->>BLE: connect(device)
BLE->>SYS: createGattClientDevice + connect + MTU 协商(512)
SYS-->>BLE: 连接成功 / MTU 协商结果
BLE-->>OTA: CONNECT_STATE_CHANGE / CONNECT_MTU_CHANGE 事件
OTA->>OTA: 通知 RCSP SDK 设备就绪 (OTAWrapper)
OTA->>BLE: sendData(device, AE00/AE01, rcspFrame)
BLE->>BLE: BleSendDataHandler 按 MTU 分包
BLE->>SYS: gatt.write(AE01) 逐包写入
SYS-->>DEV: BLE 无线帧
DEV-->>SYS: 通知回调 (AE02)
SYS-->>BLE: characteristicChange 事件
BLE-->>OTA: CONNECT_BLE_CHARACTERISTIC_CHANGE (BLECharacteristicInfo)
OTA-->>OTA: 转译给 OTAWrapper (设备应答/升级进度)
OTA-->>App: onProgress / onStateChange 回调
Note over DEV,OTA: 设备重启进入升级模式,BLE 地址变化
OTA->>OTA: onNeedReconnect(ReconnectInfo, reconnectCallback)
OTA->>BM: startScan(10s, "BLE") 重新扫描
BLE-->>OTA: 扫描到升级广播(含 D60541544F4C4A 魔数)
OTA->>OTA: isReconnectDevice 解析新 MAC 并比对
OTA->>BM: connect(new BleDevice(新MAC))
OTA->>OTA: reconnectCallback.onResult(新deviceId)
OTA->>BLE: 继续 sendData 续传固件
OTA-->>App: onFinishOTA
流程要点:
- 扫描阶段:
startOTA触发startScan(10s, "BLE"),BleImpl以均衡占空比扫描并持续上报BleDevice[],页面据此展示可升级设备列表。 - 连接阶段:选择设备后经
BluetoothManager.connect→BleImpl.connect完成 GATT 连接与 MTU 协商(目标 512),连接状态与 MTU 结果通过事件回调上报。 - 认证与数据传输:RCSP SDK 通过
OTAWrapperOption.sendData把每个协议帧写入 AE01;设备应答经 AE02 通知特征值回传,BleImpl以CONNECT_BLE_CHARACTERISTIC_CHANGE事件交给 OTA 层解析,驱动升级进度。 - 升级回连:设备重启进升级模式后连接断开,SDK 回调
onNeedReconnect;OTA 管理层重新扫描,通过广播包魔数D60541544F4C4A识别设备并解析新 MAC,回连成功后以新deviceId通知 SDK 续传。
Usage Examples
示例一:初始化 BLE 升级通道并启动升级
以下代码演示了 SDK 使用者如何完成通道初始化(订阅事件 + 创建 OTAWrapper)并启动一次升级:
public init() {
//蓝牙 初始化
this.initBluetooth()
//OTAWrapper 初始化
this.initRcsp()
}
public startOTA(deviceId: string, otaCallback: OTAUpgradeCallback) {
const otaConfig: JL_OTA.OtaConfig = new JL_OTA.OtaConfig()
otaConfig.isSupportNewRebootWay = true //支持新回连方式
const tempOtaUpgradeCallback: OTAUpgradeCallback = {
onStartOTA: () => {
otaCallback.onStartOTA()
},
onExecuteDisconnectDevice: (): void => {
otaCallback.onExecuteDisconnectDevice()
},
onNeedReconnect: (reConnectMsg: JL_OTA.ReconnectInfo, reconnectCallback: JL_OTA.OnResultCallback<string>) => {
otaCallback.onNeedReconnect(reConnectMsg,reconnectCallback)
if (this.otaWrapperOption?.isInnerReconnect()==false) {//使用自定义回连方式,不使用sdk内部回连方式
const oldDeviceMac = reConnectMsg.deviceBleMac?.toUpperCase().replace(/:/g, "");
...
}
}
}
Source: BluetoothOTAManager.ets
示例二:向设备写入 RCSP 数据帧
OTA SDK 通过 OTAWrapperOption.sendData 发送数据,最终落到 BLE 写特征值。使用者也可以绕过 OTA 层直接发送自定义 RCSP 指令:
sendData: (device: OTADevice, data: Uint8Array) => {
// 此处需判断设备类型
const isBle =
this.bluetoothInstance.bleImpl.getConnectedDevice().findIndex(item => item.deviceId == device.deviceId) != -1
const deviceId = device.deviceId
if (isBle) {
this.bluetoothInstance.bleImpl.sendData(new BleDevice(deviceId), RCSP_UUID_SERVICE, RCSP_UUID_WRITE, data)
} else {
this.bluetoothInstance.sppImpl.sendData(new SppDevice(deviceId), RCSP_SOCKET_UUID, data)
}
}
Source: BluetoothOTAManager.ets
示例三:订阅 BLE 事件流
底层事件模型可直接复用,例如监听扫描结果与特征值通知:
this.bluetoothInstance.bleImpl.on(ScanEventType.SCAN_DEVICE_FIND, this.deviceFindCallbackFun)
this.bluetoothInstance.bleImpl.on(BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE, this.connectStateCallbackFun)
this.bluetoothInstance.bleImpl.on(BleConnectEventTypeConstant.CONNECT_BLE_CHARACTERISTIC_CHANGE,
this.characteristicInfoCallbackFun)
Source: BluetoothOTAManager.ets
Configuration Options
BleConnectSettingConfigure(连接参数)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
mtu | number | 512 | GATT MTU 大小,有效范围 23~512,决定单包可承载数据量 |
notifyCharacteristicArray | Array<ble.BLECharacteristic> | [AE02 通知特征值] | 使能的接收特征值列表 |
necessaryNotifyCharacteristicArray | Array<ble.BLECharacteristic> | [AE02 通知特征值] | 必须使能的特征值,连接必要条件 |
writeCharacteristicArray | Array<ble.BLECharacteristic> | [AE01 写特征值] | 数据发送特征值列表 |
RCSP_UUID_SERVICE(模块常量) | string | 0000AE00-0000-1000-8000-00805F9B34FB | RCSP GATT 服务 UUID |
RCSP_UUID_WRITE(模块常量) | string | 0000AE01-0000-1000-8000-00805F9B34FB | 数据写特征值 UUID |
RCSP_UUID_NOTIFY(模块常量) | string | 0000AE02-0000-1000-8000-00805F9B34FB | 设备通知特征值 UUID |
扫描参数(BleScanSettingConfigure 对应字段)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
scanTimeOut | number | 由调用方指定(OTA 场景为 10 秒) | 单次扫描超时,超时自动停止并通知 |
| 系统扫描 interval | number | 500 | ble.startBLEScan 的扫描间隔(毫秒) |
| 系统扫描 dutyMode | enum | SCAN_MODE_BALANCED | 均衡占空比,功耗与发现速度折中 |
| 系统扫描 matchMode | enum | MATCH_MODE_AGGRESSIVE | 激进匹配,最大化设备发现率 |
OTAWrapperOption(通道桥接配置)
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
isUseAuth | () => boolean | true | 升级会话是否需要 RCSP 认证 |
isInnerReconnect | () => boolean | true | 是否使用 SDK 内置回连;设为 false 时走自定义回连 |
sanDevice | () => void | BLE 扫描 10s | SDK 要求重新扫描设备时的回调 |
connectDevice | (device) => void | BLE 连接 | 回连/连接设备回调,当前固定按 BLE 处理 |
disconnectDevice | (device) => void | 按类型分发 | 断开设备回调,按已连接列表判断类型 |
sendData | (device, data) => void | BLE 写 AE01 | 发送 RCSP 数据帧回调 |
API Reference
BleImpl(实现 IBleScan、IBleConnect)
扫描接口
startScan(scanTimeOut?: number): void
- 参数:
scanTimeOut(可选)扫描超时毫秒数,传入时更新配置;正在扫描时刷新扫描。 - 说明:首次调用清空设备列表、启动定时并调用系统
startBLEScan。
refreshScan(): void
- 说明:清空已发现设备列表并重启超时定时器(仅当正在扫描时生效)。
stopScan(): void
- 说明:停止超时定时器、清理系统已连接设备检查定时器并调用
_stopScan()。
isScanning(): boolean
- 返回:当前是否处于扫描中。
getScanSettingConfigure(): BleScanSettingConfigure / setScanSettingConfigure(cfg): void
- 说明:读写扫描配置对象。
连接与数据接口
connect(device: BleDevice, success?: Callback<BluetoothDevice>, fail?: Callback<BusinessError>)
- 说明:发起 GATT 连接(含 MTU 协商与特征值使能)。
- 失败时通过
fail回调携带BusinessError。
disconnect(device: BleDevice): void
- 说明:断开指定设备的 GATT 连接。
sendData(bluetoothDevice: BleDevice, serviceId: string, characteristicId: string, data: Uint8Array): void
- 参数:
bluetoothDevice:目标 BLE 设备serviceId:目标服务 UUID(升级场景为RCSP_UUID_SERVICE)characteristicId:目标写特征值 UUID(升级场景为RCSP_UUID_WRITE)data:要发送的原始字节
- 行为:查找设备写特征值后交给
BleSendDataHandler.sendData分包写入。 - 失败场景:设备未连接或未找到匹配写特征值时仅记录错误日志,不抛异常。
getConnectedDevice(): Array<BleDevice>
- 返回:当前已连接的 BLE 设备列表。
事件接口
on(type, callback): void
- 支持事件类型:
ScanEventType.SCAN_STATE_CHANGE→Callback<ScanStateInfo>ScanEventType.SCAN_DEVICE_FIND→Callback<BleDevice[]>BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE→Callback<ConnectStateInfo<BleDevice>>BleConnectEventTypeConstant.CONNECT_MTU_CHANGE→Callback<MTUInfo>BleConnectEventTypeConstant.CONNECT_BLE_CHARACTERISTIC_CHANGE→Callback<BLECharacteristicInfo>
- 说明:按事件类型追加回调到
callbacksMap。
off(type, callback?): void
- 说明:
callback缺省时清除该类型全部回调;否则仅移除指定回调。
BluetoothManager(单例 bluetoothInstance)
| 方法 | 参数 | 返回 | 说明 |
|---|---|---|---|
connect | device, success?, fail? | void | 按设备类型分发到 BLE/SPP |
disconnect | device | void | 按设备类型分发 |
getConnectedDevice | type?("EDR"/"BLE") | 设备数组 | 未传 type 时按 communicationWay |
isConnecting / isConnected | device | boolean | 连接状态查询 |
startScan / refreshScan / stopScan | scanTimeOut?, type? | void | 扫描控制,未传 type 时按 communicationWay(默认 BLE) |
communicationWay | - | "BLE" / "EDR" | 当前默认通信方式,可读写 |
BluetoothOTAManager
| 方法 | 参数 | 说明 |
|---|---|---|
init() | - | 订阅蓝牙事件 + 创建 OTAWrapper,通道初始化入口 |
startOTA(deviceId, otaCallback) | deviceId: string, otaCallback: OTAUpgradeCallback | 创建 OtaConfig 并启动一次固件升级 |
onScanState / onDeviceFind / onConnectStateChange / onBLECharacteristicInfo | 事件负载 | 内部事件处理器,将 BLE 事件转译为 OTA 层语义 |
Failure Modes、边界情况与并发
蓝牙开关状态异常
BleImpl 构造时监听系统蓝牙状态:STATE_OFF 时遍历断开所有已连接设备,未断开成功的设备 ID 暂存于 waitDisconnectDevices;STATE_ON 时对暂存设备重建 GattClientDevice 强制 disconnect() + close()。这一机制保证系统蓝牙被强制关闭再打开后,不会残留脏 GATT 连接导致后续 connect 失败。注意:重建连接对象阶段使用 try/catch 包裹,单设备清理失败不影响其他设备。
写数据失败静默降级
sendData 对"设备未连接"和"未找到写特征值"两类错误只写错误日志、不向调用方抛异常。设计取舍:RCSP 协议自身具备应答机制(设备通过 AE02 返回错误码/重传请求),传输层不重复报错;但这也意味着上层必须依赖协议层超时与重传兜底,而非底层异常。
升级回连的设备识别歧义
回连匹配依赖广播包内容:
- 新回连方式要求设备广播中必须携带
D60541544F4C4A魔数 + 新 MAC,且 MAC 字节序为小端(需reverse())。若固件版本不支持该广播格式,魔数查找返回-1,回连判定失败。 - 旧回连方式使用
deviceId精确匹配,并辅以前缀模糊匹配(MAC 前 10 位)。模糊匹配存在误连同前缀设备的边界风险,源码注释也承认"回连打印太多有问题",实际以精确匹配为准、模糊匹配仅作日志优化。
并发与重入
BleImpl的扫描状态机由mIsScanning标志保护:startScan在扫描中会转为refreshScan,避免重复启动系统扫描。- 事件订阅使用
callbacksMap数组存储,on/off为同步操作;同一事件多订阅者按注册顺序回调。回调执行均在系统事件线程,业务回调内应避免耗时操作,防止阻塞后续通知(固件传输时 AE02 通知频率高)。
系统能力异常
HarmonyOS BLE API(如 ble.startBLEScan、createGattClientDevice)可能抛出 BusinessError;源码在扫描启动与蓝牙恢复清理等路径使用 try/catch 捕获并记录 errCode/errMessage。扩展新调用点时应保持同样的防御式写法。
Performance 与运维考量
- MTU 512:BLE 4.2+ 的数据长度扩展(DLE)下单包可达 512 字节(扣除 ATT 头约 509 字节有效载荷),相比默认 23 字节 MTU 可将固件传输效率提升约 20 倍。
BleSendDataHandler负责按协商后的 MTU 分包,是固件大文件传输的吞吐关键。 - 扫描功耗:采用
SCAN_MODE_BALANCED均衡占空比 + 500ms 间隔,兼顾发现速度与功耗;OTA 回连扫描窗口固定 10 秒,超时自动停止。 - 日志策略:发数路径中的逐包数据日志(
u8ToHexStr)被注释关闭,仅保留错误与关键状态日志,避免高吞吐升级时日志 I/O 成为瓶颈。排查问题时可按需打开。 - 回连耗时:一次回连包含重新扫描(≤10s)+ 连接 + MTU 协商,升级体验高度依赖该窗口内设备广播的可发现性;若使用自定义回连,应确保
isReconnectDevice判断足够快(当前实现为纯内存计算)。
Extension Points
- 自定义回连:将
OTAWrapperOption.isInnerReconnect设为false,在onNeedReconnect中实现自己的扫描/识别/连接逻辑(参考ReconnectOp),连接成功后调用reconnectCallback.onResult(新deviceId)通知 SDK。 - 新增通信通道:
BluetoothManager按BluetoothDeviceType("BLE"/"EDR")分发,新增通道只需实现IBleScan/IBleConnect风格的接口(或对应 SPP 接口),在BluetoothManager增加分支并扩展BluetoothDeviceType。 - 自定义 RCSP 指令:
BleImpl.sendData是通用写入口,业务方可直接按(serviceId, characteristicId)组合发送非升级类指令(如设备状态查询、EQ 设置等),前提是遵循 RCSP 帧格式。 - 扫描策略调优:通过
setScanSettingConfigure替换BleScanSettingConfigure,可自定义扫描超时与过滤规则;_startScan内的系统级scanOptions也可按产品需求调整 dutyMode。
Related Links
- BLE 通道实现:BleImpl.ets
- BLE 连接配置与 RCSP UUID:BleConnectSettingConfigure.ets
- 双通道门面:BluetoothManager.ets
- OTA 桥接层:BluetoothOTAManager.ets
- BLE 接口契约:
ble/IBleScan.ets、ble/IBleConnect.ets、ble/BleDevice.ets、ble/BleSendDataHandler.ets - 通用抽象:
base/IConnect.ets、base/IScan.ets、base/BluetoothDevice.ets、base/BluetoothErrorConstant.ets - 兄弟页面:SPP 通道(
bluetooth/spp/)、OTA 升级流程(ota/目录)