标签: panda

  • 大型 ADAS 代码库怎么读:四步阅读顺序与三类边界

    结论:读一个大型 ADAS 代码库,正确顺序不是从目录名或进程启动顺序开始,而是按职责与信任边界组织:先分清运行底座、输入感知、状态估计、车辆接入、决策执行、旁路验证六大块,再沿「车况从 CAN 进来到控制从 CAN 出去」这条链逐层追。本文给出一条可复用的四步阅读顺序——从进程与消息契约出发,追车况解析、追使能状态机、追候选发送与板端安全钩子——并强调三类不能混同的边界。源代码根提交为 c8fb906,归档缺少模型权重实体,以下为静态分析,不代表构建通过或实车验证。

    按职责与信任边界看,而不是按目录名

    这个代码库的关键结构不是「哪个目录属于哪个功能」,而是各部分所处的信任层级。可以用一张源码目录地图定位:

    目录 在系统中的位置 主要产物或边界
    系统与平台目录 设备与运行平台 进程管理、相机与传感器采集、记录、设备状态与远程服务
    消息契约与传输 数据契约与传输 日志结构定义、服务频率定义、跨进程消息队列与图像缓冲
    共享运行时 共享基础 参数持久化、时间与调度、日志、设备抽象与版本信息
    视觉推理目录 视觉推理 道路与驾驶员模型输入处理与推理输出;模型权重属于缺失的实体
    定位与监控目录 状态估计 定位、标定、车辆参数学习与驾驶员注意力策略
    车辆 I/O 目录 主机侧车辆 I/O 车况标准化、候选发送帧生成、与安全板通信
    决策与控制目录 决策与执行请求 雷达融合、纵向规划、控制器与使能告警状态机
    车型协议与安全目录 车型协议与独立安全边界 DBC、车型状态与控制、safety 规则;安全 MCU 执行收发与发送限制
    开发验证目录 开发验证 回放、仿真、CAN 分析与诊断,不属于车上实时控制路径

    要特别记住一点:安全板固件、车型协议、跨进程通信与推理运行时分别位于不同的源码归档,不要把它们误认为同一个可热插拔模块。这条边界决定了后面所有替换与移植工作的可行性。

    对照这张地图时,一个实用判断是看某模块是否参与了实时控制路径:属于开发验证的部分不应成为车上的必要依赖,而安全相关部分则必须与车型、总线与固件联合版本化。

    四步阅读顺序

    公开资料给出了一条从函数追到消息的阅读顺序,可以照此执行:

    1. 从进程配置与进程管理函数找到运行进程,再对照服务频率定义与日志结构定义的主题字段;
    2. 从车况更新函数追车型接口更新、车型状态与雷达轨迹,再追模型输出、雷达状态、纵向计划的生产者;
    3. 从事件汇总与状态机更新的函数追使能 / 退出条件,再看控制器如何生成执行请求;
    4. 从车型编码函数追车型控制器的更新、候选发送帧、主机到板传输与板端安全钩子;任何可替换模块都不能跨越最后一层安全门。

    顺序的关键在最后一步:候选发送帧在到达总线之前,必须经过安全 MCU 的独立检查。也就是说,主机侧插件、车型控制器和配置项都不能替代板端规则。

    这四步的意义在于把「消息契约」放在第一位:先知道有哪些主题、频率与字段,再去看谁生产它们。否则很容易在某个子目录里困住,却不知道它在闭环中的位置。追控制链时,建议始终回到「车况进来、控制出去」这两条主线上做交叉验证。

    运行阶段与消息速查

    下表按理解闭环的顺序排列,不是全局同步调用栈。进程各自运行,消息的发布、接收与板端执行发生在不同时间,表内的「输出」也不等于车辆已经执行:

    阶段 主要输入与产出 硬件或插件边界
    设备与进程 板 / 电源 / 温度等状态形成设备启动状态,进程管理据此增减受管进程 新平台必须先可靠报告点火、热状态和电源;进程启动不代表传感器健康
    物理采集 相机图像进入模型;CAN 经车型解析形成车况与雷达轨迹 帧时间、坐标系、CAN 来源总线及采样年龄必须随适配器一起验收
    观测发布 分别发布模型输出、相机里程计、车况与雷达轨迹 同一发布周期内的多个主题也可能有不同有效条件
    融合与规划 雷达轨迹与模型输出形成雷达状态;模型更新触发纵向计划 规划插件须声明输入时间轴、有效性和输出期限;计划存在不等于控制获准
    使能与控制 事件产生使能状态;控制器综合车况、模型和计划发布控制请求 使能、横向、纵向是不同权限;控制请求的 valid 不是最终硬阻断
    车型编码 控制请求经车型转换成为候选发送帧 主分支检查控制请求存活,不统一检查其有效位
    主机到 MCU 主机按消息年龄发送候选帧,另维护板状态与心跳 消息年龄门和心跳不是逐帧安全授权
    板端安全 按安全模式、发送白名单、车型钩子及故障决定是否接受候选帧 车型 / DBC / 安全参数 / 固件必须联合版本化

    建议先用只读 CAN 与图像采集验证前四阶段,再以离线回放验证控制请求,最后独立验证网关与 MCU 的拒帧、超时及故障退化。

    三类边界:输入、能力与验证

    从源码职责推导,面向自制硬件与插件重构有三条边界,对应四类插件:

    插件类型 可定义的契约 不可交给插件自我授权的部分
    传感器 / 感知 时间戳、坐标系、健康状态和标准消息输出 无效或过期输入继续参与控制的许可
    车型适配 指纹、DBC、车况与控制帧、受控总线清单 安全模式、发送白名单、驾驶员覆盖规则
    规划 / 控制 标准模型 / 雷达 / 控制契约及回放等价性 无条件接管发送或绕过使能状态机
    记录 / UI / 远程 只读订阅、存储和远程诊断能力 将远程命令直接转为车辆控制权限

    第一条边界是物理输入与安全发送:车型、总线、线束、摄像头时间戳以及安全板配置必须版本绑定。第二条是算法与辅助能力:感知、定位、日志和 UI 可以探索可替换接口,但控制相关组件不能仅凭「插件已安装」取得发送权。第三条是验证:先只读采集、进程回放和故障注入,再做台架与受控场地测试;这类文档不是接线图或上车许可。

    常见问题

    能不能只看目录名理解这套代码?

    不建议。应优先按职责与信任边界组织:安全板固件、车型协议、跨进程通信和推理运行时分别位于不同源码归档,按目录名会被误导。可用文中的目录地图定位各部分所处层级。

    控制请求发出去了就等于车辆执行了吗?

    不等于。候选发送帧在到达总线前还要经过安全 MCU 的独立检查;主机侧的消息年龄门和心跳也不是逐帧安全授权。文中所有表中的「输出」都不等于车辆已经执行。

    使能、横向、纵向是同一个开关吗?

    不是。公开资料特别指出它们是不同权限,且控制请求的 valid 不是最终硬阻断。判断能否执行应回到使能状态机与板端安全规则,而不是单一布尔值。

    这份分析能作为上车依据吗?

    不能。归档缺少模型权重实体,本文为静态源码分析,不代表构建通过或实车验证;它不是接线图或上车许可,任何执行能力都需要台架与受控场地另行验证。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/ARCHITECTURE_GUIDE_CN.md(取用源码目录地图、四步阅读顺序、运行阶段与消息速查、三类边界与插件类型表)。

    说明:本文为对固定源码快照(根提交 c8fb906)的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有入口、阶段与边界均出自源文档,标注为未验证的结论保留其原有边界。

    更新记录

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

  • GM panda safety:三个参数位切换拓扑,白名单不等于字段认证

    结论:GM panda safety 按 HW_CAM、HW_CAM_LONG、EV 三个参数位切换硬件拓扑与 TX 表,三者的使能与转发策略互不相同:ASCM 分支 disable_forwarding=true 完全禁转发、由按钮沿使能;相机原车纵向跟随 PCM;相机自控纵向仅在调试构建且 HW_CAM_LONG 置位时才把 gm_pcm_cruise 改为假。白名单只控制 CAN 地址、总线、长度,不代表所有字段都经安全认证。

    本文基于固定源码快照逐函数静态解析 opendbc/safety/modes/gm.h,结合通用 safety 分发与本机原版 GM 测试。未编译目标固件或接入车辆;这里的总线号、编码和阈值仅适用于该源码快照及匹配车型,不能直接当作新硬件配置。

    RX:来源、使能与完整性缺口

    GM_COMMON_RX_CHECKS 监视总线 0 的 0x184 驾驶员扭矩、0x34A 后轮速度、0x1E1 按钮、0xBE 踏板、0x1C4 油门/巡航和 0xC9 制动状态;0xBE 允许三种长度以适配不同车型。各项暂以 10 Hz 标称检查,且显式忽略校验和、计数器、质量位——源码 TODO 指出这不是完整报文完整性校验。EV 参数位另把 0xBD 再生制动消息纳入 RX 表,频率配置为 40 Hz。

    报文 用途 完整性处理
    0x184 驾驶员扭矩(11 位有符号,进共享采样窗口) 忽略校验和/计数器/质量位
    0x34A 后轮双速;两轮原始速度均不超过 10 判静止 停车判据,不是完整车速估计
    0x1E1 按钮沿使能(ASCM 用 SET 松开沿或 RESUME 按下沿) 忽略完整性字段
    0xBE ASCM 刹车(字节阈值);允许三种长度 与相机刹车帧 0xC9 不是同义来源
    0x1C4 相机原车纵向读取巡航接合状态 走 pcm_cruise_check()
    0xC9 相机刹车(bit 40) 与 0xBE 不能互换
    0xBD EV 再生制动,仅 EV 参数位置位时入 RX 表 配置为 40 Hz

    safety_rx_hook() 只在检查通过、命中表后调用 gm_rx_hook(),因此再生制动分支并非在非 EV 模式下无条件接收。ASCM 以 0x1E1 的 SET 松开沿或 RESUME 按下沿使能,CANCEL 按下退出;相机原车纵向则从 0x1C4 读取巡航接合状态。

    TX 与模式矩阵

    GM 自控纵向时 gm_pcm_cruise=false;相机原车纵向时为 true。相机自控纵向仅在调试构建且 HW_CAM_LONG 位置位时把它改为假。这决定使能来自按钮沿还是 PCM,影响告警与执行权限,不只是 TX 白名单的变化。

    模式 纵向 TX 转发与使能
    ASCM,HW_CAM=0 0x2CB 位于总线 0,0x315 位于总线 2;另有观测/keepalive 帧 disable_forwarding=true,由按钮沿使能
    相机,默认或 release 无 0x2CB/0x315;0x1E1 总线 2 仅允许取消 跟随 PCM 巡航接合,仍可发横向 0x180
    相机,ALLOW_DEBUG 且 HW_CAM_LONG=1 0x2CB/0x315 总线 0 改用按钮沿,禁原车 0x1E1 TX
    上述模式叠加 EV 不改变所选 TX 表 仅增加 0xBD 再生制动 RX

    gm_tx_hook() 对 0x180 调用共享扭矩安全检查,含 300 原始单位上限、变化率、驾驶员扭矩与实时增量限制。0x315 的 12 位原始编码先反转为非负制动量,再用 longitudinal_brake_checks() 限制到 max_brake=400 且未使能时必须为零。0x2CB 将油门/再生制动信号偏移到整数域后分别按 ASCM/相机限值检查,apply 位在 controls_allowed=false 时禁止。相机原车纵向的 0x1E1 只有原车巡航已接合且按钮编码为 CANCEL 时放行。

    白名单不等于字段认证

    并非所有 GM 白名单帧都有逐信号检查:例如 ASCM 的 keepalive/观测帧和相机的 0x184 状态帧,在 gm_tx_hook() 中没有对应载荷分支。白名单控制的是 CAN 地址、总线、长度。

    转发层面同样分层:相机模式中 0x184 总线 2 的 check_relay=true 会使共享转发逻辑阻断来自总线 0、目标为总线 2 的同地址帧;ASCM 则完全禁转发。应把「是否允许发」「是否阻断原车转发」「是否检测原车 ECU」分别建模。

    验证范围与插件边界

    test_gm.py 有 ASCM、相机、EV、相机自控纵向测试类,包含按钮、踏板、扭矩/纵向限值和点火用例。本机运行原版 GM 模块测试为 Ran 192 tests、OK (skipped=19),即 173 项执行且通过,19 项未执行。跳过项来自无 _user_regen_msg 的通用再生制动用例、无第二车速消息的通用检查,以及四个抽象基类的 setUpClass,不能计入通过。

    测试调用 libsafety_py._build_libsafety() 默认加 ALLOW_DEBUG,因此相机自控纵向用例不能证明 release 固件接受 HW_CAM_LONG。本机另用严格警告把测试库 safety.c 分别编译为不含/含 ALLOW_DEBUG 的目标文件,只在后者找到 GM_CAM_LONG_TX_MSGS——这核对的是本机编译分支,不是 STM32 固件或运行时许可。实板转发、实际 CAN 来源和目标 ECU 响应仍未测试。

    • 自制硬件的 GM 插件契约应固定车型/DBC、ASCM 或相机接线、EV 位、总线号、固件构建类型和 safetyParam。
    • 只读采集和回放先验证 0xBE 长度变体、0xC9 刹车、0xBD 再生制动、按钮沿、PCM 巡航及 0x184 驾驶员扭矩。
    • 台架再做丢帧、重放、错总线、故障继电器和 release/debug 差异。
    • 普通插件只产生候选控制,不能设置安全钩子或绕过板端 TX/转发判定。

    常见问题

    GM 白名单里的帧都经过字段级安全检查吗?

    不是。白名单只控制 CAN 地址、总线、长度;ASCM 的 keepalive/观测帧和相机的 0x184 状态帧在 gm_tx_hook() 中没有对应载荷分支,不能据此声称所有字段都经过安全认证。

    相机自控纵向在 release 固件里能用吗?

    测试不能证明。相机自控纵向仅在调试构建且 HW_CAM_LONG 位置位时把 gm_pcm_cruise 改为假;而测试默认加 ALLOW_DEBUG,本机只在含该宏的目标文件里找到 GM_CAM_LONG_TX_MSGS,这核对的是编译分支而非运行时许可。

    ASCM 和相机的刹车帧可以互换吗?

    不能。ASCM 的刹车取 0xBE 字节阈值,相机刹车取 0xC9 的 bit 40,切换拓扑时不能把两种刹车帧视为同义来源;0xBE 还允许三种长度以适配不同车型。

    GM 的 RX 完整性与校验和有关系吗?

    没有做完整校验。各项暂以 10 Hz 标称检查,且显式忽略校验和、计数器、质量位;源码 TODO 指出这不是完整报文完整性校验。所以「命中 RX 表」不等于「报文完整可信」。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/GM_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了 GM 三参数位模式矩阵、RX/TX 与转发边界、白名单不等于字段认证,以及本机测试计数)。
    • 项目 02 —— openpilot-research/ACTUATION_GATE_CHAIN_DEEP_DIVE.md(仅用于确认执行链分层语义,未取用其未公开内容)。

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

    更新记录

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

  • Cabana CAN 分析:五种输入源、MessageId 严格解析与保存陷阱

    结论:Cabana 是一个明确的只读采集与分析工具:它把路段回放、本地/设备 msgq、ZMQ 桥接、USB Panda、Linux SocketCAN 五种输入汇入同一套 CAN 事件与消息状态模型,并在这之上做 DBC 编辑与 CSV 导出。MessageId 严格解析为「总线:十六进制地址」,不能只用地址当唯一键。要特别记住一个保存陷阱:saveFile() 没有检查 DBCFile::save() 的布尔返回值就把撤销栈标记为 clean 并显示「File saved」,因此磁盘写入失败时 UI 状态不能当作保存成功证据。此外,界面能解码不等于允许发送帧——DBC 文件不等于固件安全策略。

    五种输入源汇入同一事件层

    main() 解析 CLI 后按 --msgq、--zmq、--panda、Linux --socketcan、路段回放的顺序选择一个输入源;路段加载被延后到窗口出现后执行,避免网络文件列表阻塞首屏。各源最终进入 AbstractStream 的 CAN 事件和消息状态模型。下表是各输入组件的关键行为与限制。

    输入组件 关键函数 源码行为与限制
    路段回放 ReplayStream::loadRoute()、mergeSegments() 用 Replay 加载 can、视频索引与 carParams;回放线程解析段内 CAN,主线程插入排序索引。鉴权、网络、文件缺失有不同错误文案。
    本地 / 设备消息 DeviceStream::start()、streamThread() 本地从 can msgq 订阅;指定 ZMQ 地址时先 fork/exec 桥进程并用 can 过滤,失败经 self-pipe 报错,析构时终止桥进程。
    Panda PandaStream::connect()、streamThread() 连接后设置 NO_OUTPUT 安全模式,配置三个总线速率及适用设备的 CAN-FD 数据速率;循环接收 CAN、发 heartbeat(false),断线尝试重连。
    SocketCAN SocketCanStream::connect()、streamThread() Linux RAW CAN socket 绑定指定接口、尝试启用 CAN-FD,读取帧后映射到 src=0 的 cereal CAN。当前此流只读。
    实时共用层 LiveStream::handleEvent()、updateLastMessages() 可按一分钟目录写本地 rlog;非主线程收帧,主线程合并、节流刷新并支持暂停/回看。是否落盘取决于设置,不应默认视为无数据留存。

    需要提醒两点:Panda 的连接与速率设置仍是硬件操作,不能当作纯软件只读模拟;SocketCAN 的 src=0 映射也不适合直接表达多总线车辆拓扑。

    MessageId 是「总线:地址」,不能用地址当唯一键

    AbstractStream::newEvent() 从 cereal CanData 复制时间、地址、来源和字节;mergeEvents() 按 (source, address) 建立每消息索引,并维护总时间序列。MessageId 严格解析「总线:十六进制地址」,不能只用地址当唯一键。updateLastMessages() 把后台新数据合并到可供 UI 消费的最后消息状态,seek 则按时间重建消息快照。

    这里还有一个容易被当成结论的提示:CanData::compute() 的颜色与翻转频率只是观察和提示信号,不构成 DBC 正确性证明。它能帮你发现可疑的字节变化,但不能替代逐车型的 DBC 校验。

    边界 源码事实 正确理解
    消息键 MessageId 严格解析「总线:地址」 地址可跨总线重复,单用地址会串帧
    字节频率 CanData::compute() 给颜色与翻转频率 是观察提示,不是 DBC 正确性证明
    SocketCAN 来源 读取帧后映射到 src=0 不适合直接表达多总线车辆拓扑
    实时落盘 可按一分钟目录写本地 rlog 是否落盘取决于设置,不应默认无留存

    DBC 编辑:UI 状态不是保存成功证据

    DBCFile::parse() 解析 BO_ 消息、SG_ 信号、VAL_ 枚举和 CM_ 注释,保留首个识别语句之前的头部;遇到重复地址或重复信号会报错,异常附带行号。DBCManager::open() 把文件关联到一个或多个来源总线,同一个 DBC 可共享给多个总线;没有专属文件时 findDBCFile() 回退全来源 DBC。因此源总线与文件关联影响解码结果,不能只按车型名称自动推定。

    编辑路径同样要注意状态与磁盘的分离:DBCManager::addSignal() 等编辑接口发出变化通知;updateMasks() 在「抑制已定义信号」开启时按 DBC 掩码清理高亮位计数;UndoStack::push() 立即执行命令并维护撤销/重做,只有 saveFiles() 才尝试写文件。最关键的失败模式是 saveFile() 不检查 DBCFile::save() 的布尔返回值就把撤销栈标记 clean 并显示「File saved」——磁盘写入失败时,界面提示不能作为保存成功的证据。

    导出与安全边界:只读采集不是控制许可

    utils::exportToCSV() 导出时间、地址、总线与十六进制原始数据;exportSignalsToCSV() 按当前 DBC 对选中消息解码信号值。导出的是观察数据和当前解释版本,时间戳被格式化为秒的小数三位;用于控制验证时应保留原始日志、DBC 版本与更高精度时间源。

    面向自制硬件与插件化,源文档给出三条要求:第一,把 Cabana 当成只读采集与分析插件,接入自制硬件时分别记录物理总线、接口速率、时间戳来源、丢帧与 CAN-FD 能力,保留原始帧以供回放;第二,DBC 编辑器产物必须经过版本管理、逐车型测试和 opendbc/panda 独立安全策略审查,「界面能解码」不等于允许发送帧;第三,直播桥与本地 rlog 涉及车内数据外传/留存,应有明确的用户授权、加密、存储期限和网络访问边界,开发工具不应共享车辆控制进程的写权限。

    常见问题

    Cabana 能直接当成车辆控制工具吗?

    不能。源文档把它定位为协议分析工具,明确「不是车型安全许可或控制执行器」。Panda 输入路径虽设置 NO_OUTPUT 安全模式,SocketCAN 流也只读,但连接与速率设置仍是硬件操作;「界面能解码」不等于允许发送帧。

    为什么 MessageId 不能只用地址?

    因为 MessageId 严格解析「总线:十六进制地址」,mergeEvents() 按 (source, address) 建立每消息索引。同一个地址可能出现在不同总线上,只用地址当唯一键会导致串帧,也无法正确表达多总线拓扑。

    为什么点了「File saved」仍可能没保存成功?

    因为 saveFile() 没有检查 DBCFile::save() 的布尔返回值,就把撤销栈标记为 clean 并显示「File saved」。源文档明确指出磁盘写入失败时,UI 状态不能当作保存成功证据,需要另行核对磁盘文件内容。

    字节变化热图能否证明 DBC 解析正确?

    不能。CanData::compute() 的颜色和翻转频率是观察与提示信号,源文档明确它不构成 DBC 正确性证明。发现可疑字节后仍需按车型核对 DBC 版本与解码结果。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CABANA_CAN_ANALYSIS_DEEP_DIVE.md(取用了五种输入源、MessageId 严格解析、DBC 编辑与保存失败模式、CSV 导出与只读安全边界)。

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

    更新记录

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

  • Subaru Global safety:0x221 只允许取消,纵向不放行

    结论:Subaru Global safety 的 TX 权限极窄:转向帧 0x122 受扭矩与请求位门约束,而 0x221 ES_Distance 只允许取消——任何非 inactive_gas=1818 的 Cruise_Throttle 或未设置 Cruise_Cancel 都被拒,主动油门、制动或巡航 RPM 命令一律不放行。该快照下 alphaLongitudinalAvailable=False,不能因限值结构存在就向插件授予纵向执行权限。

    本文依据固定源码快照的 subaru.h、车型接口和相邻安全测试做静态分析,不证明目标固件、线束、ECU 或实车安全。本文覆盖 Global 模式;它与 Preglobal 是不同安全钩子,能力不能互相继承。

    模式与总线矩阵:Gen2 只搬了三组 RX

    Global 选择 subaru 安全模式,Gen2 额外设置 GEN2 参数。subaru_init() 按 Gen2 标志选择两组 RX/TX 总线表,没有纵向 TX 表分支。一个常见误解是「Gen2 把全部 RX 移到 bus 1」——实际只有轮速、制动、巡航三组从 bus 0 改到 bus 1,油门与驾驶员扭矩仍留在 bus 0。

    报文职责 Gen1 Gen2 钩子使用
    0x40 油门、0x119 驾驶员扭矩 RX bus 0 bus 0 两代相同
    0x13A 轮速、0x13C 制动、0x240 巡航 RX bus 0 bus 1 Gen2 的 alt bus 影响这三组,不能把全部 RX 一起移到 bus 1
    0x122 ES_LKAS TX bus 0 / 8 字节 bus 0 / 8 字节 两代均检查扭矩与请求位
    0x221 ES_Distance TX bus 0 / 8 字节 bus 1 / 8 字节 均只允许取消,Cruise_Throttle=1818
    0x321/0x322/0x323 HUD TX bus 0 / 8 字节 bus 0 / 8 字节 白名单与继电器检查;无本模式逐字段校验

    五组 RX 均要求 8 字节、8 位累加校验、四位计数器和存活检查;声明频率依次为油门 100 Hz、扭矩 50 Hz、轮速 50 Hz、制动 50 Hz、巡航 20 Hz。这些频率用于健康检查,不等于每帧独立实时截止时间。subaru_get_checksum()、subaru_get_counter()、subaru_compute_checksum() 定下字节 0 存校验和、字节 1 低四位为计数器;校验值为地址两字节加字节 1 起的数据之和并截断为 8 位。

    校验与计数器:一个已知的盲点

    一个值得写进契约盲点的现象来自离线探针:把已打包 0x221 帧的字节 4 异或 0xFF、保持原校验字节不变,两代仍能通过 TX 钩子。这说明该数据字节及由此产生的校验和失配不在本模式的 TX 专属检查内。需要强调边界:这不意味着 ECU 会接受畸形帧,只是固件这一层没有拦。

    项目 规则 边界
    校验和 字节 0 存校验和;地址两字节加字节 1 起数据求和截断为 8 位 8 位累加,非通用 CRC
    计数器 字节 1 低四位 四位回绕,配合存活检查
    TX 校验盲点 改字节 4 保持原校验字节仍可通过 TX 钩子 不在本模式 TX 专属检查内,不代表 ECU 接受

    转向、纵向与转发边界

    subaru_tx_hook() 对 0x122 解码 13 位有符号转向扭矩并反转符号,检查请求位。Gen1 最大扭矩 2047、上升/下降分别 50/70;Gen2 为 1000、40/40。两代共用实时差 940、驾驶员余量 60、倍数 50,以及至少 7 帧有效请求、最多 1 帧无效请求和 144 ms 最小间隔的容忍配置。这些是固件限值,不等于主机控制器一定按相同步长构帧。

    纵向一侧,源码定义了较多 SUBARU_LONG_LIMITS 字段,但当前 TX 钩子只在 0x221 读取其 inactive_gas=1818,没有放行主动油门、制动或巡航 RPM 命令。与之对应,接口显式关闭自控纵向能力。

    同时,两代的 0x122 与 HUD TX 均标 check_relay=true,若本机发送总线出现同地址原车帧,可触发共享继电器故障;0x221 为 check_relay=false。该模式无专属 fwd 钩子,通用转发只在 bus 0 与 bus 2 之间;Gen2 的 bus 1 不是该转发对。不能只看上层相机/网关接线位置就假定 0x221 已被正确转发或替代。

    状态侧,subaru_rx_hook() 对 0x119 读取 11 位驾驶员扭矩并反转符号;轮速解析四轮 13 位原始值,只要任一非零即认为移动,并以均值形成速度。通用 RX 门继续处理踏板退出。

    验证与插件准入

    三项原钩子离线探针用真实 DBC 打包 0x221 取消帧并调用编译版 safety_tx_hook():Gen1 仅 bus 0、Gen2 仅 bus 1 可通过;取消位缺失或油门从 1818 变为 1819 都被拒。相邻 test_subaru.py 覆盖 Gen1/Gen2 扭矩、请求位及取消帧;本机运行该模块 64 项,12 项跳过,其余通过。

    • 身份插件需证明 Global Gen1/Gen2、DBC 和物理总线布局;Preglobal、Hybrid、角度 LKAS 的能力不能继承本模式推断。
    • 车辆输入插件保留五组 RX 的来源与时间戳;Gen2 特别区分 bus 0 的油门/扭矩与 bus 1 的轮速/制动/巡航。
    • 控制插件只提交转向意图和受限取消请求;安全模式读回、CRC/计数、继电器、失联降级仍由独立 MCU/网关裁决。
    • 验证需覆盖错总线、校验/计数故障、巡航边沿、驾驶员反扭矩、请求位切断、0x221 非活动油门和 HUD 载荷,再进入台架与受控场地。

    HUD 三帧仅做通用地址/总线/长度白名单及继电器检查,未在本模式限制载荷字段。自制硬件应把这些帧的构造权留给可信任网关,限制模板与发送节拍,避免普通插件直接写原始 CAN。当前无目标板或实车验证。

    常见问题

    Subaru Global 支持自控纵向吗?

    不支持。该快照下 alphaLongitudinalAvailable=False,普通参数路径不会启用自控纵向;subaru_init() 也没有纵向 TX 表分支。当前 TX 钩子只在 0x221 读取 inactive_gas=1818,任何非该值的 Cruise_Throttle 或未设置 Cruise_Cancel 都被拒。

    Gen2 是把所有 RX 都移到 bus 1 吗?

    不是。Gen2 只把轮速、制动、巡航三组从 bus 0 改到 bus 1,油门与驾驶员扭矩仍留在 bus 0。不能把全部 RX 一起移到 bus 1,否则会读错来源。

    改了 0x221 的数据字节还能通过 TX 钩子,是不是说明固件有漏洞?

    该数据字节及由此产生的校验和失配不在本模式的 TX 专属检查内,所以按探针做法仍能通过 TX 钩子;但这不等于 ECU 会接受畸形帧。这里应把它写成契约盲点,而不是定性为整车级缺陷。

    为什么不能只按接线位置判断 0x221 是否被转发?

    因为该模式没有专属 fwd 钩子,通用转发只在 bus 0 与 bus 2 之间,Gen2 的 bus 1 不是该转发对;而 0x221 的 check_relay=false。只看上层相机/网关接线位置,无法推断 0x221 已被正确转发或替代。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SUBARU_GLOBAL_PANDA_SAFETY_DEEP_DIVE.md(取用了 Global Gen1/Gen2 总线矩阵、TX 取消帧约束、转向限值与三项离线探针结论)。
    • 项目 02 —— openpilot-research/SUBARU_PREGLOBAL_PANDA_SAFETY_DEEP_DIVE.md(仅用于确认 Preglobal 是不同安全钩子、能力不可继承)。

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

    更新记录

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

  • pandad 硬件测试:DFU 恢复、SPI 注错与 loopback 边界

    结论:pandad 的三个设备测试给出三种不同强度的证据:恢复测试只证明设备可见性,CAN loopback 证明期望集合被逐条消耗,SPI 注错证明服务存活与粗时序。它们都会复位/刷写 panda、切换 allOutput safety 并发送随机 CAN,绝不可在连接车辆的环境运行。源文档明确,三者都不能证明永久 NACK、非法长度、短健康包、热插拔、多 Panda、心跳失效后的 MCU 静默或实际车型 RX/TX safety 钩子。本文为静态核对,未启动测试、未刷写、未发送 CAN、未连接任何设备。

    恢复测试:只证明设备可被看到

    TestPandad 要求 comma 硬件;每个测试的 setUp() 先执行测试基类的硬件初始化,再停止已有 pandad,如果当前枚举不到 Panda 就尝试启动包装器恢复。核心 _run_test() 启动 pandad,在 30 秒内订阅 pandaStates,看到至少一块且首块 pandaType != unknown 即停止并返回耗时。

    源文档强调它没有断言 CAN 能收发、固件签名值、心跳健康或目标安全模式。因此「看到 pandaStates」不等于整条安全链通过。

    场景 实际操作 静态覆盖范围
    test_in_dfu() 主机 GPIO 进入 ROM DFU,再让包装器恢复 验证可恢复到非 unknown 状态,不测刷写中断
    test_in_bootstub() 经 Panda API 进入 bootstub,确认标志,再让包装器恢复 验证 bootstub 起点的正常恢复
    test_in_reset() 操作 STM_RST_N,确认暂时枚举不到 Panda,再启动包装器 属于物理复位线测试,不是纯软件 mock
    test_release_to_devel_bootstub() _flash_bootstub() 选择 MCU 配套 bootstub 文件并通过 PandaDFU.program_bootstub() 写入 有真实固件修改;名称里的 release/devel 不能替代证书/签名核验
    test_recover_from_bad_bootstub() 进入 DFU 后向 bootstub 起始地址写入 100 个零字节,复位并确认普通/DFU 枚举为空,再靠包装器恢复 主动制造启动损坏,失败时可能需要外部救援

    公共台架夹具与 allOutput 的放开范围

    setup_pandad() 有三次重试:清除当前测试 Prefix 下的 Params,反复发布 deviceState.started=False 等待非 unknown pandaStates,再写 FirmwareQueryDone、ControlsReady 和仅含 allOutput 的 CarParams.safetyConfigs,发布 started=True。

    两处边界需要注意:测试基类的 OpenpilotPrefix 隔离的是软件路径,不隔离接在 panda 上的物理 CAN;allOutput 的 tx_hook 无帧内容限制,通用发送门也对该模式放开白名单,这不是车型量产 safety 策略。台架必须与车辆 CAN 物理断开,不能仅依靠软件设置「loopback」。

    loopback 与 SPI 注错的断言边界

    send_random_can_messages() 对 0~2 号总线生成随机 29 位以内地址和 1~8 字节载荷,用 sendcan 发布;没有 CAN FD 长帧。TestBoarddLoopback.test_loopback() 运行 200 轮,每轮除原总线集合外还期待 src+128 的发送回执集合;收到消息就从集合移除,最后要求所有期望集合清空。源文档指出这比只看「有 CAN 流量」强,但不测负载极限、重复帧保序、超时恢复或车辆控制的安全限幅;每轮接收循环最多 500 次 wait_for_one,没有外层总时间上限。

    TestBoarddSpi.setup_class() 设置 STARTED=1、SPI_ERR_PROB=0.001,默认还开启 BOARDD_LOOPBACK;SPI 层在配置概率下扰动传输长度、TX/RX 缓冲,然后继续真实 ioctl,故它是传输层随机注错而非确定性故障脚本。test_spi_corruption() 在默认约 20 秒循环中发送随机 CAN,检查状态消息有效与设备类型/点火/电压/风扇范围,并统计三种主题的平均/最大消息间隔和数量。

    测试 断言边界 不能证明
    test_loopback() 200 轮,期望集合(含 src+128 回执)全部清空 负载极限、重复帧保序、超时恢复、安全限幅
    test_spi_corruption() CAN Event 即使 valid=False 仍进入测试;只断言收到且 src<=4 的(地址,载荷)曾在对应发送集合中;最后只要求 total_recv_count > 20(集合移除被注释) 接收数等于发送数、每条只到达一次或全部发送帧均回传
    JUNGLE_SPAM 模式 校验地址、源总线和 0xff 载荷模式 不产生本测试自己的发送集合

    三种证据各自不证明什么

    源文档收束得很清楚:恢复测试的设备可见性、loopback 的期望集合消耗、SPI 注错时的服务存活/粗时序,三者都不能证明永久 NACK、非法长度、短健康包、热插拔、多 Panda、心跳失效后的 MCU 静默或实际车型 RX/TX safety 钩子。

    硬件插件验证的推荐顺序是:先用离线消息/协议测试,再在断开车辆的台架做可重复错误矩阵与固件恢复,最后另由独立 safety 层和受监督实车流程验收。已静态核对三个测试文件、公共 helper、SPI 注错点与 allOutput 安全钩子;未运行设备测试或执行任何刷写/CAN 操作。

    源文档也把恢复包装器列为待验证对象:真实包装器的复位、签名和重试边界需要另做专题核对,本节列出的是测试证据。分支和计数结论是源码可见的断言范围,目标硬件时序和异常恢复结果仍需专用台架验证;全项目其他组件仍待逐项审查。

    常见问题

    pandad 恢复测试通过是否代表固件与安全链正常?

    不代表。源文档明确 _run_test() 只要求在 30 秒内看到至少一块非 unknown 的 pandaStates,没有断言 CAN 能收发、固件签名值、心跳健康或目标安全模式。

    为什么 SPI 注错测试通过不能说明接收完整?

    因为它的断言很窄。源文档指出,test_spi_corruption() 只断言收到且 src<=4 的帧曾在对应发送集合中,集合移除被注释,最后只要求 total_recv_count > 20,并不要求接收数等于发送数或全部发送帧均回传。

    台架上把 safety 设成 allOutput 就安全吗?

    不安全。allOutput 的 tx_hook 无帧内容限制,通用发送门也放开白名单,它不是车型量产 safety 策略;且 OpenpilotPrefix 只隔离软件路径,不隔离物理 CAN,台架必须与车辆 CAN 物理断开。

    这些测试能在连接车辆的环境下跑吗?

    绝对不能。源文档在开头即警示:它们会复位/刷写 panda、切换 allOutput 并发送随机 CAN,绝不可在连接车辆的环境运行。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PANDAD_HARDWARE_TEST_DFU_SPI_LOOPBACK_SAFETY_BOUNDARY_DEEP_DIVE.md(取用了恢复测试场景表、公共台架夹具、loopback 与 SPI 注错的断言范围、三类证据边界)。

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

    更新记录

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

  • Tesla panda 安全模式:Autopark 优先与选择性转发矩阵

    结论:Tesla panda 安全模式的优先级不是「谁能发」而是「谁优先」:启动时先假定 Autopark/Summon 活跃并拒绝所有主机 Tesla 候选帧;原车 LKAS 锁存时把 0x488 从主机 TX 切回原车透传;原车 AEB 活跃时恢复 0x2B9 透传并拒绝主机取消帧。因此「白名单里有某地址」不等于该地址的每个字段都经过逐信号安全校验。

    RX 完整性:9 类监视帧与校验

    tesla_model3_y_rx_checks 含 9 类帧:相机总线 2 的 0x2B9/0x488,主总线 0 的 DI 速度、ESP 第二车速、EPAS 状态、油门、刹车、DI 巡航/Autopark 和 UI 警告。各自配置地址、长度、名义频率及计数上限;仅 ESP 轮速和刹车未忽略质量位。UI 警告进入通用 RX 健康监控,但 tesla_rx_hook() 并无对应的状态提取分支。

    共享 safety_rx_hook() 先验证校验/计数/质量位,再对命中且有效的帧运行车型钩子;坏 RX 帧不可当作有效的原车 AEB/LKAS 状态更新。校验按地址选择校验字节,计算地址两字节与其它载荷字节的 8 位和。

    函数 责任
    tesla_get_counter() 按地址从不同字节/位域取得 3 或 4 位计数器。
    tesla_get_checksum_byte()、tesla_get_checksum()、tesla_compute_checksum() 按地址选择校验字节,计算地址两字节与其它载荷字节的 8 位和。
    tesla_get_quality_flag_valid() 0x155 ESP 轮速质量位必须为 1;0x145 制动状态不能为未初始化或故障编码。
    tesla_rx_hook() 更新角度/接管、DI/ESP 双车速、踏板、巡航、Autopark、原车 AEB/LKAS 锁存。
    tesla_init() 选择默认或调试纵向 TX 表,重置原车辅助状态,并在启动时先假定 Autopark/Summon 活跃。

    原车状态锁存:Autopark、LKAS 与 AEB 的上升沿

    tesla_rx_hook() 的 DI 主车速与 0x155 ESP 第二车速按不同原始编码解码;后者经 speed_mismatch_check() 比较,差超过 2 m/s 时撤销控制许可。0x370 EPAS 角度进入采样窗口;高等级手握或特定高角速度故障置 steering_disengage,由共享接管逻辑消费。两速度帧及 EPAS 帧异步到达,静态代码不保证共时。

    0x286 的 Autopark 活跃编码包括 3、4、9;只有在原车巡航未接合的上升沿才锁存 tesla_autopark,状态退出时清除。随后的原车巡航状态须再与 !tesla_autopark 合取后送 pcm_cruise_check()。0x488 相机报文仅在自身 LKAS 状态上升沿且 controls_allowed=false 时锁存原车 LKAS;状态消失时清除。0x2B9 相机报文的 AEB 活动位则直接更新 tesla_stock_aeb。插件必须保留三种原车状态的来源、有效性和时间信息,不可只发布合并后的「辅助驾驶占用」布尔量。

    TX 与转发优先级矩阵

    条件 主机 TX 相机总线 2 到主总线 0
    初始化后尚未收到解除 Autopark 的 0x286,或锁存的 Autopark 活跃 tesla_tx_hook() 拒绝所有 Tesla 候选帧 车型动态钩子不阻断 0x488/0x27D/0x2B9,允许原车路径继续。
    普通非 Autopark、原车 LKAS 未锁存 0x488 可在角度/控制类型许可下发送 0x488 与 0x27D 被 tesla_fwd_hook() 阻断。
    原车 LKAS 已锁存 0x488 被车型 TX 钩子拒绝 0x488 透传给主总线。
    默认原车纵向 0x2B9 仅允许无执行量的 ACC_CANCEL_GENERIC_SILENT,且不得宣称 AEB 事件 0x2B9 一直透传;原车 AEB 活跃时主机取消帧也被拒绝。
    ALLOW_DEBUG 且纵向参数位置位 0x2B9 可按加速度限值发送,仍禁止主机 AEB 事件 无原车 AEB 时阻断原车 0x2B9;原车 AEB 活跃时恢复透传并拒绝主机 0x2B9。

    tesla_tx_hook() 对 0x488 只接受控制类型 NONE 或 ANGLE_CONTROL,并用车辆模型参数限制 0.1 度转角及横向动力学;原车 LKAS 锁存时即使数值合规仍拒绝。0x2B9 禁止主机设置任何 AEB 事件;默认模式仅可送加速度上下限均为中性 375 的取消编码。调试纵向模式的原始加速度范围为 288–425,中性为 375;两端都低于中性时额外拒绝,以防静止后倒车风险。

    白名单不等于逐信号校验:0x27D 的边界

    tesla_init() 的两个 TX 表都含 0x488、0x2B9、0x27D/总线 0。0x488 与 0x27D 保留 check_relay=true,但设 disable_static_blocking=true,因此共享 safety_fwd_hook() 不会先把它们恒定阻断,留给 Tesla 的动态钩子选择透传。默认模式的 0x2B9 check_relay=false;纵向模式则为 true 但仍关闭静态阻断。

    关键在于 0x27D 没有 Tesla TX 载荷分支:非 Autopark 时,它只经过通用地址/总线/长度、继电器状态和车型总阻断门,不能据此认定各字段经过逐信号安全校验。

    证据与新硬件边界

    test_tesla.py 包含校验/计数、速度失配、制动质量位、驾驶员接管、Autopark、原车 LKAS/AEB 透传、转角和倒车限制用例;其测试库默认加 ALLOW_DEBUG。新增的本机转发矩阵探针在严格编译和 UBSan 下分别运行 release/debug 变体,两者均通过:核对启动 Autopark 阻断、普通/原车 LKAS 转向透传,以及调试纵向和原车 AEB 的 0x2B9 转发切换。但它只设置隔离状态并检查通用 safety 函数,没有覆盖真实 RX 报文时序、目标编译器、板级继电器或 ECU 接受。

    自制硬件与插件化重构应把固件构建类型、Tesla 平台/转向控制字段代际、CAN 来源、车辆模型参数和安全参数版本绑定。感知/控制插件只能提供候选角度或加速度;Autopark、原车 LKAS/AEB 优先权、双车速、质量位与故障降级仍须由独立受信任安全层执行。

    常见问题

    为什么启动时会拒绝所有主机 Tesla 候选帧?

    因为 tesla_init() 在启动时先假定 Autopark/Summon 活跃,在收到解除 Autopark 的 0x286 之前,tesla_tx_hook() 会拒绝所有 Tesla 候选帧;Autopark 锁存活跃时同样如此。

    原车 LKAS 锁存后,0x488 还能由主机发送吗?

    不能。原车 LKAS 已锁存时 0x488 被车型 TX 钩子拒绝,主总线方向则改为把 0x488 透传给主总线。普通非 Autopark 且原车 LKAS 未锁存时,0x488 才可在角度/控制类型许可下发送。

    0x27D 在白名单里是否代表它经过逐信号校验?

    不代表。0x27D 没有 Tesla TX 载荷分支;非 Autopark 时它只经过通用地址/总线/长度、继电器状态和车型总阻断门,不能据此认定各字段经过逐信号安全校验。

    原车 AEB 活跃时 0x2B9 会怎样处理?

    原车 AEB 活跃时恢复 0x2B9 透传并拒绝主机 0x2B9;默认原车纵向模式下,主机侧 0x2B9 仅允许无执行量的 ACC_CANCEL_GENERIC_SILENT,且不得宣称 AEB 事件。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TESLA_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了 RX 监视帧、原车状态锁存、TX 与转发优先级矩阵及证据边界)。
    • 项目 02 —— openpilot-research/FORD_PANDA_SAFETY_MODE_DEEP_DIVE.md(仅用于对照品牌安全模式的通用分发结构)。

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

    更新记录

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

  • Chrysler 四套构帧:0x292 与 0xa6 为什么不能互换

    结论:Chrysler 的构帧不是一套模板,而是四套并存的布局:Pacifica/常规 Jeep、Ram DT、Ram HD 与 Cherokee CUSW,三帧消息的地址、字节长度和所在总线都不同,不能互相套用。几个最容易踩的点是:Ram 的按钮帧走 bus 2 而其他走 bus 0;Ram HD 与 CUSW 的 TX/RX 只在 ALLOW_DEBUG 构建中注册且参数层标 dashcamOnly;Pacifica、Ram DT、Ram HD 的转向扭矩上限分别是 261、261、361,CUSW 为 250。字段名相同并不代表可共用原始载荷。以下为固定快照 c8fb906 的静态分析,未向车辆发送 CAN。

    四套发帧布局

    主机侧三类输出分别是 LKAS 转向命令、DAS_6 图标/车系码/警示,以及巡航按钮。CANPacker.pack() 会在未显式提供时按地址递增 COUNTER,并调用 DBC 对应的 CHECKSUM 函数;chrysler_ 系列 DBC 使用 chrysler_checksum(),即最后一字节之外的 CRC-8 J1850,初值与尾异或都是 0xff。

    平台族 LKAS / DAS_6 / 按钮(地址/长度) 主机 bus panda 限制
    Pacifica / 常规 Jeep 0x292/6 B、0x2a6/8 B、0x23b/3 B 三种都在 bus 0 扭矩最大值 261,速率上/下限每次 3。
    Ram DT 0xa6/8 B、0xfa/8 B、0xb1/3 B LKAS/HUD bus 0,按钮 bus 2 主机扭矩上限 261,固件白名单 350,实际受主机更紧约束。
    Ram HD 0x276/8 B、0x275/8 B、0x23a/3 B LKAS/HUD bus 0,按钮 bus 2 主机/固件扭矩上限均 361,速率 14;TX/RX 仅 ALLOW_DEBUG 注册。
    Cherokee CUSW 0x1f6/4 B、0x5dc/4 B、0x2fa/3 B 三种都在 bus 0 独立 safety model,扭矩 250,速率 4;参数层标 dashcamOnly。

    控制周期与按钮时序

    CarController.update() 以 DT_CTRL = 0.01 s 的帧计数运行。按钮只在距上次发送严格超过 0.05 s 时生成,优先取消、其次恢复;计数用 CS.button_counter + 1,再由 create_cruise_buttons() 截成 4 位。这里有一个平台差异:CUSW 的 update_cusw() 没有像常规分支那样刷新 button_counter,若生成按钮帧会反复使用初始化值加一,是否触发原车计数拒收仍需台架确认。

    DAS_6 的字段由 create_lkas_hud() 生成:它把激活态、车系码和告警转成图标与车道线字段;Ram 额外带原车自动远光状态,而 CUSW 的告警字段固定为零。也就是说,同一个「车道保持已激活」在四套布局里落在不同字段上,跨平台复用图标语义需要分别核对。

    HUD 每 25 帧尝试一次,且只有 CS.lkas_car_model != -1 才发送。hud_count 小于 4 的启动告警只覆盖最初四个实际发送的 HUD 帧,源码注释里的「first 3 seconds」不能当作精确时长。转向周期由 CarControllerParams 设为普通/Ram 50 Hz、CUSW 100 Hz;控制位则依 minSteerSpeed、平台迟滞和最近下降沿约 200 个控制帧的等待决定。由于 lkas_active 在本次控制位更新前读取上一状态,控制位首次置位的周期不会产生非零扭矩。

    时序项 取值 备注
    控制步长 DT_CTRL = 0.01 s 按钮与 HUD 都按帧计数节流。
    按钮最小间隔 严格大于 0.05 s 优先取消,其次恢复。
    HUD 周期 每 25 帧 仅当 lkas_car_model != -1。
    转向速率 普通/Ram 50 Hz、CUSW 100 Hz 由 CarControllerParams 决定。
    控制位迟滞 约 200 个控制帧 自最近下降沿起等待。

    控制字段与原始载荷不能共用

    create_lkas_command() 对 Ram 的控制字段编码为 2,其他为 1。这对应固件里的不同检查:常规固件在 Pacifica 上检查 bit 4、在 Ram 上检查字段值 2,而 CUSW 独立检查 bit 12。字段名相同并不代表可以共用原始载荷,这也是四套布局必须分别验证的原因之一。

    panda 是最后一道独立门

    主机发出的三类帧仍要经过 panda 安全钩子。常规模式仅允许表中的地址、总线与长度;转向再做扭矩与请求位检查;按钮只接受纯取消,或在 controls_allowed 下的纯恢复,CUSW 的取消/恢复位解析则不同。这套白名单与按钮限制应被视为独立于应用插件的最后一道门。生成式 Ram DBC 的地址映射(DAS_6 与按钮来自 Stellantis 共用 DBC 的地址替换)也应纳入构建一致性检查。

    硬件与插件验收边界

    把 DBC、总线、帧长、安全模式与固件构建作为同一车型插件的版本化契约;Ram 的按钮 bus 2 与普通平台的 bus 0 不可互换。台架应分别验证编码后的计数、CRC、控制位与 panda TX 的接受/拒绝矩阵,尤其覆盖 Pacifica、Ram DT、Ram HD、CUSW 四套布局——软件里有构帧函数,并不意味 HD/CUSW 的 release 固件允许发送。

    实车验证前应先只读记录原车的 DAS_6、按钮计数与测得 EPS 扭矩以及 LKAS 故障时序,确认线束与转发路径。本文没有 DBC 动态编码回放、目标板或车辆响应证据,不构成上路控制许可。

    常见问题

    四套 Chrysler 布局能共用同一套构帧代码吗?

    不能。Pacifica/常规 Jeep、Ram DT、Ram HD、Cherokee CUSW 的地址、长度和总线都不同:例如 Ram 的按钮帧走 bus 2,其他走 bus 0;Ram HD 与 CUSW 的 TX/RX 还只在 ALLOW_DEBUG 构建中注册。

    为什么控制字段取值在 Ram 和其他平台上不同?

    create_lkas_command() 对 Ram 的控制字段编码为 2、其他为 1,对应固件的不同检查:常规固件在 Pacifica 检查 bit 4、在 Ram 检查字段值 2,CUSW 则独立检查 bit 12。字段名相同不等于原始载荷可共用。

    按钮计数在 CUSW 上有什么特殊之处?

    CUSW 的 update_cusw() 没有像常规分支那样刷新 button_counter,若生成按钮帧会反复使用初始化值加一;这是否触发原车计数拒收仍需台架确认。

    软件里有构帧函数就说明 HD/CUSW 可发送吗?

    不能这样推断。Ram HD 与 CUSW 的参数层标 dashcamOnly,HD 的 TX/RX 只在 ALLOW_DEBUG 构建中注册;台架应分别验证四套布局的 panda 接受/拒绝矩阵,构帧函数存在不代表 release 固件允许发送。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CHRYSLER_CAN_FRAME_CONTROL_CONTRACT_DEEP_DIVE.md(取用了四平台发帧矩阵、控制周期与按钮/HUD 时序、控制字段编码差异与 panda 安全门边界)。

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

    更新记录

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

  • Chestnut 告警锁存:告警消失不等于故障已清

    结论:Chestnut 的故障告警大多锁存到下一个行车周期边沿,不是按当前测量实时刷新:usb_failed、model_failed、power_lost、pcie_failed 都在 offroad→onroad 边沿才复位。因此「告警消失了」不等于「故障已清」,而 state is None(遥测缺失)这一轮既不更新供电/PCIe/温度,也不会自动清除原故障——没有新有效样本既不证明健康,也不必然生成新告警。

    本文基于固定源码快照追踪 ChestnutStatus.update() 的状态转换及其上下游,并以原类方法离线探针核对部分迁移。探针使用假 USB 快照、时钟和告警回调,未接真实桥接层、USB 或设备,不代表实板故障已复现。USB/GPU 原始遥测的有效位属于相邻专题。

    状态迁移与复位

    ChestnutStatus.update() 每次由 USB、模型和遥测输入更新锁存状态,再计算七类 offroad 告警。hardwared.hardware_thread() 把慢速 USB 快照、固件失败、Params 和存活且有效的遥测传入状态机;set_offroad_alert_if_changed() 以 (show_alert, extra_text) 去重,只在变化时写 Params。

    状态 置位/更新条件 清除条件与后果
    usb_seen 仅在 offroad→onroad 边沿取当时的 firmware_ok 下次行车边沿重取;若出发时未识别成功,之后出现再消失不会触发本轮 usb_failed
    usb_failed 行车中曾在边沿看见 USB,后来 firmware_ok 为假 下次行车边沿清除;置位后即使设备恢复也维持缺失告警,并压下 PCIe/供电锁存
    model_loading_seen / model_attempted 本次行车边沿后曾见加载为真,随后加载为假且 model_active 非 None 下次行车边沿清除;驻车阶段曾见加载会在切入行车时清空
    model_failed 行车中、已尝试且 model_active is False 下次行车边沿清除;显示时再受 missing 或 pcie_failed 抑制
    power_seen / power_lost 行车中有效遥测电压不低于 5000 mV;已尝试后供电故障或低于阈值 下次行车边沿清除;power_lost 锁存,power_restored 记录后续恢复用于改告警文本
    link_failures / pcie_failed 已尝试且有效遥测中 LTSSM 连续两次不为 0x78,或供电丢失 计数在下次 0x78 样本清零,故障位仍锁存至下次行车边沿;USB 失败会清故障位
    overheated 有效遥测 GPU 不低于 100°C 或显存不低于 95°C 已过热时降至各阈值减 5°C 以下才清除;行车边沿不重置该位

    usb_failed 优先级最高:源码会把若干供电/PCIe 锁存清掉,并用 missing 抑制模型错误显示;pcie_failed 也抑制模型错误,以免同一根因重复报错。锁存用于保留行车周期内已发生的事件,其恢复语义要与实时测量分开。

    缺测不等于正常:state is None 的语义

    hardwared 只在 chestnutState 同时 alive 且 valid 时传具体 state,否则传 None。state is None 时本轮不更新供电、PCIe 和温度状态,不会把原故障自动清除。离线探针确认:一次异常 LTSSM、一次 state=None、再一次异常 LTSSM 会累计到两次并锁存 pcie_failed;之后正常样本把计数清零,却不会在当前行车周期清除故障位。

    观测 源码行为 不能推出的结论
    本轮 state is None 不更新供电/PCIe/温度,也不清原故障 不能判定「故障已恢复」
    两次异常 LTSSM 后正常样本 计数清零,但故障位锁存至下次行车边沿 不能判定本周期内 PCIe 已健康
    USB 快照重复消费 快照来自较慢线程,可能被数次调用重复消费 不能按调用次数换算固定毫秒数

    两次异常不能简单换算成固定毫秒数:link_failures 按有效融合消息的消费次数计,不按 PCIe 原始采样时间计;USB 快照也来自较慢的线程,状态机可能在数次调用中重复消费同一枚举结果。

    告警条件与其边界

    Offroad_ChestnutNotDetected 有两条路径:本轮行车周期内 usb_failed 锁存,或处于 release 分支且驻车、进程启动超过 10 秒、运行态加 ROM 态候选数不等于一。这里的 10 秒是 ChestnutStatus 创建后的时间,不是每次进入驻车后重新计时。

    其余告警各有窄边界:Offroad_ChestnutUsbSlow 仅在驻车且恰好一个运行态设备时检查速度低于 5000 Mbps,并不校验产品字符串,sysfs 缺测读成零速时也符合该比较;Offroad_ChestnutBranch 是非 release 分支且恰好一个运行态设备的提示;Offroad_ChestnutUpdateFailed 只在驻车展示固件更新失败。

    Offroad_ChestnutPcieUnavailable 的文本按电源失去、恢复或纯链路失败选择:首次失电前是否曾达到 5 V 决定「连接断开」还是「运行中丢电」文案;恢复后仍要求重新点火,而不是在本周期自动解除锁存。温度告警可在驻车和行车更新,其附加文字只展示 GPU 温度,即使触发源实际上是显存温度。以上均为代码显示语义,不等于硬件电气诊断结论。

    自研硬件与插件化约束

    硬件诊断插件应输出独立的 observation 与 latched_fault:每条观测携带设备身份、采样时间、有效位和新鲜度;故障事件携带首次发生时刻、恢复时刻及重置原因。告警展示由宿主统一仲裁,避免插件自己清除更高优先级的安全故障。

    1. 车辆控制安全链不应依据这些展示用 offroad 告警直接授权执行;外置计算丢失时仍须经模型有效位、控制状态机和独立网关处理。
    2. 台架需分别注入:启动边沿 USB 缺失后再恢复/断开、运行中 USB 消失后恢复、连续与间隔的 LTSSM 错误、供电跌落后恢复、模型加载中/失败/回退。
    3. 还须覆盖温度在 5°C 迟滞带附近抖动、遥测失效和慢速 USB 快照;比较故障锁存、告警可见性与实际控制退出。
    4. 不得把告警消失当作故障已清。

    此仓库没有 ChestnutStatus 的专门单测;五项原类方法离线探针核对驻车加载历史在行车边沿清空、USB 消失优先级、遥测缺失时的 PCIe 计数、温度迟滞和 release 缺失计时。

    常见问题

    告警消失是不是说明故障已经清除?

    不一定。usb_failed、model_failed、power_lost、pcie_failed 都锁存到下一个 offroad→onroad 边沿才复位,用于保留行车周期内已发生的事件;其恢复语义与实时测量是两回事,不能把告警消失当作故障已清。

    state 为 None 会清掉原故障吗?

    不会。hardwared 只在 chestnutState 同时 alive 且 valid 时传具体 state,否则传 None;本轮不更新供电、PCIe 和温度状态,也不会自动清除原故障。没有新有效样本既不证明健康,也不必然生成新告警。

    两次 LTSSM 异常等于固定毫秒数吗?

    不等于。源码把 link_failures 按有效融合消息的消费次数计,不按 PCIe 原始采样时间计;USB 快照来自较慢线程,可能被数次调用重复消费同一枚举结果,所以两次异常不能简单换算成固定毫秒数。

    温度告警文字为什么只显示 GPU 温度?

    因为显示语义就是这样:温度告警的附加文字只展示 GPU 温度,即使触发源实际上是显存温度。过热位在有效遥测 GPU 不低于 100°C 或显存不低于 95°C 时置位,已过热时降至各阈值减 5°C 以下才清除,行车边沿不重置。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CHESTNUT_STATUS_ALERT_LATCH_LIFECYCLE_DEEP_DIVE.md(取用了 ChestnutStatus.update() 状态迁移表、缺测语义、告警条件与五项离线探针结论)。
    • 项目 02 —— openpilot-research/CHESTNUT_TELEMETRY_VALIDITY_CONTRACT_DEEP_DIVE.md(仅用于确认遥测有效位边界,未取用其未公开内容)。

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

    更新记录

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

  • tinygrad BNXT 固件控制面:HWRM 长度信任与队列所有权缺口

    结论:tinygrad 的 BNXT 驱动控制面是一条「初始化—队列—故障」三段式链路:BNXTDev 负责 PCI/固件邮箱,BNXTQP 负责 QP 状态机与 doorbell。它的静态缺口集中在两处——hwrm() 未约束固件返回的 resp_len,fini() 仅注销功能驱动而无显式 QP/CQ 销毁与内存逐项释放。它是计算数据搬运组件,不是车辆控制或安全网关。

    分层初始化:从 PCI reset 到 QP 的 RTS

    BNXTDev.__init__() 复位 PCI 设备、开启 Bus Master、映射 BAR0/BAR2,再用 hwrm() 取得版本/能力、复位功能、注册驱动并获取 doorbell 偏移。setup_backing_store() 按固件报告的 entry size、子类型和最小容量分配系统内存并建立 PBL,随后 _open_rcfw() 建 CREQ/CMDQ/NQ 并初始化 RCFW。

    _open_l2() 建 L2 CQ、RX ring、VNIC 和 MAC filter,供 RoCE ingress 使用;源码注释称不向这个 L2 RX ring 投递接收缓冲。最后注册本地 GID。BNXTQP.__init__() 建两个 CQ 与 SQ/RQ,创建 QP 并置 INIT;connect() 通过两次 modify_qp 转 RTR/RTS。

    HWRM/RCFW 回包:长度信任与状态断言

    函数/组件 输入到输出 关键实现细节
    build_pbl() 物理页列表到 PBL 层级/地址 单页直指;多页分配页表;超过一张底层表时加顶层表,顶层最多 512 项;队列尾两页带终止标志。
    BNXTQueue.read()/write() 逻辑 index 到 ring 槽位 通过 index % entries 环绕;aux=True 写 SQ 后半的 MSN 表。调用方承担数据不跨槽与写入顺序。
    db_value() QP/CQ/NQ ID、index、epoch 到 64 位 doorbell 高位含 RoCE path/type/valid,低位含索引和 epoch;doorbell() 写前调用内存屏障。
    hwrm() 固件请求到 HWRM 回包 seq 递增、清空响应页、写邮箱、等待 resp_len/seq/valid,再解结构并断言 error_code == 0。
    rcfw() RCFW 请求到 CREQ 回包 16 字节槽位、首包标记、等待 toggle、推进 consumer 和 NQ_ARM doorbell,断言 status == 0。
    register_mem() 物理页/VA/长度到 MR key PBL 只取覆盖 size 的页;启用本地/远端写。unregister_mem() 按 key 注销。
    msn_entry() WQE 号、PSN、长度到 MSN 项和下一 PSN 每 4096 字节一包,最少耗一个 PSN;24 位回绕。
    cqe_ready() CQE 与 consumer 到可读布尔值 比较 toggle 与 CQ 环绕 epoch;poll() 另查状态字节,避免把错误完成视作成功。

    hwrm() 在读取响应页末项前未约束固件给出的 resp_len 是否超过响应页长度,也未先验证它足以覆盖期望结构。畸形大长度可产生 IndexError 而非设计的超时/协议错误;这是静态可见的输入校验缺口,真实固件是否会返回此值尚未验证。

    队列与 doorbell:环绕、epoch 与内存屏障

    队列通过 index % entries 环绕,调用方承担数据不跨槽与写入顺序的责任;db_value() 把 RoCE path/type/valid、索引与 epoch 拼成 64 位 doorbell,写前调用内存屏障。cqe_ready() 比较 toggle 与 CQ 环绕 epoch 判断可读,poll() 另查状态字节,以避免把错误完成当成成功。

    值得注意的是双端协议与完成语义的差别:connect() 设置 RoCEv2 IPv4、MTU 4096、目标 QPN/GID/MAC 与重试参数,这些是源码写入的配置,并未证明网络拓扑、交换机拥塞策略或异机互通可靠。CPU 示例的 poll() 检查 CQE status,而 GPU 提交路径只等待 toggle,两条路径的「成功」判据不能混同。

    环绕与终止标志同样需要调用方自己负责:BNXTQueue.read()/write() 通过 index % entries 环绕,aux=True 写 SQ 后半的 MSN 表,调用方需承担数据不跨槽与写入顺序;build_pbl() 在队列尾两页写入终止标志。这些约定在代码里由调用方保证,而不是由驱动在每次写入时校验。

    故障与资源所有权:fini 只调 unrgtr

    同步调用方面,wait_cond() 使用毫秒超时的轮询;HWRM/RCFW 各自有超时和非零状态断言,但没有在这里定义重试、故障分类或失联后的 QP 重建。硬件平台需要把超时、固件错误和传输错误分别上报。

    所有权方面,fini() 仅调用 func_drv_unrgtr。这份文件没有显式 destroy_qp/destroy_cq、backing store 逐项释放或初始化异常回滚;资源可能由功能复位/进程退出收走,但不能据此认定热重启无泄漏,需要目标驱动/固件证据和重启压力测试。

    阶段 当前证据 下一验收
    无设备封包 12 项单测通过,覆盖 PBL/backing store、邮箱请求、RCFW 环绕、MR/QP/WQE/CQ 格式。 增加畸形 HWRM resp_len、RCFW 超时、非零状态、重复初始化与清理注入。
    BNXT 台架 loopback.py 与 connect.py 存在,但本轮未运行。 本地 PHY loopback、异机 SEND/RECV、环绕、乱序启动、断线重连、长时间稳定性。
    GPU 数据面 仅静态追到 tinygrad rdma_copies()。 双节点 GPU 校验和、CQE 错误传播、缓冲视图/越界、缓存可见性、尾延迟。
    车端集成 未连接目标硬件、模型实体或车辆。 只把 BNXT 暴露为可选计算传输插件;网络或固件失效不得获得 CAN 发送与 panda safety 权限。

    验证矩阵与插件边界

    BNXT 插件的最小声明应包括 PCI ID、固件/芯片版本、拓扑 peer group、MR 对齐与生命周期、队列容量、端到端超时、CQE 状态映射和故障后的资源回收。该驱动是计算数据搬运组件,不是车辆控制或安全网关;把它当作推理加速之外的东西会越过它本不承担的安全边界。

    还要强调一点:HWRM 与 RCFW 的超时和状态断言都属于「失败即停」,而不是「失败即分类」。在自制硬件上,固件版本、邮箱响应页长度、doorbell 偏移都必须由设备身份绑定后写入契约;任何绕过 hwrm() 直接写邮箱的路径,都会跳过这里的版本与能力协商,也就失去了一次可核对的一致性检查。

    常见问题

    HWRM 回包的长度为什么是风险点?

    因为 hwrm() 在读取响应页末项前未约束固件给出的 resp_len 不超过响应页长度,也未先验证它足以覆盖期望结构。畸形大长度可产生 IndexError 而非设计的超时/协议错误,属静态可见的输入校验缺口。

    fini() 之后 QP 和内存会被释放吗?

    源文档未给出显式 destroy_qp/destroy_cq、backing store 逐项释放或初始化异常回滚。fini() 仅调用 func_drv_unrgtr,资源可能由功能复位/进程退出收走,但不能据此认定热重启无泄漏。

    CPU 示例与 GPU 路径的「完成」判据相同吗?

    不相同。CPU 示例的 BNXTQP.poll() 检查 CQE status,而 GPU 提交路径只等待 toggle。两条路径的成功判据不能混同。

    BNXT 驱动能承担车辆控制或安全网关吗?

    不能。源文档明确它是计算数据搬运组件,不是车辆控制或安全网关;车端集成时应只把它暴露为可选计算传输插件,网络或固件失效不得获得 CAN 发送与 panda safety 配置权限。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TINYGRAD_BNXT_FIRMWARE_QUEUE_TRANSPORT_FAILURE_CONTRACT_DEEP_DIVE.md(取用了 BNXT 分层初始化、HWRM/RCFW 回包、队列与故障边界及验证矩阵)。
    • 项目 02 —— openpilot-research/TINYGRAD_RDMA_BNXT_QUEUE_MEMORY_COMPLETION_CONTRACT_DEEP_DIVE.md(仅用于确认 RDMA 提交与完成语义的边界)。

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

    更新记录

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

  • 回放三入口:可视化订阅与 CAN 硬件回放的边界完全不同

    结论:openpilot 的「回放」有三个入口,用途完全不同:C++ replay 可执行文件发布历史消息与 VisionIPC 视频并驱动终端 UI;Python ui.py 只订阅数据做调试可视化;而 can_replay.py 会直接向 Panda Jungle 发 CAN 帧,是唯一对硬件有直接副作用的入口。三者中最容易被误用的就是把 --allow 当成 CAN TX 防火墙,或把可视化订阅当成只读安全——can_replay.py 不是只读分析工具。

    本文基于固定源码快照,静态梳理命令行、终端 UI、Python 调试画面与独立的 CAN 硬件回放脚本。仅做静态阅读,未运行任一回放命令、下载数据、连接/重置/刷写硬件或发送 CAN。CAN 报文解析的健康语义见相邻专题。

    三类入口:能力边界必须先分清

    C++ Replay 是开发进程,启动时会把日志中的 CarParams 写入本地 Params,并可切换 ZMQ=1 让其他 UI/PlotJuggler 订阅。--prefix 为实例指定前缀,但仅靠服务过滤或视觉界面并不能隔离同机所有消息、参数和设备资源,必须在独立开发环境核对具体命名空间。

    入口 实现 能力 关键边界
    C++ replay 可执行文件 main.cc 发布历史消息与视频、终端 UI、benchmark 启动改写本地 Params;--cache 最终仍被钳制为至少五段
    Python 可视化 ui.py raylib 窗口、相机画面、模型轨迹、告警曲线 只订阅数据;曲线是调试可视化,不是严格多主题时间对齐采样
    CAN 硬件回放 can_replay.py 直接向 Jungle 发 CAN 帧 对板卡状态有直接副作用,绝非只读分析工具

    parseArgs() 解析 --allow/--block、段缓存数、起点、倍速、--auto、--data_dir、--prefix、视频与 benchmark 标志,参数转换用 atoi/atof;缓存和倍速在 main 中按正数过滤,起点由 Replay::start() 处理。未知路段、无可用片段仍在后续加载失败。

    main() 随后可选创建 OpenpilotPrefix,实例化 Replay,设置缓存与倍速并加载路段:普通模式启动 ConsoleUI,benchmark 模式直接等待并打印时间线。两个边界要记牢:--cache 最终仍由 setSegmentCacheLimit() 钳制为至少五段;benchmark 只是发布吞吐统计,不是控制算法正确性验证。

    终端 UI 与 Python 调试画面

    ConsoleUI::exec() 约 20 Hz 刷新终端:空格暂停、s/m 前后跳、e/d/t/i/w/c 按时间轴跳、+/- 变速、回车输入绝对秒数。这里的 seek 输入会先暂停再恢复,但未检查 scanw 返回值;错误输入会保持初始 choice=0 并尝试 seek。这是 UI 层行为,不等同于引擎 seek 的参数校验。

    函数 功能 边界
    handleKey() 空格暂停、+/- 变速、回车输入绝对秒数 未检查 scanw 返回值,错误输入仍会 seek
    updateStatus() 从 carState/vehicleParameters 展示车速与转向参数 数据来自回放主题,不是实时车辆状态
    updateTimeline() 从 qlog 生成的 Timeline 绘制使能与告警 缺 qlog 时时间轴可不完整

    Python 侧 ui_thread() 创建 raylib 窗口,用 CameraView("camerad", NARROW_ROAD) 读取 VisionIPC,相机画面与消息图层分开获取;SubMaster 订阅车况、规划、控制、雷达、标定、模型、告警等主题。一个明确的窄边界:get_arg_parser() 接受 ip_address 和 --frame-address,但主入口只用 ip_address 设置 ZMQ 并传给 SubMaster,--frame-address 在本文件中没有被传入 CameraView。远端消息可连接不代表远端帧可显示,该参数在当前实现中无效。

    CAN 硬件回放:独立且危险的边界

    can_replay.py 的四个阶段各有副作用。load_route() 读取 carParams 和所有 can 消息转为逐帧 CAN 列表,无入参时使用默认公开路段;脚本没有依据目标车型、总线拓扑或插入设备自动校验安全适配。connect() 持续枚举 Jungle,每个序列号启动独立发送线程——新连接设备会自动进入发送流程。

    阶段 源码行为 安全含义
    载入 load_route() 读 carParams 与全部 can 消息 来源与内容必须固定、审计;不自动校验安全适配
    发现 connect() 枚举 Jungle 并起发送线程 新设备自动进入发送流程,不是只读工具
    设备配置 send_thread() 在 FLASH 存在时调用 flash(),随后 reset()、清缓冲、设 500 kbps、开点火与供电 对板卡状态有直接副作用;绝不能连实车总线运行
    发报 以 DT_CTRL 节拍按记录帧组循环,仅保留 bus 不高于 2 仅过滤总线号不构成安全认证;依赖物理隔离台架

    FLASH 是「存在即触发」,不是解析布尔值。发送循环仅过滤总线号,空日志会在取模位置失败,USB 超时被吞掉,不能把循环存活视作实际发送成功。

    插件化落点与验证顺序

    把三类入口分开:ReplayControl 只管理路线、时间、速率和服务白名单;ReplayVisualization 只订阅数据且不得取得 CAN 发送能力;HardwareCanStimulus 必须是独立、显式启用的台架插件,含设备白名单、物理隔离确认、报文/总线过滤、发送上限、急停和全量审计。

    1. 不能让通用插件注册机制自动发现并启用硬件刺激插件。
    2. 不能把 --allow 当作 CAN TX 防火墙。
    3. 验证先从固定日志与 mock sink 开始,再查 Params/ZMQ/VisionIPC 隔离、CLI 异常输入、UI 缺主题/缺帧、消息时序。
    4. CAN 输出仅在独立 HIL 台架上用目标硬件与外部监测完成。

    当前目录的 test_route.cc 只覆盖路线范围解析;本文没有运行 GUI、benchmark 或 HIL 测试,也未发现针对上述入口分支的同目录测试。

    常见问题

    can_replay.py 是只读分析工具吗?

    不是。connect() 会持续枚举 Jungle,每个序列号启动独立发送线程;send_thread() 在 FLASH 存在时调用 flash(),随后 reset()、清 CAN 缓冲、设 500 kbps、打开点火与供电。它对板卡状态有直接副作用,绝不能在连接实车总线时运行。

    replay 的 –allow 能当 CAN 发送防火墙吗?

    不能。--allow/--block 只是服务过滤;can_replay.py 的发送循环仅按记录帧组循环发报、只保留 bus 不高于 2,仅过滤总线号不构成安全认证,脚本依赖物理隔离的 Jungle 台架。

    为什么 ui.py 的 –frame-address 不生效?

    源码里 get_arg_parser() 接受 ip_address 和 --frame-address,但主入口只用 ip_address 设置 ZMQ 并传给 SubMaster,--frame-address 在本文件中没有被传入 CameraView,所以远端消息可连接不代表远端帧可显示。

    replay 曲线是严格时间对齐采样吗?

    不是。曲线历史仅在 carState 更新时前移,其余主题值写入最后一行,因此它是调试可视化,不是严格的多主题时间对齐采样;终端时间轴缺 qlog 时也会不完整。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/REPLAY_FRONTENDS_CAN_DEEP_DIVE.md(取用了 C++/Python/CAN 三类入口的功能矩阵、终端 UI 边界与硬件回放四阶段副作用)。
    • 项目 02 —— openpilot-research/REPLAY_ENGINE_DEEP_DIVE.md(仅用于确认回放引擎的消息发布与加载边界,未取用其未公开内容)。

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

    更新记录

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