杰理 SDK 文档中心
首页
首页
  • 概览与快速开始

    • 项目概述与能力总览
    • 快速开始与 SDK 集成
  • SDK 核心接口

    • 发送接口 BleMethod
    • 接收接口 BleEventStream
    • 数据模型与常量定义
  • 平台原生实现

    • Android 原生层
    • iOS 原生层架构
    • iOS 蓝牙管理与 SDK 运行
    • 辅助连接与广播音箱
  • OTA 升级功能

    • 升级流程与传输通道
    • 自动回连机制
    • 复用空间升级
    • 自定义命令
  • 示例应用

    • 页面结构与用户旅程
    • 设备扫描与连接管理
    • 固件文件管理
    • 升级执行与状态展示
    • 设置与调试
  • 文档与支持

    • 接口文档与收发说明
    • 调试与问题排查

复用空间升级

复用空间升级(Reuse Space Upgrade)是 JL OTA Flutter 插件在 V1.1.0 中引入的特殊 OTA 升级流程:当设备(如单备份方案的耳机/手表)不具备独立升级分区时,App 复用设备上的资源存储空间来完成固件(Firmware)与资源(Source)的升级。该能力由设备能力位 isSupportReuseSpaceOTA 驱动,配合 otaSourceMode、bootloaderType、otaStatus 等状态决定走 Loader 升级、资源升级还是普通升级路径。

Purpose and Scope

本页面向 JL_OTA Flutter 插件的复用空间特殊升级流程,覆盖:

  • 复用空间能力的检测与状态模型(isSupportReuseSpaceOTA、otaSourceMode、bootloaderType、otaPartition、otaStatus);
  • iOS 侧升级流程的决策逻辑(JLBleManager.otaFeatureResult: 与 JL_RunSDK 中的对应实现);
  • OTA 流程处理器 JLOtaFlowProcessor 的两段式升级(otaFirstStep / otaSecondStep)与命令集 cmd_OTA_1 ~ cmd_OTA_8;
  • 单备份 OTA 自动回连(JLOtaReConnectMgr)、OTA 文件下载与授权校验(JLOTAFile)、超时管理(JLOtaTimeoutManager)等配套机制。

以下主题属于同级/兄弟页面,不在本页展开:常规 OTA 升级整体流程(见「OTA 升级总览」)、双备份分区升级(JL_PartitionDouble)、表盘等资源更新(通过 DialManager 更新资源)、蓝牙连接管理(JLBleManager / JL_BLEMultiple)与自定义命令(JLOtaCustom)。

概述

在传统双备份(Double Partition)方案中,设备拥有独立的升级分区,App 可以在后台分区写入新固件,升级完成后切换启动。而复用空间(Reuse Space)方案通常用于单备份(Single Partition)设备——设备没有专用升级区,必须复用资源存储空间(例如表盘、资源文件所在区域)临时承载升级数据。这类升级属于「特殊升级流程」,自 SDK V1.1.0(2026/07/03)起在 Android 与 iOS 侧获得支持,官方变更记录原文为「增加复用空间特殊升级流程支持」:

| V1.1.0 | 2026/07/03 | Android:
1.增加复用空间特殊升级流程支持.
2.增加单备份OTA自动回连BLE功能.
3.增加Gatt Over BR/EDR连接方式支持.
4.增加自定义命令. |

来源:Jieli OTA Upgrade (Flutter) Send:Receive Interface Introduction.md

复用空间升级的关键特征:

  1. 能力探测:设备信息中包含 isSupportReuseSpaceOTA 位,表示该设备是否支持复用空间特殊升级流程。设备模型 JLModel_Device 在打印设备信息时输出 --->isSupportReuseSpaceOTA:%d,OTA 管理器 JL_OTAManager 同样持有该属性。
  2. 模式化升级:otaSourceMode 决定升级内容语义——Disable(0)、Source Update(1)、Only Update Source(2)、Only Update Firmware(3)。复用空间升级通常要求模式为 Normal 或 Disable,此时由 bootloaderType 决定进入 Loader 升级还是资源升级。
  3. 强制升级状态:当设备处于 JL_OtaStatusForce(强制升级)时,升级不可跳过,App 必须完成升级才能恢复设备功能;普通状态(JL_OtaStatusNormal)下则按耳机类型与 source mode 走常规路径。
  4. 两段式流程:JLOtaFlowProcessor 将升级拆为 otaFirstStep(首段:协商/进入升级)与 otaSecondStep(次段:传输数据/退出升级),配合 JL_PartitionSingle / JL_PartitionDouble 分区模型。
  5. 自动回连:单备份 OTA 过程中设备会重启进入 Loader,BLE 链路必然断开,JLOtaReConnectMgr 负责按 MAC 地址或 UUID 自动回连,对应 JL_OTAResultReconnectWithMacAddr / JL_OTAResultReconnectWithUUid。

架构

flowchart TD
    subgraph sg_App["Flutter 应用层"]
        UI["OTA 页面 / ota_connection_manager<br/>ota_file_manager"]
    end

    subgraph sg_Plugin["JL_OTA 插件层(Platform Channel)"]
        BLEMgr["JLBleManager(BleManager 通道)"]
        SDK["JL_RunSDK(SDK 通道)"]
    end

    subgraph sg_SDK["iOS 原生 OTA SDK(JL_OTALib)"]
        OTAMgr["JL_OTAManager<br/>持有 isSupportReuseSpaceOTA"]
        Flow["JLOtaFlowProcessor<br/>otaFirstStep / otaSecondStep"]
        Data["JLOtaDataHandler<br/>cmd_OTA_1 ~ cmd_OTA_8"]
        Reconn["JLOtaReConnectMgr<br/>单备份自动回连"]
        Timeout["JLOtaTimeoutManager"]
        File["JLOTAFile<br/>授权校验 + 下载"]
        Custom["JLOtaCustom 自定义命令"]
    end

    subgraph sg_Ext["外部服务"]
        License["profile.jieliapp.com<br/>license 授权服务器"]
    end

    subgraph sg_Device["蓝牙设备"]
        Dev["固件 / Loader / 资源分区<br/>(复用空间)"]
    end

    UI --> BLEMgr
    UI --> SDK
    BLEMgr --> OTAMgr
    SDK --> OTAMgr
    OTAMgr --> Flow
    OTAMgr --> Data
    Flow --> Data
    Data -->|"BLE 特征值写"| Dev
    Flow --> Reconn
    Flow --> Timeout
    OTAMgr --> File
    OTAMgr --> Custom
    File -->|"HTTPS check / check2 / 下载"| License

架构说明:

  • Flutter 应用层通过 ota_connection_manager.dart、ota_file_manager.dart 等数据管理器发起升级并接收进度回调(示例工程位于 code/JL_OTA/example/lib/data/)。
  • 插件层提供两条 iOS 实现路径:BleManager/JLBleManager.m(由插件自身管理蓝牙)与 SDKBleManager/JL_RunSDK.m(交由 JL_BLEKit SDK 管理蓝牙)。两者在 otaFeatureResult: 回调中实现了几乎相同的复用空间决策逻辑。
  • **iOS OTA SDK(JL_OTALib)**是本能力的核心:JL_OTAManager 是总入口,JLOtaFlowProcessor 是流程状态机,JLOtaDataHandler 负责命令打包/解包,JLOtaReConnectMgr 负责断链回连,JLOTAFile 负责固件包授权与下载。
  • 设备端存在固件、Loader、资源分区;复用空间升级会把资源区当作临时升级缓冲区,因此 bootloaderType 与 otaSourceMode 的组合直接决定流程分支。

能力检测与状态模型

复用空间升级不是一个独立的 API,而是一组设备能力位与模式枚举的组合。升级前必须读取设备信息并解析出以下状态:

状态字段类型含义关键取值
isSupportReuseSpaceOTABOOL设备是否支持复用空间特殊升级流程YES / NO
otaStatus枚举升级状态/类型JL_OtaStatusNormal(普通)、JL_OtaStatusForce(强制)
otaSourceMode枚举升级内容模式Disable(0)、Source Update(1)、Only Update Source(2)、Only Update Firmware(3)
bootloaderType枚举是否需要/进入 LoaderJL_BootLoaderYES / 其他(资源模式)
otaPartition枚举分区方案JL_PartitionSingle(单备份)、JL_PartitionDouble(双备份)
otaHeadset枚举是否耳机设备JL_OtaHeadsetYES / NO
otaReconnectType枚举回连方式MAC 地址回连 / UUID 回连

设备信息由 JLOtaDeviceInfoParser 的类方法 +[JLOtaDeviceInfoParser parseInfo:intoManager:] 解析并写入 JL_OTAManager;解析结果包括分区类型(Single/Double)、升级状态(Normal/Force)、Loader 需求(no need download loader/need download loader)以及 source mode 的四种语义(Disable/Source Update/Only Update Source/Only Update Firmware)。同时 JLModel_Device(JL_BLEKit)在设备信息打印中输出 --->isSupportReuseSpaceOTA:%d,并在日志中提示「Single backup communication method %d, iOS currently only supports BLE」,即单备份方案在 iOS 上仅支持 BLE 通道。

这些标识来自 JL_OTALib 与 JL_BLEKit 的符号表(isSupportReuseSpaceOTA、setIsSupportReuseSpaceOTA:、otaSourceMode、otaPartition、bootloaderType 等),说明复用空间能力同时存在于设备模型层与 OTA 管理器层,二者共同驱动流程决策。

升级决策逻辑(iOS 实现)

复用空间升级的入口分支位于 OTA 功能探测回调 otaFeatureResult:。以下是 BleManager/JLBleManager.m 中的真实实现:

-(void)otaFeatureResult:(JL_OTAManager *)manager{
    
    kJLLog(JLLOG_DEBUG, @"getDeviceInfo:%d",__LINE__);
    if (manager.otaStatus == JL_OtaStatusForce) {
        if (manager.isSupportReuseSpaceOTA) {
            if (manager.otaSourceMode  == JLSourcesExtendModeNormal
                || manager.otaSourceMode == JLSourcesExtendModeDisable) {
                if (manager.bootloaderType == JL_BootLoaderYES) {
                    kJLLog(JLLOG_DEBUG, @"---> Entering Loader upgrade.");
                }else{
                    kJLLog(JLLOG_DEBUG, @"---> Entering resource upgrade.");
                }
            }
            kJLLog(JLLOG_DEBUG, @"---> Current mode is: otaSourceMode: %d, bootloaderType: %d", manager.otaSourceMode, manager.bootloaderType);
        }
        if (self.selectedOtaFilePath) {
            [self otaFuncWithFilePath:self.selectedOtaFilePath];
        } else {
            if (_getCallback) {
                _getCallback(true);
                _getCallback = nil;
            }
        }
        return;
    } else {
        if (manager.otaHeadset == JL_OtaHeadsetYES) {
            if (self.selectedOtaFilePath) {
                [self otaFuncWithFilePath:self.selectedOtaFilePath];
            } else {
                if (_getCallback) {
                    _getCallback(true);
                    _getCallback = nil;
                }
            }
            return;
        }
        if (manager.otaSourceMode == JLSourcesExtendModeFirmwareOnly) {
            if (self.selectedOtaFilePath) {
                [self otaFuncWithFilePath:self.selectedOtaFilePath];
            } else {
                if (_getCallback) {
                    _getCallback(true);
                    _getCallback = nil;
                }
            }
            return;
        }
    }
    dispatch_async(dispatch_get_main_queue(), ^{
        /*--- Get common information ---*/
        [self->_otaManager cmdSystemFunction];
        if (self->_getCallback) {
            self->_getCallback(false);
            self->_getCallback = nil;
        }
    });

}

来源:JLBleManager.m

决策树解读

flowchart TD
    Start(["otaFeatureResult(manager)"]) --> Force{"otaStatus ==<br/>JL_OtaStatusForce?"}
    Force -->|"是"| Reuse{"isSupportReuseSpaceOTA?"}
    Reuse -->|"是"| Mode{"otaSourceMode 为<br/>Normal 或 Disable?"}
    Mode -->|"是"| Boot{"bootloaderType ==<br/>JL_BootLoaderYES?"}
    Boot -->|"是"| Loader["Loader 升级<br/>(日志 Entering Loader upgrade)"]
    Boot -->|"否"| Res["资源升级<br/>(日志 Entering resource upgrade)"]
    Reuse -->|"否"| Path1{"已选 OTA 文件?"}
    Mode -->|"否"| Path1
    Force -->|"否"| Headset{"otaHeadset ==<br/>JL_OtaHeadsetYES?"}
    Headset -->|"是"| Path2{"已选 OTA 文件?"}
    Headset -->|"否"| FwOnly{"otaSourceMode ==<br/>FirmwareOnly?"}
    FwOnly -->|"是"| Path2
    Path1 -->|"是"| Start["otaFuncWithFilePath 启动升级"]
    Path2 -->|"是"| Start
    Path1 -->|"否"| CbTrue["getCallback(true)"]
    Path2 -->|"否"| CbTrue
    FwOnly -->|"否"| Sys["cmdSystemFunction<br/>获取通用信息"]
    Loader --> Start
    Res --> Start
    Sys --> CbFalse["getCallback(false)"]

决策逻辑的要点与设计意图:

  1. 强制升级优先:JL_OtaStatusForce 状态下,设备要求必须升级。此时如果设备支持复用空间(isSupportReuseSpaceOTA == YES)且 otaSourceMode 为 Normal/Disable,则进一步看 bootloaderType——YES 表示需要先进入 Loader(Entering Loader upgrade),否则直接进行资源升级(Entering resource upgrade)。日志输出用于确认当前模式组合。
  2. 文件就绪即升级:无论哪条分支,只要已选定 OTA 文件(selectedOtaFilePath)就立即调用 otaFuncWithFilePath: 开始升级;否则通过 _getCallback(true) 通知上层「已就绪、等待文件」。
  3. 普通状态分流:非强制状态下,耳机设备(JL_OtaHeadsetYES)或 otaSourceMode == JLSourcesExtendModeFirmwareOnly(仅升级固件)直接走升级;否则视为普通功能设备,通过 cmdSystemFunction 拉取通用系统信息并回调 _getCallback(false),进入常规 OTA 前的信息准备阶段。
  4. 主线程切换:cmdSystemFunction 与回调派发被包在 dispatch_async(dispatch_get_main_queue(), ...) 中,保证 UI 层回调在主线执行。

SDKBleManager/JL_RunSDK.m 中实现了同样的分支(if (otaManager.otaStatus == JL_OtaStatusForce) { if (otaManager.isSupportReuseSpaceOTA) { ... } }),两条 iOS 通道行为一致,只是蓝牙栈来源不同。

来源:JL_RunSDK.m

启动升级与取消

确认文件后通过 OTA 管理器启动升级,Option 用于指定回连所需的蓝牙服务/特征 UUID 与写模式:

option.serviceUUID = @"AE00";
[_otaManager cmdUpgrade:otaData Option:option Result:^(JL_OTAResult result, float progress) {
    for (id<JLBleManagerOtaDelegate> objc in self.delegates) {
        if([objc respondsToSelector:@selector(otaProgressWithOtaResult:withProgress:)]){
            [objc otaProgressWithOtaResult:result withProgress:progress];
        }
    }
}];

来源:JLBleManager.m

取消升级则发送 cmdOTACancelResult:,将设备返回的 status 透传给调用方:

- (void)otaFuncCancel:(CANCEL_CALLBACK _Nonnull)result{
    
    [_otaManager cmdOTACancelResult:^(uint8_t status, uint8_t sn, NSData * _Nullable data) {
        result(status);
    }];
}

来源:JLBleManager.m

两段式升级流程(JLOtaFlowProcessor)

JLOtaFlowProcessor 是升级流程的状态机,startOTAWithData:resultBlock: 为流程入口。从其日志与符号可还原出以下结构:

  • 分区模型:JL_PartitionSingle(单备份)与 JL_PartitionDouble(双备份)分别对应 otaPartitionSingle: 与 otaPartitionDouble 两条实现路径;复用空间升级属于单备份路径。
  • 升级状态:JL_OtaStatusNormal / JL_OtaStatusForce,强制模式下升级不可取消。
  • 特殊更新模式:日志 OTA Special Update Mode. Update firmware only 与 OTA Special Update Mode. Update source only,should use DialManager to update sources 表明——Only Update Firmware 时仅传输固件;Only Update Source 时固件已就绪,资源部分交由 DialManager(表盘管理器)完成。
  • 两段执行:otaFirstStep:(首段:cmdOTA_1_GetMarkSeekResult: 获取烧写偏移、cmdOTA_2_AskForUpgrade:Result: 询问是否可以升级、cmdOTA_3_EnterUpgradeResult: 进入升级模式)与 otaSecondStep(次段:cmdOTA_6_QueryUpgradeStatusResult: 查询升级状态、cmdUpdateBuffer: 分包传输数据、cmdOTA_7_RebootDevice:Result: 重启设备)。

数据通道由 JLOtaDataHandler 承载,命令集如下(对应 cmd_OTA_1 ~ cmd_OTA_8、cmd_OTA_rt、cmd_OTA_url):

命令发送方法(JLOtaDataHandler)用途
cmd_OTA_1sendMarkSeekWithSN:UUID:获取写入偏移(Mark/Seek)
cmd_OTA_2sendCheckCanUpdateWithSN:data:UUID:询问是否可以升级
cmd_OTA_3sendEnterUpdateWithSN:UUID:进入升级模式
cmd_OTA_4sendExitUpdateWithSN:UUID: / sendExitUpdateSuccessWithSN:UUID:退出/完成升级
cmd_OTA_5sendOtaDataWithSN:data:UUID: / sendOtaDataErrorWithSN:UUID:传输 OTA 数据 / 上报数据错误
cmd_OTA_6sendOtaStatusWithSN:UUID:查询升级状态
cmd_OTA_7sendRebootWithSN:type:UUID:重启设备
cmd_OTA_8sendBleSppWithSN:type:reconnectType:UUID:回连类型协商(SPP/BLE)
cmd_OTA_rtcmdOTA_Route:withOtaReconnectType:Result:回连路由决策
cmd_OTA_urldownloadOtaFileWithUrl:(JLOTAFile)按 URL 下载固件包

数据包在 JLOtaDataHandler 内通过 rcspMakePkg:needRes:Opcode:playload: 打包、parseInputPackage: 解析,并支持 CRC16(addCrc:、isSupportCRC)。单次发送通过 cmdUpdateBuffer: 按 MTU 分包,otaSent / otaLength 记录传输进度,maxLostCount 控制最大丢包容忍次数。

单备份自动回连(JLOtaReConnectMgr)

单备份升级中设备重启进入 Loader 会导致 BLE 断开,JLOtaReConnectMgr 专门负责恢复链路:

  • 回连方式由 JLOtaReConnectOption 描述:deviceAuthorize(设备授权)、authKey、serviceUUID、writeUUID、readUUID、isWriteWithResponse;JLOtaReConnectMgr 通过 CBCentralManager 扫描广播数据中的厂商 MAC 匹配目标设备。
  • 流程接口:reconnectWithMac:Result: 与 reconnectWithUUID:Result: 分别按 MAC 地址与 UUID 回连;startTimer/timerAction/maxTimerCount 实现带超时次数的扫描循环;otaDataSend: 在回连成功后继续发送未传完的数据。
  • 对外结果:JL_OTAResultReconnectWithMacAddr 与 JL_OTAResultReconnectWithUUid 两种回连结果通知上层;updateRelinkType: 在流程中根据当前进度切换回连类型。
  • 强制升级提示:日志 Reboot without resp, Suggest adding the 0xe7 command to the firmware before disconnecting Bluetooth 说明在断开蓝牙前,固件应实现 0xe7 命令以保证重启时序。

核心流程

以下序列图展示一次完整的复用空间(单备份)升级过程。步骤基于 JLOtaFlowProcessor 的 otaFirstStep / otaSecondStep 与 JLOtaDataHandler 的命令集还原:

sequenceDiagram
    participant App as Flutter App
    participant BLE as JLBleManager / JL_RunSDK
    participant OM as JL_OTAManager
    participant FP as JLOtaFlowProcessor
    participant DH as JLOtaDataHandler
    participant RM as JLOtaReConnectMgr
    participant Dev as 设备(Loader/资源区)

    App->>BLE: 发起升级(选择 OTA 文件)
    BLE->>OM: otaFeatureResult 探测能力
    OM-->>BLE: isSupportReuseSpaceOTA / otaStatus / otaSourceMode
    BLE->>OM: cmdUpgrade:Option:Result:
    OM->>FP: startOTAWithData:resultBlock:
    FP->>FP: otaFirstStep
    FP->>DH: cmd_OTA_1 sendMarkSeek
    DH->>Dev: 获取写入偏移
    Dev-->>FP: MarkSeek 结果
    FP->>DH: cmd_OTA_2 sendCheckCanUpdate
    Dev-->>FP: 允许升级(SN 确认)
    FP->>DH: cmd_OTA_3 sendEnterUpdate
    Dev-->>FP: 进入升级模式
    FP->>FP: otaSecondStep
    loop 分包传输(按 MTU)
        FP->>DH: cmd_OTA_5 sendOtaData(cmdUpdateBuffer)
        DH->>Dev: 数据包 + CRC16
        Dev-->>FP: ACK / 进度
    end
    FP->>DH: cmd_OTA_6 sendOtaStatus
    Dev-->>FP: 升级状态查询
    Note over FP,RM: 设备重启进入 Loader,BLE 断开
    FP->>RM: reconnectWithMac / reconnectWithUUID
    RM->>Dev: 扫描 + 回连(JL_OTAResultReconnectWithMacAddr/UUid)
    RM-->>FP: 回连成功,继续流程
    FP->>DH: cmd_OTA_7 sendReboot
    DH->>Dev: 重启设备
    FP-->>OM: otaUpgradeResult:Progress:
    OM-->>BLE: JL_OTAResult 回调
    BLE-->>App: otaProgressWithOtaResult:withProgress:

流程要点

  1. 能力探测先行:otaFeatureResult: 在启动前确认设备是否走复用空间分支(强制 + 支持复用空间 + Normal/Disable 模式 + bootloader 类型),决定 Loader 升级还是资源升级。
  2. 协商三连(首段):cmd_OTA_1 拿偏移 → cmd_OTA_2 询问升级许可 → cmd_OTA_3 进入升级模式,任何一步失败都会触发超时/错误处理(handleTimeout: / cmdOTA_5_Err:)。
  3. 数据搬运(次段):cmdUpdateBuffer: 按 MTU 分包发送 cmd_OTA_5 数据,携带 CRC16 校验;otaSent / otaLength 推进进度,maxLostCount 兜底丢包。
  4. 断链回连:Loader 重启导致链路断开后,JLOtaReConnectMgr 按 MAC/UUID 回连(iOS 单备份仅支持 BLE),回连成功后继续剩余流程。
  5. 收尾重启:cmd_OTA_6 查询状态确认完成后 cmd_OTA_7 重启设备,最终以 JL_OTAResult + 进度回调到 Flutter 层。

使用示例

示例一:探测复用空间能力并触发升级(iOS 通道)

升级前 otaFeatureResult: 是判断是否进入复用空间特殊流程的唯一入口;isSupportReuseSpaceOTA 与 otaSourceMode/bootloaderType 组合决定 Loader 或资源升级:

if (manager.otaStatus == JL_OtaStatusForce) {
    if (manager.isSupportReuseSpaceOTA) {
        if (manager.otaSourceMode  == JLSourcesExtendModeNormal
            || manager.otaSourceMode == JLSourcesExtendModeDisable) {
            if (manager.bootloaderType == JL_BootLoaderYES) {
                kJLLog(JLLOG_DEBUG, @"---> Entering Loader upgrade.");
            }else{
                kJLLog(JLLOG_DEBUG, @"---> Entering resource upgrade.");
            }
        }
        kJLLog(JLLOG_DEBUG, @"---> Current mode is: otaSourceMode: %d, bootloaderType: %d", manager.otaSourceMode, manager.bootloaderType);
    }
    ...
}

来源:JLBleManager.m

示例二:指定回连 Option 并启动升级

复用空间升级必然经历设备重启断链,因此启动时通过 JLOtaReConnectOption 指定回连所需的 Service/Write/Read UUID:

option.serviceUUID = @"AE00";
[_otaManager cmdUpgrade:otaData Option:option Result:^(JL_OTAResult result, float progress) {
    for (id<JLBleManagerOtaDelegate> objc in self.delegates) {
        if([objc respondsToSelector:@selector(otaProgressWithOtaResult:withProgress:)]){
            [objc otaProgressWithOtaResult:result withProgress:progress];
        }
    }
}];

来源:JLBleManager.m

示例三:按 MTU 分包发送数据

复用空间升级传输的是大体积固件/资源数据,iOS 通道按当前 _bleMtu 切包发送,避免超过 BLE 单包上限:

-(void)writeDataByCbp:(NSData *)data{
    if (_mBlePeripheral && self.mRcspWrite) {
        if (data.length > 0 ) {
            NSInteger len = data.length;
            while (len>0) {
                if (len <= _bleMtu) {
                    NSData *mtuData = [data subdataWithRange:NSMakeRange(0, data.length)];
                    [self selectSendAction:mtuData];
                    len -= data.length;
                }else{
                    NSData *mtuData = [data subdataWithRange:NSMakeRange(0, _bleMtu)];
                    [self selectSendAction:mtuData];
                    len -= _bleMtu;
                    data = [data subdataWithRange:NSMakeRange(_bleMtu, len)];
                }
            }
        }
    }
}

来源:JLBleManager.m

配置选项

复用空间升级的开关全部来自设备信息解析,App 侧无需额外配置;可调的行为参数集中在 JL_OTAManager / JLOtaReConnectMgr:

选项类型默认/取值说明
isSupportReuseSpaceOTABOOL设备上报是否启用复用空间特殊升级流程
otaStatus枚举JL_OtaStatusNormalJL_OtaStatusForce 时进入强制升级分支
otaSourceMode枚举JLSourcesExtendModeNormalDisable/Source Update/Only Update Source/Only Update Firmware
bootloaderType枚举设备上报JL_BootLoaderYES 走 Loader 升级,否则资源升级
otaPartition枚举JL_PartitionSingle单备份(复用空间)或双备份
otaReconnectType枚举按需MAC 回连 / UUID 回连
kOtaTimeOutMax数值SDK 默认单命令超时上限(JLOtaTimeoutManager)
maxLostCount数值SDK 默认数据传输最大丢包容忍次数
maxTimerCount数值SDK 默认回连扫描最大尝试次数(JLOtaReConnectMgr)
isSupportCRCBOOL设备上报数据包是否携带 CRC16 校验
logOtaSendDataBOOLNO是否打印 OTA 发送数据日志
isWriteWithResponseBOOLOption 配置回连后写特征值是否需要响应

API 参考

以下方法签名取自 JLBleManager.m(插件层)与 JL_OTALib 符号(SDK 层)。

-[JLBleManager otaFeatureResult:(JL_OTAManager *)manager]

复用空间升级的决策回调。设备信息解析完成后由 SDK 回调,插件在此判断升级分支并触发升级或拉取通用信息。

参数:

  • manager(JL_OTAManager *):携带 otaStatus、isSupportReuseSpaceOTA、otaSourceMode、bootloaderType 等状态。

行为: 强制 + 复用空间 + Normal/Disable 时按 bootloader 分支;文件就绪则 otaFuncWithFilePath:,否则 getCallback(true/false)。

-[JLBleManager otaFuncWithFilePath:](经 cmdUpgrade:Option:Result: 启动)

[_otaManager cmdUpgrade:otaData Option:option Result:^(JL_OTAResult result, float progress) { ... }];

参数: otaData(升级包数据)、option(JLOtaReConnectOption:回连 UUID 与写模式)。 回调: JL_OTAResult result(阶段结果)+ float progress(0.0 ~ 1.0)。

-[JLBleManager otaFuncCancel:(CANCEL_CALLBACK)result]

[_otaManager cmdOTACancelResult:^(uint8_t status, uint8_t sn, NSData * _Nullable data) { ... }];

参数: result 回调。 回调参数: status(设备返回状态)、sn(命令序号)、data(可选负载)。

-[JL_OTAManager cmdOTACancelResult:] / -[JL_OTAManager cmdRebootDevice] / -[JL_OTAManager cmdRebootForceDevice]

取消升级 / 正常重启 / 强制重启设备。

+[JLOtaDeviceInfoParser parseInfo:intoManager:]

解析设备信息(分区、状态、bootloader、source mode、复用空间能力位)并写入 JL_OTAManager。

-[JLOtaFlowProcessor requestCancel] / -[JLOtaFlowProcessor requestReboot:]

流程层的取消与重启请求;requestReboot: 在 isSendRebootWitoutResp 场景下不等待响应直接断开。

-[JLOtaReConnectMgr reconnectWithMac:Result:] / reconnectWithUUID:Result:

按 MAC 地址或 UUID 回连设备,回连成功后通过 otaDataSend: 恢复数据传输。

失败模式、边界情况与并发

强制升级中断

JL_OtaStatusForce 表示设备必须完成升级,App 不应在此时取消或退出升级流程。otaFeatureResult: 中强制分支 return 早退,确保不会落入普通信息拉取路径。若上层在强制升级中终止进程,设备将停留在 Loader/异常状态,需重新连接并继续升级。

断链与回连失败

单备份升级中设备重启必然断链。回连失败表现为两种结果码:JL_OTAResultReconnectWithMacAddr(按 MAC 扫描回连)与 JL_OTAResultReconnectWithUUid(按 UUID 回连)。JLOtaReConnectMgr 通过 startTimer/maxTimerCount 限制扫描次数,超时后进入 handleTimeout: 错误路径。集成方应监听该结果码并向用户提示「升级中断,请靠近设备重试」。

丢包与超时

  • JLOtaDataHandler 每次写包由 JLOtaTimeoutManager/JLOTATimeoutInfo 登记超时(kOtaTimeOutMax),超时回调 handleTimeout:。
  • 数据传输以 maxLostCount 容忍连续丢包,超过阈值则判定传输失败(cmdOTA_5_Err:)。
  • 超时管理是全局单例(+[JLOtaTimeoutManager share]),clearAllTimeOut 在流程终止时清理全部挂起命令。

重启命令的响应时序

日志明确建议:「Reboot without resp, Suggest adding the 0xe7 command to the firmware before disconnecting Bluetooth」。isSendRebootWitoutResp 为真时重启命令不等待响应,若固件未实现 0xe7 时序,可能出现断开前数据未落盘导致升级失败。此边界需固件侧配合。

退出码与异常状态

isOTA_EXIT_E4 表示设备返回 E4 异常退出码,流程会据此走错误上报而非正常结束。cmdOTA_4_InterruptResult: 处理升级中断响应,保证退出升级模式时设备侧状态一致。

并发与重入

  • 升级期间 JL_OTAManager.isUpdating 为真,cmdOTAData:Result: / cmdUpgrade:Option:Result: 不应重入;重复触发需由上层 UI 通过进度回调状态机(如 JL_OTAResult 的阶段值)自行防抖。
  • 回连扫描与数据传输同处 JLOtaReConnectMgr,isSearching 标志防止扫描重入。
  • 超时管理器以命令序号 sn 为键管理多个挂起命令,同一 sn 的重复响应通过 countSameSn 统计,避免乱序响应破坏流程。

性能与运维考量

  • MTU 分包:writeDataByCbp: 按 _bleMtu 切包发送,MTU 越大单包吞吐越高;升级前可通过 cmdSetMTU: 协商更大 MTU 以缩短升级耗时。
  • 进度回调:otaUpgradeResult:Progress: → otaFlowDidUpdateStatus:progress: → 插件层 otaProgressWithOtaResult:withProgress: 逐级透传,UI 无需轮询。
  • 日志开关:logOtaSendData 开启后打印每个发送包(数据量大时日志开销显著),线上建议关闭;kJLLog 分级输出(JLLOG_DEBUG 含模式决策日志)。
  • 授权下载:JLOTAFile 通过 https://profile.jieliapp.com/license/v1/fileupdate/check(含 auth_key/proj_code)校验固件授权,check2 额外携带 hash;downloadOtaFileWithUrl: 下载后本地校验 CRC16(addCrc:)再进入传输,避免无效包占用复用空间升级窗口。
  • 回连窗口:JLOtaReConnectMgr 的 timerAction 周期性扫描,maxTimerCount 决定最长回连等待;设备重启时间波动大时,可适当调大该值提高成功率。

扩展点

  • 自定义命令:JLOtaCustom / JLOtaCustomData 允许在升级通道上发送自定义 RCSP 命令(cmdSendCommandData:needResponse:Result:),用于厂商私有协议扩展(例如复用空间升级前的擦除/校验指令)。
  • 扩展信息:sendExpandInfoWithSN:type:UUID: 与 cmdGetExpandInfo(isSupportExpand)用于获取设备扩展信息,可作为复用空间分区布局的补充依据。
  • 资源升级路由:Only Update Source 模式日志指示「should use DialManager to update sources」——资源部分不在本流程传输,而是交给表盘/资源管理器(DialManager)完成,集成方可复用该管理器实现资源单独升级。
  • 回连类型协商:cmd_OTA_8 sendBleSppWithSN:type:reconnectType:UUID: 支持 BLE/SPP(BR/EDR)回连协商;V1.1.0 起 iOS 增加 Gatt Over BR/EDR 连接方式,otaReconnectType 可扩展新的回连类型。

测试

  • 插件工程提供集成测试入口:code/JL_OTA/example/integration_test/plugin_integration_test.dart,覆盖插件通道的端到端调用(升级流程的真实设备联调需配合支持 isSupportReuseSpaceOTA 的固件)。
  • 升级流程的正确性主要依赖设备端返回的状态组合;建议测试矩阵覆盖:Force + ReuseSpace + Normal/Disable + BootLoaderYES/NO(Loader 升级 vs 资源升级)、FirmwareOnly(仅固件)、Only Update Source(资源走 DialManager)、断链回连成功/失败、丢包超限、E4 异常退出。

相关链接

  • OTA 升级总览 — 常规 OTA 流程与本特殊流程的关系
  • 单备份 OTA 自动回连 — JLOtaReConnectMgr 详细机制
  • 双备份分区升级 — JL_PartitionDouble 路径(本页仅涉及单备份)
  • 自定义命令 — JLOtaCustom 扩展协议
  • JLBleManager.m(决策逻辑)
  • JL_RunSDK.m(SDK 通道决策逻辑)
  • README.md(功能与版本记录)
  • 接口介绍文档(V1.1.0 变更记录)
Prev
自动回连机制
Next
自定义命令