2. OTA基础与演进
OTA升级,说白了就是给车“打补丁”。但车不是手机,不能死机、不能变砖。所以架构设计是整件事的基石。我做了这么多年OTA,见过太多因为架构没想清楚,后期疯狂返工的项目。
这一章,我把整个OTA系统拆成三块来讲:云端怎么管包、怎么调度;车端怎么接任务、怎么执行、怎么回滚;中间通信链路怎么保证安全可靠。你把这三点吃透了,OTA架构就算入门了。
2.1 云端架构:升级包管理
云端是OTA的大脑。所有升级包都从这里发出去。我见过最糟糕的设计,是把所有ECU的固件打成一个zip包直接推下去——结果车端解压到一半内存爆了。
升级包管理,核心要解决三件事:
- 版本存储:每个ECU的每个版本都要有唯一标识,不能覆盖。我习惯用「硬件ID + 软件版本号 + 构建时间戳」做三元组。
- 差分生成:全量包太大,车机流量扛不住。差分算法(bsdiff、hdiffpatch)只传变化的部分。我记得有一次,一个20MB的固件差分后只有3MB,下载时间从10分钟降到1分半。
- 包签名:每个升级包必须带数字签名。车端验签不通过,直接拒绝执行。这是底线。
2.2 云端架构:任务调度
任务调度,说白了就是「什么时候、给谁、推什么包」。不能一股脑全推,否则服务器扛不住,车机也扛不住。
我常用的调度策略:
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 灰度发布 | 先推1%的车,观察24小时没问题再逐步放开 | 所有OTA升级,尤其是涉及安全模块的 |
| 时间窗口 | 只允许在凌晨2:00-5:00执行升级 | 避免影响用户白天用车 |
| 地域分批 | 按区域分批推送,避免基站拥塞 | 全国范围的大版本推送 |
| 条件触发 | 电量>70%、车速=0、P档 | 防止升级过程中断电或移动 |
2.3 云端架构:用户鉴权
车不是你的私有设备。用户鉴权要解决两个问题:一是「这辆车是不是合法用户的车」,二是「这个用户有没有权限发起升级」。
我推荐的做法:
- 证书体系:每辆车出厂时烧录唯一设备证书。云端通过证书链验证车辆身份。
- Token机制:用户App登录后获取临时Token,车端与云端通信时携带Token。Token有过期时间,防止重放攻击。
- 权限分级:普通用户只能升级娱乐系统,售后技师可以升级动力域,工厂模式可以刷写Bootloader。
嗯,这里要注意:鉴权失败不能直接拒绝,要记录日志并告警。我遇到过有人批量扫描车辆VIN尝试非法升级,日志帮我们锁定了攻击源。
2.4 车端架构:OTA Manager
OTA Manager是车端的大脑。它不负责具体刷写,而是负责「管理」整个升级流程。
它的核心职责:
- 接收任务:从云端拉取升级任务描述(升级哪个ECU、目标版本、下载地址)。
- 状态管理:维护一个状态机——IDLE → DOWNLOADING → VERIFYING → UPDATING → REBOOTING → COMPLETED。
- 条件检查:升级前检查电量、车速、档位、温度。任何一条不满足,就拒绝执行并上报原因。
- 进度上报:每10秒向云端上报一次进度。用户App上看到的进度条,就是靠这个刷新的。
2.5 车端架构:Update Agent
Update Agent是干活的。它负责把升级包刷进ECU的Flash里。
每个ECU通常有一个独立的Update Agent。为什么?因为不同ECU的刷写协议不一样——UDS、DoIP、XCP……你没法用一个Agent搞定所有。
Update Agent的工作流程:
- 预编程:让ECU进入刷写模式(比如通过UDS的10 02服务)。
- 数据传输:按块传输数据,每块带CRC校验。我习惯用128KB一个块,太大容易超时,太小效率低。
- 完整性校验:刷完后读回全量数据做SHA256比对。
- 激活:写入激活标记,下次重启时ECU加载新固件。
// Update Agent 伪代码示例
void update_ecu(uint32_t ecu_id, uint8_t* data, uint32_t size) {
enter_programming_mode(ecu_id); // 预编程
for (uint32_t offset = 0; offset < size; offset += BLOCK_SIZE) {
uint32_t block_len = min(BLOCK_SIZE, size - offset);
uint32_t crc = calculate_crc32(data + offset, block_len);
transfer_block(ecu_id, offset, data + offset, block_len, crc);
}
uint8_t sha256_hash[32];
verify_integrity(ecu_id, sha256_hash); // 完整性校验
activate_firmware(ecu_id); // 激活
}
2.6 车端架构:回滚机制
回滚是OTA的最后一道防线。升级失败了怎么办?不能把车扔在路上。
目前主流方案是A/B分区:
| 分区 | 状态 | 说明 |
|---|---|---|
| Slot A | 当前运行 | 升级前正在使用的固件 |
| Slot B | 待升级 | 新固件刷入此分区 |
| Bootloader | 决策者 | 启动时根据标记选择启动哪个Slot |
升级流程:
- 刷写时只写Slot B,不动Slot A。
- 刷完后标记Slot B为「待激活」。
- 重启后Bootloader检查Slot B的完整性。通过则启动Slot B,失败则回退到Slot A。
- 如果Slot B启动成功,用户正常用车,后台标记升级完成。
- 如果Slot B启动失败(比如黑屏、死循环),看门狗复位后Bootloader自动切回Slot A。
2.7 通信链路设计:HTTPS、MQTT、差分下载
通信链路是OTA的血管。数据传不过去,架构再牛也没用。
我一般用三条通道:
- HTTPS:用于控制信令。比如云端下发升级任务、车端上报状态。请求-响应模式,可靠但实时性一般。
- MQTT:用于实时状态上报和推送。车端订阅自己的Topic,云端有紧急任务可以秒级推送。我习惯用QoS 1,保证至少一次送达。
- 差分下载:用于传输升级包数据。基于HTTP/2的分块下载,支持断点续传。车端下载到一半断网了,恢复后从断点继续,不用重头来。
为什么不用一条通道搞定?你想想看,HTTPS的握手开销太大,不适合频繁上报;MQTT的payload有限制,不适合传大文件。各司其职才是正解。
差分下载还有一个细节:还原算法必须在车端本地执行。云端只传差分数据,车端用旧版本固件+差分数据还原出新版本。这样即使通信被截获,攻击者也拿不到完整的固件。