OTA 是所有物联网项目里最容易低估的功能。产品规划阶段大家都觉得「不就是把固件推下去让设备自己升吗」,真正上线后才发现,这是最容易造成批量事故的环节。
我见过最惨的一次:一个新版本固件全量推给了三千多台设备,其中一批设备的 Flash 空间比预期小 200KB,升级写到一半空间不足,设备卡在 bootloader 里起不来。现场没人,只能派人一台台去刷。那次之后,那家公司把所有 OTA 都改成了灰度。
这篇文章讲 OTA 必须做对的五件事:传输、分区、灰度、回滚、限速。按这个顺序设计,出事的概率会低很多。
一、传输:断点续传和分片校验是底线
设备端的网络环境通常比你想的差。4G 信号弱、WiFi 被挤占、NB-IoT 单次传输有大小限制,一个 2MB 的固件包传到 80% 断掉是常态。如果没有断点续传,下次只能从头再来,弱网环境下可能永远传不完。
实现要点:
- 固件按固定大小分片,比如 4KB 或 16KB 一片
- 设备上报已收到的分片位图,服务端返回缺失的分片
- 每片带 CRC32 或 MD5,收到后立即校验,错了只重传这一片
- 整包传完后做一次全量 SHA256 校验,通过才允许进入升级流程
位图可以用一个字节数组表示,1MB 的固件按 4KB 分片是 256 片,32 字节就能表示完整进度。这个位图存在设备的非易失存储里,断电重启后能继续。
不要用 HTTP Range 就以为做了断点续传。Range 只能从断点续传,如果中间某一片损坏,它发现不了。必须自己维护分片状态。
还有一个细节:分片大小要匹配设备的内存。MCU 只有 64KB RAM,你一次让它接收 64KB 的分片,加上协议栈开销就爆了。4KB 是个比较安全的默认值。
二、分区:A/B 双分区是唯一可靠方案
单分区升级的流程是:擦除应用区 → 写入新固件 → 重启。这个流程有个致命问题:擦除之后、写完之前断电,设备就变砖了。没有 bootloader 兜底,只能返厂。
A/B 双分区的做法:Flash 分成两个等大的应用区 A 和 B,再加一个小的 bootloader 区。
- 设备运行在 A 区时,新固件写到 B 区,A 区不受影响
- 写完校验通过,在 bootloader 里标记「下次从 B 启动」
- 重启进 B 区,如果能正常启动并连上服务器,标记 B 为有效,A 变成可覆盖区
- 如果 B 区启动失败(看门狗超时或主动上报失败),bootloader 自动回退到 A
代价是 Flash 要双倍。128KB 的 MCU 可能装不下,那就得选 256KB 的型号,或者用外部 SPI Flash 存固件包。
小 Flash 设备的折中方案:如果实在做不了双分区,至少要做到「先下载完整包到外部存储,校验通过后再擦除写入」。这样把「下载失败」和「写入失败」两个风险隔开,只剩写入阶段的风险。再配合掉电检测,写入中断后从 bootloader 重新进入写入流程。
bootloader 必须独立且不可升级。如果把 bootloader 也做成可升级的,一次升级失败就彻底救不回来了。bootloader 区的写保护要打开。
三、灰度:1% → 10% → 50% → 100%
全量推送是赌博。正确的做法是分批,每一批观察足够长的时间再放大。
| 阶段 | 比例 | 观察时长 | 放行条件 |
|---|---|---|---|
| 内测 | 3-10 台(自有设备) | 24 小时 | 全部升级成功且功能正常 |
| 灰度一 | 1% | 24-48 小时 | 成功率 ≥98%,无异常上报 |
| 灰度二 | 10% | 24 小时 | 成功率 ≥98%,崩溃率不高于旧版本 |
| 灰度三 | 50% | 12-24 小时 | 同上 |
| 全量 | 100% | — | — |
灰度分组不要随机。随机分组会把不同批次、不同硬件版本、不同地域的设备混在一起,出问题时看不出规律。按维度分组更有价值:
- 先选硬件版本最新的(兼容性最好,出问题影响面小)
- 再选网络条件好的(排除网络因素干扰)
- 再按地域分批(某个区域出问题能快速定位)
更实用的做法:先升内部测试设备,再升「配合度高」的客户设备,最后升全部。第一批出问题,你能打电话让人配合抓日志;等到大面积出问题,就来不及了。
关键指标:升级成功率、升级后 24 小时内的在线率、升级后 24 小时内的重启次数、业务数据上报是否正常。只看「升级成功」不够,有些固件升上去了但功能坏了,要业务指标才能发现。
四、回滚:什么条件下自动回滚
回滚分两种:主动回滚和被动回滚。
被动回滚是设备自己做的,在 bootloader 层实现:新固件启动后,如果在规定时间内没有完成「标记有效」的动作,bootloader 就认为启动失败,退回旧分区。这个动作通常是设备连上服务器并收到确认。
超时时间要留够:设备冷启动、连 WiFi、做 TLS 握手、上报第一条数据,整个过程可能需要 30-60 秒。设成 10 秒会导致正常启动的设备也被误判。
主动回滚是服务端发起的:发现某个版本有问题,下发指令让已升级的设备退回旧版本。这要求旧固件还留在设备上——如果已经被新固件覆盖,就回不去了。
所以 A/B 分区方案里,新固件运行稳定后,不要立即擦除旧分区。至少保留 7 天,或者保留到下一版本开始推送前。多占一点 Flash,换的是出问题时的退路。
触发主动回滚的条件要提前定义好,不能等出事了再临时判断:
- 新版本设备在线率比旧版本低 5 个百分点以上,持续 2 小时
- 新版本设备重启次数超过阈值(比如 24 小时内平均每台 3 次)
- 新版本关键业务数据上报成功率下降超过 3%
- 出现已知的致命缺陷(比如某个型号的模组不兼容)
五、限速:别让升级把带宽打满
这是最容易被忽略的一条。几千台设备同时开始下载固件,会瞬间打满服务器带宽,不仅 OTA 本身传不动,正常业务数据也上不来。
服务端限速:
- 控制同时下载的设备数,比如并发上限 200 台
- 单设备限速,比如每台 100KB/s
- 按时间段限速,业务高峰期(白天)降低推送速率,夜间加速
设备端限速:
- 设备检测到电池电量低于 30% 时暂不升级(除非是市电供电设备)
- 4G 设备检测信号强度低于阈值时推迟,避免弱网重传浪费流量
- NB-IoT 设备特别要注意:单次传输有大小限制,而且流量按次计费,一个大固件可能产生几十块钱流量费
流量成本要算清楚。2MB 固件 × 5000 台 = 10GB。如果设备用的是按流量计费的物联网卡,这笔钱可能是几千到上万。有些项目会在升级前给用户发通知,说明会产生流量,避免纠纷。
六、状态追踪:设备不回报怎么办
OTA 最难处理的状态是「不知道设备现在什么情况」。指令发出去了,设备没响应;下载开始了,进度停在 40% 不动了;重启了,但没上报结果。
状态机要设计完整:
| 状态 | 含义 | 超时处理 |
|---|---|---|
| pending | 任务已创建,未下发 | — |
| sent | 已下发,等待设备确认 | 24 小时无响应 → 标记失败 |
| downloading | 设备正在下载 | 进度 2 小时无变化 → 重试 |
| verifying | 校验中 | 30 分钟无变化 → 失败 |
| installing | 正在写入 | 10 分钟无变化 → 失败 |
| rebooting | 重启中 | 5 分钟无变化 → 检查是否变砖 |
| success | 升级成功,设备已上报新版本号 | — |
| failed | 失败,需人工介入 | — |
「success」的判定必须是设备主动上报新版本号,不能靠「重启后连上了就算成功」。因为回滚后设备也会连上,但版本号是旧的。
设备侧要做的:每个状态变化都上报一次,并且把当前状态存在非易失存储里。设备重启后能接着上报,不会因为重启丢状态。
对于失联设备,要有兜底机制:设备下次上线时主动查询有没有待执行的升级任务。这样离线期间错过的推送不会永久丢失。
七、低功耗设备的特殊处理
电池供电的设备(水表、气表、地磁、土壤传感器)OTA 要单独设计,因为它们大部分时间在休眠,只有定时唤醒上报数据的那几十秒是醒着的。
策略:
- 把升级任务挂在唤醒周期里,每次唤醒只下载几个分片,下载完再睡
- 一个 500KB 的固件按每天唤醒 4 次、每次传 8KB 算,需要 16 天
- 升级期间不要执行其他业务,避免电量消耗叠加
- 电量低于 50% 时暂停升级,等换电池或充电后继续
这类设备的 OTA 周期通常按月算,不适合做紧急修复。所以低功耗设备的关键缺陷修复要留后路:要么设计时预留远程配置能力(改参数不用改固件),要么接受「必须现场处理」的现实。
还有一个坑:有些低功耗设备用深度休眠,升级过程中如果看门狗没喂好会被复位。复位后如果没做断点续传,前面下的分片全丢。所以断点续传对这类设备是刚需,不是可选项。
上线前的检查表
- 固件包分片了吗?分片大小匹配设备内存吗?
- 每片有校验吗?整包有校验吗?
- 断点续传的位图存在哪?断电会丢吗?
- 是 A/B 双分区吗?bootloader 有写保护吗?
- 新固件启动后多久内必须标记有效?超时时间够吗?
- 旧分区保留多久?回滚还能回去吗?
- 灰度分组按什么维度?第一批选谁?
- 每一阶段的放行条件写死了吗?谁来判?
- 主动回滚的触发条件定义了吗?谁有权触发?
- 并发数和单设备速率限制了吗?
- 流量成本算过吗?谁承担?
- 状态机完整吗?每个状态的超时处理有吗?
- 失联设备下次上线会补推吗?
- 低功耗设备的唤醒周期内能传完吗?
OTA 的核心不是技术难度,是对失败的设计。假设每一次升级都会失败,然后设计好失败之后怎么办——这样的 OTA 才能放心用。