TWS 耳机 OTA 架构设计
文档版本: v1.0 | 最后更新: 2026-05-17 芯片: 杰理 AC7002 | BLE 协议: docs/ble-protocol.md §3.3
1. 概览
OTA (Over-The-Air) 升级是 TWS 耳机的关键能力。升级数据流为 三段式 架构:
| [云端/CDN] ──https──> [手机 APP] ──BLE──> [耳机固件]
↑
(用户交互 / 进度展示)
|
1.1 核心原则
| 原则 | 说明 |
| 手机为代理 | 手机是唯一的网络入口,耳机不直接访问互联网 |
| 断点续传 | OTA 过程中 BLE 断开后,重连可恢复(需重新 OTA_START) |
| 签名验证 | 所有固件包携带 HMAC-SHA256 签名,耳机端验证 |
| 进度透明 | 手机端展示实时进度(精度 0.01%),提升用户体验 |
| 异常回滚 | 升级失败自动回滚到旧固件,保持设备可用 |
2. 固件包格式
2.1 固件包结构 (.twsfw)
| ┌──────────────────────────────────────────────┐
│ 文件头 (64 bytes) │
│ ├─ Magic: "TWSFW" + v1 (4+1 bytes) │
│ ├─ Version: BCD 编码 (3 bytes) │
│ ├─ Hardware ID: 目标硬件型号 (2 bytes) │
│ ├─ Image Size: 固件大小 (4 bytes, LE) │
│ ├─ CRC32: 固件数据 CRC (4 bytes) │
│ ├─ Signature: HMAC-SHA256 (32 bytes) │
│ ├─ Reserved: 14 bytes │
├──────────────────────────────────────────────┤
│ 固件数据 (N bytes) │
│ └─ 原始固件二进制 (AC7002 可执行映像) │
├──────────────────────────────────────────────┤
│ 填充 (对齐到 16 bytes) │
└──────────────────────────────────────────────┘
|
2.2 固件包元信息 (Manifest)
云端同时维护一份 JSON manifest,APP 下载固件前先检查:
| {
"firmware_version": "1.24",
"hardware_id": 0x0100,
"file_url": "https://ota.example.com/tws/v1.24.twsfw",
"file_size": 1048576,
"file_crc32": "0xA1B2C3D4",
"file_sha256": "e3b0c44298fc1c149afbf4c8996fb924...",
"release_notes": "修复蓝牙断连问题",
"release_date": "2026-05-17",
"min_app_version": "0.1.0",
"force_update": false
}
|
3. 云端架构
3.1 组件
| 组件 | 技术选型 | 说明 |
| CDN | AWS S3 / CloudFront | 存储和分发固件包 |
| OTA API | RESTful (FastAPI/Go) | 查询最新版本、下载地址 |
| 固件签名服务 | 私有密钥 (HSM) | 生成 HMAC-SHA256 签名 |
| 管理后台 | Web 控制台 | 上传固件、灰度发布、AB 测试 |
3.2 API 接口
| GET /api/v1/ota/latest?hw=0x0100&fw=0x0123
→ { has_update: bool, manifest: {...} }
GET /api/v1/ota/download/<version>/<hw>.twsfw
→ 固件包二进制 (通过 CDN 302 跳转)
POST /api/v1/ota/report
Body: { device_id, from_version, to_version, result, error_code }
→ 设备升级结果上报
|
3.3 灰度发布策略
- 白名单 — 仅指定设备 ID 可获取更新
- 百分比 — 按设备 ID hash 分配(1% → 5% → 50% → 100%)
- 区域/渠道 — 按固件渠道号区分
4. APP 端架构
4.1 升级流程 (手机端)
| ┌──────────────────────────────────────────────────────────┐
│ APP │
├──────────────────────────────────────────────────────────┤
│ 1. 检查更新 │
│ ├─ 读取当前版本 (BLE → FEE1 Read) │
│ └─ 请求 OTA API → /latest │
│ │
│ 2. 下载固件 (后台 Service) │
│ ├─ 使用 download manager / WorkManager │
│ ├─ 支持断点续传 │
│ └─ 下载完成后校验 SHA256 + CRC32 │
│ │
│ 3. 传输固件 (BLE) │
│ ├─ OTA_START → 通知耳机准备 │
│ ├─ 分块写入 (Write Without Response, 默认 128 bytes) │
│ ├─ 监听进度 (FEE7 Notify) │
│ └─ OTA_COMMIT → 触发耳机校验 │
│ │
│ 4. 结果确认 │
│ ├─ 收到 RESULT_SUCCESS → 提示重启 │
│ └─ 收到 RESULT_FAIL → 展示错误码 + 重试 │
└──────────────────────────────────────────────────────────┘
|
4.2 状态机
| ┌─────────┐
│ IDLE │ ◄───────────────────┐
└────┬────┘ │
│ 用户点击"检查更新" │
▼ │
┌─────────┐ │
│ CHECKING│ │
└────┬────┘ │
│ 有更新? │
┌────┴────┐ │
▼ ▼ │
┌─────────┐ ┌──────┐ │
│DOWNLOAD │ │ IDLE│ (已是最新) │
└────┬────┘ └──────┘ │
│ 下载完成 │
▼ │
┌─────────┐ │
│TRANSFER │ ◄──── 重连后 OTA_START │
└────┬────┘ │
│ 传输完成 │
▼ │
┌─────────┐ ┌────────┐ │
│ COMMIT │─────>│ RESULT │───────────┘
└─────────┘ └────────┘
│
┌────┴────┐
▼ ▼
┌──────┐ ┌──────┐
│SUCCESS│ │ FAIL │
└──────┘ └──────┘
|
4.3 关键类设计
| ┌─────────────────────────────────────────────┐
│ OtaManager │
├─────────────────────────────────────────────┤
│ - state: OtaState │
│ - progress: double │
│ - currentFirmware: FirmwareInfo │
│ - bleService: EarbudBleService │
│ - apiClient: OtaApiClient │
├─────────────────────────────────────────────┤
│ + checkForUpdate(): Future<UpdateInfo> │
│ + downloadFirmware(url): Future<File> │
│ + startTransfer(file): Future<void> │
│ + abortTransfer(): Future<void> │
│ + retry(): Future<void> │
│ - _writeBlocks(): Future<void> │
│ - _verifyFile(file): Future<bool> │
│ - _reportResult(result): Future<void> │
└─────────────────────────────────────────────┘
|
4.4 性能考量
| 参数 | 建议值 | 说明 |
| BLE MTU | ≥ 512 bytes | 减少协议开销 |
| 数据块大小 | 128~512 bytes | 默认 128, 可协商 |
| 写入间隔 | 无 (Write Without Response) | 连续写入 |
| 连接间隔 | 7.5 ms | OTA 期间缩短间隔 |
| 超时 | 30s 无响应视为超时 | 触发重连/重试 |
| 下载缓存 | 原始文件 + SHA256 | 下载完成后再传输 |
1 MB 固件预估传输时间:
| 块大小 | 块数 | 写入延迟 | 总时间 (约) |
| 128 B | 8192 | ~10 ms/块 | ~82 s |
| 256 B | 4096 | ~12 ms/块 | ~49 s |
| 512 B | 2048 | ~15 ms/块 | ~31 s |
5. 耳机端架构 (AC7002)
5.1 固件存储布局
| ┌──────────────────────────────────────────┐
│ Bootloader (32 KB) │
│ - 上电自检 │
│ - 固件完整性校验 │
│ - 回滚逻辑 │
├──────────────────────────────────────────┤
│ Active Bank A (512 KB) │
│ - 当前运行固件 │
├──────────────────────────────────────────┤
│ Active Bank B (512 KB) │
│ - OTA 新固件写入目标 │
├──────────────────────────────────────────┤
│ Config / Calibration (16 KB) │
│ - 配对信息、校准数据 │
├──────────────────────────────────────────┤
│ OTA Scratch (64 KB) │
│ - 临时缓冲 / 日志 │
└──────────────────────────────────────────┘
|
5.2 升级流程 (耳机端)
| ┌───────────────────────────────────────────────┐
│ 耳机 │
├───────────────────────────────────────────────┤
│ 1. 收到 OTA_START (size=1048576) │
│ ├─ 擦除 Bank B │
│ └─ 回复 ACK │
│ │
│ 2. 接收数据块 (循环) │
│ ├─ 校验 Sequence Number 连续性 │
│ ├─ 写入 Bank B (offset = seq * block_size) │
│ └─ 定期发送 OTA_PROGRESS Notify │
│ │
│ 3. 收到 OTA_COMMIT (crc32) │
│ ├─ 读取整个 Bank B 计算 CRC │
│ ├─ 验证固件签名 (HMAC-SHA256) │
│ ├─ 验证 Hardware ID 匹配 │
│ ├─ 成功 → 标记 Bank B 为 active │
│ │ → 发送 RESULT_SUCCESS │
│ │ → 系统重启 (从 Bank B 启动) │
│ └─ 失败 → 保留 Bank A │
│ → 发送 RESULT_FAIL + 错误码 │
│ │
│ 4. 重启后 │
│ ├─ Bootloader 检查 Bank B 有效性 │
│ ├─ 有效 → 启动 Bank B │
│ └─ 无效 → 回滚 Bank A │
└───────────────────────────────────────────────┘
|
5.3 安全机制
| 机制 | 说明 |
| HMAC-SHA256 签名 | 固件包头携带 32 字节签名,耳机使用预置密钥验证 |
| CRC32 校验 | 每块数据 + 整体固件的 CRC 双重校验 |
| 递增序列号 | 防重放攻击 |
| Hardware ID 匹配 | 防止刷入不兼容固件变砖 |
| 双 Bank 备份 | 升级失败可回滚,保证设备永远可升级 |
| Watchdog 超时 | OTA 过程中若 30s 无数据,自动中止并回滚 |
6. 异常处理
6.1 异常场景与恢复策略
| 场景 | 表现 | 恢复策略 |
| BLE 断开 | OTA 中断, FEE7 上报断连错误 | APP 重连后重新 OTA_START (已擦除的 Bank B 可复用) |
| 下载失败 | 固件下载不完整 | APP 重试下载 (断点续传) |
| 电量不足 | 升级中低电量关机 | APP 在升级前检查电量 > 30% |
| Flash 写入失败 | 耳机回复 NACK | 重试; 若持续失败, 提示硬件故障 |
| CRC 校验失败 | OTA_COMMIT 阶段回复 NACK | 重新下载 + 重新传输 |
| 签名无效 | 固件被拒绝 | 提示固件不合法, 联系技术支持 |
| APP 崩溃 | 升级中断 | 重启 APP 后检查设备状态, 提供恢复选项 |
6.2 恢复流程
| [升级中断]
│
├─ 耳机侧: Bank B 未标记为 active
│ → 重启后仍从 Bank A 启动
│
└─ APP 侧: 重连后
├─ 读取设备版本 → 判断是否已升级成功
├─ 若版本未变 → 提供继续/重新升级选项
└─ 若版本已变 → 升级已完成, 更新 UI
|
7. 数据流时序图
7.1 正常升级
| 手机 APP 云端/CDN 耳机
│ │ │
│──── GET /latest ──────>│ │
│<─── manifest.json ─────│ │
│ │ │
│──── GET firmware ─────>│ │
│<─── .twsfw 文件 ───────│ │
│ (后台下载, 显示进度) │ │
│ │ │
│ ─── OTA_START(1MB) ─────────────────────────>│
│ <── ACK ─────────────────────────────────────│
│ │
│ ─── DATA Block #0 (seq=0, 128B) ───────────>│
│ ─── DATA Block #1 (seq=1, 128B) ───────────>│
│ ─── ... │
│ <── NOTIFY(PROGRESS=12.34%) ────────────────│
│ ─── ... │
│ ─── DATA Block #8191 ──────────────────────>│
│ │
│ ─── OTA_COMMIT(CRC32) ─────────────────────>│
│ <── NOTIFY(PROGRESS=100.00%) ───────────────│
│ <── NOTIFY(RESULT_SUCCESS, v1.24) ──────────│
│ │
│──── POST /report (success) ──>│ │
│ │ │
│ 提示用户 "升级成功, 重启中" │
│ │ │
│ │
│ [耳机重启, 从 Bank B 启动] │
|
7.2 升级失败 + 回滚
| 手机 APP 耳机
│ │
├─── DATA Block #500 ──────>│ (Flash 写入失败)
│<── NOTIFY(RESULT_FAIL, │
│ 0x0001) ───────────│
│ │
│ 提示 "存储写入失败, 重试" │
│ │
├─── OTA_ABORT ────────────>│
│ │ (保留 Bank A)
│ │
│ [重连后重新开始] │
├─── OTA_START(1MB) ───────>│ (重新擦除 Bank B)
└─── ... │
|
8. 开发计划
Phase 1 — 基础能力 (当前阶段)
Phase 2 — 联调测试
Phase 3 — 云端集成
Phase 4 — 生产就绪
9. 附录
9.1 相关文档
| 文档 | 说明 |
| docs/ble-protocol.md | BLE 通信协议 (含 OTA 详细定义) |
| docs/ble-services-final.md | 最终确认的 GATT 服务表 |
| firmware/src/main.c | 固件主程序入口 |
| companion-app/lib/screens/ota_screen.dart | APP 端 OTA UI 原型 |
9.2 关键指标
| 指标 | 目标值 |
| 1 MB 固件传输时间 | < 60s (MTU=512, 块大小=512) |
| 升级成功率 | > 99.5% |
| 失败回滚恢复率 | 100% |
| 最低可升级电量 | 30% |
| 固件包最大大小 | 2 MB (受 Bank B 容量限制) |
文档结束