杰理 SDK 文档中心
首页
首页
  • 项目概览

    • 项目简介与核心能力
    • 快速开始
    • 工程结构与依赖库
  • 核心功能

    • RCSP OTA 升级流程
    • BLE 升级通道
    • SPP 升级通道
    • 自动回连机制
  • 蓝牙通信架构

    • 蓝牙抽象层与基础组件
    • BLE 模块实现
    • SPP 模块实现
    • 蓝牙管理与 OTA 管理器
  • 示例应用

    • 应用入口与启动流程
    • 主界面与设备连接交互
    • 关于、日志与辅助页面
  • 调试与运维

    • 日志系统与调试技巧
    • 问题排查与技术支持
  • 开发者指南

    • SDK 版本历史
    • 集成与二次开发指南

自动回连机制

单备份 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 流程继续推进。

核心设计思路:

  1. 可插拔的回连操作抽象:Reconnect 本身不关心底层蓝牙细节,只依赖 ReconnectOp(扫描、判断、连接三个操作)与 ReconnectCallback(四种结果回调)两个接口,上层可注入任意实现。
  2. 两种回连模式共存:SDK 内部回连(isInnerReconnect() 返回 true)直接由 SDK 完成;自定义回连(返回 false)由应用层在 onNeedReconnect 中自行实现,且源码提供了完整的参考实现。
  3. 新旧两代设备识别协议:新回连方式依赖 RCSP 协议在广播包中携带设备新 BLE 地址(D60541544F4C4A 魔数头 + 反转 MAC),旧回连方式直接比较扫描到的 deviceId,兼容不同固件版本。
  4. 超时兜底:回连在 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&lt;string, Reconnect&gt;"]
    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;
  }

来源:Reconnect.ets

关键字段:

字段类型作用
reconnectOpReconnectOp注入的回连操作(扫描/识别/连接),由上层提供
reconnectCallbackReconnectCallback注入的结果回调,回连生命周期事件的上报通道
isFinishedboolean回连是否已结束(成功或显式停止),所有事件处理器都会先检查它
connectingDeviceBluetoothDevice | undefined当前正在尝试连接的目标设备,用于事件匹配
timeoutNumbernumbersetTimeout 句柄,用于回连超时控制

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)
  }

来源:Reconnect.ets

设计意图:

  • 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)
      }
    }
  }

来源:Reconnect.ets

四个事件入口的行为:

  1. onScanStop():蓝牙扫描是耗电且独占的操作,可能被系统或其他逻辑暂停。扫描一停,Reconnect 立刻重新拉起扫描(startScanDevice),形成"扫描 → 暂停 → 重扫"的自愈循环,直到找到目标设备或超时。
  2. onDiscoveryDevices(devices):批量上报的入口,内部逐个转调 onDiscoveryDevice,保持单一设备处理逻辑。
  3. onDiscoveryDevice(device):核心决策点。先检查 isFinished,再调用 reconnectOp.isReconnectDevice(device) 判断该设备是否为回连目标;命中则记录到 connectingDevice 并发起连接。注意:扫描会持续发现设备,因此 connectingDevice 可能被多次覆盖——只有最后命中的设备会成为"当前连接目标"。
  4. 连接结果(成功/失败/断开)处理器都要求 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;
  }

来源:Reconnect.ets

成功路径的关键细节: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;
}

来源:Reconnect.ets

接口语义解读:

  • 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内部回连方式

来源:BluetoothOTAManager.ets

流程分支:

  • 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();
                  }

来源:BluetoothOTAManager.ets

解析算法拆解:

  1. 将广播数据 advertiseData 转成大写十六进制字符串;
  2. 用 indexOf("D60541544F4C4A") 定位 RCSP 魔数头(该十六进制串对应 RCSP 厂商自定义广播段);
  3. 命中后,从魔数头之后偏移 8 字节(即 4 字节)处截取 6 字节,reverse() 反转字节序——广播包中 MAC 以小端序存放,需反转回标准顺序;
  4. 将提取的 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;

来源:BluetoothOTAManager.ets

旧回连方式适用于设备重启后 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))
        }
      },

来源:BluetoothOTAManager.ets

  • 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);

来源:BluetoothOTAManager.ets

_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, ""));
            },

来源:BluetoothOTAManager.ets

成功:通过 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()
            }

来源:BluetoothOTAManager.ets

即"连不上就重新扫描"的朴素重试循环,直到超时兜底。

升级终止时停止回连

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))
          })
        }

来源:BluetoothOTAManager.ets

回连成功以 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);

来源:BluetoothOTAManager.ets

使用说明:这是"自定义回连"的模板——应用层可复制该模式,替换 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_TIMEOUTSDK 常量回连超时时间(毫秒),传给 startReconnect
JL_OTA.OtaError.ERR_OTA_RECONNECT_DEVICE_TIMEOUTSDK 错误码回连超时错误,经 reconnectCallback.onError 上报
JL_OTA.ReconnectInfoSDK 类型回连信息:deviceBleMac(原 BLE 地址)、isSupportNewReconnectADV(是否支持新回连广播)
JL_OTA.OnResultCallback<string>SDK 类型回连结果回调:onResult(新 deviceId) / onError(code, desc)
USER_CUSTOM_RECONNECTcommon/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

  1. 自定义回连实现(主要扩展点):将 OTAWrapperOption.isInnerReconnect 改为返回 false,并在 OTAUpgradeCallback.onNeedReconnect 中按 示例一 的模板实现自己的 ReconnectOp/ReconnectCallback。可定制:设备识别策略(名称/服务 UUID/私有广播段)、连接参数、重试间隔。
  2. ReconnectOp.isReconnectDevice 识别逻辑替换:这是最常见的定制点——例如按厂商私有广播、设备名称前缀、或自定义魔数识别设备,而无需改动 Reconnect 状态机。
  3. 重试策略定制:onDeviceConnectFailed / onDeviceConnectDisconnected 回调中除重新扫描外,可插入退避等待、次数限制等逻辑(BluetoothOTAManager 参考实现仅直接重扫)。
  4. 超时参数调整: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。
Prev
SPP 升级通道