蓝牙管理与 OTA 管理器
本文档深入剖析 HarmonyOS-JL_OTA 工程中「管理器层」的两个核心类:BluetoothManager(蓝牙连接/扫描的双栈门面)与 BluetoothOTAManager(OTA 升级编排器),说明它们如何把底层 BLE/SPP 实现与杰理 rcsp OTA SDK 桥接成一个完整的"扫描 → 连接 → 升级 → 回连"闭环。
Purpose and Scope
本页面覆盖以下内容:
BluetoothManager:单例门面,按设备类型(BleDevice/SppDevice)把扫描、连接、断开、状态查询等操作分发到BleImpl/SppImpl。BluetoothOTAManager:OTA 升级的总编排器,负责初始化事件订阅、构造OTAWrapperOption适配层、启动升级、桥接蓝牙回调到 rcsp SDK,以及实现可选的"自定义回连"逻辑。- 支撑两者的基础契约:
IConnect、ConnectState、ConnectStateInfo、BaseConnectEventTypeConstant。
以下主题属于兄弟页面,不在本页展开:
- BLE 具体实现(
BleImpl、BleDevice、扫描/连接配置)→ 见「BLE 协议实现」相关页面。 - SPP 具体实现(
SppImpl、SppDevice、Socket 配置)→ 见「SPP 协议实现」相关页面。 - rcsp SDK 侧封装(
OTAWrapper、OTAWrapperOption、Reconnect、IOTAWrapper)→ 见「OTA 升级 SDK 封装」相关页面。
Overview
在 JL_OTA 的蓝牙架构中,管理器层位于应用与协议实现之间,承担三个职责:
- 协议无关的门面:
BluetoothManager向应用暴露统一的"扫描/连接/断开/状态查询"API,内部通过instanceof判断设备类型,把调用分发给 BLE 或 SPP 实现。应用侧无需关心设备走的是 GATT 还是 RFCOMM。 - 事件中枢:
BluetoothOTAManager在init()时订阅BleImpl/SppImpl的全部关键事件(扫描状态、发现设备、连接状态、特征值变化、SPP 数据读取),再统一转发给 rcsp SDK 的OTAWrapper,完成"蓝牙世界"与"RCSP 协议世界"的解耦。 - 升级编排:
startOTA()负责组装JL_OTA.OtaConfig、包装升级回调(OTAUpgradeCallback),并在需要时驱动"自定义回连"流程——通过Reconnect类在升级中断后重新扫描、识别、连接设备,最后把新的deviceId回传给 SDK 恢复升级。
整个设计遵循**桥接(Bridge)+ 门面(Facade)+ 策略分发(instanceof dispatch)**的组合模式:管理器是唯一入口,协议差异被封装在 impl 层,OTA 协议逻辑完全由 rcsp SDK 承载,管理器只做翻译与路由。
Architecture
flowchart TD
subgraph sg_App["应用层 (App/UI)"]
App["页面 / 业务代码"]
end
subgraph sg_Manager["管理器层 (Managers)"]
BTM["BluetoothOTAManager"]
BM["BluetoothManager (单例 bluetoothInstance)"]
end
subgraph sg_Impl["协议实现层 (Impl)"]
BleImpl["BleImpl"]
SppImpl["SppImpl"]
end
subgraph sg_Base["基础抽象层 (base)"]
IConnect["IConnect 接口"]
IScan["IScan 接口"]
BTDevice["BluetoothDevice"]
end
subgraph sg_OTA["OTA SDK 层 (rcsp)"]
OTAWrapper["OTAWrapper"]
Reconnect["Reconnect"]
end
subgraph sg_Sys["HarmonyOS 系统能力"]
BLE["蓝牙 BLE (GATT)"]
SPP["蓝牙 SPP (RFCOMM)"]
end
App -->|"startOTA / 扫描 / 连接"| BTM
App -->|"扫描 / 连接 / 收发"| BM
BTM -->|"复用单例"| BM
BTM -->|"OTAWrapperOption 适配回调"| OTAWrapper
OTAWrapper -->|"升级事件转发"| BTM
BTM -->|"自定义回连"| Reconnect
BM -->|"设备类型分发"| BleImpl
BM -->|"设备类型分发"| SppImpl
BleImpl -.->|"实现"| IConnect
BleImpl -.->|"实现"| IScan
SppImpl -.->|"实现"| IConnect
SppImpl -.->|"实现"| IScan
BleImpl --> BLE
SppImpl --> SPP
架构解读:
BluetoothManager(BluetoothManager.ets)在构造时即创建BleImpl与SppImpl,并导出进程级单例bluetoothInstance,供BluetoothOTAManager与业务页面共享——保证全工程只有一套蓝牙连接状态机。BluetoothOTAManager不直接持有 impl 引用,而是通过单例访问,并在init()中注册回调;回调经过内部xxxCallbackFun箭头函数包装后统一转发给otaWrapper。- 数据流向是双向的:应用 → 管理器 → impl → 系统蓝牙(下行);系统蓝牙 → impl 事件 → 管理器回调 → OTAWrapper(上行)。
BluetoothManager:双栈门面与设备类型分发
单例与成员
BluetoothManager 的成员极简:两个协议实现 + 一个"当前通讯方式"标记。默认通讯方式为 "BLE"(源码 L17-L26):
export class BluetoothManager {
private _bleImpl: BleImpl
private _sppImpl: SppImpl
// 当前通讯方式
private _communicationWay: BluetoothDeviceType = "BLE"
constructor() {
this._bleImpl = new BleImpl()
this._sppImpl = new SppImpl()
}
communicationWay 提供 getter/setter(BluetoothDeviceType 取值如 "BLE" / "EDR"),它是"未显式指定通讯方式时"的兜底默认值;bleImpl / sppImpl 两个 getter 暴露具体实现供上层直接调用(如 bluetoothInstance.bleImpl.sendData(...))。
按设备类型分发的核心模式
连接与断开 API 采用 instanceof 策略分发——这是整个门面最核心的设计意图:调用方只传 BluetoothDevice 抽象,由管理器根据真实子类决定走哪条协议栈(源码 L44-L61):
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 { //无法判断设备类型
}
}
disconnect(device: BluetoothDevice) {
if (device instanceof SppDevice) { //根据设备类型判断-Spp
this.sppImpl.disconnect(device)
} else if (device instanceof BleDevice) { //根据设备类型判断-Ble
this.bleImpl.disconnect(device)
} else { //无法判断设备类型
}
}
注意 else 分支为空——若传入既非 SppDevice 也非 BleDevice 的对象(例如直接 new BluetoothDevice()),调用会被静默忽略。这是设计上刻意的"防御性空操作",避免向错误协议栈发起连接。
通讯方式回退逻辑
查询类与扫描类 API(getConnectedDevice / isScanning / startScan / refreshScan / stopScan)允许调用方通过可选参数 type 指定通讯方式;不指定时回退到 _communicationWay。以 startScan 为例(源码 L113-L123):
startScan(scanTimeOut?: number, type?: BluetoothDeviceType | undefined): void {
let communicationWay = type
if (communicationWay == undefined) { //未明指通讯方式
communicationWay = this._communicationWay
}
if (communicationWay === "EDR") {
return this.sppImpl.startScan(scanTimeOut)
} else if (communicationWay === "BLE") {
return this.bleImpl.startScan(scanTimeOut)
}
}
这套回退机制让 BluetoothOTAManager 在升级回连时能同时启动两种扫描(见后文 sanDevice),而普通应用只需设置一次 communicationWay 即可。
BluetoothOTAManager:OTA 升级编排器
BluetoothOTAManager(BluetoothOTAManager.ets)是连接蓝牙层与 rcsp SDK 的"翻译官"。它维护 5 个私有回调包装器(scanStateCallbackFun、deviceFindCallbackFun、connectStateCallbackFun、characteristicInfoCallbackFun、sppDataReadInfoCallbackFun),一个 _ReconnectMap(设备维度到 Reconnect 实例的映射,仅自定义回连时使用),以及 otaWrapperOption / otaWrapper 两个核心成员。
init():两段式初始化
init() 顺序执行 initBluetooth() 与 initRcsp()(源码 L50-L55)。先订阅蓝牙事件、再构建 SDK 适配层——顺序不可颠倒,因为 OTAWrapper 的回连逻辑依赖扫描/连接事件已经就绪。
initBluetooth() 通过单例 bluetoothInstance 订阅 6 类事件(源码 L57-L68):
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)
}
设计要点:连接状态事件(CONNECT_STATE_CHANGE)被 BLE 与 SPP 共享同一个回调,因为上层只关心"连上/失败/断开"三种语义,协议栈差异在 impl 层已归一化为 ConnectStateInfo<BTDevice>。
OTAWrapperOption:蓝牙能力对 SDK 的适配面
initRcsp() 构造一个实现了全部蓝牙侧能力的 OTAWrapperOption 对象,随后 new OTAWrapper(otaWrapperOption)(源码 L70-L124):
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)
}
这一段是理解整个桥接的关键:
sanDevice固定以 10 秒超时启动 BLE 扫描,且当当前通讯方式为EDR时并行启动 SPP 扫描——因为升级过程中设备可能以 BLE 广播出现,平时则以经典蓝牙出现。connectDevice目前硬编码走 BLE(const isBle = true),注释明确"目前回连只有 BLE 设备",EDR 分支为未来扩展保留。disconnectDevice/sendData则通过查询bleImpl.getConnectedDevice()是否包含该deviceId来动态判定协议栈,而非硬编码——sendData在 BLE 通道写入RCSP_UUID_SERVICE服务的RCSP_UUID_WRITE特征值,在 SPP 通道写入RCSP_SOCKET_UUID对应 Socket。isUseAuth返回true表示 RCSP 协议需完成设备认证;isInnerReconnect返回true表示使用 SDK 内部回连(关闭时才会走本管理器的自定义回连分支)。
startOTA():升级入口与回调包装
startOTA(deviceId, otaCallback) 是升级的唯一入口(源码 L126-L252)。它做三件事:
- 组装升级配置:
new JL_OTA.OtaConfig()并设置isSupportNewRebootWay = true(启用"新回连方式":设备重启后通过特殊广播包携带自身 BLE 地址,供回连识别)。 - 包装回调:构造
tempOtaUpgradeCallback,把 SDK 的每个回调事件透传给调用方,并在关键节点插入回连管理逻辑(见下节)。 - 启动升级:
this.otaWrapper?.startOTA(new OTADevice(deviceId), otaConfig, tempOtaUpgradeCallback)。
回调包装中值得注意的行为:
onNeedReconnect:当 SDK 判定需要回连时触发。若isInnerReconnect() == false,则进入自定义回连流程——用reConnectMsg.deviceBleMac构造目标识别规则,实例化Reconnect存入_ReconnectMap并启动(超时为JL_OTA.OTAImpl.RECONNECT_DEVICE_TIMEOUT)。同时原始回调也被透传,业务方可自行处理。onStopOTA/onCancelOTA/onError:除了透传,还会从_ReconnectMap取出对应Reconnect并调用stopReconnect(),防止升级结束后回连任务残留。onReadData(offset, size):透传给调用方,用于从固件文件按偏移读取分片数据。onRCSPInit(deviceId, isInit):RCSP 初始化成功后,遍历_ReconnectMap通知所有Reconnect"设备已连接"(onDeviceConnected(new BluetoothDevice(deviceId))),推动回连状态机前进。
蓝牙事件 → SDK 事件桥接
管理器把 5 类蓝牙回调翻译成 SDK 能理解的事件:
| 蓝牙事件 | 桥接方法 | 转发目标 |
|---|---|---|
扫描结束 SCAN_STATE_FINISH | onScanState | otaWrapper.onSanDeviceStop() + 各 Reconnect.onScanStop() |
| 发现设备 | onDeviceFind | otaWrapper.onScanFound(foundDevs) + Reconnect.onDiscoveryDevices(...) |
| 连接成功 | onConnectStateChange | otaWrapper.onConnectStateSuccess(...) |
| 连接失败 | onConnectStateChange | otaWrapper.onConnectStateFailed(...) + Reconnect.onDeviceConnectFailed(...) |
| 连接断开 | onConnectStateChange | otaWrapper.onConnectStateDisconnect(...) + Reconnect.onDeviceConnectDisconnected(...) |
| BLE Notify 数据 | onBLECharacteristicInfo | 若 characteristicUuid === RCSP_UUID_NOTIFY 则 otaWrapper.onReceiveData(...) |
| SPP 数据读取 | onSppDataReadInfo | 若 uuid === RCSP_SOCKET_UUID 则 otaWrapper.onReceiveData(...) |
onDeviceFind 的转换逻辑(源码 L268-L280)展示了设备模型的适配:扫描到的 BleDevice 被转成 rcsp 侧 OTADevice,并把广播原始数据 scanResult?.data 挂到 advertiseData 上——这正是回连时解析"新回连广播包"所需的数据来源。
数据上行同理(源码 L304-L322):BLE 通道只接受 RCSP_UUID_NOTIFY 特征值变化,SPP 通道只接受 RCSP_SOCKET_UUID 的数据,二者最终都汇入 otaWrapper.onReceiveData(new OTADevice(deviceId), new Uint8Array(data)),由 SDK 完成 RCSP 协议解析。
自定义回连(Reconnect)识别算法
当 SDK 内部回连被禁用时,onNeedReconnect 内构造 ReconnectOp,其 isReconnectDevice 是回连成败的核心判定(源码 L141-L194):
- 新回连方式(
reConnectMsg.isSupportNewReconnectADV == true):在扫描设备的广播数据中查找魔数"D60541544F4C4A"(即 JL 厂商私有广播标识),命中后取其后 6 字节做字节反转得到设备真实 BLE MAC,与升级前记录的oldDeviceMac(deviceBleMac去冒号、大写)比对。同时用"广播包含旧 MAC / MAC 前缀模糊匹配"做日志优化,避免海量打印。 - 旧回连方式:直接比对扫描到的
deviceId与升级前deviceId(含前 10 位前缀模糊匹配日志)。
回连成功回调 onReconnectSuccess 会 _ReconnectMap.delete(deviceId) 并调用 reconnectCallback.onResult(device.deviceId)——把新 deviceId 交还 SDK,SDK 据此继续后续升级流程;失败则 onError(ERR_OTA_RECONNECT_DEVICE_TIMEOUT, ...)。
Core Flow:一次完整的 OTA 升级旅程
下面用时序图展示从应用调用 init() 到升级完成的完整控制流。图中可见管理器层如何作为"双通道翻译器"工作:
sequenceDiagram
participant App as 应用 / 页面
participant BTM as BluetoothOTAManager
participant BM as BluetoothManager (单例)
participant Impl as BleImpl / SppImpl
participant OW as OTAWrapper (rcsp SDK)
participant Dev as 蓝牙设备
App->>BTM: init()
BTM->>BM: 订阅扫描/连接/特征值/SPP数据事件
BTM->>OW: new OTAWrapper(otaWrapperOption)
Note over OW: SDK 持有蓝牙能力适配回调 (sanDevice/connectDevice/sendData)
App->>BTM: startOTA(deviceId, otaCallback)
BTM->>OW: startOTA(OTADevice, OtaConfig, 包装回调)
OW->>BM: sendData(deviceId, RCSP_UUID_WRITE, 升级指令)
BM->>Impl: instanceof 分发到 BleImpl
Impl->>Dev: GATT 写入特征值
Dev-->>Impl: Notify 特征值变化
Impl-->>BTM: onBLECharacteristicInfo (RCSP_UUID_NOTIFY)
BTM->>OW: onReceiveData(device, data)
Note over OW: RCSP 协议解析 -> 进度回调
OW-->>App: onProgress(type, progress)
Note over OW,BTM: 设备重启,需要回连
OW->>BTM: onNeedReconnect(reConnectMsg, reconnectCallback)
BTM->>BM: sanDevice() -> startScan(10s, BLE/EDR)
BM->>Impl: 启动扫描
Impl-->>BTM: 发现设备 (SCAN_DEVICE_FIND)
BTM->>OW: onScanFound(devList)
Note over BTM: 自定义回连时 Reconnect.isReconnectDevice 判定
BTM->>BM: connectDevice -> connect(new BleDevice(deviceId))
Impl-->>BTM: CONNECT_STATE_SUCCESS
BTM->>OW: onConnectStateSuccess(device)
BTM-->>OW: reconnectCallback.onResult(newDeviceId)
OW-->>App: 继续升级 / onStopOTA / onError
自定义回连决策流
当 isInnerReconnect() == false 时,回连完全由管理器接管。其判定分支如下:
flowchart TD
Start([升级中断需回连]) --> Check{"isInnerReconnect() == true?"}
Check -->|"是"| SDK["SDK 内部回连 (不创建 Reconnect)"]
Check -->|"否"| Custom["new Reconnect(op, callback) 存入 _ReconnectMap"]
Custom --> Scan["sanDevice() 启动 BLE(+EDR) 扫描 10s"]
Scan --> Find{"发现设备 isReconnectDevice?"}
Find -->|"新回连广播"| New{"广播含 D60541544F4C4A?"}
New -->|"是"| Parse["解析字节反转 MAC 与 oldDeviceMac 比对"]
New -->|"否"| ReScan["继续扫描"]
Parse --> Match{"MAC 命中?"}
Match -->|"是"| Connect["connectDevice 建立连接"]
Match -->|"否"| ReScan
Find -->|"旧方式"| Old{"deviceId 相等?"}
Old -->|"是"| Connect
Old -->|"否"| ReScan
Connect --> Success{"onReconnectSuccess?"}
Success -->|"是"| Notify["onResult(newDeviceId) 通知 SDK"]
Success -->|"否/超时"| Fail["onError ERR_OTA_RECONNECT_DEVICE_TIMEOUT<br/>并从 _ReconnectMap 移除"]
Notify --> Done([升级继续])
Fail --> Done
ReScan --> Timeout{"RECONNECT_DEVICE_TIMEOUT?"}
Timeout -->|"是"| Fail
Timeout -->|"否"| ReScan
关键时序约束:onRCSPInit 中 _ReconnectMap.forEach(reconnect => reconnect.onDeviceConnected(...)) 必须在连接成功且 RCSP 初始化完成后才执行,否则 Reconnect 状态机无法推进到"已连接"阶段。
事件与接口模型
管理器依赖的基础契约定义在 base/IConnect.ets:
classDiagram
class IConnectSettingConfigure {
<<interface>>
}
class IConnect~T, C~ {
<<interface>>
+connect(device, success?, fail?) void
+disconnect(device) void
+setConnectSettingConfigure(config) void
+getConnectSettingConfigure() C
+getConnectedDevice() Array~T~
+isConnecting(device) boolean
+isConnected(device) boolean
+on(type, callback) void
+off(type, callback?) void
}
class ConnectStateInfo~T~ {
+device T
+state ConnectState
+error? BusinessError
}
class ConnectState {
<<enumeration>>
CONNECT_STATE_DISCONNECT
CONNECT_STATE_SUCCESS
CONNECT_STATE_FAILED
}
IConnect~T, C~ --> ConnectStateInfo~T~ : 回调负载
ConnectStateInfo~T~ --> ConnectState : state
ConnectStateInfo~T~ ..> IConnectSettingConfigure : 泛型约束 C
契约要点:
ConnectState是三态枚举(DISCONNECT/SUCCESS/FAILED,见 IConnect.ets L21-L28),BLE 与 SPP 实现共享同一语义,这是BluetoothOTAManager能用同一个connectStateCallbackFun订阅两类实现的前提。ConnectStateInfo<T>携带device、state与可选的error: BusinessError,失败原因随事件一并送达。IConnect<T, C>是泛型接口:T为设备类型(BleDevice/SppDevice),C为连接配置(IConnectSettingConfigure标记接口),由各 impl 实现具体行为。BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE = 'connectStateChange'是 BLE/SPP 共用的连接事件名常量;off支持不传回调以取消该类型全部订阅。
数据模型与关键常量
| 常量 / 类型 | 定义位置 | 说明 |
|---|---|---|
bluetoothInstance | BluetoothManager.ets L152 | 进程级单例,BluetoothOTAManager 与业务共用 |
RCSP_UUID_SERVICE / RCSP_UUID_WRITE / RCSP_UUID_NOTIFY | ble/BleConnectSettingConfigure.ets | RCSP GATT 服务/写/通知特征值,升级数据下行与上行通道 |
RCSP_SOCKET_UUID | spp/SppConnectSettingConfigure.ets | RCSP SPP Socket 标识 |
_ReconnectMap: Map<string, Reconnect> | BluetoothOTAManager.ets L33 | 以 deviceId 为键的回连任务表,支持多设备并发回连 |
ScanStateInfo / BleDevice | base/IScan.ets / ble/BleDevice.ets | 扫描状态与扫描结果模型,advertiseData 承载广播原始字节 |
Usage Examples
示例 1:初始化管理器并启动 OTA 升级
业务方在页面 aboutToAppear 中初始化管理器,之后即可发起升级(调用方式取自源码对 init() / startOTA() 的公开定义,见 BluetoothOTAManager.ets L50-L55 与 L126-L141):
// 初始化:订阅蓝牙事件 + 构造 OTAWrapper
otaManager.init()
// 启动升级
otaManager.startOTA(deviceId, {
onStartOTA: () => { /* UI: 显示升级中 */ },
onProgress: (type, progress) => { /* UI: 更新进度条 */ },
onNeedReconnect: (reConnectMsg, reconnectCallback) => {
// 业务方可在此展示"设备重启,正在回连…"
},
onReadData: (offset, size) => { /* 按偏移返回固件分片 Uint8Array */ },
onStopOTA: () => { /* 升级完成 */ },
onCancelOTA: () => { /* 用户取消 */ },
onError: (error, message) => { /* 升级失败 */ }
})
示例 2:利用单例直接进行扫描与连接
应用不经过 OTA 时,可直接使用 BluetoothManager 单例做设备发现与连接(签名见 BluetoothManager.ets L44-L52 与 L113-L123):
import { bluetoothInstance } from './bluetooth/BluetoothManager'
import { BleDevice } from './bluetooth/ble/BleDevice'
// 默认通讯方式为 BLE,亦可显式指定
bluetoothInstance.communicationWay = "BLE"
bluetoothInstance.startScan(10 * 1000) // 扫描 10 秒
// 扫描到设备后:
const device = new BleDevice("AA:BB:CC:DD:EE:FF")
bluetoothInstance.connect(device,
(dev) => { /* 连接成功 */ },
(err) => { /* 连接失败: BusinessError */ })
示例 3:订阅连接状态事件
连接状态统一通过 CONNECT_STATE_CHANGE 事件获取,BLE 与 SPP 共用(事件常量与负载定义见 IConnect.ets L14-L42):
import { BaseConnectEventTypeConstant, ConnectState } from './base/IConnect'
bluetoothInstance.bleImpl.on(BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE, (info) => {
switch (info.state) {
case ConnectState.CONNECT_STATE_SUCCESS: // 连接成功
case ConnectState.CONNECT_STATE_FAILED: // 连接失败(info.error 携带原因)
case ConnectState.CONNECT_STATE_DISCONNECT // 连接断开
}
})
Configuration Options
BluetoothOTAManager 的可配置面集中在 OTAWrapperOption 适配层(BluetoothOTAManager.ets L70-L124):
| 配置项 | 类型 | 默认行为 | 说明 |
|---|---|---|---|
isUseAuth | () => boolean | true | 是否启用 RCSP 设备认证 |
isInnerReconnect | () => boolean | true | true 用 SDK 内部回连;false 走 Reconnect 自定义回连 |
sanDevice | () => void | 10s 超时 startScan("BLE"),EDR 时并行扫 | SDK 要求重新扫描设备时调用 |
connectDevice | (device) => void | 构造 BleDevice 并 connect | 回连时建立连接(当前固定 BLE) |
disconnectDevice | (device) => void | 查询已连接列表判定 BLE/SPP 后断开 | 升级前断开旧连接 |
sendData | (device, data) => void | BLE 写 RCSP_UUID_WRITE / SPP 写 RCSP_SOCKET_UUID | RCSP 指令下行通道 |
升级参数相关:
| 配置项 | 位置 | 默认 | 说明 |
|---|---|---|---|
OtaConfig.isSupportNewRebootWay | startOTA() 内 | true | 启用新回连方式(广播携带 BLE 地址) |
RECONNECT_DEVICE_TIMEOUT | startOTA() 内 | JL_OTA.OTAImpl 常量 | 自定义回连总超时 |
| 扫描超时 | sanDevice 内 | 10 * 1000 ms | 回连扫描窗口 |
API Reference
BluetoothManager(BluetoothManager.ets)
| 方法 | 签名 | 说明 |
|---|---|---|
connect | (device: BluetoothDevice, success?: Callback<BluetoothDevice>, fail?: Callback<BusinessError>): void | 按 instanceof 分发到 SPP/BLE 连接;未知类型静默忽略 |
disconnect | (device: BluetoothDevice): void | 按设备类型分发断开 |
getConnectedDevice | (type?: BluetoothDeviceType): Array<BluetoothDevice> | undefined | 未传 type 时回退 communicationWay;未知类型返回 undefined |
isConnecting | (device: BluetoothDevice): boolean | 未知类型返回 false |
isConnected | (device: BluetoothDevice): boolean | 未知类型返回 false |
isScanning | (type?: BluetoothDeviceType): boolean | 按通讯方式查询 |
startScan | (scanTimeOut?: number, type?: BluetoothDeviceType): void | 按通讯方式启动扫描 |
refreshScan | (type?: BluetoothDeviceType): void | 刷新扫描 |
stopScan | (type?: BluetoothDeviceType): void | 停止扫描 |
属性:communicationWay(读写,默认 "BLE")、bleImpl(只读)、sppImpl(只读)。
BluetoothOTAManager(BluetoothOTAManager.ets)
| 方法 | 签名 | 说明 |
|---|---|---|
init | (): void | 订阅蓝牙事件 + 构造 OTAWrapper,须在 startOTA 前调用 |
startOTA | (deviceId: string, otaCallback: OTAUpgradeCallback): void | 组装 OtaConfig、包装回调、启动升级;含自定义回连分支 |
getOTAWrapper | (): IOTAWrapper | undefined | 供上层直接访问 SDK 包装器(如手动调用 onReceiveData 调试) |
私有桥接回调(由 initBluetooth 注册):onScanState、onDeviceFind、onConnectStateChange、onBLECharacteristicInfo、onSppDataReadInfo。
IConnect<T, C>(IConnect.ets L45-L75)
connect(device: T, success?: Callback<T>, fail?: Callback<BusinessError>): voiddisconnect(device: T): voidsetConnectSettingConfigure(config: C): void/getConnectSettingConfigure(): CgetConnectedDevice(): Array<T>isConnecting(device: T): boolean/isConnected(device: T): booleanon(type: BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE, callback: Callback<ConnectStateInfo<T>>): voidoff(type, callback?): void— 不传callback时取消该类型全部订阅
Failure Modes、边界与并发
设备类型不可判定
BluetoothManager.connect/disconnect/isConnecting/isConnected 对既非 SppDevice 也非 BleDevice 的对象走空分支:连接被静默忽略、状态查询返回 false(见 BluetoothManager.ets L44-L96)。调用方必须使用 BleDevice / SppDevice 的具体子类构造设备,不能传基类实例——这是 instanceof 分发方案的硬约束。
回连失败与超时
- 自定义回连在
RECONNECT_DEVICE_TIMEOUT内未命中目标设备时,Reconnect触发onReconnectFailed,管理器透传JL_OTA.OtaError.ERR_OTA_RECONNECT_DEVICE_TIMEOUT错误并清理_ReconnectMap条目(BluetoothOTAManager.ets L205-L210)。 - 回连期间设备连接失败/断开(
onDeviceConnectFailed/onDeviceConnectDisconnected)会再次调用sanDevice()重新扫描,形成"扫描→识别→连接→失败→再扫描"的重试循环,直到超时。 - 新回连方式下若 RCSP 未从协议层拿到设备 BLE 地址(
oldDeviceMac为空),isReconnectDevice直接返回false,只能依赖旧方式匹配deviceId。
升级生命周期中的资源清理
onStopOTA / onCancelOTA / onError 三个回调都会执行 _ReconnectMap.get(deviceId)?.stopReconnect()(BluetoothOTAManager.ets L226-L240)。如果业务方在收到 onError 后不结束页面,务必保证该回调被 SDK 触发,否则回连任务可能残留并继续扫描。
并发与多设备
_ReconnectMap以deviceId为键支持多设备并发回连;回调中通过forEach遍历,属于"读多写少"场景。ArkTS 事件回调运行在单线程事件循环上,delete与forEach不会交错执行,因此无需额外加锁;但不应在回调中同步调用startOTA修改同一设备。- 扫描回调(
onScanState/onDeviceFind)与连接回调(onConnectStateChange)可能在同一扫描窗口内频繁触发,管理器只做转发与状态机推进,不做节流——高频事件下的性能取决于BleImpl/SppImpl的缓冲队列(BufferQueue/BaseSendDataHandler属于兄弟页面范畴)。
性能与运维注意
- 扫描开销:
sanDevice每次回连都会启动 10 秒扫描,且 EDR 模式下 BLE + SPP 双栈并行扫描,功耗与系统负载较高;回连频繁失败时会形成连续扫描循环,建议业务侧在onError后主动stopScan并引导用户。 - 广播数据解析:
isReconnectDevice对每个扫描到的设备执行JL_OTA.toHexString(advertiseData)与indexOf查找,属于 O(广播长度) 操作;源码中已用"仅命中模糊前缀才打印日志"来避免日志 IO 拖慢扫描回调。 - 单例生命周期:
bluetoothInstance为模块级单例,init()多次调用会重复注册事件回调(on不判重),建议在页面入口只调用一次;应用退出时若需要释放资源,应调用各 impl 的off取消订阅(BluetoothOTAManager当前未提供显式销毁方法)。
Extension Points
| 扩展点 | 位置 | 如何扩展 |
|---|---|---|
| 自定义回连 | onNeedReconnect 内 isInnerReconnect() == false 分支 | 替换 ReconnectOp.isReconnectDevice 的匹配算法;onReconnectSuccess 后通过 reconnectCallback.onResult(newDeviceId) 交还 SDK |
| 新回连广播识别 | isReconnectDevice | 修改魔数 "D60541544F4C4A" 与 MAC 提取偏移(当前为魔数后 6 字节反转),适配私有广播格式变化 |
| 回连协议栈扩展 | connectDevice 内 const isBle = true | 当设备支持 EDR 回连时改为按设备类型/固件能力判断,走 SppDevice 分支 |
| 升级数据源 | onReadData(offset, size) | 固件分片可来自文件、内存或网络,只需返回对应偏移的 Uint8Array |
| 认证策略 | isUseAuth | 返回 false 可跳过 RCSP 认证(仅用于调试,正式固件应保持 true) |
Tests
本目录(entry/src/main/ets/bluetooth/)未包含针对管理器的独立测试文件;管理器行为的验证依赖集成场景(连接设备 → 触发升级 → 观察 onProgress / onNeedReconnect 回调链)。源码注释中的调试日志(Log.i(TAG, "isReconnectDevice,mac:...")、"newReconnect"、"oldReconnect")可作为手动验证回连判定是否命中的观测点。