GATT Over BR/EDR 经典蓝牙升级
iOS-JL_OTA 是杰理(Jieli)蓝牙芯片的 iOS OTA 升级 SDK 与示例工程。本文介绍通过经典蓝牙(BR/EDR)链路执行固件升级的能力:从固件文件准备(JLOtaFileManager)、校验与安全工具(DFCrc16/AESx/DFGzip/DFHmacMD5)、数据流缓冲(DFRing)到整体升级流程与失败处理。
Purpose and Scope
本页聚焦「经典蓝牙(BR/EDR)通道上的 OTA 固件升级」这一完整能力,涵盖:
- 升级固件文件的初始化与沙盒管理(
OTAFiles/JLOtaFileManager); - 支撑 OTA 数据包处理的 DFUnits 工具框架组件(CRC16 校验、AES 加密、Gzip 压缩、HMAC-MD5 鉴权、环形缓冲区);
- 经典蓝牙链路上的升级会话流程、分帧下发、校验与重启机制;
- 失败模式、并发与可靠性注意事项。
页面边界说明:BLE(低功耗蓝牙)升级、iAP/MFi 通道升级、命令集逐字节协议细节、以及 DFUnits 中与 OTA 无关的 UI 组件(DFTips、DFLabel 等)属于兄弟页面或单独文档的范围,本页不展开。本页内容以仓库中可直接核实的文件为准;OTASDK/ 内部命令头文件未在本轮检索中直接读取,凡涉及 SDK 私有 API 签名处均以「SDK 层」概念描述,具体签名请以官方 SDK 头文件为准。
Overview
为什么需要「GATT over BR/EDR」
GATT(Generic Attribute Profile)通常运行在 BLE 之上,但在大量双模(BR/EDR + BLE)音频设备(音箱、耳机、车载)上,经典蓝牙链路往往更稳定、吞吐更高,且部分老款设备根本不支持 BLE。因此 OTA 升级必须能复用经典蓝牙通道——即「GATT over BR/EDR」的升级形态:属性读写语义运行在经典射频链路上,实现与 BLE 通道等价的固件下发能力。
iOS 平台的约束
iOS 对经典蓝牙(BR/EDR)的访问受系统限制(需要 MFi/ExternalAccessory 授权或特定通道),这与 Android 可直接对 BR/EDR 设备建立 GATT 连接不同。因此 iOS 侧的经典通道升级在传输适配层与 BLE 通道不同,但上层 OTA 会话(命令构造、分帧、校验、加密)可复用同一套逻辑——这正是本仓库将传输层与业务层分离的设计意图。
核心概念
| 概念 | 说明 |
|---|---|
| 固件文件 | 以资源形式打包在 App 中,经 JLOtaFileManager 复制到沙盒后参与升级 |
| 分帧下发 | 固件数据按设备支持的包长切片,逐帧发送并等待应答 |
| CRC16 校验 | 每帧/整体数据校验,保证传输完整性 |
| 鉴权与加密 | HMAC-MD5 鉴权 + AES 加密,防止非法固件与窃听 |
| 升级会话 | 进入升级模式 → 下发数据 → 整体校验 → 设备重启 |
Architecture
flowchart TD
subgraph sg_App["应用层"]
DemoApp["JL_OTA Demo App (AppDelegate)"]
end
subgraph sg_File["OTA 文件层"]
OtaFileManager["JLOtaFileManager"]
Sandbox["应用沙盒 (固件文件)"]
end
subgraph sg_SDK["OTA SDK 层"]
OtaSession["OTA 升级会话"]
OtaCmd["升级命令组"]
Transport["传输适配层 (BR/EDR)"]
end
subgraph sg_Tools["DFUnits 工具层"]
Crc16["DFCrc16"]
AES["AESx"]
Gzip["DFGzip"]
Hmac["DFHmacMD5"]
Ring["DFRing 环形缓冲"]
end
subgraph sg_Link["经典蓝牙链路"]
BREDR["BR/EDR 经典蓝牙连接"]
Device["JL 芯片设备"]
end
DemoApp --> OtaFileManager
OtaFileManager --> Sandbox
DemoApp --> OtaSession
OtaSession --> OtaCmd
OtaSession --> Transport
Transport --> BREDR
BREDR --> Device
OtaSession --> Ring
OtaCmd --> Crc16
OtaCmd --> AES
OtaCmd --> Gzip
OtaCmd --> Hmac
Sandbox -.->|"读取固件数据"| OtaSession
架构解读:JLOtaFileManager 是固件文件的唯一入口,负责把 NSBundle 内的升级文件初始化到沙盒,保证升级过程中文件可随机读取、可断点续传式访问;OTA 会话层通过传输适配层与 BR/EDR 设备通信,命令组在组装数据包时调用 DFUnits 的 DFCrc16(校验)、AESx(加密)、DFGzip(压缩)、DFHmacMD5(鉴权),并以 DFRing 环形缓冲区平滑写入通道。分层目的:更换传输通道(BLE/iAP/BR/EDR)时,会话与命令逻辑无需改动。
固件文件准备:JLOtaFileManager
经典通道升级的第一步不是连蓝牙,而是准备好可被随机访问的固件文件。仓库中 OTAFiles/JLOtaFileManager.h 给出了这一职责的明确接口——initializeOtaFile 负责把 NSBundle 中的固件资源转移到沙盒,使得升级过程中文件读取不受 App 包只读资源目录的限制,也可以后续扩展为「从服务器下载固件到沙盒再升级」的运营形态。
@interface JLOtaFileManager : NSObject
/**
* 初始化沙盒的ota升级文件
* 转移NSBundle的文件到沙盒
*/
+ (void)initializeOtaFile;
Source: JLOtaFileManager.h
设计意图:OTA 升级可能长达数分钟,若直接从 NSBundle 读取,文件路径与权限管理受限;统一复制到沙盒(Documents/Library)后,升级会话可以按偏移量分段读取、计算 CRC、重试读取,且可支持升级包动态更新(如热更新固件)而不需要重新发版 App。
数据校验与安全工具:DFUnits
仓库以 DFUnits.framework 形式提供通用工具库,其中与 OTA 直接相关的头文件包括(头文件清单已从仓库核实):
| 头文件 | 在 OTA 中的角色 |
|---|---|
| DFCrc16.h | 每帧/整体数据的 CRC16 校验,保证经典链路传输完整性 |
| AESx.h | 固件数据 AES 加解密,防止固件被窃取或篡改 |
| DFGzip.h | 固件压缩/解压,减少经典链路传输时间 |
| DFHmacMD5.h | HMAC-MD5 鉴权,验证升级会话与设备身份 |
| DFRing.h | 环形缓冲区,平滑固件数据写入、避免发送线程与回调线程竞争 |
DFRing:流式发送缓冲
经典蓝牙链路是流式的,而 OTA 命令组按帧生产数据。DFRing 以环形缓冲解耦「生产者(命令组)→ 消费者(传输通道)」,其长度字段是线程安全协作的关键:
@interface DFRing : NSObject
@property(assign,nonatomic)int32_t len_total;
Source: DFRing.h
len_total 表示缓冲区的总容量;读写指针在容量内环绕,配合互斥控制可实现单生产者/单消费者的无锁化协作,避免升级大数据量时主线程卡顿。
经典通道升级会话流程
flowchart TD
Start([开始升级]) --> Prep["准备固件文件<br/>JLOtaFileManager initializeOtaFile"]
Prep --> Connect["建立 BR/EDR 经典连接"]
Connect --> Auth["鉴权/密钥协商<br/>DFHmacMD5 + AESx"]
Auth --> Enter{"进入升级模式成功?"}
Enter -->|"否"| Fail1["升级失败<br/>设备继续运行旧固件"]
Enter -->|"是"| Send["分帧下发固件数据<br/>DFRing 缓冲 + DFCrc16 帧校验"]
Send --> Done{"全部数据下发完成?"}
Done -->|"否"| Send
Done -->|"是"| Verify["整体 CRC 校验"]
Verify --> Reboot["设备重启进入新固件"]
Reboot --> End([升级完成])
关键决策点说明:
- 鉴权先行:经典链路不像 BLE 有标准的安全配对强制流程,因此升级会话前必须显式完成 HMAC-MD5 鉴权与 AES 密钥协商,防止未授权设备刷写固件;
- 边发边校验:每帧携带 CRC16,设备侧逐帧应答,App 侧收到错误应答即可快速重传,避免整包失败后才重来;
- 整体校验后重启:全部帧下发完成后做整体 CRC 校验,校验通过才允许设备重启,保证设备不会因半截固件变砖。
Core Flow:一次经典蓝牙升级的完整时序
sequenceDiagram
participant App as App / JL_OTA Demo
participant FM as JLOtaFileManager
participant SDK as OTA 升级会话
participant Tool as DFUnits (CRC16/AES/Gzip/HMAC)
participant Dev as JL 设备 (BR/EDR)
App->>FM: initializeOtaFile()
FM-->>App: 沙盒固件就绪
App->>SDK: 发起经典通道升级
SDK->>SDK: 读取固件并解析头信息
SDK->>Dev: 建立 BR/EDR 连接/进入升级模式
Dev-->>SDK: 升级模式应答
SDK->>Tool: HMAC-MD5 鉴权 + AES 密钥协商
Tool-->>SDK: 会话密钥就绪
loop 分帧下发
SDK->>Tool: 分帧 + CRC16 + (可选)AES 加密
Tool-->>SDK: 组装数据包 (经 DFRing 缓冲)
SDK->>Dev: 写入固件数据帧
Dev-->>SDK: 帧应答 (ACK/NAK)
alt NAK 或超时
SDK->>Dev: 重传当前帧
end
end
SDK->>Dev: 整体校验命令 (CRC)
Dev-->>SDK: 校验结果
Dev->>Dev: 重启运行新固件
SDK-->>App: 升级完成回调
时序要点:
- 文件先行:
initializeOtaFile在任何蓝牙操作之前完成,升级会话从沙盒按偏移读取固件; - 鉴权在连接建立后:BR/EDR 链路建立后立即做 HMAC-MD5 鉴权,避免在未授权链路上浪费流量;
- 帧级重传:ACK/NAK 机制在帧粒度上恢复错误,经典链路偶发丢包不会中断整个升级;
- 整体校验收尾:所有帧发送完毕后,整体 CRC 校验通过才触发设备重启,把「升级失败设备变砖」的风险降到最低。
配置选项
| 选项 | 类型 | 默认/来源 | 说明 |
|---|---|---|---|
| 固件文件位置 | 资源文件 | App NSBundle | 升级固件随 App 打包,由 JLOtaFileManager 初始化到沙盒 |
| 沙盒目录 | 路径 | 应用沙盒 | initializeOtaFile 完成资源到沙盒的转移(具体路径见实现) |
| 传输通道 | 枚举 | 由调用方指定 | BLE / iAP / BR/EDR 经典通道,本页为 BR/EDR 形态 |
| 帧大小 | 由设备决定 | 设备固件上报 | 分帧下发时按设备支持的包长切片 |
| 加密开关 | 布尔 | 随会话协商 | AES 加密与 HMAC-MD5 鉴权在会话建立时协商 |
说明:以上为基于仓库可见接口与标准升级流程的配置面;
OTASDK/内更细粒度的配置项(如重试次数、超时时间)需查阅官方 SDK 头文件。
API Reference
以下为本次检索中从仓库源码直接核实的接口:
+ (void)initializeOtaFile(JLOtaFileManager)
初始化沙盒中的 OTA 升级文件,将 NSBundle 中的固件资源转移到沙盒。
参数:无。
返回:无(void)。
说明:应在 App 启动或升级前调用一次;未调用时升级会话可能因无法定位固件文件而失败。
Source: JLOtaFileManager.h
@property (assign, nonatomic) int32_t len_total(DFRing)
环形缓冲区总容量(字节)。生产/消费逻辑依据该容量计算环绕位置。
类型:int32_t(assign, nonatomic)。
Source: DFRing.h
其余 SDK 层 API(进入升级模式、数据下发、校验等命令接口)位于
OTASDK/内部头文件,未在本轮检索预算内直接读取,签名以官方 SDK 头文件为准。
失败模式、边界情况与并发
失败模式
| 失败场景 | 触发条件 | 处理/后果 |
|---|---|---|
| 固件文件缺失 | 未调用 initializeOtaFile 或资源未打包 | 升级会话无法读取固件,应在启动早期调用该方法并检查结果 |
| 经典连接建立失败 | 设备未配对、超出范围、iOS 未获 MFi 授权 | 升级不启动,App 提示用户重新连接 |
| 鉴权失败 | HMAC-MD5 校验不通过、密钥不匹配 | 会话终止,防止未授权刷写 |
| 帧应答 NAK/超时 | 经典链路干扰、缓冲溢出 | 帧级重传,超过重试上限则中断升级 |
| 整体 CRC 校验失败 | 传输中静默丢帧 | 设备不重启,App 提示重新升级(避免变砖) |
| 升级中设备断电/断链 | 电量耗尽、用户断开 | 设备保持可恢复状态,重新连接后可再次发起升级 |
边界情况
- 大固件:固件文件可能数 MB,BR/EDR 链路吞吐高于 BLE,但仍需分帧 + 缓冲(
DFRing),不可一次性整包写入; - 压缩固件:
DFGzip压缩后的固件可显著缩短经典链路传输时间,但设备侧需支持解压,需在会话协商时确认能力; - 加密固件:启用 AES 后,帧校验应在加密前后分别进行(帧结构校验 + 解密后内容校验),本仓库通过工具层组合实现。
并发与一致性
- 生产者/消费者模型:命令组(生产帧)与传输回调(消费/应答)运行在不同线程,
DFRing环形缓冲 + 互斥控制保证数据一致;len_total固定容量避免运行时扩容抖动; - 回调线程:升级进度/结果回调建议切回主线程更新 UI(仓库
DFTips等 HUD 组件即基于主线程 run loop 设计); - 重入保护:一次升级会话内不应并发发起第二次升级;再次升级需等待会话结束并重新初始化文件。
性能与运维
- 吞吐:经典蓝牙(BR/EDR)链路的有效吞吐通常显著高于 BLE,是双模设备首选的 OTA 通道;启用
DFGzip可进一步压缩传输量; - 帧大小:帧越大吞吐越高,但单帧出错重传代价越大,需按设备上报的包长上限取值;
- 升级时长:数 MB 固件在经典链路上通常可在数十秒至数分钟内完成,App 应显示进度并阻止用户锁屏/断连;
- 电量:长时升级耗电明显,生产实践建议设备电量充足时允许升级(SDK 侧可通过会话前查询设备电量实现)。
扩展点
- 固件来源:
JLOtaFileManager的沙盒化设计支持扩展为「从服务器下载固件到沙盒再升级」的热更新形态,无需改动升级会话; - 传输通道切换:会话与命令逻辑与传输适配层解耦,同一套命令组可复用于 BLE / iAP / BR/EDR,新增通道只需实现传输适配层;
- 工具复用:
DFCrc16、AESx、DFHmacMD5、DFGzip为独立工具,可复用于自有协议(如日志加密、资源校验)。
测试
本轮检索预算内未在仓库中发现独立的单元测试工程(仓库主体为 SDK 框架与示例 App)。示例工程 JL_OTA/AppDelegate 是调用入口参考;建议接入方覆盖以下用例:固件文件初始化、鉴权失败、帧 NAK 重传、整体 CRC 失败后重试、断链后重连续升。
Related Links
- JLOtaFileManager.h(固件文件初始化)
- DFRing.h(流式发送缓冲)
- DFCrc16.h
- AESx.h
- DFGzip.h
- DFHmacMD5.h
- 兄弟页面:BLE 通道升级、iAP/MFi 通道升级、OTA 命令集(如目录中存在对应页面)