100 Hz 的 card 网关:sendcan 不按 valid 拦截

作者:

在

结论:card 是「车型发现 → 车况发布 → 控制帧网关」的中央进程,但它发出的东西按语义要分成四层:控制请求已生成、sendcan 已发布、panda 接受、车端确认动作。最容易踩的坑是:card 把 sendcan.valid 赋为 CS.canValid,而 pandad.can_send_thread() 本身并没有根据 Event.valid 字段筛掉 sendcan——这个元数据不是板端的发送阻断。以下基于源码快照 c8fb906 的静态阅读与假依赖探针,未构建、未做车型回放、未做实车验证。

初始化与车型身份

Car.__init__() 创建 can 订阅、pandaStates/carControl/onroadEvents 的 SubMaster,以及 sendcan/carState/carParams/carOutput/radarTracks 发布端。正常路径等首个非空 CAN,读取 AlphaLongitudinalEnabled 与 CarParamsCache,调用 get_car() 做识别、固件查询与接口构建,再按指纹取得 RadarInterface;识别失败在 get_car() 内回退 MOCK,这不是允许未知车发送通用控制帧。

阶段 关键函数/参数 含义
固件查询复用 obd_callback() 写 ObdMultiplexingEnabled 请求,先删 ObdMultiplexingChanged,再阻塞等待 pandad 写确认位;四项假 Params 探针确认假确认返回假仍记成功,且无超时或板端模式读回。
指纹完成 FirmwareQueryDone get_car() 返回后写真;pandad 在该位之前不取最终车型参数。
被动模式 OpenpilotEnabledToggle、CI.CC、CP.dashcamOnly 没有控制器、用户关闭或车型只支持 dashcam 时,CP.passive=True,并把 safetyConfigs 改为 noOutput。
SecOC secOcRequired、SecOCKey 可从指定缓存文件导入 32 个字符,再把十六进制字符串解析为 16 字节;源码只检查长度与解析结果,不等于密钥来源或权限已验证。
参数缓存 CarParamsCache、CarParamsPersistent、CarParamsPrevRoute 缓存用于本次 manager 生命周期的快速指纹;不能拿旧缓存代替当前 CAN/固件匹配。

SecOCKey 解析处调用 bytes.fromhex(),没有局部捕获非法十六进制的 ValueError;静态上长度正确但内容错误的键可令初始化异常。此处尚未构造故障注入测试,量产硬件应验证密钥导入、轮换、无效格式与清除时的安全默认态。

一次 card 步进的消息顺序

把单次 step() 展开,顺序是固定的:

  1. pandad.can → Car.state_update();
  2. 调用 CI.update(can_list) 与 RI.update(can_list)(雷达可返回 None);
  3. VCruiseHelper 更新 CS.vCruise、CS.vCruiseCluster;
  4. Car.state_publish() 依次发布 carOutput、carState、radarTracks;
  5. onroadEvents 不含 selfdriveInitializing 时进入 Car.controls_update();
  6. 首次调用 CI.init() 后写 ControlsReady;
  7. CI.apply(carControl) → sendcan → pandad.can_send_thread → panda 固件安全门。

can_comm_callbacks() 把 cereal 的 can/sendcan 消息与 CanData 列表互转,供指纹、接口初始化与诊断查询使用。state_update() 等待并批量读取原始 CAN,再分别调用车型与雷达 update();空 CAN 轮次累加 can_rcv_cum_timeout_counter,回放时使用 CAN 消息中的单调时间戳。三项原 CAN 回调探针确认:接收回调保留 Event 批次但不返回外层有效位/时间戳;诊断发送回调沿编码器默认值生成 valid=True 的 sendcan——这些回调没有按 carControl 活性或 CS.canValid 过滤。

车况发布:四个 valid 位各有定义

state_publish() 约每 50 秒发布一次 carParams;每步先发布 carOutput(上次 CI.apply() 的执行器输出回报),再发布 carState。四个有效位不能合并成一个「整车健康」标志:

字段 取值来源 边界
carState.valid CS.canValid 只反映车况信号是否有效
carOutput.valid carControl 的 SubMaster.all_checks() 与车况有效性各自独立
radarTracks.valid 雷达错误字段;None 时不发布该主题 错误与点仍在消息内
carParams 节拍 sm.frame 对 50/DT_CTRL 取模 不是独立墙钟定时器

六项原 state_update()/state_publish() 探针还确认:空原始接收批次仍会调用 CI.update()、RI.update() 并累加超时计数;只要原始事件列表非空,即使替身解码成空 CAN 列表也不累加;巡航 helper 先更新当前 CS,控制使能上升沿再用 CS_prev 初始化设定值;carOutput/carState/radarTracks 的有效位可分别为假/真/假。

控制门链:谁真正拦住了发送

step() 在发布车况后,要求 CP.passive=False、已收到 onroadEvents、且其中不含 selfdriveInitializing,才进入控制更新。首次 controls_update() 会调用 CI.init() 做车型接口/ECU 初始化,然后写 ControlsReady;pandad 的 configureSafetyMode() 只有在 FirmwareQueryDone 与 ControlsReady 两个门都满足后才读取 CarParams 配置安全模式。若 carControl 仍活跃,CI.apply() 产出请求执行器回报和 CAN 帧并发送 sendcan;否则本分支不发新帧。

七项原 controls_update()/step() 探针确认:carControl 活跃而 CS.canValid=False 时,仍会编码并发布带 valid=False 的候选帧;initialized_prev=True 时跳过重复初始化;回放模式给 CI.apply() 使用记录的 CAN 时间。这就直接指向前述结论——sendcan.valid 只是元数据,车型 CarController、panda 固件的 TX 安全钩子和 controlsAllowed 才是另外的强制约束,硬件插件不能绕过这些层。

故障或差异 当前代码可证实的处理 待验证
无非空 CAN 初始化持续等待;运行期空批次累加超时计数 线束断开、总线睡眠与线程退出时序
未识别车型或禁用控制 回退 MOCK/被动 noOutput 实板继电器与线束默认态
OBD 复用确认未到 obd_callback() 阻塞等待 Params 确认 固件重连/断电恢复、超时策略
carControl 丢失 all_alive 不满足时不再调用 CI.apply() 最后一帧、panda 超时与原厂系统恢复时延
CAN 无效但已有控制帧 发布 sendcan.valid=False;发送线程未按 valid 拦截 车型控制器与 panda 安全钩子的拒绝/回退

时序上,card 由 CAN 输入驱动,配置 100 Hz Ratekeeper 监测节拍,实际迭代频率取决于总线输入与处理时间;params_thread() 约每 100 毫秒刷新单位与实验模式,cumLagMs 来自 Ratekeeper 剩余时间。不同进程不是原子事务:不能假定最新 carState、最新 carControl 和本轮 CI.apply() 精确一一对应。

常见问题

sendcan.valid=False 会阻止 panda 发送吗?

不会自动阻止。pandad.can_send_thread() 只检查时间戳是否小于 1 秒,本身没有根据 Event.valid 字段筛掉 sendcan。真正的强制约束在车型 CarController、panda 固件的 TX 安全钩子和 controlsAllowed。

收到 carState 就说明整车数据都健康吗?

不能。carState.valid、carOutput.valid、radarTracks.valid 各自有定义,可以分别为假/真/假。它们不能合并成一个「整车健康」标志。

OBD 复用握手为什么会卡住?

obd_callback() 写请求后阻塞等待 pandad 写确认位,而这个握手没有显式超时,也没有板端模式读回。四项假 Params 探针确认假确认返回假仍记成功,因此固件重连/断电恢复与超时策略需要额外设计。

识别失败回退 MOCK 会不会乱发控制帧?

不会直接发送。回退只发生在识别阶段,仍由后续参数与安全配置决定输出;被动路径会把 safetyConfigs 改为 noOutput,而不是允许未知车发送通用控制帧。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/CARD_VEHICLE_GATEWAY_DEEP_DIVE.md(card 车型发现、车况发布与控制帧网关,含源码行号索引)
  • 项目 02 —— openpilot-research/CARD_OBD_MULTIPLEXING_HANDSHAKE_DEEP_DIVE.md(源文档指向的 OBD 复用握手专题)
  • 项目 02 —— openpilot-research/PANDAD_PYTHON_CAN_CAPNP_ADAPTER_VALIDITY_DEEP_DIVE.md(源文档指向的 CAN 适配与有效位专题)

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

更新记录

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注