复用空间升级
复用空间升级(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
复用空间升级的关键特征:
- 能力探测:设备信息中包含
isSupportReuseSpaceOTA位,表示该设备是否支持复用空间特殊升级流程。设备模型JLModel_Device在打印设备信息时输出--->isSupportReuseSpaceOTA:%d,OTA 管理器JL_OTAManager同样持有该属性。 - 模式化升级:
otaSourceMode决定升级内容语义——Disable(0)、Source Update(1)、Only Update Source(2)、Only Update Firmware(3)。复用空间升级通常要求模式为Normal或Disable,此时由bootloaderType决定进入 Loader 升级还是资源升级。 - 强制升级状态:当设备处于
JL_OtaStatusForce(强制升级)时,升级不可跳过,App 必须完成升级才能恢复设备功能;普通状态(JL_OtaStatusNormal)下则按耳机类型与 source mode 走常规路径。 - 两段式流程:
JLOtaFlowProcessor将升级拆为otaFirstStep(首段:协商/进入升级)与otaSecondStep(次段:传输数据/退出升级),配合JL_PartitionSingle/JL_PartitionDouble分区模型。 - 自动回连:单备份 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,而是一组设备能力位与模式枚举的组合。升级前必须读取设备信息并解析出以下状态:
| 状态字段 | 类型 | 含义 | 关键取值 |
|---|---|---|---|
isSupportReuseSpaceOTA | BOOL | 设备是否支持复用空间特殊升级流程 | YES / NO |
otaStatus | 枚举 | 升级状态/类型 | JL_OtaStatusNormal(普通)、JL_OtaStatusForce(强制) |
otaSourceMode | 枚举 | 升级内容模式 | Disable(0)、Source Update(1)、Only Update Source(2)、Only Update Firmware(3) |
bootloaderType | 枚举 | 是否需要/进入 Loader | JL_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;
}
});
}
决策树解读
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)"]
决策逻辑的要点与设计意图:
- 强制升级优先:
JL_OtaStatusForce状态下,设备要求必须升级。此时如果设备支持复用空间(isSupportReuseSpaceOTA == YES)且otaSourceMode为Normal/Disable,则进一步看bootloaderType——YES表示需要先进入 Loader(Entering Loader upgrade),否则直接进行资源升级(Entering resource upgrade)。日志输出用于确认当前模式组合。 - 文件就绪即升级:无论哪条分支,只要已选定 OTA 文件(
selectedOtaFilePath)就立即调用otaFuncWithFilePath:开始升级;否则通过_getCallback(true)通知上层「已就绪、等待文件」。 - 普通状态分流:非强制状态下,耳机设备(
JL_OtaHeadsetYES)或otaSourceMode == JLSourcesExtendModeFirmwareOnly(仅升级固件)直接走升级;否则视为普通功能设备,通过cmdSystemFunction拉取通用系统信息并回调_getCallback(false),进入常规 OTA 前的信息准备阶段。 - 主线程切换:
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];
}
}
}];
取消升级则发送 cmdOTACancelResult:,将设备返回的 status 透传给调用方:
- (void)otaFuncCancel:(CANCEL_CALLBACK _Nonnull)result{
[_otaManager cmdOTACancelResult:^(uint8_t status, uint8_t sn, NSData * _Nullable data) {
result(status);
}];
}
两段式升级流程(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_1 | sendMarkSeekWithSN:UUID: | 获取写入偏移(Mark/Seek) |
cmd_OTA_2 | sendCheckCanUpdateWithSN:data:UUID: | 询问是否可以升级 |
cmd_OTA_3 | sendEnterUpdateWithSN:UUID: | 进入升级模式 |
cmd_OTA_4 | sendExitUpdateWithSN:UUID: / sendExitUpdateSuccessWithSN:UUID: | 退出/完成升级 |
cmd_OTA_5 | sendOtaDataWithSN:data:UUID: / sendOtaDataErrorWithSN:UUID: | 传输 OTA 数据 / 上报数据错误 |
cmd_OTA_6 | sendOtaStatusWithSN:UUID: | 查询升级状态 |
cmd_OTA_7 | sendRebootWithSN:type:UUID: | 重启设备 |
cmd_OTA_8 | sendBleSppWithSN:type:reconnectType:UUID: | 回连类型协商(SPP/BLE) |
cmd_OTA_rt | cmdOTA_Route:withOtaReconnectType:Result: | 回连路由决策 |
cmd_OTA_url | downloadOtaFileWithUrl:(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:
流程要点
- 能力探测先行:
otaFeatureResult:在启动前确认设备是否走复用空间分支(强制 + 支持复用空间 + Normal/Disable 模式 + bootloader 类型),决定 Loader 升级还是资源升级。 - 协商三连(首段):
cmd_OTA_1拿偏移 →cmd_OTA_2询问升级许可 →cmd_OTA_3进入升级模式,任何一步失败都会触发超时/错误处理(handleTimeout:/cmdOTA_5_Err:)。 - 数据搬运(次段):
cmdUpdateBuffer:按 MTU 分包发送cmd_OTA_5数据,携带 CRC16 校验;otaSent/otaLength推进进度,maxLostCount兜底丢包。 - 断链回连:Loader 重启导致链路断开后,
JLOtaReConnectMgr按 MAC/UUID 回连(iOS 单备份仅支持 BLE),回连成功后继续剩余流程。 - 收尾重启:
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);
}
...
}
示例二:指定回连 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];
}
}
}];
示例三:按 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)];
}
}
}
}
}
配置选项
复用空间升级的开关全部来自设备信息解析,App 侧无需额外配置;可调的行为参数集中在 JL_OTAManager / JLOtaReConnectMgr:
| 选项 | 类型 | 默认/取值 | 说明 |
|---|---|---|---|
isSupportReuseSpaceOTA | BOOL | 设备上报 | 是否启用复用空间特殊升级流程 |
otaStatus | 枚举 | JL_OtaStatusNormal | JL_OtaStatusForce 时进入强制升级分支 |
otaSourceMode | 枚举 | JLSourcesExtendModeNormal | Disable/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) |
isSupportCRC | BOOL | 设备上报 | 数据包是否携带 CRC16 校验 |
logOtaSendData | BOOL | NO | 是否打印 OTA 发送数据日志 |
isWriteWithResponse | BOOL | Option 配置 | 回连后写特征值是否需要响应 |
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 变更记录)