OTA 升级架构
设备固件远程升级的端到端架构设计,涵盖固件存储、分发、升级流程和异常处理。
时序图见 02.核心业务流程 第 5 节,MQTT Topic 见 06.MQTT通信协议 3.3 节,API 接口见 08.API接口规范 固件升级部分。
1. 整体架构
数据流: 运维人员通过 Web 后台上传固件 → 存储到 OSS → 后端通过 MQTT 通知设备 → 设备通过 HTTPS 下载 → 校验安装 → 重启上报新版本。
四个参与方:
| 参与方 | 职责 |
|---|---|
| Web 后台 | 固件上传入口、升级任务管理、进度展示 |
| 后端服务 | 固件存储代理、升级任务调度、MQTT 通知下发、进度汇总 |
| OSS/MinIO | 固件文件持久化存储,提供 HTTPS 下载 |
| 设备 ESP32 | 接收升级通知、HTTPS 下载固件、MD5 校验、写入 OTA 分区、重启 |
2. 固件存储方案
| 方案 | 优点 | 缺点 | 结论 |
|---|---|---|---|
| 阿里云 OSS | 高可用、CDN 加速、免运维 | 有费用、依赖公网 | 生产环境推荐 |
| MinIO | 自托管、S3 兼容、无流量费 | 需自行维护高可用 | 内网/开发环境 |
| 本地文件系统 | 零依赖 | 不支持分布式、扩容困难 | 仅单机测试 |
选型建议: 开发/内网用 MinIO,生产用阿里云 OSS。二者均为 S3 兼容 API,后端代码可无缝切换。
存储路径规范: firmware/{productKey}/{version}/{filename}.bin
例如:firmware/p_relay_4ch/1.1.0/relay_4ch_v1.1.0.bin
3. 升级流程拆解
分五个阶段,补充时序图中未展开的技术细节。
阶段一:固件上传
- Web 后台上传
.bin文件 - 后端校验文件格式:检查 ELF 头/magic bytes,确认是否为有效 ESP32 固件
- 计算文件 MD5 和大小
- 上传到 OSS/MinIO,获取文件 URL
- 在 PostgreSQL 记录固件版本元数据:
| 字段 | 说明 |
|---|---|
productKey | 适用产品 |
version | 语义化版本号 |
url | OSS 下载 URL |
size | 文件大小(字节) |
md5 | 文件 MD5 校验值 |
changelog | 版本更新说明 |
uploadTime | 上传时间 |
阶段二:升级通知
- 运维选择目标设备:单设备 / 批量 / 按产品全量
- 创建升级任务,后端通过 MQTT 向每个目标设备发布
sys/{pid}/{did}/thing/ota/upgrade - Payload 含固件 URL、版本号、大小、MD5
- 离线设备处理: 标记任务为 pending,设备下次上线首次属性上报时,后端检查是否有待执行 OTA 任务,有则补发通知
阶段三:固件下载
- 设备收到通知,通过 HTTPS GET 从 OSS 下载固件
- 断点续传: 使用 HTTP Range 请求头,ESP-IDF 的
esp_https_ota组件原生支持。下载中断后下次从断点继续,不需要重新下载 - 每下载 10% 通过 MQTT 上报进度到
thing/ota/progress(Payload 格式见 MQTT 协议文档 3.3 节) - 后端通过 WebSocket 实时推送进度到 Web 后台
阶段四:校验与安装
- 下载完成后,设备计算文件 MD5,与通知中的值比对
- 校验通过:
- 写入 OTA 分区(ESP32 双分区机制:当前运行 active 分区,OTA 写入 inactive 分区)
- 设置启动标记(bootloader 识别下次启动使用哪个分区)
- 上报
progress=100, desc="flashing"→desc="done"
- 校验失败:
- 上报
desc="verify_failed" - 终止升级,可选自动重试(最多 2 次,每次重新下载)
- 上报
阶段五:重启与确认
- 设备调用
esp_restart()重启 - Bootloader 检测启动标记,切换到新固件分区
- 连接 WiFi + MQTT,属性上报时携带新固件版本号
- 后端比对版本号:
- 匹配目标版本 → 标记升级任务成功
- 不匹配(自动回滚到旧版)→ 标记任务失败
- ESP32 自带回滚机制: 新固件启动后若在 watchdog 时间内未确认稳定运行,自动回滚到旧版本,无需人工介入
4. 固件版本管理
版本号规范
采用语义化版本:主版本.次版本.修订号(如 1.1.0)。设备当前版本记录在 PostgreSQL Device 表的 firmwareVersion 字段中。
升级类型
| 类型 | 说明 | 示例 |
|---|---|---|
| 强制升级 | 安全漏洞修复、协议不兼容变更 | 旧版本设备必须升级,否则无法连接 |
| 可选升级 | 功能新增、Bug 修复 | 用户自行决定是否升级 |
版本回滚
- 设备端: ESP32 双分区自动回滚(新固件启动失败 → bootloader 切回旧分区)
- 云端主动回滚: 发起一次"升级到旧版本号"的 OTA 任务即可,流程完全相同
5. 批量升级与灰度
批量升级
| 参数 | 限制 | 说明 |
|---|---|---|
| 单次任务最大设备数 | 500 台 | 避免瞬时带宽冲击 |
| 分批间隔 | 30 秒 | 逐批下发通知,防止 OSS 带宽被打满 |
| Web 后台展示 | 总设备数 / 成功 / 失败 / 进行中 / 等待中 | 全局进度实时刷新 |
灰度策略(预留 v0.5+)
- 按设备比例分批:第一批 10% → 观察 1 小时无异常 → 第二批 30% → 全量
- 灰度期间出现异常(失败率 > 5%)→ 自动暂停任务,防止影响全部设备
6. 异常处理
| 异常场景 | 处理方式 |
|---|---|
| 设备下载中断(WiFi 断开) | 支持断点续传(HTTP Range),ESP-IDF 原生支持;设备下次上线后从断点继续 |
| MD5 校验失败 | 设备上报 verify_failed,后端自动重试下发 2 次,仍失败则标记任务失败 |
| 新固件启动后崩溃 | ESP32 bootloader watchdog 检测,自动回滚到旧固件;设备重新上线后上报旧版本号 |
| 设备存储空间不足 | 下载前先检查剩余 OTA 分区空间,不足则拒绝下载并上报错误(ESP32 OTA 分区大小由编译时决定,正常不会出现) |
| OSS 不可用 | 设备下载失败 → 上报错误 → 后端记录异常 → 运维检查 OSS 状态后重试 |
| 升级期间设备断电 | 重启后 bootloader 检测 OTA 分区未完整写入 → 自动回滚到旧固件 → 云端收到旧版本号,判定任务失败 |
7. 安全考虑
| 措施 | 说明 | 优先级 |
|---|---|---|
| HTTPS 下载 | 设备必须通过 HTTPS 从 OSS 下载固件,防止中间人篡改 | P0 |
| MD5 校验 | 设备端验证固件完整性 | P0 |
| 预签名 URL | 固件文件默认私有,设备通过预签名 URL 下载(默认 24 小时过期),避免暴露 OSS Bucket | P1 |
| 固件签名 | 上传固件时后端用私钥签名,设备端用内置公钥验签,防止恶意固件刷入 | P2(预留 v0.5+) |
8. 与 MQTT Topic 的对应
| 流程阶段 | MQTT Topic | 方向 |
|---|---|---|
| 通知设备 | sys/{pid}/{did}/thing/ota/upgrade | 云端 → 设备 |
| 进度上报 | sys/{pid}/{did}/thing/ota/progress | 设备 → 云端 |
Payload 格式详见 06.MQTT通信协议 3.3 节。
