自动回连机制
单备份 OTA 升级过程中,设备会主动断开蓝牙连接并重新广播,自动回连机制负责在升级的"断连窗口期"内自动扫描并重新连接目标 BLE 设备,使升级流程无需用户手动干预即可继续。该机制由 ota/Reconnect.ets 提供核心状态机,由 bluetooth/BluetoothOTAManager.ets 负责与 OTA 升级流程的集成与设备识别策略。
Purpose and Scope
本文档深入解析 JL OTA HarmonyOS SDK 中的自动回连机制,涵盖:
Reconnect回连状态机的完整实现(超时控制、扫描、连接、成功/失败回调);ReconnectOp/ReconnectCallback两个扩展接口的契约与语义;- 新回连方式(通过 RCSP 广播包
D60541544F4C4A解析新 MAC 地址)与旧回连方式(deviceId 直接匹配)的设备识别策略; BluetoothOTAManager中 SDK 内部回连与自定义回连两条路径的切换逻辑(isInnerReconnect);- 回连与 OTA 升级生命周期(
onStartOTA、onStopOTA、onCancelOTA、onError)的联动。
不涉及:BLE 底层连接/扫描的具体实现(见蓝牙连接管理相关页面)、OTA 固件升级的数据传输协议(见 OTA 升级流程页面)、SPP 通道升级(见 SPP 升级页面)。自动回连当前仅针对 BLE 设备(源码注释明确"目前回连只有BLE设备")。
Overview
在单备份(single-bank)OTA 升级中,设备烧写固件后必须重启才能进入可升级状态,重启会导致 BLE 连接断开、设备以新的广播参数重新出现。如果没有自动回连,App 需要用户手动重新配对/连接,体验差且容易超时失败。自动回连机制的设计目标就是:在升级断连期间自动完成"扫描 → 识别 → 连接"闭环,并把连接结果交还给 OTA 流程继续推进。
核心设计思路:
- 可插拔的回连操作抽象:
Reconnect本身不关心底层蓝牙细节,只依赖ReconnectOp(扫描、判断、连接三个操作)与ReconnectCallback(四种结果回调)两个接口,上层可注入任意实现。 - 两种回连模式共存:SDK 内部回连(
isInnerReconnect()返回true)直接由 SDK 完成;自定义回连(返回false)由应用层在onNeedReconnect中自行实现,且源码提供了完整的参考实现。 - 新旧两代设备识别协议:新回连方式依赖 RCSP 协议在广播包中携带设备新 BLE 地址(
D60541544F4C4A魔数头 + 反转 MAC),旧回连方式直接比较扫描到的deviceId,兼容不同固件版本。 - 超时兜底:回连在
RECONNECT_DEVICE_TIMEOUT超时后回调onReconnectFailed,避免无限等待。
Architecture
flowchart TD
subgraph sg_OTA["OTA 升级层"]
OTAWrapper["OTAWrapper"]
OTAConfig["OtaConfig<br/>isSupportNewRebootWay = true"]
end
subgraph sg_BT["蓝牙管理层"]
BTOAManager["BluetoothOTAManager"]
OTAUpgradeCallback["OTAUpgradeCallback<br/>onNeedReconnect"]
ReconnectMap["_ReconnectMap<br/>Map<string, Reconnect>"]
end
subgraph sg_Reconnect["回连机制核心"]
Reconnect["Reconnect 状态机"]
ReconnectOp["ReconnectOp 接口<br/>startScanDevice / isReconnectDevice / connectDevice"]
ReconnectCallback["ReconnectCallback 接口<br/>成功/失败/连接失败/断开"]
end
subgraph sg_Bluetooth["蓝牙底层"]
BleImpl["BleImpl"]
ScanDevice["扫描到的设备<br/>BluetoothDevice"]
end
OTAWrapper -->|"onNeedReconnect(msg, callback)"| OTAUpgradeCallback
OTAUpgradeCallback --> BTOAManager
BTOAManager --> ReconnectMap
BTOAManager -->|"isInnerReconnect() == false 时创建"| Reconnect
Reconnect --> ReconnectOp
Reconnect --> ReconnectCallback
ReconnectOp -->|"startScanDevice / connectDevice"| BleImpl
BleImpl -->|"onDiscoveryDevices"| Reconnect
BleImpl -->|"onDeviceConnected"| Reconnect
Reconnect -->|"onReconnectSuccess(device)"| BTOAManager
BTOAManager -->|"reconnectCallback.onResult(新 deviceId)"| OTAWrapper
OTAConfig -->|"isSupportNewRebootWay 决定广播包解析方式"| BTOAManager
架构说明:
- OTAWrapper / OtaConfig:升级流程的入口。
startOTA中设置otaConfig.isSupportNewRebootWay = true(支持新回连方式),当 SDK 检测到设备重启、需要重新连接时通过OTAUpgradeCallback.onNeedReconnect通知上层。 - BluetoothOTAManager:桥接层,持有
_ReconnectMap(Map<string, Reconnect>,以旧 deviceId 为键)管理多个设备的回连任务;isInnerReconnect()决定走 SDK 内部回连还是把控制权交给应用层。 - Reconnect 状态机:回连核心,管理超时定时器、当前正在连接的设备、任务是否完成等状态;所有蓝牙事件经
onDiscoveryDevices/onDeviceConnected/onDeviceConnectFailed/onDeviceConnectDisconnected注入。 - ReconnectOp / ReconnectCallback:依赖倒置的关键——
Reconnect只依赖这两个接口,具体"怎么扫、怎么认、怎么连"由BluetoothOTAManager(或应用层)注入,实现回连策略与状态机的解耦。
核心实现:Reconnect 状态机
Reconnect 是回连机制的核心类,位于 ota/Reconnect.ets。它把"回连"抽象为一个有超时保护的、事件驱动的状态机,自身不持有任何蓝牙 API,全部依赖注入。
类结构与状态字段
export class Reconnect {
reconnectOp: ReconnectOp;
reconnectCallback: ReconnectCallback;
isFinished: boolean = false;
connectingDevice: BluetoothDevice | undefined;
timeoutNumber: number = -1;
constructor(op: ReconnectOp, callback: ReconnectCallback) {
this.reconnectOp = op;
this.reconnectCallback = callback;
}
关键字段:
| 字段 | 类型 | 作用 |
|---|---|---|
reconnectOp | ReconnectOp | 注入的回连操作(扫描/识别/连接),由上层提供 |
reconnectCallback | ReconnectCallback | 注入的结果回调,回连生命周期事件的上报通道 |
isFinished | boolean | 回连是否已结束(成功或显式停止),所有事件处理器都会先检查它 |
connectingDevice | BluetoothDevice | undefined | 当前正在尝试连接的目标设备,用于事件匹配 |
timeoutNumber | number | setTimeout 句柄,用于回连超时控制 |
isFinished 是状态机的守卫标志:onDiscoveryDevice、onDeviceConnected、onDeviceConnectFailed、onDeviceConnectDisconnected、onScanStop 五个事件入口全部先调用 isFinishedReconnect() 判断,一旦回连结束(成功或停止)就不再响应任何蓝牙事件,避免任务结束后产生副作用。
启动与停止:startReconnect / stopReconnect
startReconnect(timeout: number) {
this.timeoutNumber = setTimeout(() => {
clearTimeout(this.timeoutNumber)
this.reconnectCallback.onReconnectFailed()
}, timeout);
this.reconnectOp.startScanDevice()
}
stopReconnect() {
this.isFinished = true
clearTimeout(this.timeoutNumber)
}
设计意图:
startReconnect(timeout)先注册超时定时器、再启动扫描。超时一到,无论扫描/连接进行到哪一步,直接回调onReconnectFailed()。这保证回连任务必定收敛——要么成功、要么超时失败,不会悬空。- 超时回调中先
clearTimeout再回调,是防御性写法:JS 定时器回调执行后句柄已失效,显式清理可避免句柄泄漏。 stopReconnect()由上层在 OTA 停止/取消/出错时调用,设置isFinished = true并清除定时器。注意:停止不会回调onReconnectFailed,它只是静默终止——因为停止本身不是"失败"语义,上层(如onStopOTA)已经知道流程结束了。
事件驱动:扫描与连接结果处理
//上层扫描暂停通知
onScanStop() {
if (!this.isFinishedReconnect()) {
this.reconnectOp.startScanDevice();
}
}
//上层扫描发现设备
onDiscoveryDevices(devices: BluetoothDevice[]) {
devices.forEach(device => {
this.onDiscoveryDevice(device)
});
}
//上层扫描发现设备
onDiscoveryDevice(device: BluetoothDevice) {
if (!this.isFinishedReconnect()) {
if (this.reconnectOp.isReconnectDevice(device)) {
this.connectingDevice = device
this.reconnectOp.connectDevice(device)
}
}
}
四个事件入口的行为:
onScanStop():蓝牙扫描是耗电且独占的操作,可能被系统或其他逻辑暂停。扫描一停,Reconnect立刻重新拉起扫描(startScanDevice),形成"扫描 → 暂停 → 重扫"的自愈循环,直到找到目标设备或超时。onDiscoveryDevices(devices):批量上报的入口,内部逐个转调onDiscoveryDevice,保持单一设备处理逻辑。onDiscoveryDevice(device):核心决策点。先检查isFinished,再调用reconnectOp.isReconnectDevice(device)判断该设备是否为回连目标;命中则记录到connectingDevice并发起连接。注意:扫描会持续发现设备,因此connectingDevice可能被多次覆盖——只有最后命中的设备会成为"当前连接目标"。- 连接结果(成功/失败/断开)处理器都要求
device.deviceId == connectingDevice?.deviceId才响应,用 deviceId 精确匹配当前目标,避免其他设备的事件串扰:
//上层连接设备成功-
onDeviceConnected(device: BluetoothDevice) {
if (!this.isFinishedReconnect()) {
if (this.connectingDevice != null && device.deviceId == this.connectingDevice?.deviceId) {
clearTimeout(this.timeoutNumber)
this.reconnectCallback.onReconnectSuccess(device)
this.isFinished = true
}
}
}
//上层连接设备失败-
onDeviceConnectFailed(device: BluetoothDevice) {
if (!this.isFinishedReconnect()) {
if (this.connectingDevice != null && device.deviceId == this.connectingDevice?.deviceId) {
this.reconnectCallback.onDeviceConnectFailed(device)
}
}
}
//上层连接设备断开-
onDeviceConnectDisconnected(device: BluetoothDevice) {
if (!this.isFinishedReconnect()) {
if (this.connectingDevice != null && device.deviceId == this.connectingDevice?.deviceId) {
this.reconnectCallback.onDeviceConnectDisconnected(device)
}
}
}
private isFinishedReconnect(): boolean {
return this.isFinished;
}
成功路径的关键细节:onDeviceConnected 中先 clearTimeout 清除超时定时器,再回调 onReconnectSuccess(device),最后置 isFinished = true。顺序很重要——先停表、再通知、后置位,确保回调执行期间不会再被任何事件打断。
失败/断开路径的语义:onDeviceConnectFailed 与 onDeviceConnectDisconnected 回调给上层后,Reconnect 自身不重置状态,而是由上层(BluetoothOTAManager 中回调 sanDevice() 重新扫描)驱动下一轮尝试。这保持了状态机的简洁——"重试策略"是上层的责任。
契约接口:ReconnectOp 与 ReconnectCallback
export interface ReconnectOp {
startScanDevice: () => void; //扫描设备
isReconnectDevice: (scanDevice: BluetoothDevice) => boolean //判断是不是回连设备
connectDevice: (device: BluetoothDevice) => void; //连接设备
}
export interface ReconnectCallback {
/**回连成功*/
onReconnectSuccess: (device: BluetoothDevice) => void;
/**回连失败*/
onReconnectFailed: () => void;
/**回连-连接失败*/
onDeviceConnectFailed: (device: BluetoothDevice) => void;
/**回连-连接断开*/
onDeviceConnectDisconnected: (device: BluetoothDevice) => void;
}
接口语义解读:
ReconnectOp是回连的能力注入(三个操作),ReconnectCallback是回连的结果上报(四个回调)。Reconnect夹在中间,像一个小型"控制反转"容器:它决定何时调用操作、何时上报结果,而怎么做完全由注入方决定。onDeviceConnectFailed/onDeviceConnectDisconnected与onReconnectFailed的区别:前者是"连接目标设备时失败/断开"(可重试,上层通常会重新扫描),后者是"整个回连任务超时"(终止,上报 OTA 错误ERR_OTA_RECONNECT_DEVICE_TIMEOUT)。
设备识别策略:新回连方式 vs 旧回连方式
回连的关键难点是如何在设备重启后认出它。设备重启后可能更换了 BLE 地址(取决于固件行为),因此 SDK 提供了两代识别协议,由 BluetoothOTAManager.onNeedReconnect 参考实现中的 reConnectMsg.isSupportNewReconnectADV 标志区分。
回连触发与模式选择
onNeedReconnect: (reConnectMsg: JL_OTA.ReconnectInfo, reconnectCallback: JL_OTA.OnResultCallback<string>) => {
/** note: 如果需要使用自定义回连方式,请在此处实现。下面是实现的参考方式。
* 连接成功时,调用 reconnectCallback.onResult(device.deviceId);通知SDK 设备的新的deviceId。
* **/
otaCallback.onNeedReconnect(reConnectMsg,reconnectCallback)
if (this.otaWrapperOption?.isInnerReconnect()==false) {//使用自定义回连方式,不使用sdk内部回连方式
流程分支:
- SDK 升级流程检测到设备重启后,通过
onNeedReconnect(reConnectMsg, reconnectCallback)通知上层; - 若
isInnerReconnect() == true(SDK 内部回连),上层无需处理,SDK 自行完成; - 若为
false(自定义回连),BluetoothOTAManager执行下面的参考实现:解析ReconnectInfo、构造ReconnectOp/ReconnectCallback、创建Reconnect并启动。
ReconnectInfo 提供两个关键字段:deviceBleMac(设备原 BLE 地址,用于识别)与 isSupportNewReconnectADV(固件是否支持新回连广播)。
新回连方式:广播包解析新 MAC
新回连方式下,设备重启后会广播一个包含 RCSP 协议魔数头和自身新 BLE 地址的广播包。识别逻辑从广播数据中提取新 MAC 并与旧 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();
// logd("新回连广播包 newMAC : " + ab2hex(macArray).toUpperCase())
result = oldDeviceMac == JL_OTA.toHexString(macArray).toUpperCase();
}
解析算法拆解:
- 将广播数据
advertiseData转成大写十六进制字符串; - 用
indexOf("D60541544F4C4A")定位 RCSP 魔数头(该十六进制串对应 RCSP 厂商自定义广播段); - 命中后,从魔数头之后偏移 8 字节(即 4 字节)处截取 6 字节,
reverse()反转字节序——广播包中 MAC 以小端序存放,需反转回标准顺序; - 将提取的 MAC 与
oldDeviceMac(由deviceBleMac去掉冒号并大写化而来)精确比较。
广播数据中同时包含旧 MAC 或反转 MAC、扫描到的 deviceId 前缀匹配时,仅打印调试日志(源码注释说明"回连打印太多有问题",即广播包数据量大,不做全部打印以避免日志风暴)。新回连方式兼容设备更换 BLE 地址的场景,是 isSupportNewRebootWay = true(startOTA 中配置)的配套能力。
旧回连方式:deviceId 直接匹配
} 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地址中部分相似),回连打印太多有问题
Log.i(TAG, "oldReconnect,mac:" + scanDevice.deviceId + ", result: " + result);
}
}
}
return result;
旧回连方式适用于设备重启后 BLE 地址不变的固件:扫描到的 scanDevice.deviceId 与升级前的 deviceId 完全相等即命中。这是最简单的识别策略,兼容性最广,但无法应对"重启后换地址"的设备。
与 OTA 生命周期的集成
配置注入:otaWrapperOption 与 OtaConfig
initRcsp() 构造 otaWrapperOption,其中三个方法与回连直接相关:
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))
}
},
isInnerReconnect:默认返回true(SDK 内部回连);应用层可通过 OTAWrapperOption 覆盖为false走自定义回连。sanDevice:回连扫描入口,扫描时长 10 秒、类型"BLE";当通信方式为"EDR"(经典蓝牙)时还会额外启动"EDR"扫描——虽然回连目标是 BLE,但保持与整体通信方式一致。connectDevice:统一连接入口,内部按设备类型构造BleDevice/SppDevice并交给bluetoothInstance.connect。
startOTA 中通过 otaConfig.isSupportNewRebootWay = true 开启新回连方式支持(见 BluetoothOTAManager.ets L127-L128),该配置让 SDK 在升级断连时发出携带 isSupportNewReconnectADV 信息的 ReconnectInfo。
回连任务的创建、登记与清理
const reconnect = new Reconnect(op, callback);
this._ReconnectMap.set(deviceId, reconnect);
reconnect.startReconnect(JL_OTA.OTAImpl.RECONNECT_DEVICE_TIMEOUT);
_ReconnectMap 以升级前的 deviceId 为键登记回连任务,支持多设备并发升级场景。回连超时时间来自 JL_OTA.OTAImpl.RECONNECT_DEVICE_TIMEOUT 常量。
回连成功时删除登记并回报新 deviceId:
onReconnectSuccess: (device: BluetoothDevice) => {
Log.i(TAG, "onReconnectSuccess : " + device);
this._ReconnectMap.delete(deviceId);
reconnectCallback.onResult(device.deviceId);
},
onReconnectFailed: () => {
Log.i(TAG, "onReconnectFailed : ");
this._ReconnectMap.delete(deviceId);
reconnectCallback.onError(JL_OTA.OtaError.ERR_OTA_RECONNECT_DEVICE_TIMEOUT,
JL_OTA.OtaError.getErrorDesc(JL_OTA.OtaError.ERR_OTA_RECONNECT_DEVICE_TIMEOUT, ""));
},
成功:通过 reconnectCallback.onResult(device.deviceId) 把设备新的 deviceId 交还 SDK,SDK 据此恢复升级会话(这也是回调参数为 OnResultCallback<string> 的原因——必须上报新地址);超时失败:删除登记,并通过 reconnectCallback.onError(ERR_OTA_RECONNECT_DEVICE_TIMEOUT, ...) 上报标准 OTA 错误码,交由 onError 链路处理。
连接失败/断开后重新扫描的重试策略:
onDeviceConnectFailed: (_dev) => {
this.otaWrapperOption?.sanDevice()
},
onDeviceConnectDisconnected: (dev) => {
this.otaWrapperOption?.sanDevice()
}
即"连不上就重新扫描"的朴素重试循环,直到超时兜底。
升级终止时停止回连
onStopOTA / onCancelOTA / onError 三个终止路径都会取出对应 Reconnect 并调用 stopReconnect()(见 BluetoothOTAManager.ets L226-L240),确保任何终止场景下回连任务都被静默清理,不会在升级结束后继续扫描设备。
RCSP 初始化成功触发回连成功判定
onRCSPInit:(deviceId: string, isInit: boolean)=>{
if (isInit) {// Rcsp回调-初始化成功
this._ReconnectMap.forEach(reconnect => {
reconnect.onDeviceConnected(new BluetoothDevice(deviceId))
})
}
回连成功以 RCSP 协议初始化成功为最终判据:连接上设备后,SDK 会对新连接执行 RCSP 初始化(onRCSPInit),只有初始化成功才说明设备可通信、升级可继续。此时对所有登记中的 Reconnect 调用 onDeviceConnected(new BluetoothDevice(deviceId))——而 Reconnect.onDeviceConnected 内部会校验 deviceId 与 connectingDevice 匹配,所以只有当前目标设备会真正触发 onReconnectSuccess。
Core Flow:回连全流程
时序图:自定义回连完整链路
sequenceDiagram
participant SDK as OTA SDK (OTAWrapper)
participant MGR as BluetoothOTAManager
participant REC as Reconnect 状态机
participant BLE as BleImpl / 蓝牙底层
participant DEV as BLE 设备
SDK->>MGR: onNeedReconnect(reConnectMsg, reconnectCallback)
MGR->>MGR: isInnerReconnect() == false ?
MGR->>REC: new Reconnect(op, callback)
REC->>REC: startReconnect(RECONNECT_DEVICE_TIMEOUT)
REC->>BLE: startScanDevice() (10s BLE 扫描)
BLE->>DEV: 扫描广播包
BLE-->>REC: onDiscoveryDevices(devices)
REC->>REC: isReconnectDevice(device)?
Note over REC: 新方式: 解析 D60541544F4C4A 广播段<br/>旧方式: deviceId 完全匹配
REC->>BLE: connectDevice(device)
BLE->>DEV: 建立连接
BLE-->>REC: onDeviceConnected(device)
REC->>REC: deviceId 匹配 connectingDevice?
REC->>REC: clearTimeout + onReconnectSuccess
REC->>MGR: onReconnectSuccess(device)
MGR->>MGR: _ReconnectMap.delete(deviceId)
MGR->>SDK: reconnectCallback.onResult(新 deviceId)
SDK->>SDK: 恢复 RCSP 会话,继续升级
流程图:回连状态转移
flowchart TD
Start([OTA 升级断连]) --> Trigger["SDK 触发 onNeedReconnect"]
Trigger --> Mode{"isInnerReconnect?"}
Mode -->|"true"| Inner["SDK 内部回连(不经过 Reconnect 类)"]
Mode -->|"false"| Create["创建 Reconnect 实例<br/>登记 _ReconnectMap"]
Create --> StartRec["startReconnect(timeout)<br/>启动扫描"]
StartRec --> Scan{"扫描发现设备?"}
Scan -->|"否 / 扫描暂停"| Rescan["onScanStop → 重新 startScanDevice"]
Rescan --> Scan
Scan -->|"是"| Match{"isReconnectDevice?"}
Match -->|"否"| Scan
Match -->|"是"| Connect["connectDevice(device)<br/>记录 connectingDevice"]
Connect --> ConnResult{"连接结果"}
ConnResult -->|"RCSP 初始化成功"| Success["onDeviceConnected<br/>onReconnectSuccess"]
ConnResult -->|"连接失败/断开"| Retry["onDeviceConnectFailed/<br/>onDeviceConnectDisconnected<br/>→ 重新扫描"]
Retry --> Scan
Success --> Done["onResult(新 deviceId)<br/>SDK 恢复升级"]
StartRec --> Timeout{"超时?"}
Timeout -->|"是"| Fail["onReconnectFailed<br/>onError(ERR_OTA_RECONNECT_DEVICE_TIMEOUT)"]
Fail --> End([升级失败])
Done --> End2([升级继续])
Inner --> End2
Start --> Stop{"OTA 停止/取消/出错?"}
Stop -->|"是"| StopRec["stopReconnect()<br/>静默终止"]
StopRec --> End
流程要点:
- 状态机只有三个稳定阶段:扫描中(反复发现/判断/重扫)、连接中(等待连接结果)、已结束(成功或超时/停止)。
- 扫描暂停自愈:
onScanStop立即重拉扫描,保证回连窗口内扫描持续有效。 - 连接失败不终止任务:回调上层后由上层重新扫描,形成重试环;唯一终止回连的"失败"是超时。
- 成功判据是双重的:连接成功 + RCSP 初始化成功(
onRCSPInit才触发onDeviceConnected),确保设备真正可通信。
Usage Examples
示例一:自定义回连的完整参考实现(BluetoothOTAManager 内)
以下是 onNeedReconnect 中自定义回连的参考实现——构造 ReconnectOp(扫描/识别/连接)、ReconnectCallback(四种结果)、创建并启动 Reconnect:
const op: ReconnectOp = {
startScanDevice: () => {
this.otaWrapperOption?.sanDevice()
},
isReconnectDevice: (scanDevice: BluetoothDevice) => {//判断设备是不是目标回连设备
Log.i(TAG, "isReconnectDevice,mac:" + scanDevice.deviceId + ",rawData:" +
JL_OTA.toHexString(scanDevice.advertiseData));
let result = false;
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;
}
}
}
return result;
},
connectDevice: (device: BluetoothDevice) => {
this.otaWrapperOption?.connectDevice(device)
}
};
const callback: ReconnectCallback = {
onReconnectSuccess: (device: BluetoothDevice) => {
Log.i(TAG, "onReconnectSuccess : " + device);
this._ReconnectMap.delete(deviceId);
reconnectCallback.onResult(device.deviceId);
},
onReconnectFailed: () => {
Log.i(TAG, "onReconnectFailed : ");
this._ReconnectMap.delete(deviceId);
reconnectCallback.onError(JL_OTA.OtaError.ERR_OTA_RECONNECT_DEVICE_TIMEOUT,
JL_OTA.OtaError.getErrorDesc(JL_OTA.OtaError.ERR_OTA_RECONNECT_DEVICE_TIMEOUT, ""));
},
onDeviceConnectFailed: (_dev) => {
this.otaWrapperOption?.sanDevice()
},
onDeviceConnectDisconnected: (dev) => {
this.otaWrapperOption?.sanDevice()
}
};
const reconnect = new Reconnect(op, callback);
this._ReconnectMap.set(deviceId, reconnect);
reconnect.startReconnect(JL_OTA.OTAImpl.RECONNECT_DEVICE_TIMEOUT);
使用说明:这是"自定义回连"的模板——应用层可复制该模式,替换 isReconnectDevice 中的识别逻辑(例如改为按设备名、服务 UUID 或厂商私有广播识别),即可在不修改 SDK 的情况下定制回连策略。onReconnectSuccess 中调用 reconnectCallback.onResult(device.deviceId) 是必须的——SDK 依赖新 deviceId 恢复升级。
示例二:使用 SDK 内部回连(默认配置)
如果不需要自定义回连,只需保持 isInnerReconnect 返回 true 并在 startOTA 中开启新回连方式即可:
isInnerReconnect: () => {
return true
},
const otaConfig: JL_OTA.OtaConfig = new JL_OTA.OtaConfig()
otaConfig.isSupportNewRebootWay = true //支持新回连方式
来源:BluetoothOTAManager.ets 与 BluetoothOTAManager.ets L127-L128
此时 onNeedReconnect 中 isInnerReconnect()==false 分支不会执行,SDK 内部自行完成回连,应用层只需在 onNeedReconnect 回调中透传消息(可选)。
API Reference
Reconnect 类
位于 ota/Reconnect.ets,回连状态机的核心实现。
constructor(op: ReconnectOp, callback: ReconnectCallback)
创建回连任务,注入操作与回调。参数:
op(ReconnectOp):扫描/识别/连接三个操作的实现;callback(ReconnectCallback):结果回调。
startReconnect(timeout: number): void
启动回连:注册 timeout 毫秒超时定时器并开始扫描。
- 超时触发时回调
reconnectCallback.onReconnectFailed(); - 调用后应立即开始扫描,由
reconnectOp.startScanDevice()完成。
stopReconnect(): void
静默终止回连:置 isFinished = true 并清除定时器。不触发任何回调。
onScanStop(): void
扫描暂停通知。若任务未结束,重新调用 reconnectOp.startScanDevice() 恢复扫描。
onDiscoveryDevices(devices: BluetoothDevice[]): void
批量设备发现入口,逐个转调 onDiscoveryDevice。
onDiscoveryDevice(device: BluetoothDevice): void
单个设备发现处理:任务未结束时,若 reconnectOp.isReconnectDevice(device) 返回 true,记录为 connectingDevice 并调用 reconnectOp.connectDevice(device)。
onDeviceConnected(device: BluetoothDevice): void
连接成功通知。仅当 device.deviceId 等于 connectingDevice.deviceId 时:清除超时定时器 → 回调 onReconnectSuccess(device) → 置 isFinished = true。
onDeviceConnectFailed(device: BluetoothDevice): void
连接失败通知。匹配当前目标设备时回调 onDeviceConnectFailed(device),不终止任务。
onDeviceConnectDisconnected(device: BluetoothDevice): void
连接断开通知。匹配当前目标设备时回调 onDeviceConnectDisconnected(device),不终止任务。
ReconnectOp 接口
| 成员 | 签名 | 语义 |
|---|---|---|
startScanDevice | () => void | 开始扫描设备(通常为 BLE 扫描) |
isReconnectDevice | (scanDevice: BluetoothDevice) => boolean | 判断扫描到的设备是否为回连目标 |
connectDevice | (device: BluetoothDevice) => void | 连接目标设备 |
ReconnectCallback 接口
| 成员 | 签名 | 触发时机 |
|---|---|---|
onReconnectSuccess | (device: BluetoothDevice) => void | 回连成功(连接 + RCSP 初始化成功) |
onReconnectFailed | () => void | 回连超时失败 |
onDeviceConnectFailed | (device: BluetoothDevice) => void | 连接目标设备失败(可重试) |
onDeviceConnectDisconnected | (device: BluetoothDevice) => void | 连接建立后断开(可重试) |
相关常量与错误码
| 标识 | 位置 | 说明 |
|---|---|---|
JL_OTA.OTAImpl.RECONNECT_DEVICE_TIMEOUT | SDK 常量 | 回连超时时间(毫秒),传给 startReconnect |
JL_OTA.OtaError.ERR_OTA_RECONNECT_DEVICE_TIMEOUT | SDK 错误码 | 回连超时错误,经 reconnectCallback.onError 上报 |
JL_OTA.ReconnectInfo | SDK 类型 | 回连信息:deviceBleMac(原 BLE 地址)、isSupportNewReconnectADV(是否支持新回连广播) |
JL_OTA.OnResultCallback<string> | SDK 类型 | 回连结果回调:onResult(新 deviceId) / onError(code, desc) |
USER_CUSTOM_RECONNECT | common/Constant.ets | 自定义回连场景标识常量 |
Failure Modes, Edge Cases & Concurrency
超时失败(主要失败模式)
回连超时后 onReconnectFailed 触发,BluetoothOTAManager 以 ERR_OTA_RECONNECT_DEVICE_TIMEOUT 上报错误。原因分析:设备重启时间过长、广播包丢失、或 isReconnectDevice 识别条件不满足(如新回连方式下广播中无 D60541544F4C4A 魔数头)。超时兜底设计保证了升级流程不会无限挂起。
连接失败/断开的无限重试风险
onDeviceConnectFailed / onDeviceConnectDisconnected 触发重新扫描,形成重试环。重试本身无次数限制,依赖超时定时器兜底——这正是 startReconnect 先设超时再扫描的原因:确保重试环最终收敛。若上层自定义实现忽略了超时(例如未调用 startReconnect),则可能无限重试,属实现风险。
多设备并发回连
_ReconnectMap 以 deviceId 为键支持多设备并发。Reconnect.onDeviceConnected 内部用 connectingDevice.deviceId 精确匹配,防止 A 设备的连接事件误触发 B 设备的成功回调。RCSP 初始化成功事件(onRCSPInit)会广播给所有登记中的 Reconnect,但只有目标匹配者响应。
扫描暂停与系统资源竞争
BLE 扫描是独占/耗电操作,可能被系统暂停。onScanStop 的自愈重扫保证了回连窗口内扫描尽力持续;但也意味着若系统反复暂停扫描,重扫会产生额外功耗与射频开销,属于可接受的工程权衡。
广播包解析的边界条件
- 新回连方式解析依赖魔数头
D60541544F4C4A恰好出现在广播数据中;若广播被截断或分段(advertiseData不完整),indexOf返回 -1,该设备被跳过,等待下一轮扫描发现; - MAC 提取后需
reverse()反转字节序,若固件广播序与预期不符,识别失败; oldDeviceMac为空时(RCSP 协议未拿到设备 BLE 地址),新方式直接跳过精确匹配,仅靠日志提示。
与 OTA 终止路径的竞态
onStopOTA / onCancelOTA / onError 调用 stopReconnect() 静默终止;而回连成功路径也会置 isFinished。两者并发时,stopReconnect 先置位可阻止后续事件回调;若 onReconnectSuccess 已执行完再终止,_ReconnectMap.delete(deviceId) 幂等,无副作用。
Performance & Operational Considerations
- 超时窗口:回连超时取
RECONNECT_DEVICE_TIMEOUT,需大于设备重启+广播时间,否则会误报超时;过大会延长失败感知时间。实际调优应按固件重启耗时设定。 - 扫描参数:参考实现中扫描时长 10 秒(
startScan(10 * 1000, "BLE")),每轮扫描可能多次触发onDiscoveryDevices;isReconnectDevice内的日志打印做了定向抑制(仅对包含旧 MAC/前缀匹配的设备打印),避免广播数据量大时日志风暴。 - 多任务开销:每个回连任务独立持有定时器与扫描触发,多设备并发时扫描请求会被去重/合并到
BleImpl层;任务结束后务必走stopReconnect或成功路径清理定时器,防止句柄泄漏。 - 日志观察点:
BluetoothOTAManager中以TAG输出isReconnectDevice(含 rawData)、onReconnectSuccess、onReconnectFailed、newReconnect/oldReconnect等日志,是线上排查回连问题(识别失败、超时、误连)的首要入口。
Extension Points
- 自定义回连实现(主要扩展点):将
OTAWrapperOption.isInnerReconnect改为返回false,并在OTAUpgradeCallback.onNeedReconnect中按 示例一 的模板实现自己的ReconnectOp/ReconnectCallback。可定制:设备识别策略(名称/服务 UUID/私有广播段)、连接参数、重试间隔。 ReconnectOp.isReconnectDevice识别逻辑替换:这是最常见的定制点——例如按厂商私有广播、设备名称前缀、或自定义魔数识别设备,而无需改动Reconnect状态机。- 重试策略定制:
onDeviceConnectFailed/onDeviceConnectDisconnected回调中除重新扫描外,可插入退避等待、次数限制等逻辑(BluetoothOTAManager参考实现仅直接重扫)。 - 超时参数调整:
startReconnect(JL_OTA.OTAImpl.RECONNECT_DEVICE_TIMEOUT)的入参可替换为应用自定义值,按设备固件特性调整回连窗口。
Related Links
- OTA 升级流程:回连机制的上游触发方——
onNeedReconnect由 OTA 升级流程在设备断连后发出。 - 蓝牙连接管理:
BleImpl/SppImpl提供connect/disconnect/sendData底层能力,回连通过bluetoothInstance调用。 - SPP 升级:经典蓝牙通道升级,与 BLE 回连相互独立(回连目前仅针对 BLE)。
- README 功能特性:自动回连作为单备份 OTA 的核心体验特性。
- 核心源码:Reconnect.ets、BluetoothOTAManager.ets。