标签: Cap’n Proto

  • 车辆消息字段契约:请求、软件回报与硬件执行的三层之分

    结论:车端消息契约里最容易混淆的是「请求、软件回报、硬件执行」三层。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 报文。

    延伸阅读

    参考资料

    • 项目 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

  • CAN 与 Cap’n Proto 适配层:有效位为何在跨语言边界丢失

    结论:在这套 CAN 与 Cap’n Proto 适配层里,valid 只在 Event 外层,帧本身没有独立有效位:编码器只写外层 logMonoTime 与 valid,解码器只返回时间戳与帧三元组,不返回外层有效位。更关键的是发送侧——C++ 发送线程只读 logMonoTime 和 sendcan,用一秒年龄门与 fake_send 条件决定是否调用打包发送,没有检查 event.getValid(),因此 CS.canValid=False 只是日志与下游可见的标注,并不单独阻止该线程尝试发送。是否实际发上车总线仍受后续编码、传输与 MCU 安全钩子控制。本文为固定源码快照 c8fb906 的静态阅读与假 Event 原函数探针,未运行真实 Cap’n Proto、总线进程或连接车辆。

    组件与关键函数

    CAN 帧在 schema 里有 32 位地址、数据载荷和 8 位来源;Event 有 logMonoTime、默认恒真的 valid,且 can 与 sendcan 是不同 union 成员。编码器首次遇到非空帧时缓存字段对象,后续按相同 schema 访问。

    函数/组件 行为
    CanData/Event 帧有 32 位地址、数据载荷与 8 位来源;Event 有 logMonoTime 与默认真的 valid
    _get_reader_fields()/_get_writer_fields() 首次非空帧时缓存字段对象,后续按相同 CanData schema 访问
    can_list_to_can_capnp() 新建 Event,填入 can 或 sendcan 帧列表并序列化;valid 与时间戳只写在外层
    can_capnp_to_list() 逐条解析 bytes,返回时间戳与帧三元组,不返回 Event valid
    controls_update() 控制侧用 CS.canValid 标注 sendcan Event
    can_send_thread()/can_recv() 发送侧按时间戳与一秒年龄门筛选;接收侧把接收布尔值设为 Event valid

    从帧到 Event 的数据流

    编码器接受三元组列表,按调用方 msgtype 在 Event 上初始化 union 列表,外层 logMonoTime 来自单调时钟。首次遇到非空列表才缓存 CanData 的三个 schema 字段对象;空列表仍可产生合法的空 can 或 sendcan Event。函数自身不检查地址范围、总线号、CAN 或 CAN FD 合法长度与消息数量,依赖 schema 字段类型以及后续边界处理——因此不能把 schema 能序列化当作总线发送合法性证明。

    控制更新时,若控制消息存活则生成 sendcan,以状态里的 canValid 填外层 valid;而车型识别回调使用默认 valid=True。发送线程只读 logMonoTime 和 sendcan,用一秒年龄门与 fake_send 条件决定是否调用打包发送,没有检查 event.getValid()。所以状态标注与是否尝试发送是两件事,日志里看到的无效标注不能推断「危险动作已经发生」或「一定不会发送」。

    接收侧与解码资源边界

    接收侧即使底层接收返回假,仍构造一个有效位为假的 can Event,并放入当前收集到的帧。Python 解码只保留时间戳与帧、不传递外层有效位;状态更新先把这些帧送给车型状态与雷达接口,再以原始消息列表是否非空判断本轮是否收到 CAN。这样「收到一个无效 Event」仍算消息到达,其中已有的帧仍可能进入车型解析——当然车型解析器本身还有 CAN 检查,这里的结论仅限适配层与状态更新的直接门控。

    解码函数用接近无穷的遍历上限解析输入,相当于不在此调用处施加实用的遍历预算,输入消息大小和队列限制需沿上游单独核对。它直接按 msgtype 取 union 字段,没有先核对 union 实际类型,调用方传入错误类型或坏 bytes 时可能抛异常,而非返回结构化的坏消息结果。字段对象全局缓存假设后续仍是同一个 CanData schema,当前正常路径成立;缓存不保存消息内容或设备状态,也不等于逐帧验证。

    离线原函数探针与证据范围

    四项隔离测试从归档文件抽取四个原函数运行,仅替换 Event、帧字段对象与序列化存储。

    输入 原函数观察结果 证据边界
    空 can 列表 可生成并解出空 Event;字段缓存仍为空 假 Event 只实现最小接口,不证明真实序列化字节
    非空 sendcan,有效位为假 帧与时间戳往返;外层有效位不出现在解码结果;首次帧填充缓存 只证明本适配函数的字段路径与返回形状
    错误 union 与坏载荷 假 Event 抛出的属性或解析异常直接向上传播 真实解析库的错误类型与 union 语义仍未验证
    缺字段三元组、非法编码类型 原函数没有预检查;前者索引失败,后者委托初始化失败 假 Event 只模拟非法 union 拒绝,不能外推 schema 约束

    回放与仿真也复用这套适配函数:回放会把日志中的 can Event 转 bytes 后经该解码器导出帧列表,仿真则复用编码器发布合成 can。它们既服务实车运行也服务回放,但信任级别与时间域并不相同。

    自研硬件与插件契约

    消息插件契约应明确区分载荷有效位、总线链路健康、schema 可解析、时间戳年龄和 MCU 安全放行。只读观察插件可拿到带来源与有效位的 CAN 快照;控制候选插件不得直接发布 sendcan 到受信任网关。网关需逐 Event 核对 union 类型、有效位、时间域与年龄、帧数、地址、总线、长度和车型授权,再把送达传输、MCU 接受与安全拒绝分开记录。诊断与回放输入应有明确大小与遍历预算,异常帧应隔离而非让主车控线程意外退出。离线验收至少覆盖空列表、错误 union、有效位为假但含帧、CAN 事件无效且仍有先前好帧、坏 bytes、极大列表与载荷、未来或跨时钟域时间戳。

    常见问题

    CS.canValid=False 会阻止发送吗?

    不会单独阻止。控制更新会把 CS.canValid 写进 sendcan Event 的外层有效位,但 C++ 发送线程只读 logMonoTime 与 sendcan,用一秒年龄门和 fake_send 条件筛帧,没有检查 event.getValid()。是否真正发上车仍受后续编码、传输与 MCU 安全钩子控制。

    为什么解码后拿不到 valid?

    因为有效位只在 Event 外层。编码器把 valid 与时间戳写在外层,解码函数只返回时间戳与帧三元组列表,不返回外层有效位;因此跨语言往返后,帧层面看不到原始的无效标注,需要在上层另行传递。

    收到一个无效 CAN 事件算收到消息吗?

    在这层算到达。接收侧即使底层接收返回假仍会构造有效位为假的 can Event 并放入已收集帧;状态更新以原始消息列表是否非空判断本轮是否收到 CAN,所以无效事件里的帧仍可能进入车型解析,导致「丢帧」判断被低估。

    探针证明了真实 Cap’n Proto 行为吗?

    没有。四项探针替换的是 Event、帧字段对象与序列化存储,属于局部回调与编码器证据;真实解析库的错误类型、union 访问语义、遍历预算下的内存与时间行为都未验证,也不是诊断帧取得板级发送授权的证明。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PANDAD_PYTHON_CAN_CAPNP_ADAPTER_VALIDITY_DEEP_DIVE.md(取用了编码与解码函数、发送侧不读有效位、接收侧无效事件构造与四项原函数探针)。
    • 项目 02 —— openpilot-research/CAN_PARSER_HEALTH_DEEP_DIVE.md(仅用于确认车型解析器自身的 CAN 检查位置)。

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

    首次发布 2026-10-11 最近核对:2026-10-11