结论:车端消息契约里最容易混淆的是「请求、软件回报、硬件执行」三层。CarOutput.actuatorsOutput 只是软件计算后的回报,不表示 panda TX 钩子接受、ECU 已执行或车辆真实响应;CarState.valid 直接取 CS.canValid,不自动合并全部逐字段新鲜度;SafetyConfig 虽是列表,但当前所读 setSafetyMode() 只取 safetyConfigs[0]。schema 注释是意图,运行时许可必须以代码及 panda 固件为准。本文为固定源码快照 c8fb906 的静态阅读,未运行回放或测试。
生产和消费链
card.state_update() 从 CAN 批次构造 CarState,card.state_publish() 发 carState、carOutput 和周期性 carParams;controlsd 用 CarState、selfdriveState 与计划形成 CarControl,再由车型控制器转成候选 CAN 帧。CarParams 还在启动时写入 Params,pandad 需等待 FirmwareQueryDone 和 ControlsReady 后读取其安全配置。
| 消息 | 生产者 | 主要消费者 |
|---|---|---|
CarState |
card.state_update() |
状态机、控制器 |
CarControl |
controlsd |
车型控制器 apply() |
CarOutput |
card(取上次保存值) |
控制器饱和逻辑参考 |
CarParams |
启动时写入 Params | pandad 读取安全配置 |
把这条链当成单向管道同样不准确。同一轮里 controlsd 既消费 CarState,又把自己的输出回报成 CarOutput 供下一轮饱和判断使用;CarParams 虽在启动时写入,却是 pandad、控制器与规划层共同读取的配置来源。理解这种「请求—回报—配置」的多向关系,是判断某个字段能否作为执行依据的前提。
CarState:归一化车况
| 字段簇 | schema 语义 | 运行时注意点 |
|---|---|---|
| CAN 健康 | canValid、canTimeout、canErrorCounter |
前两者由车型接口汇总解析器健康;canErrorCounter 累计的是空 CAN 接收周期,不是校验和错误计数;顶层 carState.valid 直接取 CS.canValid |
| 速度与巡航 | vEgo 估计、vEgoRaw 原始轮速、vEgoCluster 仪表速度 |
单位/估计来源有别,不能把仪表显示值直接反馈给动力学控制;CruiseState 区分可用、已启用和静止 |
| 驾驶员与转向 | 踏板为用户输入;驾驶员扭矩和 EPS 扭矩使用原生 CAN 单位,另有 steeringPressed、steeringDisengage 与暂时/永久故障 |
跨车型不得把原生扭矩直接比较;原车 AEB/LKAS/FCW 故障与功能状态是不同字段 |
| 交互与车身 | 档位、按键边沿、转向灯、门、安全带、盲区 | ButtonEvent 应在变化时发送;布尔默认假值不代表传感器已观测到安全无障碍 |
车型基类先更新所有 CANParser,再调用品牌 CarState.update(),最后计算 canValid/canTimeout、仪表速度迟滞及 buttonEnable。需要明确:canValid 并不是所有源的数字签名。
逐字段看,vEgo、vEgoRaw、vEgoCluster 三者单位相同但含义不同:一个是估计值,一个是原始轮速,一个是经迟滞处理的仪表速度。把它们当作可互换的「车速」会导致控制层用到被截零或迟滞过的值。同理,驾驶员扭矩与 EPS 扭矩使用原生 CAN 单位,跨车型直接比较会得到错误结论。
CarControl 与 CarOutput:请求和回报
CarControl.enabled 是总使能,latActive/longActive 分别表达横纵向活动。controlsd.state_control() 从 selfdriveState、车况故障、低速门、纵向覆写事件及 CarParams.openpilotLongitudinalControl 计算它们,三者不应合成一个开关。Actuators 中 torque、steeringAngleDeg、curvature 是按控制类型选用的横向请求,accel 是纵向 m/s² 请求;另有 gas、brake、torqueOutputCan、speed 用于记录车型控制器实际计算的下层值。
| 字段 | 含义 | 不能推断的结论 |
|---|---|---|
CruiseControl |
取消/恢复/覆写意图 | 不能等同具体 CAN 帧,需品牌控制器映射 |
HUDControl |
发给原车仪表的提示数据 | 同样是请求,不代表仪表已响应 |
CarOutput.actuatorsOutput |
车型控制器计算值,注释称反映发送前的车型限幅/特性 | 不是 panda TX hook 接受、ECU 已执行或车辆真实响应的确认 |
carOutput.valid |
取 carControl 的 all_checks |
不能升级成物理回执 |
controlsd 会比较 CarOutput 与本轮 CarControl.actuators,估计转向受限以影响控制器饱和逻辑。controlsd 发布 carControl 时把外层 Event.valid 设为 CS.canValid;card.controls_update() 的本地门控是 carControl 存活后调用品牌 apply(),并用 CS.canValid 标记 sendcan 的外层有效位。它不在这一函数再次以 carControl.valid 为硬许可条件;完整许可仍依赖上游状态机、车型控制器及 panda 安全层。
这里存在一个容易忽略的非对称:carControl 的外层 Event.valid 取 CS.canValid,而 card.controls_update() 在调用品牌 apply() 时并不再以 carControl.valid 为硬条件,只要求 carControl 存活。也就是说,消息层有效性、上游状态机、车型控制器与 panda 安全层各自负责一段,任何单一有效位都不能代表整条链已获授权。
CarParams:跨进程配置
| 字段簇 | 作用及信任界限 |
|---|---|
| 身份/证据 | brand、carFingerprint、VIN、carFw、fingerprintSource、fuzzyFingerprint 记录识别结果;来源标记不是身份认证或执行许可 |
| 动力学/调参 | 质量、轴距、重心、转向比、惯量、轮胎刚度、PID/扭矩调参、执行器延迟进入规划控制;标称值需按目标车辆验证 |
| 执行配置 | pcmCruise、最低速度、横纵向控制能力、steerControlType、safetyConfigs、alternativeExperience、passive、dashcamOnly 决定不同层行为;任意单字段都不是最终执行许可 |
| 认证状态 | secOcRequired、secOcKeyAvailable 记录车型需求和密钥可用状态;不能把「存在密钥」理解为报文已通过对端认证 |
SafetyConfig 是 (safetyModel, safetyParam) 列表,但当前所读 PandaSafety::setSafetyMode() 对其连接的 panda 只取 safetyConfigs[0] 并设置 alternativeExperience;多 panda 的分配须看上层设备编排。card 在被动模式会把列表替换成 noOutput。
身份/证据类字段尤其需要小心:fingerprintSource 与 fuzzyFingerprint 只是来源标记,说明识别结果是如何得到的,而不是身份认证;secOcKeyAvailable 说明密钥可用,也不表示对端报文已通过认证。两者都不能升级为执行许可。动力学与调参字段同理,它们是标称值,进入规划控制前必须按目标车辆验证。
RadarData 与扩展边界
RadarData.errors 包含 CAN 错误、雷达故障、错误配置及临时不可用;点数据包含轨迹 ID、相对距离/横移/速度。schema 注释称这些点字段必需,但 Cap’n Proto 默认值仍可能存在,消费者应结合生产者健康和点轨迹年龄;card 只在收到 RD 时发 radarTracks,外层 valid 由错误位汇总。对自制硬件和插件化,建议将字段单位、坐标系、采样频率、时间戳、最大年龄、缺失值与信号来源做成版本化契约,并把外层 valid、字段默认值、软件输出回报和硬件执行确认分别建模。
常见问题
CarOutput 能证明车辆真实执行了吗?
不能。它来自车型控制器 apply() 的计算值,注释称反映发送前的车型限幅/特性,是软件计算后的回报;不是 panda TX 钩子接受、ECU 已执行或车辆真实响应的确认。
carState.valid 为真代表所有字段都新鲜吗?
不代表。顶层 carState.valid 直接取 CS.canValid,并不自动合并全部逐字段新鲜度;消费关键字段时仍需结合解析器健康与字段年龄判断。
SafetyConfig 列表里的每个配置都会生效吗?
不能直接推断。当前所读 setSafetyMode() 只取 safetyConfigs[0] 并设置 alternativeExperience;多 panda 的分配须看上层设备编排。
CarControl 里的计划字段等于具体 CAN 帧吗?
不等于。CruiseControl、HUDControl 等是意图与提示数据,需要品牌控制器映射为具体帧;schema 字段不能直接等同 CAN 报文。
延伸阅读
- 从使能到车轮:执行链上的八道门,以及它们各自不保证什么 —— 承接本文「请求/回报/执行」三层之分。
- CAN 报文解析的「健康位」在说什么:校验、计数器与总线空窗 —— 解释
canValid/canErrorCounter的确切含义。 - 车载信号为什么会说谎:TTL、源时间与 500 ms 新鲜度阈值 —— 与本文逐字段新鲜度边界相互印证。
- panda 健康包 ABI:版本协商、主机解码与字段映射 —— 另一处「原始观测与发布字段并非无损镜像」的例子。
- 进程生命周期、通信与设备服务 —— 说明这些消息所依赖的 cereal 与 msgq 基础。
参考资料
- 项目 02 ——
openpilot-research/CAR_MESSAGES_FIELD_CONTRACT_DEEP_DIVE.md(取用了生产消费链、CarState/CarControl/CarOutput/CarParams字段簇、RadarData与扩展边界)。 - 项目 02 ——
openpilot-research/ECU_DIAGNOSTIC_TRANSPORT_DEEP_DIVE.md(仅用于确认指纹与固件证据进入CarParams的位置)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11