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

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

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

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

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

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

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

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 升级通道就是前者的完整实现:

  1. 物理层:调用 HarmonyOS @kit.ConnectivityKit 提供的 ble、access、connection 等能力完成 BLE 广播扫描、GATT 连接与特征值读写。
  2. 协议层:约定杰理 RCSP 的 GATT 服务 UUID(0000AE00-...)、写特征值(0000AE01-...)与通知特征值(0000AE02-...),MTU 默认 512,实现大块升级数据的拆分与流控。
  3. 业务层:BluetoothOTAManager 将扫描、连接、发数等操作包装成 RCSP OTA SDK 要求的回调(OTAWrapperOption),使 OTAWrapper / JL_OTA 无需感知底层是 BLE 还是 SPP。
  4. 异常恢复:升级过程中设备会断开并进入升级模式(可能更换 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:扫描状态变化,回调携带 ScanStateInfo
  • ScanEventType.SCAN_DEVICE_FIND:发现设备,回调携带 BleDevice[]
  • BaseConnectEventTypeConstant.CONNECT_STATE_CHANGE:连接状态变化,回调携带 ConnectStateInfo<BleDevice>
  • BleConnectEventTypeConstant.CONNECT_MTU_CHANGE:MTU 协商结果,回调携带 MTUInfo
  • BleConnectEventTypeConstant.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

流程要点:

  1. 扫描阶段:startOTA 触发 startScan(10s, "BLE"),BleImpl 以均衡占空比扫描并持续上报 BleDevice[],页面据此展示可升级设备列表。
  2. 连接阶段:选择设备后经 BluetoothManager.connect → BleImpl.connect 完成 GATT 连接与 MTU 协商(目标 512),连接状态与 MTU 结果通过事件回调上报。
  3. 认证与数据传输:RCSP SDK 通过 OTAWrapperOption.sendData 把每个协议帧写入 AE01;设备应答经 AE02 通知特征值回传,BleImpl 以 CONNECT_BLE_CHARACTERISTIC_CHANGE 事件交给 OTA 层解析,驱动升级进度。
  4. 升级回连:设备重启进升级模式后连接断开,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(连接参数)

选项类型默认值说明
mtunumber512GATT MTU 大小,有效范围 23~512,决定单包可承载数据量
notifyCharacteristicArrayArray<ble.BLECharacteristic>[AE02 通知特征值]使能的接收特征值列表
necessaryNotifyCharacteristicArrayArray<ble.BLECharacteristic>[AE02 通知特征值]必须使能的特征值,连接必要条件
writeCharacteristicArrayArray<ble.BLECharacteristic>[AE01 写特征值]数据发送特征值列表
RCSP_UUID_SERVICE(模块常量)string0000AE00-0000-1000-8000-00805F9B34FBRCSP GATT 服务 UUID
RCSP_UUID_WRITE(模块常量)string0000AE01-0000-1000-8000-00805F9B34FB数据写特征值 UUID
RCSP_UUID_NOTIFY(模块常量)string0000AE02-0000-1000-8000-00805F9B34FB设备通知特征值 UUID

扫描参数(BleScanSettingConfigure 对应字段)

选项类型默认值说明
scanTimeOutnumber由调用方指定(OTA 场景为 10 秒)单次扫描超时,超时自动停止并通知
系统扫描 intervalnumber500ble.startBLEScan 的扫描间隔(毫秒)
系统扫描 dutyModeenumSCAN_MODE_BALANCED均衡占空比,功耗与发现速度折中
系统扫描 matchModeenumMATCH_MODE_AGGRESSIVE激进匹配,最大化设备发现率

OTAWrapperOption(通道桥接配置)

选项类型默认值说明
isUseAuth() => booleantrue升级会话是否需要 RCSP 认证
isInnerReconnect() => booleantrue是否使用 SDK 内置回连;设为 false 时走自定义回连
sanDevice() => voidBLE 扫描 10sSDK 要求重新扫描设备时的回调
connectDevice(device) => voidBLE 连接回连/连接设备回调,当前固定按 BLE 处理
disconnectDevice(device) => void按类型分发断开设备回调,按已连接列表判断类型
sendData(device, data) => voidBLE 写 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)

方法参数返回说明
connectdevice, success?, fail?void按设备类型分发到 BLE/SPP
disconnectdevicevoid按设备类型分发
getConnectedDevicetype?("EDR"/"BLE")设备数组未传 type 时按 communicationWay
isConnecting / isConnecteddeviceboolean连接状态查询
startScan / refreshScan / stopScanscanTimeOut?, 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

  1. 自定义回连:将 OTAWrapperOption.isInnerReconnect 设为 false,在 onNeedReconnect 中实现自己的扫描/识别/连接逻辑(参考 ReconnectOp),连接成功后调用 reconnectCallback.onResult(新deviceId) 通知 SDK。
  2. 新增通信通道:BluetoothManager 按 BluetoothDeviceType("BLE"/"EDR")分发,新增通道只需实现 IBleScan/IBleConnect 风格的接口(或对应 SPP 接口),在 BluetoothManager 增加分支并扩展 BluetoothDeviceType。
  3. 自定义 RCSP 指令:BleImpl.sendData 是通用写入口,业务方可直接按 (serviceId, characteristicId) 组合发送非升级类指令(如设备状态查询、EQ 设置等),前提是遵循 RCSP 帧格式。
  4. 扫描策略调优:通过 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/ 目录)
Prev
RCSP OTA 升级流程
Next
SPP 升级通道