结论:车型识别不是「看到某条报文就认出车」,而是一条固定链路:panda 收帧 → CAN 指纹 → ECU 固件匹配 → 生成 CarParams / safetyConfigs → DBC 编解码 → 车型 CarState/CarController。在固定源码快照里,17 个车型目录只证明「实现入口存在」,不构成对任何年款、平台的适用性声明;未识别或模糊匹配的候选会被降为被动(noOutput),而不是「先跑起来再说」。
车型实现的共同骨架:五个模块各管一段
车型能力的加载入口是 load_interfaces(),它由 BRANDS 加载车型 CarInterface,不是运行时扫描任意插件目录。也就是说,能不能被识别,先取决于是否在注册表里,而不是目录里有没有文件。
每个品牌常规包含四个模块:values.py 负责车型枚举与物理配置;interface.py 负责车型参数与安全模式;carstate.py 把接收帧变成 CarState;carcontroller.py 把 CarControl 变成候选发送帧。部分品牌另有 radar_interface.py。四者职责不能互相替代——这是接口契约,不是风格问题。
把整条链路摊开看:panda 接收 CAN,交给 card;card 做指纹与 ECU 固件匹配,得到 CarParams 与 safetyConfigs;同一路用 DBC 加 CANParser 解出车型 CarState.update;controlsd 产出 carControl,交回车型 CarController.update;再经 DBC 加 CANPacker 编码为 sendcan,由 pandad 经 USB 发出,最终落在 panda 固件的 safety_tx_hook。主机侧解码和编码只是前段,是否真正上总线由固件独立许可。
17 个车型目录索引:是入口索引,不是适用性声明
下表仅证明固定快照中的实现入口存在——每个目录的 interface.py、carstate.py、carcontroller.py、values.py 均已在目录中核对。它不代表已完成 17 个品牌、所有平台和年款的报文与安全审计。
| 品牌目录 | 额外雷达接口 | 当前深度 |
|---|---|---|
| body | 无 | 非汽车双轮平台链路已追踪 |
| byd | 无 | Atto 3 转角链路已追踪,仅调试安全模式 |
| chrysler | 有 | Pacifica/Ram/CUSW 主要链路已追踪 |
| ford | 有 | 经典 CAN/CAN FD 主要链路已追踪 |
| gm | 有 | ASCM/相机 ACC 主要链路已追踪 |
| honda | 有 | Nidec/Bosch 主要链路已追踪 |
| hyundai | 有 | 经典 CAN/CAN FD 主要链路已追踪 |
| mazda | 无 | 扭矩/速度迟滞主要链路已追踪 |
| mg | 无 | 扭矩链路已追踪,仅调试安全模式 |
| mock | 无 | 非实车空发送回退已追踪 |
| nissan | 无 | 转角/车型取消主要链路已追踪 |
| psa | 无 | 三总线转角链路已追踪,仅调试安全模式 |
| rivian | 有 | Gen1/Gen2 主要链路已追踪 |
| subaru | 无 | Global/Preglobal 主要链路已追踪 |
| tesla | 有 | 转角/原车转发主要链路已追踪 |
| toyota | 有 | 本篇样本已逐层追踪 |
| volkswagen | 有 | MQB/MEB/MLB/PQ 主要链路已追踪 |
这张表不能用同一品牌的安全文件代替平台差异验证。例如 Hyundai 有 CAN FD 分支,Volkswagen 有多种平台安全模式,Subaru 有 preglobal 分支。尤其 set_safety_hooks() 注册表把 MG、PSA、BYD、Subaru Preglobal、Volkswagen MLB/PQ 等模式置于 ALLOW_DEBUG 条件下——实现目录和测试存在,并不表示发布固件可启用执行。
从 CAN 指纹到 safetyConfigs:启动阶段的门
识别发生在启动阶段,顺序由若干入口固定。下面每一行的边界都需要单独记住:
| 入口 | 行为与边界 |
|---|---|
can_fingerprint() |
在总线 0/1 以 CAN 地址及数据长度逐步排除候选;超过限定帧数后停止,不保证未知车型能识别。 |
fingerprint() |
优先查询 VIN、ECU 存在性及固件版本,再收集 CAN 指纹;单一固件候选可覆盖 CAN 结果;环境变量可强制指纹,仅用于受控开发。 |
match_fw_to_car() |
先尝试精确固件匹配,失败再尝试模糊或品牌自定义匹配;返回是否精确及候选集。 |
get_car() |
无候选时回退 MOCK;用候选接口生成 CarParams,并保留 VIN、固件、指纹来源及模糊匹配标记。 |
Car.__init__() |
发布 CarParams;无控制器、开关关闭或仅仪表记录时设为 passive,安全配置改为 noOutput。 |
Car.controls_update() |
仅在进程初始化、非被动、carControl 存活的路径调用车型 apply() 形成 sendcan;首次进入时设置 ControlsReady。 |
MOCK、passive、dashcamOnly 是不可等同的三种状态,但都不能作为「未知车型可以开始执行」的理由。用户配置或环境变量强制指纹,也不构成车辆协议兼容性验证。
DBC 编解码的函数级语义
DBC 层决定「字节怎么变成数、数怎么变成字节」,但它既不负责安全,也不负责身份。以下入口的语义是这张表的核心:
| 入口 | 关键实现与含义 |
|---|---|
DBC.__init__() / get_checksum_state() |
从生成 DBC 或文件加载消息、信号、枚举;按 DBC 系列选择校验算法。 |
get_raw_value() |
按大小端与 bit 位提取原始整数;符号、倍率和偏移由 MessageState.parse() 处理。 |
MessageState.parse() |
解码信号、计算校验和与计数器;失败时返回 False,不把该帧的部分值提交到 vl。 |
MessageState.update_counter() |
递增错误计数,达到阈值拒绝;有效后逐步恢复。 |
CANParser.update() |
仅接收指定 bus 和已注册地址,更新 vl、本次所有值和时间戳,返回成功解析的地址集合。 |
CANParser.can_valid / bus_timeout |
前者检查已注册报文的最近成功时间、计数器及累计缺失次数;后者单独观察整条总线空窗,两者语义不同。 |
CANPacker.pack() |
反向应用倍率/偏移,编码信号,自动补计数器与校验和;未知信号仅记录错误并跳过,不负责安全许可。 |
两个容易踩的边界:CANParser 的 vl 只保存最近一次成功解析值,不能只看数值而忽略 can_valid、时间戳与 bus 健康;can_valid 在读取时也会更新缺失计数,所以调用频率会影响去抖过程,它不是纯无副作用的 getter。另外,CANPacker 是协议编码器,不是转向扭矩或加速度安全门。
面向插件化的最低契约
把上面几层抽象成可替换的插件时,有五条底线必须写进契约:
- 车型插件声明平台、ECU 固件范围、DBC 版本、总线拓扑、能力位、
CarParams与安全模式;未识别或模糊匹配默认被动。 - 解码插件输出值、时间戳、来源与校验/计数器/超时状态;不能把上次成功值伪装成当前有效观测。
- 控制插件只产生结构化
CarControl或候选 CAN 帧;不能自行改固件白名单、安全模式、ControlsReady,或绕过card/pandad。 - 硬件安全控制器独立于应用 CPU,实现 RX/TX/转发限制、心跳失效回退与板级故障处理;新硬件须用故障注入、SIL/HIL 与封闭场地验证。
- 每个目标车型逐一建立解析、编码、固件安全和回放基线;17 个品牌目录不是「一套通用线束或固件配置」规格。
换句话说,能不能读到一条报文,与"这套配置能否在目标车上安全执行"是两个不同层级的问题。把前者当成后者,正是识别与安全最常被混淆的地方。
常见问题
为什么不能靠一条 CAN 报文就确定车型?
因为 can_fingerprint() 是在总线 0/1 上以地址和数据长度逐步排除候选,超过限定帧数后会停止,源文档明确它不保证未知车型能识别;而 fingerprint() 还要结合 VIN、ECU 存在性与固件版本,单一固件候选才可能覆盖 CAN 结果。单条报文只是排除过程的一个输入。
车型目录里有文件,是否代表这辆车就能用?
不代表。17 个目录只证明实现入口存在,源文档特别说明它不代表已完成 17 个品牌、所有平台和年款的报文/安全审计;而且 MG、PSA、BYD 等模式被放在 ALLOW_DEBUG 条件下,实现目录和测试存在并不表示发布固件可启用执行。
DBC 解析成功,是否意味着数据可信?
不等于。CANParser 的 vl 只保存最近一次成功解析值,必须同时看 can_valid、时间戳与 bus 健康;can_valid 读取时会更新缺失计数,调用频率会影响去抖。数值正确但报文陈旧或总线超时,仍属于不可信输入。
未识别或模糊匹配时,系统会怎样处理?
get_car() 在无候选时回退 MOCK;当车型被设为 passive(无控制器、开关关闭或仅仪表记录)时,安全配置改为 noOutput。源文档强调 MOCK、passive、dashcamOnly 不可等同,且都不能作为未知车型开始执行的理由。
延伸阅读
- openpilot 的 100 Hz 控制回路:开源 ADAS 能给车载设备什么 —— 理解
carControl到sendcan为何是分层契约。 - CAN 报文解析的「健康位」在说什么:校验、计数器与总线空窗 —— 与本文的
can_valid/bus_timeout语义直接衔接。 - 同一个「转向灯」在三家车厂是三种实现:车辆状态归一化的坑 —— 解释
CarState.update为何必须按品牌拆分。 - 车型指纹与固件匹配:识别链上的两道关口 —— 承接
match_fw_to_car()的精确与模糊两级顺序。 - 从使能到车轮:执行链上的八道门,以及它们各自不保证什么 —— 说明主机编码之后仍由固件独立许可。
参考资料
- 项目 02 ——
openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(取用了 17 个车型目录索引、启动识别门、DBC 编解码函数语义与插件化最低契约)。 - 项目 02 ——
openpilot-research/CAN_PARSER_HEALTH_DEEP_DIVE.md(仅用于确认can_valid/bus_timeout的健康位语义边界,未取用其未公开内容)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11