2. OTA系统架构设计:云端、车端与通信链路

OTA升级,说白了就是给车“打补丁”。但车不是手机,不能死机、不能变砖。所以架构设计是整件事的基石。我做了这么多年OTA,见过太多因为架构没想清楚,后期疯狂返工的项目。

这一章,我把整个OTA系统拆成三块来讲:云端怎么管包、怎么调度;车端怎么接任务、怎么执行、怎么回滚;中间通信链路怎么保证安全可靠。你把这三点吃透了,OTA架构就算入门了。

OTA系统整体架构 云端平台 升级包管理 版本存储 · 差分生成 任务调度 灰度策略 · 时间窗口 用户鉴权 证书 · Token · 权限 日志与监控 进度跟踪 · 异常告警 通信链路 HTTPS(控制信令) MQTT(状态上报) 差分下载(数据通道) TLS 1.2+ · 断点续传 车端系统 OTA Manager 任务解析 · 状态管理 Update Agent 刷写执行 · 校验 回滚机制 A/B分区 · 备份恢复 安全校验 签名验证 · 完整性

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档防止升级过程中断电或移动
踩坑记录:我曾经把灰度比例设成5%,但没考虑地域分布。结果5%的车全集中在华东地区,当地基站直接被打爆。后来我加了「地域均匀分布」的调度策略。

2.3 云端架构:用户鉴权

车不是你的私有设备。用户鉴权要解决两个问题:一是「这辆车是不是合法用户的车」,二是「这个用户有没有权限发起升级」。

我推荐的做法:

  • 证书体系:每辆车出厂时烧录唯一设备证书。云端通过证书链验证车辆身份。
  • Token机制:用户App登录后获取临时Token,车端与云端通信时携带Token。Token有过期时间,防止重放攻击。
  • 权限分级:普通用户只能升级娱乐系统,售后技师可以升级动力域,工厂模式可以刷写Bootloader。

嗯,这里要注意:鉴权失败不能直接拒绝,要记录日志并告警。我遇到过有人批量扫描车辆VIN尝试非法升级,日志帮我们锁定了攻击源。

2.4 车端架构:OTA Manager

OTA Manager是车端的大脑。它不负责具体刷写,而是负责「管理」整个升级流程。

它的核心职责:

  1. 接收任务:从云端拉取升级任务描述(升级哪个ECU、目标版本、下载地址)。
  2. 状态管理:维护一个状态机——IDLE → DOWNLOADING → VERIFYING → UPDATING → REBOOTING → COMPLETED。
  3. 条件检查:升级前检查电量、车速、档位、温度。任何一条不满足,就拒绝执行并上报原因。
  4. 进度上报:每10秒向云端上报一次进度。用户App上看到的进度条,就是靠这个刷新的。
关键设计:OTA Manager必须是一个独立进程,不能依赖任何业务模块。即使中控屏死机了,OTA Manager也要能正常工作。我习惯把它放在安全域(Safety Domain)里。

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。
我曾经踩过的坑:A/B分区方案里,Bootloader本身不能升级。如果非要升级Bootloader,必须用「恢复模式」或「双Bootloader」。否则一旦Bootloader刷坏了,车就真的变砖了。别问我怎么知道的……

2.7 通信链路设计:HTTPS、MQTT、差分下载

通信链路是OTA的血管。数据传不过去,架构再牛也没用。

我一般用三条通道:

  • HTTPS:用于控制信令。比如云端下发升级任务、车端上报状态。请求-响应模式,可靠但实时性一般。
  • MQTT:用于实时状态上报和推送。车端订阅自己的Topic,云端有紧急任务可以秒级推送。我习惯用QoS 1,保证至少一次送达。
  • 差分下载:用于传输升级包数据。基于HTTP/2的分块下载,支持断点续传。车端下载到一半断网了,恢复后从断点继续,不用重头来。

为什么不用一条通道搞定?你想想看,HTTPS的握手开销太大,不适合频繁上报;MQTT的payload有限制,不适合传大文件。各司其职才是正解。

安全建议:所有通信链路必须走TLS 1.2以上。证书用双向认证——不仅车要验证云端,云端也要验证车。我曾经见过只做单向认证的项目,结果被中间人攻击伪造了升级包。

差分下载还有一个细节:还原算法必须在车端本地执行。云端只传差分数据,车端用旧版本固件+差分数据还原出新版本。这样即使通信被截获,攻击者也拿不到完整的固件。