标签: panda

  • 实时车辆诊断 CLI:ECU 地址、VIN、固件与通信控制边界

    结论:这四个实时诊断 CLI 不是只读日志分析器:它们都经 sendcan 向车辆发送诊断帧,其中 disable_ecu.py 会请求改变 ECU 通信状态。判断结果是「脚本打印」还是「目标 ECU 真的接受」,是两条不同层级的问题——发布成功只表示请求进入了发送链,能否真正上总线还取决于 pandad 与 panda 固件状态。多个工具还会读写 Params、切换 OBD 复用,且没有 finally 恢复。固定快照基线为 c8fb906,本文未执行脚本、连接车辆或发送 CAN。

    共同调用链

    四个 CLI 都调用 can_comm_callbacks():从 can 读取 CanData,把请求封装为 sendcan Event。后者被 can_send_thread() 接收并交给 panda;能否真正上总线还取决于 pandad 与 panda 固件状态。不能把「脚本打印结果」或「发布成功」直接等同于目标 ECU 接收成功。

    入口 请求与输出 本机状态变化
    ecu_addrs.py 默认 bus 1、超时 1 秒;生成 256 个 11-bit 和 256 个 29-bit 候选地址,批量发送 tester-present;仅打印响应地址及可选子地址。 删除 FirmwareQueryDone,IsOffroad 写真、等 0.2 秒再写假;依 --no-obd 切换 OBD 复用。
    vin.py 默认 bus 1、单次超时 0.1 秒、最多 5 轮;尝试该总线允许的 UDS / OBD 或品牌特例,打印响应地址、bus 和 VIN。 脚本本身不写 Params、不切 OBD 复用;仍向 sendcan 发诊断请求。
    fw_versions.py 先查 bus 0/1 的 VIN,再查品牌固件,输出原始版本字节及可能车型;--brand 只缩小固件查询范围。 同样删除 / 改写 Params;VIN 前先请求 OBD 复用,固件请求期间可逐请求切换。
    disable_ecu.py 固定在 bus 1 向 Honda Bosch 雷达地址请求扩展会话,再发送通信控制请求;打印布尔值。 不写 Params、不切 OBD 复用;会尝试改变目标 ECU 的收发状态,且没有恢复调用。

    OBD 复用与状态位

    obd_callback() 在目标模式与 Params 当前值不同时,删除 ObdMultiplexingChanged、写 ObdMultiplexingEnabled,然后无独立期限地阻塞等待确认;pandad 的 updateMultiplexingMode() 在实际切换后写确认。ECU 地址与固件脚本没有恢复先前 Params / OBD 状态的路径。

    FirmwareQueryDone 被删除后,pandad 的 fetchCarParams() 会拒绝继续取得车辆参数,直到其他流程重新置位;常规 Car.__init__() 会在车型识别后置位。IsOffroad 的真 / 假写入是脚本行为,不能据此断言 pandad 已同步切换安全模式——其安全模式配置使用 deviceState.started。

    工具 是否读写 Params 是否切 OBD 复用 是否恢复原状态
    ecu_addrs.py 删除 FirmwareQueryDone,改写 IsOffroad 是(--no-obd 控制) 否(无 finally)
    vin.py 否 否 不适用
    fw_versions.py 删除 / 改写同 ECU 地址脚本 是(VIN 前与固件期间) 否
    disable_ecu.py 否 否 否(改 ECU 收发状态且无恢复调用)

    失效与证据边界

    1. --scan 当前不可用。 脚本把 256 组三种地址形状放进 extra={"any": {"debug": extra}},但固件查询按品牌执行 FW_QUERY_CONFIGS[brand];当前归档没有 any 品牌配置。因此 VIN 查询和 Params / OBD 副作用已经发生后,固件地址构建阶段抛 KeyError('any');--brand 也不会消除此额外键。
    2. ECU 清单不是全量证明。 get_all_ecu_addrs() 把 TX 候选集合直接当 RX 白名单;29-bit 响应通常采用交换后的地址,可能被过滤。get_ecu_addrs() 捕获异常后返回已收集的部分集合,timeout 也不能限制阻塞式接收的整个墙钟时间。
    3. VIN 既不验证身份,也不保证隐藏。 get_vin() 对命中地址的结果做少量去填充后直接解码返回,CLI 原文打印 VIN。失败时返回 (-1, -1, "00000000000000000"),不等同于「车辆没有 VIN」;源码另有 is_valid_vin(),此 CLI 没有调用它。
    4. 固件列表可以是部分结果。 固件查询按请求配置与子地址分组,单个请求异常记录后继续;返回版本可能缺 ECU 或缺品牌。候选车型匹配给的是候选集合,不是执行许可。原始固件字节与 VIN 可能包含可识别车辆的信息。
    5. 手动禁用雷达不确认禁用成功。 通用 disable_ecu() 最多重试 10 次,先要求扩展会话的有效应答,再调用 get_data(0) 发送通信控制请求并丢弃返回字典;零超时和空的期望响应前缀不能提供可靠的正响应证据。帮助函数注释明确警告会禁用原车 AEB,脚本没有配套恢复路径。

    面向硬件与插件化的接口约束

    以下是设计建议,并非当前实现:将前三类查询工具改为只提交有界诊断任务的证据插件,sendcan 与 OBD 复用由唯一诊断代理持有。代理校验点火 / 车速、bus、地址、服务、速率和总时长,记录每次请求与局部响应;--scan 需先修复配置建模,再在隔离台架验证总线负载。通信控制应归入单独的高危能力,需要目标 ECU / 版本白名单、原车 AEB 影响评估、正响应和总线状态回读、持续保活及恢复方案。身份插件只返回带来源和时间戳的 VIN / 固件证据,不提供控制权限;执行权限仍由独立 MCU / panda safety 和车型适配门决定,不能由插件加载或候选车型自动授予。

    常见问题

    这些 CLI 会向车辆发 CAN 吗?

    会。四个工具都经 sendcan 向车辆发送诊断帧,其中 disable_ecu.py 会请求改变目标 ECU 的通信状态。它们不是只读日志分析器;「脚本打印结果」不等同于目标 ECU 接收成功。

    运行这些脚本会留下什么状态?

    ECU 地址与固件脚本会删除 FirmwareQueryDone,把 IsOffroad 写真、等 0.2 秒再写假,并切换 OBD 复用,且没有恢复先前 Params / OBD 状态的 finally。FirmwareQueryDone 被删后,pandad 会拒绝继续取得车辆参数直到重新置位。

    –scan 现在能用吗?

    不能。脚本把地址形状放进 extra={"any": ...},但固件查询按品牌执行 FW_QUERY_CONFIGS[brand],当前归档没有 any 品牌配置,固件地址构建阶段会抛 KeyError('any');--brand 也不会消除该额外键。VIN 查询和 Params / OBD 副作用已经发生。

    打印 disabled: True 就说明雷达被禁用了?

    不能这样认为。通用 disable_ecu() 丢弃查询结果,零超时与空期望响应前缀无法提供可靠的正响应证据;即使底层打印成功,也不等于已证实 ECU 停发、AEB 状态或会话保持。帮助函数注释明确警告会禁用原车 AEB。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CAR_LIVE_DIAGNOSTIC_CLI_CONTRACT_DEEP_DIVE.md(取用了四个 CLI 的请求与副作用、失效边界与插件化接口约束分析)。

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

    更新记录

    首次发布 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

  • Toyota panda safety:LKA/LTA 与纵向模式矩阵

    结论:Toyota panda safety 是MCU 本地规则,不验证 SecOC CMAC、雷达静默的正响应或车辆 ECU 是否接受命令。同一套 safety 代码按「原车 / 自控纵向 × 非 SecOC / SecOC × LKA / LTA」组合出不同模式,各模式允许的 TX 帧、长度与字段门都不同,不能把两个标志当成可随意组合。SecOC 相关位只在 ALLOW_DEBUG 构建下生效,上层即便能构帧也不能绕开普通固件或发布版 dashcamOnly 的能力边界。固定快照基线为 c8fb906,没有目标固件、车辆密钥、ECU 或实车验证。

    关键函数与调用顺序

    CarInterface._get_params() 配置 Toyota safety 参数、LTA / 替代制动 / SecOC、原车或自控纵向——注意上层参数和目标 ECU 已接受控制不是同一事实。toyota_init() 从 safetyParam 低八位读取 EPS 扭矩比例,高位选择替代制动、原车纵向、LTA;SecOC 位只在 ALLOW_DEBUG 下生效。

    toyota_rx_hook() 更新 EPS / 驾驶员扭矩、测量角、巡航 / 油门 / 制动和轮速;toyota_tx_hook() 分别检查 ACC、LKA 扭矩、LTA 角度 / 请求位、SecOC LTA2、AEB 和诊断保活。更外层,safety_tx_hook() 与 safety_fwd_hook() 先做地址 / 总线 / 长度白名单,继电器状态与静态转发阻断是独立的最终门。

    模式矩阵

    维度 选择与实际影响
    原车纵向 STOCK_LONGITUDINAL 非 SecOC 的 TX 仅含基础 LKA / LTA / HUD 与 0x343 取消;0x343 必须置 CANCEL_REQ 且加速度为零。
    自控纵向,非 SecOC 在基础 TX 上增加 DSU bus 0/1、PCS_HUD、0x750、0x283;0x343 可携带受限加速度,并另以 check_relay=true 登记。
    SecOC + 原车纵向 0x2E4 长度由 5 改 8,增加 0x131;0x343 仍只准零加速度取消;没有 0x183。
    SecOC + 自控纵向 0x2E4 长度 8,增加 0x131 和 0x183;0x343 只许零加速度,实际加速度走 0x183;没有非 SecOC DSU / 雷达诊断扩展表。
    LTA 标志 0x191 是否可执行受限角度由 LTA 位决定,即使同时设置 SecOC 位也如此;0x2E4 必须零扭矩 / 无请求。LKA 模式相反,0x191 必须无角度执行。SecOC 的 0x131 LTA2 在当前钩子中始终不准执行。
    替代制动 仅非 SecOC、非 LTA 的 RX 分支选择 0x224;普通分支用 0x226。LTA 分支固定普通 RX 表,SecOC 固定 0x101。

    自控纵向表里 0x343 同时经基础宏以 check_relay=false、扩展项以 check_relay=true 列出;通用继电器和转发代码遍历整个表,所以后者仍会触发对应故障 / 静态阻断,不能只看第一条白名单记录。

    RX 信号与健康

    所有 safety RX 均在总线 0。0x0AA 轮速 83 Hz,忽略 checksum / counter,但四个轮速故障位必须为零;0x260 转向传感器 50 Hz 检查 Toyota 累加校验,非 SecOC 的 LTA RX 表还要求 STEER_ANGLE_INITIALIZING=0。SecOC 初始化优先选择 TOYOTA_SECOC_RX_CHECKS,即使 LTA 位同时设置,该质量位也不作为 RX 有效性门。忽略某一完整性字段不等于不要求报文存活。

    报文 频率与门
    0x0AA 轮速 83 Hz,忽略 checksum / counter,四路故障位必须为零
    0x260 转向传感器 50 Hz,检查累加校验;非 SecOC LTA 表要求转角初始化位为 0
    0x1D2 巡航 / 油门 普通车 33 Hz,检查校验
    0x226 / 0x224 制动 40 Hz,忽略校验 / 计数;替代制动改用 0x224
    SecOC 路径 使用 0x176 巡航、0x116 油门和 0x101 制动

    toyota_rx_hook() 从 0x260 读取 16 位 EPS 扭矩,乘 safetyParam 低八位比例后采样,并把窗口最小值减 1、最大值加 1 以覆盖整数取整;驾驶员扭矩和测量角分别取独立字段。测量角仅在初始化位关闭时更新,不能把上一样本当成当前有效角。0x0AA 四轮原始值各减 6767 后求和,和不为零即视为移动。

    TX 权限与未覆盖载荷

    1. 扭矩 LKA:非 LTA 时 0x2E4 检查最大 1500、每帧上升 15 / 下降 25、相对 EPS 实测误差 350、实时差 450 与请求位切断节拍;LTA 模式仅允许零扭矩且请求位为零。SecOC 的 0x2E4 长度为 8,普通车为 5。
    2. 角度 LTA:非 LTA 时 0x191 的两个请求位、角度、TORQUE_WIND_DOWN 都必须不执行;LTA 时角度受速度相关变化率、最大 1657 原始单位及测量角邻域约束,两个请求位必须一致,wind-down 只可为 0 / 100,且驾驶员 / EPS 扭矩过高时禁止 100。SecOC 的 0x131 只允许请求位和角度字段均为零——这不是车辆认证成功的证明。
    3. 纵向:0x343 或 SecOC 0x183 的有符号加速度活动范围 -3.5 至 2.0 m/s²,未激活码为 0;共享门还要求允许控制且未踩油门。
    4. AEB 与诊断:仅非 SecOC 自控纵向白名单包含 0x283,前六字节必须全零,避免主机执行 AEB;0x750 仅允许固定载荷 0F 02 3E 00 00 00 00 00,用于雷达 tester-present。

    自制硬件与插件化契约

    车型插件提交 DBC / 平台、LKA / LTA、SecOC、制动地址及 EPS 比例的证据;未知 / 矛盾组合保持 noOutput。受信任网关独占 SecOC 密钥与签名、UDS 雷达静默、DSU / HUD 模板及节拍;普通插件只提交高层转向 / 加速度 / 取消意图,MCU safety 保留独立限幅。验证分层覆盖模式参数组合、错误 CRC / 质量位、双请求位不一致、wind-down、油门覆盖、非零 AEB、诊断错误载荷、签名失败与失联降级;随后是无负载台架和受控场地。当前没有这些目标车证据。

    常见问题

    LKA 和 LTA 模式能随便组合吗?

    不能。0x191 是否可执行受限角度由 LTA 位决定,即使同时设置 SecOC 位也如此;LKA 模式相反,0x191 必须无角度执行。0x2E4 在 LTA 模式必须零扭矩 / 无请求。未知或矛盾组合应保持 noOutput。

    「四个轮速故障位为零」是不是意味着数据都可信?

    不等于。0x0AA 忽略 checksum / counter,只用四个故障位做门;0x260 才检查 Toyota 累加校验并要求转角初始化位为 0。忽略某一完整性字段不等于不要求报文存活,健康判定要按报文明细区分。

    AEB 帧在 safety 里怎么处理?

    仅非 SecOC 自控纵向白名单包含 0x283,且前六字节必须全零,以避免主机执行 AEB。其它 DSU、PCS_HUD、0x1D2 / 0x412 等白名单帧没有同等级的逐字段门。

    panda safety 通过就等于车辆 ECU 接受命令了吗?

    不等于。这些只是 MCU 本地规则,不验证 SecOC CMAC、雷达静默的正响应或车辆 ECU 是否接受命令。SecOC 相关位只在 ALLOW_DEBUG 构建下生效,也不能绕开 dashcamOnly 等能力边界。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TOYOTA_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了模式矩阵、RX 健康门、TX 权限与插件化契约分析)。

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

    更新记录

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

  • 公共运行时原语:DT 常量、Ratekeeper 与 I2C 契约

    结论:common/ 里的时间常量、滤波器、PID、系统统计与 I2C 只是被复用的基础函数,它们不是独立进程,也不能单独证明端到端实时性。DT_CTRL 等定义的是名义时间步长,不是操作系统保障的周期;monitor_time() 只记录进度与超期,keep_time() 才在剩余时间为正时睡眠。因此「没有 lag 日志」并不能理解为满足控制时限。

    一、时间常量、优先级与调度

    原语 具体行为 调用边界
    时间常量 DT_CTRL 等定义 100/20/2/20 Hz 的名义时间步长。 控制器、模型、hardwared 和驾驶员监测用作状态更新或滤波参数;不是操作系统保障的周期。
    Priority 定义控制进程两级 FIFO 数值。 controlsd、card 等选择 CPU/优先级;数值只对当前平台布局有意义。
    config_realtime_process() 禁用 GC;非 PC Linux 调用 SCHED_FIFO 并设 CPU 亲和性。 modeld、controlsd、selfdrived、card 等入口;PC 上仍禁用 GC,但跳过内核调度设置。
    drop_realtime() / set_core_affinity() 恢复 SCHED_OTHER;单独改 CPU 亲和性。 UI 部分操作、Athena、上传器等避免抢占高优先级控制任务。
    Ratekeeper.monitor_time() / keep_time() 前者用单调时间累积下一帧截止点、记录移动平均和超期;后者仅在剩余时间为正时睡眠。 card、controlsd、selfdrived 调用 monitor_time(),不调用 keep_time();轮询传感器调用 keep_time()(sensord.py)。

    MovingAverage.add_value()/get_average() 保留 100 个间隔的移动平均;Ratekeeper.lagging 在均值超过名义间隔约 11.1% 时为真——该布尔值不等于最坏延迟或丢消息率。

    二、滤波与 PID

    原语 关键公式 / 行为 使用注意
    FirstOrderFilter.update() 使用 alpha=dt/(rc+dt);initialized=False 时直接接受首样本。 调用方传固定 dt,无自动适配实际帧间隔;hardwared 温度链采用 initialized=False。
    BounceFilter.update() 在一阶位置滤波上加速度/阻尼状态,并按 60 FPS 基准缩放。 主要用于按钮、滚动、键盘等界面动画;不应误用作车辆动态滤波。
    PIDController.update() 按速度插值 P/I/D 增益,积分项按 rate 固定步长推进并做抗积分饱和,最后限幅。 横向/纵向控制和风扇;实际调用周期偏离配置速率时,积分动态会变化。
    LinuxSystemStats cpu_usage_percent()/memory_usage_percent() 读 /proc/stat 与 /proc/meminfo。 CPU 新出现或计数回退时报告 0;缺字段会抛异常;它是遥测输入,不能代替控制循环的 99.9 分位时延。

    三、运行时约束与故障边界

    原生线程侧还有一组 C++ 入口:set_realtime_priority() 与 set_core_affinity() 对当前 Linux TID 设置 FIFO/CPU,非 Linux 返回 -1。源文档给出两种态度:pandad 在非 PC 上要求设置成功(使用断言),而 camerad 只设亲和性、驻车时允许失败。这也说明「能否设置实时优先级」是平台能力问题,普通插件不应默认拥有该权限。

    Ratekeeper 的 _next_frame_time 按固定间隔递增,循环落后时不会跳帧重设截止点;连续超期可能积累负的 remaining。当 print_delay_threshold=None 时,它不会打印超期,也不会让 monitor_time() 返回 True,但仍计算 remaining 和窗口均值——所以不能把「无 lag 日志」理解为满足控制时限。config_realtime_process() 的内核调用失败会抛异常;C++ 侧对 pandad 使用断言要求成功,而 camerad 只设亲和性、驻车时允许失败。

    源文档的六项原函数/假时钟与调度探针给出一个直观例子:10 Hz 下首次正余量约 0.1 秒,后续某轮 0.25 秒的迟到仍让下一截止点只加 0.1 秒,连续轮次继续报迟到;keep_time() 只为正余量调用睡眠。config_realtime_process() 先禁用 GC,再尝试 FIFO,最后设置 CPU 亲和性——若 FIFO 调用抛错,GC 已禁用而亲和性尚未设置。探针不使用真实内核优先级或时钟,因此不证明车端尾延迟。

    FirstOrderFilter 默认把 x0 当有效初值,固定 dt 意味着跳帧/超时后仍按一次名义更新,不会自动按真实经过时间补偿。PIDController 的积分步长也在构造时固定为 1/rate,所以硬件移植和插件执行线程必须记录实际间隔,再用回放/故障注入验证误差与饱和恢复。SMBus.read_i2c_block_data() 在发 ioctl 前限制请求为 0–32 字节,回读长度上限为请求长度;底层没有统一重试、设备健康位或锁,共享总线上的驱动冲突与电气隔离由上层/平台负责。

    四、插件化落点与验证

    新增可运行插件应在 manifest 中声明输入频率、最大处理时长、消息最大年龄、CPU/内存上限和故障处理;受信任调度器保留唯一控制权。SCHED_FIFO/亲和性仅供经过基线测试的核心进程申请,普通插件默认低优先级、进程隔离。传感器插件必须明确总线地址、单位、时间基准、错误/重试语义,并在断连时发布无效/超时状态,不能把 0 遥测值当作健康证据。

    现有 common/tests/ 未找到专门覆盖这几个调度/滤波/I2C 原语的测试文件;本篇已对 Python 调度分支做隔离探针,未测真实内核调度权限、总线时序或车辆级截止时间。建议先在台架采集控制和传感器消息年龄、循环延迟分布、CPU 热限流与故障恢复,再考虑调整优先级或将插件接入热路径。若确实要把这类原语接入热路径,应记录实际调用间隔并据此校准滤波与 PID 的 dt,而不是沿用名义值。

    常见问题

    标称 100 Hz 是不是操作系统保障的周期?

    不是。DT_CTRL 等只是名义时间步长。控制类循环调用 monitor_time() 而非 keep_time(),受输入/工作负载驱动;源文档明确 100 Hz 是期望节拍,而非主动节流,更不是操作系统保障。

    没有 lag 日志是否说明满足控制时限?

    不能说明。当 print_delay_threshold=None 时既不打印超期,也不让 monitor_time() 返回 True,但仍计算 remaining 和窗口均值。因此「无 lag 日志」不能理解为满足控制时限。

    FirstOrderFilter 会在跳帧后按真实时间补偿吗?

    不会。调用方传固定 dt,alpha=dt/(rc+dt),跳帧/超时后仍按一次名义更新,不会自动按真实经过时间补偿。同理 PIDController 的积分步长在构造时固定为 1/rate。

    I2C 一次最多能读多少字节?有统一重试吗?

    SMBus.read_i2c_block_data() 在发 ioctl 前限制请求为 0–32 字节,回读长度上限为请求长度;SMBus 打开 /dev/i2c-N 后经 ioctl 读写,force=True 选 I2C_SLAVE_FORCE。底层没有统一重试、设备健康位或锁,错误透传给调用方。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/COMMON_RUNTIME_PRIMITIVES_DEEP_DIVE.md(取用了时间常量与调度、滤波与 PID、系统统计与 I2C 的函数级契约及插件化要求)。

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

    更新记录

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

  • 大众 MQB panda safety:双总线按钮、冗余制动与纵向编译门

    结论:Volkswagen MQB 的 panda safety 用同一张 RX 表区分两种模式,差异几乎全在 TX 白名单:Stock ACC 模式允许在 bus 0 与 bus 2 发 GRA_ACC_01,自控纵向模式则完全不发该帧,改为允许 ACC_02/06/07。纵向配置依赖 FLAG_VOLKSWAGEN_LONG_CONTROL,且只有 ALLOW_DEBUG 构建才读取它,所以上层 CarParams 设置了纵向能力,并不能单独证明发布固件允许发送 ACC。本文为固定源码快照 c8fb906 的静态阅读,bus 号是单个 safety 实例的本地编号。

    模式与总线矩阵

    volkswagen_mqb_init() 初始化共享状态与同一张 RX 表;仅在 ALLOW_DEBUG 构建中读取纵向参数位,从而选择 Stock 或 Long 的 TX 白名单。两种模式都没有专属转发钩子,通用转发只在 bus 0 与 bus 2 之间,check_relay 地址在目标总线上被静态阻断。

    一个容易误读的点是方向:LH_EPS_03 的 RX 在 bus 0,候选 TX 在 bus 2,不能因为 ID 相同就把两个方向混为一个。

    模式 RX 关键 TX 使能来源
    Stock ACC 七组均在 bus 0 HCA_01、LDW_02 在 bus 0;LH_EPS_03 在 bus 2;GRA_ACC_01 在 bus 0 及 bus 2 TSK_06 原车巡航接合沿;主开关关闭或 GRA Cancel 退出
    自控纵向 同一七组均在 bus 0 HCA_01、LDW_02、ACC_02/06/07 在 bus 0;LH_EPS_03 在 bus 2;无 GRA_ACC_01 TX TSK_06 主开关可用时,GRA Set/Resume 释放沿进入;Cancel 退出

    RX 完整性与状态来源

    volkswagen_mqb_rx_hook() 更新移动状态、驾驶员扭矩、巡航许可、按钮、油门和两种制动观测。制动状态是 Motor_14 踏板开关或 ESP_05 压力检测的组合,之后由共享安全层处理制动退出。ESP_19 的四轮原始值求和大于零即判移动,它不形成第二车速估计。

    需要注意,忽略 checksum 与计数器的两帧仍留在 RX 存活表里,因此不能写成「完全不检查」。频率是检查配置,并非对每帧的实时 deadline 保证。

    RX 帧 检查配置 钩子消费
    ESP_19 0x0B2 8 字节、100 Hz;忽略 checksum/计数 四轮原始值求和大于 0 判移动
    LH_EPS_03 0x09F 8 字节、100 Hz;CRC + 4 位计数 带符号驾驶员扭矩采样
    ESP_05 0x106 8 字节、50 Hz;CRC + 4 位计数 制动压力超过门限的布尔值
    TSK_06 0x120 8 字节、50 Hz;CRC + 4 位计数 ACC 状态 3/4/5 视为接合,2 视为主开关可用
    Motor_20 0x121 8 字节、50 Hz;CRC + 4 位计数 原始油门字段非零
    Motor_14 0x3BE 8 字节、10 Hz;忽略 checksum/计数 踏板开关位
    GRA_ACC_01 0x12B 8 字节、33 Hz;CRC + 4 位计数 纵向 Set/Resume 释放沿,任意模式 Cancel 退出

    TX 字段门与空白

    volkswagen_mqb_tx_hook() 检查 HCA_01 的扭矩与请求位、ACC_06/07 的加速度,以及在未允许控制时 GRA 的设置/恢复位。HCA_01 由共享扭矩钩子检查最大 300、每帧上升 4 与下降 10、实时差 75、驾驶员扭矩余量 80 和请求位;LH_EPS_03 bus 2 虽在 TX 表中用于 Emergency Assist 兼容,但本模式 TX 钩子没有该帧的专属扭矩或载荷检查,其准入只受白名单和继电器故障约束。

    GRA_ACC_01 在 Stock 模式允许 bus 0/2 与 8 字节;未允许控制时只禁止字节 2 的 Set/Resume 对应位,钩子并不要求 Cancel 必须置位,也不全面约束其余按钮位。已允许控制时,这一专属按钮门不再限制这些位。ACC_06/07 只在纵向 TX 表中,两帧主加速度都用 -3.5 至 2.0 m/s² 活动范围与 3.01 m/s² 未激活编码,ACC_07 的另一加速度字段固定要求 3.02 m/s²。

    TX 帧 专属检查 空白与边界
    HCA_01 最大 300、每帧上升 4/下降 10、实时差 75、驾驶员扭矩余量 80、请求位 共享扭矩钩子生效
    LH_EPS_03(bus 2) 仅白名单与继电器故障约束 本模式无专属扭矩/载荷检查
    GRA_ACC_01 Stock 模式;未允许时禁字节 2 Set/Resume 位 不要求 Cancel 置位,不全面约束其余按钮位
    ACC_06/07 仅纵向表;主加速度 -3.5 至 2.0,未激活 3.01 ACC_07 另一字段固定要求 3.02
    LDW_02 / ACC_02 无本模式专属载荷分支 白名单不证明仪表语义或 ECU 接受

    面向自制硬件的插件契约

    车型插件应提交 MQB 平台、DBC、网关或相机线束、固件构建及纵向授权证据,未知组合保持只读。总线插件要分别标注 LH_EPS_03 的 bus 0 RX 与 bus 2 TX,以及 Stock 模式 GRA_ACC_01 的 bus 0/2 TX,不能按单一 ID 推断方向。

    转向与纵向插件只提供意图;可信任网关负责编码、节拍、HUD 与 LH_EPS_03 模板及 ECU 静默回执,最终 TX 仍由独立 MCU safety 决定。故障注入须覆盖两路制动输入失效、主开关或按钮沿错序、CRC 与计数故障、拒帧、错总线,以及纵向固件不支持时的降级。

    证据与缺口

    三项模式离线探针直接运行编译版 safety:调试构建的 Stock 模式允许 bus 2 的 GRA_ACC_01,纵向模式拒绝它;用 DBC 打包的未激活加速度 ACC_06=3.01 m/s² 仅在调试纵向模式通过。以同一 LONG 参数但无 ALLOW_DEBUG 编译后,bus 2 的 GRA_ACC_01 再次可通过、该合法未激活 ACC_06 被拒,说明该测试构建实际退回 Stock TX 表。

    这里说的「无调试宏」只表示本机 C 测试库的预处理条件,不等于目标 MCU 固件已验证。现有 test_volkswagen_mqb.py 覆盖扭矩、冗余制动、Stock 按钮、纵向加速度与按钮使能;本机运行该模块 65 项,9 项跳过,其余通过,但仍没有目标板或实车数据,不能宣称车载可用。

    常见问题

    Stock ACC 与自控纵向模式的 TX 白名单差在哪?

    Stock 模式在 bus 0 与 bus 2 发 GRA_ACC_01;自控纵向模式完全不发该帧,改为在 bus 0 发 ACC_02/06/07。两种模式的 RX 都是同一七组、都在 bus 0。

    为什么 LH_EPS_03 的 RX 在 bus 0、TX 却在 bus 2?

    这是该实例的本地总线角色分工:RX 取自 bus 0,候选 TX 在 bus 2,用于 Emergency Assist 兼容。不能因为 ID 相同就把两个方向混为一个。

    忽略 checksum 与计数器的两帧是不是等于完全没检查?

    不等于。ESP_19 与 Motor_14 虽然忽略 checksum 与计数,但仍留在 RX 存活表中,仍受地址、总线、长度与存活检查约束。

    上层 CarParams 开了纵向能力就代表固件允许发 ACC 吗?

    不代表。纵向配置依赖 FLAG_VOLKSWAGEN_LONG_CONTROL,且只有 ALLOW_DEBUG 构建才读取它;上层能力位不能单独证明发布固件允许发送 ACC。探针显示无调试宏编译时会退回 Stock TX 表。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/VOLKSWAGEN_MQB_PANDA_SAFETY_DEEP_DIVE.md(取用了双模式总线矩阵、RX 完整性表、TX 字段门与空白、模式离线探针结论)。
    • 项目 02 —— openpilot-research/VOLKSWAGEN_MEB_PANDA_SAFETY_DEEP_DIVE.md(同品牌关联专题,用于对照 MQB 与 MEB 的差异,本文未展开其 Gen2 CRC 细节)。

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

    更新记录

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

  • process_replay 容器:初始 Params、消息合流与合成时钟

    结论:回放器的关键是分清「测试初始条件」与「真实运行」,以及分清「合成时钟」与「真实时延」。replay_process()` 先用 migrate_all() 迁移历史消息,再交给多进程调度;return_all_logs=True 时一旦某主题产生至少一条新输出,该主题全部旧消息被替换。「完整日志」不是原始日志加增量。同时 get_output_msgs() 把输出时间改成 start_time + processing_time,这是调度模型的合成值,不是进程真实运行时间、相机曝光时间或 CAN 发送时间。

    一、入口和数据所有权

    入口/对象 行为 重要边界
    replay_process_with_name() 名称经 get_process_config() 转成独立配置,再委托 replay_process()。 名称必须存在于 CONFIGS,不是 manager 的 44 个进程均可回放。
    replay_process() migrate_all() 先做历史 schema/manager/panda/相机状态准备,再调用多进程调度;return_all_logs=True 时删除迁移日志中所有与新输出同主题的旧消息,合并后按 logMonoTime 排序。 迁移会改变输入和时间顺序;同主题旧消息被整体替换,而非增量叠加。
    ProcessContainer.__init__() 创建随机 OpenpilotPrefix,深拷贝回放配置和受管进程对象,维护输入队列/计数/最近输入时间。 前缀隔离 Params、msgq、部分日志路径,不隔离 CPU、设备或权限;同一解释器内环境变量仍是进程级共享状态。
    ProcessContainer.start() 显式创建前缀目录,写 Params/环境,运行配置回调,开启 fake 事件,创建 Pub/Sub 和可选 VisionIPC,然后启动进程并运行初始化回调。 先启动后回调意味着初始化回调必须按 fake 事件握手;启动中途异常可能留下部分资源。
    ProcessContainer.run_step() 先用触发器决定周期是否结束,当前消息始终入 msg_queue;周期结束才收上周期输出、依次发布队列消息与相机帧、解锁 fake 事件。 输出采集发生在本周期输入发送之前,不能按单次调用直接对齐输入输出。
    ProcessContainer.stop() 对进程发 SIGKILL、关闭 fake 事件、删除前缀目录及回放设置的环境变量。 不恢复回放前相同变量的旧值;失败清理可能再抛异常并掩盖最初故障。

    二、初始 Params、指纹与环境

    生成函数 关键写入 语义
    generate_params_config() 默认开启 OpenpilotEnabledToggle/DisengageOnAccelerator,默认不禁用日志;含 ubloxGnss 写 UbloxAvailable,含 driverMonitoringState 写 IsRhdDetected;固件来源保存 CarParamsCache,纵向权限与 notCar 决定 AlphaLongitudinalEnabled、JoystickDebugMode。 这些是测试初始条件,不是重新判断车辆安全许可。
    generate_environ_config() 设置 PARAMS_ROOT 和 REPLAY=1,可选 LOG_ROOT;强制指纹时设 SKIP_FW_QUERY=1。 原函数的 AST 隔离探针分别确认三种字典输出,未创建任何目录或执行指纹查询。
    _setup_env() / _clean_env() 对空字符串执行「从 os.environ 删除该键」,非空值覆盖;设置 PROC_NAME;SIMULATION=1。 _clean_env() 不会恢复之前的值,故回放应运行于独立子进程或自行保留环境快照。

    环境隔离还有一层容易被忽略:_setup_env() 对配置中的空字符串执行「从 os.environ 删除该键」,非空值直接覆盖,并设置 PROC_NAME;_clean_env() 删除配置中所有环境键及 PROC_NAME/SIMULATION,不会恢复之前的值。而 OpenpilotPrefix 自己仅保存和恢复 OPENPILOT_PREFIX。因此在已有环境变量的测试宿主中,回放应运行于独立子进程或自行保留环境快照,不能把容器当成可嵌套的完整环境事务。

    OpenpilotPrefix 自己仅保存和恢复 OPENPILOT_PREFIX;因此在已有环境变量的测试宿主中,容器不是可嵌套的完整环境事务。

    三、消息合流和时钟

    _replay_multi_process() 先创建所有容器,再计算 all_pubs(注入主题)与 all_subs(输出主题)。仅把原路段中属于 all_pubs - all_subs 的消息放入外部队列;与某个受测进程输出同主题的原日志不会进入外部队列,必须依靠新的内部输出驱动下游。每次被测进程产出的消息都加入最终 log_msgs;若其主题又属于 all_pubs,则加入内部队列和按 logMonoTime 排序的小根堆。外部和内部候选比较时,时间相等优先内部消息。

    时间上,get_output_msgs() 等进程下一次 recv_called,排空输出 socket,并把所有采到的输出时间改成 start_time + processing_time;start_time 是此前收到的输入最大 logMonoTime。run_step() 内的 Timeout 只覆盖已结束的周期;没有触发周期的积压输入,以及尾部 get_output_msgs() 冲刷,不共享这个超时上下文。

    两个收尾条件值得单独验证:主循环在外部队列耗尽、所有容器的 msg_queue 都为空时会停止,即使内部堆仍有已生成消息——这些消息可能已进入最终输出,却未再次送给下游容器;结束后逐容器冲刷输出也只追加到最终结果,不重新进入内部堆。这是源码控制流可见的覆盖边界,尚无实际多进程路段复现,不能把多进程回放等同完整闭环执行。

    四、故障与硬件插件验收

    失效路径也需注意:_replay_multi_process() 先将容器加入 containers 再调用 start(),若在 self.rc 或 capture 初始化之前抛错,finally 中的 stop() 及捕获读取可能因部分初始化而再次异常。尾部冲刷的 ReplayContext.wait_for_recv_called() 最终使用默认无限等待,需另设总 deadline 才能证明崩溃/失联路径有界。以上为静态失效路径,未执行故障注入。

    要把新的传感器、计算或车辆接口做成插件,需给回放器明确的输入/输出主题、触发条件、初始 Params/环境、时钟域、消息新鲜度、最大积压和异常清理契约。验收应分开记录功能输出、端到端时延、处理器资源、断流/进程退出和 panda 安全测试;processing_time 或对齐后的参考日志不构成实车时限证据。当前缺 capnp、参考路段和目标硬件,本专题没有运行完整回放。此外迁移本身会改变输入与时间顺序,因此比较两次回放前必须固定同一迁移版本与路段快照,否则结果不可比。

    常见问题

    所有 manager 进程都能被回放吗?

    不是。replay_process_with_name() 要求名称存在于 CONFIGS,源文档明确不是 manager 的 44 个进程均可回放。名称只是转成独立配置的入口,不代表该进程具备完整的回放输入/输出契约。

    return_all_logs=True 时日志是原日志加增量吗?

    不是。一旦某主题产生至少一条新输出,该主题全部旧消息被替换,而不是叠加。源文档强调「完整日志」不是原始日志加增量,且迁移会改变输入和时间顺序。

    processing_time 是进程真实运行时间吗?

    不是。get_output_msgs() 把输出时间改成 start_time + processing_time,start_time 是此前输入最大 logMonoTime,因此它是调度模型的合成值,不是进程真实运行时间、相机曝光时间或 CAN 发送时间。

    多进程回放等于完整闭环吗?

    不等于。主循环在外部队列耗尽且所有容器 msg_queue 为空时会停止,即使内部堆仍有已生成消息;尾部冲刷也不重新进入内部堆。源文档称这是控制流可见的覆盖边界,尚无实际多进程路段复现。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PROCESS_REPLAY_CONTAINER_SCHEDULER_PARAM_LIFECYCLE_DEEP_DIVE.md(取用了容器入口与数据所有权、初始 Params/环境生成、消息合流与合成时钟、故障与插件验收)。

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

    更新记录

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

  • Honda panda safety:RX/TX 白名单与 AEB 转发

    结论:Honda panda safety 的规则分四段:RX 检查表(地址/总线/长度/频率 + nibble checksum 与二位计数器)、使能状态(主开关、PCM/SET-RESUME 使能沿)、TX 白名单与内容钩子、以及转发钩子。honda_tx_hook() 的 Honda 内容钩子在 controls_allowed=False 时拒绝非零转向,但源码并未在该钩子内对使能时的转向幅度或变化率作额外限制,那部分由主机 CarController 承担。拒帧还可能产生状态副作用:本地 C 探针显示,一帧被拒的主机 0x1FA 仍会先写入全局 honda_brake,从而影响下一次原车 AEB 转发决策。以下为固定源码快照 c8fb906 与本地 C 探针的静态分析,不证明目标板固件编译选项、实际 CAN 总线或整车安全性。

    关键函数与组件

    入口 源码 作用
    honda_nidec_init() / honda_bosch_init() honda.h 按参数选择 RX 期望、TX 地址/总线/长度白名单和平台状态。
    honda_get_checksum() / honda_compute_checksum() / honda_get_counter() honda.h 解码 Honda checksum/计数器,供通用接收检查使用。
    safety_rx_hook() / honda_rx_hook() safety.h、honda.h 通用验证通过且属于 RX 检查表后,才更新 Honda 使能、踏板与 AEB 状态。
    generic_rx_checks() / safety_tick() safety.h 踩刹车等通用退出,以及每秒检查关键 RX 消息是否超时/失效。
    safety_tx_hook() / honda_tx_hook() safety.h、honda.h 通用白名单加 Honda 内容检查;最终还需无继电器故障。
    safety_fwd_hook() / honda_nidec_fwd_hook() safety.h、honda.h 通用阻断冲突帧后,Nidec 对相机原车制动执行动态转发决策。

    RX 与使能状态

    Honda RX 检查表把 SCM_BUTTONS 两种地址、ENGINE_DATA、POWERTRAIN_DATA 纳入核心检查;多数模式还检查 SCM_FEEDBACK,备用刹车模式加 BRAKE_MODULE,Nidec 还检查相机总线 BRAKE_COMMAND。每项声明地址、总线、长度和频率,部分声明最大计数器。Honda 钩子提供 nibble checksum 和二位计数器解码;通用 safety_rx_hook() 仅在消息匹配、校验有效且属于 RX 表时才调用 honda_rx_hook()。safety_tick() 每秒检查迟到/无效关键消息并清除 controls_allowed,不能把 25/100 Hz 配置频率误读为毫秒级 fail-safe deadline。

    honda_rx_hook() 从 ENGINE_DATA 更新是否行驶,从 SCM 更新 ACC 主开关,主开关关闭直接禁止控制。Bosch 原车巡航和 Nidec 使用 PCM 巡航上升沿允许控制;Bosch 巡航退出会撤销许可,但 Nidec 允许在 PCM 退出后继续制动至零。Bosch 自控纵向改用 SET/RESUME 的释放沿使能,MAIN/CANCEL 则退出。用户制动有普通 POWERTRAIN_DATA 的连续开关样本判据或备用 BRAKE_MODULE 来源;随后通用 generic_rx_checks() 再按制动上升沿/车辆运动状态撤销控制。主机侧 CarState 字段不替代 MCU 的这一路独立采样。

    TX 白名单与内容规则

    通用 safety_tx_hook() 先核对当前模式 TX 表中的地址、总线和长度,再调用 Honda 内容钩子,最终还受 relay_malfunction 阻断。Nidec 允许指定转向、BRAKE_COMMAND、ACC_HUD 和 LKAS_HUD;Bosch 普通、纵向、radarless、CAN FD 各有不同表。Bosch 纵向 safetyParam 只在 ALLOW_DEBUG 编译条件下转成纵向模式能力,不能由 Python CarParams 位推断实板支持。

    内容钩子在 controls_allowed=False 时拒绝非零转向;源码未在该钩子内对使能时的转向扭矩幅度、变化率或驾驶员反向扭矩作额外限制。主机 CarController 自身会做变化率和车型 STEER_MAX 处理,但这是与 MCU TX 规则不同的层。当前 Honda 安全测试的转向专项只断言未使能零值可发、非零值被拒,不能把通用车型的转向扭矩安全测试结果自动套给 Honda。Nidec 对 ACC_HUD 的 PCM 速度/gas 和 BRAKE_COMMAND 的制动量做纵向许可检查,并阻止主机设置 AEB 执行位;Bosch 有雷达的 ACC_CONTROL 检查加速度、gas 和 AEB 位,radarless 的加速报文走另一地址与编码。所有这些规则只约束通过当前 TX 表的候选,不能保证 ECU 实际接受。

    Nidec AEB 转发及状态顺序

    相机总线 0x1FA 通过 RX 检查后,如果未设置「禁用原车 AEB」的替代体验位,Honda 钩子比较原车制动力与最近记录的主机制动力:原车 AEB 不活动时清掉转发锁存;AEB 活动且原车制动力不小于主机记录值时置位;较小时保持之前状态。通用转发层先按继电器故障和静态冲突表阻断,Nidec 专属钩子再决定是否允许相机 0x1FA 走向另一总线。

    honda_tx_hook() 读取 Nidec 主机 0x1FA 时,先写全局 honda_brake,后检查制动上限、是否正在转发原车 AEB 和 AEB 位。本地拒帧探针直接编译仓库的 libsafety/safety.c:在 Nidec 模式下,相机 AEB 0x1FA 请求制动 100,基线转发;重新初始化后,先送主机 0x1FA 请求制动 200 且带 AEB 执行位,该帧被 TX 钩子拒绝,再送相同的相机帧,RX 有效但转发被阻断。严格编译和 UBSan 运行输出:

    场景 相机帧 RX 转发 说明
    基线 有效(rx=1) 允许(forward=1) 无先前主机帧干扰。
    先送被拒主机帧 — 候选未被接受(accepted=0) 主机制动 200 且带 AEB 位,被 TX 钩子拒绝。
    随后相机帧 有效(rx=1) 被阻断(forward=0) RX 有效但转发被阻断,证明拒帧有状态副作用。

    这证明当前测试库实现中拒帧可影响下一次 AEB 转发决策,尚不证明目标实板的编译配置、真实消息时序或车辆制动后果。

    自制硬件与插件验收

    车型插件只能提交受限候选,MCU 必须独立持有模式、RX 有效性、TX 白名单、超时与转发规则。Honda 的不同模式要分别验证 PT/camera 总线、备用刹车、Nidec 原车 AEB、Bosch ALLOW_DEBUG 门、诊断固定载荷和失联默认状态。探针应扩展为正式回归测试:对越界、带 AEB 位、因原车 AEB 锁存而被拒的主机 0x1FA,都断言下一帧的转发状态只依据已准许发送的主机制动记录。还需检查使能时转向幅度由哪些独立物理/软件约束承担,不能把 Python 限幅写成 MCU 的保证。本文未运行完整 safety 测试或实板故障注入。

    常见问题

    Honda 的转向扭矩在 MCU 侧会被限幅吗?

    Honda 内容钩子只在 controls_allowed=False 时拒绝非零转向,源码未在该钩子内对使能时的幅度、变化率或驾驶员反向扭矩作额外限制。变化率与车型 STEER_MAX 由主机 CarController 处理,属于不同层次。

    Nidec 用哪种沿使能,Bosch 自控纵向呢?

    Nidec 与 Bosch 原车巡航用 PCM 巡航上升沿允许控制;Bosch 自控纵向改用 SET/RESUME 的释放沿使能,MAIN/CANCEL 退出。Nidec 还允许在 PCM 退出后继续制动至零。

    一帧被拒的主机 0x1FA 会影响后续转发吗?

    会。主机 0x1FA 被 TX 钩子读取时先写全局 honda_brake 再检查,探针显示被拒帧仍能改变下一次相机 AEB 0x1FA 的转发决策,输出为 baseline rx=1 forward=1; rejected candidate accepted=0; stock rx=1 forward=0。

    safety_tick() 是毫秒级 fail-safe 吗?

    不是。它每秒才检查迟到/无效关键消息并清除 controls_allowed,不能把 25/100 Hz 配置频率误读为毫秒级安全时限。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/HONDA_PANDA_SAFETY_RX_TX_FORWARD_DEEP_DIVE.md(RX/TX 白名单、使能状态、Nidec AEB 转发与拒帧探针)。
    • 项目 02 —— openpilot-research/ 同目录 Honda 接口与输出专题(模式参数与控制构帧作为相邻边界参考)。

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

    更新记录

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

  • Cabana UI 会话:流生命周期、DBC 编辑与落盘边界

    结论:Cabana 的 UI 是一条以 MainWindow 为中心的生命周期:run() 初始化 GLFW 与 ImGui 后构造主窗口,把流、消息列表、消息详情、视频与图表组成 dock;startStream() 先启动源再加载 DBC 并创建组件,releaseStream() 则先销毁对话框与消费组件再销毁流。它的 DBC 编辑缺少可靠落盘证明:MainWindow::saveFile() 未检查 DBCFile::save() 的失败返回值,所以「File saved」与 clean 状态不能当作文件已可靠写盘的证据。本文为固定源码快照 c8fb906 的静态阅读,未启动 GUI 或连接设备。

    窗口与流的生命周期

    run() 初始化 GLFW、ImGui、主题与布局,然后构造 MainWindow 并进入渲染循环。startStream() 先启动源,再加载 DBC 和创建组件,等待第一批事件合并后关闭等待提示。

    切换流时,releaseStream() 先销毁工具对话框和消费组件,再销毁流,以避免回调持有已释放的全局 can 指针。这个顺序说明「流」与「消费组件」是两类生命周期,不能只按打开顺序反着关。

    主窗口还负责窗口布局的持久化:退出时 saveSessionState() 记录当前 DBC、消息标签和图表 ID,restoreSessionState() 仅在当前加载文件名与记录的 DBC 一致时恢复,且消息标签只恢复仍能在 DBC 找到的项目。保存 UI 会话不包含原始 CAN 帧,流日志是否留存另由输入和设置决定。

    流选择与输入配置

    StreamSelector 提供路段、Panda、Linux SocketCAN 和本地/远端 msgq 入口,并允许同时选择 DBC。每次打开都会重建选择控件,但它只是输入配置,不代表已经连接。Panda 配置允许选择序列号、三路 CAN 速率及适用型号的 CAN-FD 数据速率;选择器构造时即会短暂打开 Panda 判定型号,Panda 构造还会发 USB 通信重置,真正连接时设置 NO_OUTPUT,仍须遵守台架总线接线和速率约束。

    远端路段流程先取设备列表,再按时间范围取路段;401 转登录界面,网络错误会提示。其异步回调带存活弱引用与请求序号,避免旧响应覆盖新列表。登录调用认证并异步取设备与路段,但该账户授权只用于路段可见性,不等于车端网关写权限。

    组件 关键函数 作用与边界
    流选择 StreamSelector::open() / draw() 提供多输入入口并允许选 DBC;仅是配置,不代表已连接
    路段选择 OpenReplayWidget::open() / fetchRoutes() 远端先取设备再按时间取路段;异步回调带弱引用与序号
    Panda 配置 buildConfigForm() / PandaStream::connect() 构造时短暂打开判定型号;连接设 NO_OUTPUT,仍受台架约束
    登录 signIn() / fetchDevices() 账户授权只用于路段可见性,不等于网关写权限
    设置 SettingsDialog::save() 更新主题、缓存分钟、图表高度等;开启直播记录会在本地留存原始数据

    消息详情与 DBC 编辑

    DetailWidget::setMessage() 为 (bus, address) 开标签,联动信号树、位图与历史帧;其 updateWarnings() 对未收到消息、DBC 长度与收到长度不符、信号位重叠给出提示。这些是编辑器的观察性检查,不验证车辆总线发送策略。

    BinaryView 把位图拖拽选择转为新增或调整信号,并展示字节/位翻转热图;binaryViewSignals() 只把当前多路复用分支拥有的 payload 信号送入位图,避免不同分支的位重叠被误画为同时生效。热图表示样本变化,不证明信号语义或大小端定义正确。SignalModel::setData() 从属性表编辑名称、长度、大小端、比例、偏移、单位与取值描述,saveSignal() 检查重名并通过 EditSignalCommand 入撤销栈。

    关键在于:所有编辑都先修改内存 DBC,磁盘写入是单独步骤。由于主窗口的写盘失败返回值未传播,确认保存后仍可能以 clean 状态继续关闭;自制硬件的 DBC 制作流程应以重新打开、哈希校验和版本提交为准。

    文件、会话与错误状态

    FileDialog::accept() 在覆盖已有文件时二次确认,MessageBox::draw() 顺序消费确认与警告队列。remindSaveChanges() 在关闭或切换文件前提示保存或丢弃,但同样受上述返回值未传播的影响。

    设置保存与 DBC 保存不是一回事:Settings::save() 先加文件锁,再写临时 JSON、fsync 文件、rename 并 fsync 父目录,损坏的旧设置会尝试移到 .corrupt。这说明 UI 会话与用户设置本身具备更完整的原子写入路径,而 DBC 保存没有。

    对象 写入/存储方式 边界
    DBC 文件 先改内存,写盘为单独步骤 失败返回值未传播,clean 标志不足以作发布门
    用户设置 cabana.json 锁 + 临时文件 + fsync + rename + 父目录 fsync 损坏旧设置尝试移到 .corrupt
    UI 会话 记录当前 DBC、消息标签、图表 ID 不包含原始 CAN 帧,且需文件名一致才恢复

    对自制硬件与插件平台的约束

    流选择、图表和 DBC 编辑器属于离线或只读工具能力,不应和受信任的车端发送网关共享写权限;Panda 连接即使使用 NO_OUTPUT,也需台架审查波特率与接线。协议文件、用户 UI 设置、路段授权和原始日志应分别管理,DBC 保存必须验证返回值、做原子写入与重新读取,并记录所用源码与车型版本。

    远端路段登录、直播日志与设置目录可能处理敏感车内数据,插件清单应声明读写文件、网络与留存权限,并提供可审计的禁用与清理路径。需要提醒的是,当前 test_cabana_ui.py 只启动 cabana -h 并检查帮助输出,不能据此称菜单、弹窗、持久化或图形交互已通过 UI 自动化。

    常见问题

    Cabana 的「File saved」能证明 DBC 已可靠落盘吗?

    不能。MainWindow::saveFile() 未检查 DBCFile::save() 的失败返回值,因此「File saved」提示与 clean 状态不能作为文件已可靠写盘的证明。自制硬件应以重新打开、哈希校验和版本提交为准。

    打开流选择器会连上 Panda 吗?

    选择器构造时会短暂打开 Panda 判定型号,Panda 构造还会发 USB 通信重置;但真正连接时才设置 NO_OUTPUT。这只是输入配置流程,仍须遵守台架总线接线与速率约束。

    远端路段登录会给车端写权限吗?

    不会。登录调用认证并异步取设备与路段,但该账户授权只用于路段可见性,不等于车端网关写权限。

    保存 UI 会话会把原始 CAN 帧一起存下来吗?

    不会。saveSessionState() 只记录当前 DBC、消息标签和图表 ID;保存 UI 会话不包含原始 CAN 帧,流日志是否留存由输入和设置另行决定。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CABANA_UI_SESSION_DEEP_DIVE.md(取用了主窗口与流生命周期、流选择与设置组件、DBC 编辑与落盘边界、会话持久化)。
    • 项目 02 —— openpilot-research/CABANA_VISUAL_ANALYSIS_DEEP_DIVE.md(关联的可视分析专题,用于区分 UI 会话与图表视频能力,本文未展开其实现)。

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

    更新记录

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

  • Volkswagen MEB panda safety:Gen2 CRC 边界

    结论:Volkswagen MEB 的 panda safety 不是「按品牌名放行」,而是一组可逐帧核对的约束:MEB 选择 volkswagenMeb 安全模式与曲率转向,MEB_GEN2 额外设置 MEB_ALT_CRC;所有必需 RX 在 bus 0,使用 CRC-8H2F、四位计数器与存活检查;发送侧只放行 HCA_03、ACC_18、LDW_02、KLR_01、ACC_19、TA_01 六类地址。其中最容易被误读的一点是 Gen2 的替代 CRC:它不是「Gen2 一律不校验尾部」,标准整帧 CRC 仍可能被接受。

    一、车型资格与 RX 格式

    CarInterface._get_params() 为 MEB 选择 volkswagenMeb 安全模式和曲率控制;MEB_GEN2 设置 MEB_ALT_CRC。网关接线还由指纹判断,不能从品牌名推断物理总线。volkswagen_meb_init() 初始化 Gen1/Gen2 的 RX 长度与统一 TX 白名单,其中 Gen2 的 Motor_51 由 32 字节变为 48 字节,ESC_51 由 48 字节变为 64 字节。

    RX 报文 频率 Gen1 / Gen2 长度 作用
    LH_EPS_03 100 Hz — 驾驶员扭矩。
    Motor_14 10 Hz — 制动状态,再由通用 RX 规则参与退出。
    GRA_ACC_01 33 Hz — Set/Resume 释放沿且主开关开时使能;Cancel 位使其退出。
    QFK_01 50 Hz 32 字节 测量曲率;本模式 max_curvature_error=0,不运行相对误差限幅。
    ESP_21 50 Hz — 独立速度源。
    Motor_51 50 Hz 32 / 48 字节 ACC 状态 2 表示主开关可用,3/4/5 表示巡航接合;否则撤销 controls_allowed。
    ESC_51 50 Hz 48 / 64 字节 四轮原始速度判断移动并形成主速度。

    频率是检查配置,并非对每帧的实时 deadline 保证;计数器从字节 1 低四位、报文 CRC 从字节 0 读取。双车速这条链尤其要谨慎:ESC_51 形成一个速度源,ESP_21 提供独立速度源,曲率安全 helper 调用 speed_mismatch_check(),但不能用任一消息单独证明双源一致。

    二、Gen2 替代 CRC 的真实边界

    Gen2 替代 CRC 只影响 QFK_01、ESC_51 和 Motor_51。其逻辑是:先按整帧算标准 CRC;若开启替代标志,再按 28/60/44 字节前缀和另一组 salt 计算,只有前缀计算值等于字节 0 时才返回该值。因此标准整帧 CRC 仍可能被接受,前缀算法的接受范围则要结合车辆实报文确认。

    源文档给出的三项离线探针把这条边界说得很具体:对 QFK_01 的 32 字节帧,写入前缀 CRC 后修改索引 30 仍被接受,修改索引 20 则被拒;写入整帧 CRC 的帧修改索引 30 被拒。原因是 QFK_01 前缀长度 28 含校验字节,因此前缀算法不保护尾部索引 28..31。该结论只针对本机构造的 QFK_01 报文,不能外推为 Gen2 所有消息都忽略尾部或真实 ECU 会产生这种变异。

    三、TX 白名单与安全门

    地址 bus/长度 专属载荷检查
    0x303 HCA_03 0/24 曲率、请求状态为 4 时的激活位、转向功率;共享曲率 helper。
    0x14D ACC_18 0/32 加速度、HMS 停车状态、起步/保持位与 ACC 活动状态。
    0x397 LDW_02 0/8 无本模式专属载荷分支。
    0x25D KLR_01 0/8 与 2/8 无本模式专属载荷分支;仅 bus 2 项带 check_relay。
    0x300 ACC_19 0/48 无本模式专属载荷分支。
    0x26B TA_01 0/8 无本模式专属载荷分支。

    通用 safety_tx_hook() 先按地址、bus、长度白名单过滤,再调用车型 TX 钩子,最终还受继电器故障阻断;静态转发只在 bus 0↔2 间,阻断目的总线中带 check_relay 的地址。TX 准入不是 ECU 已接受、仪表内容正确或原车 AEB 不受影响的证明。

    ACC_18 的加速度量纲为 0.001 m/s²,活动范围 -3.5 至 2.0,未激活专用码为 3.01;停车请求 HMS=1(保持)或 4(起步)仅在允许控制时可发,0(无请求)与 5(坡道释放)可在退出后发。HCA_03 采用有符号曲率、请求位和功率三类输入,最大绝对曲率 29105、功率 125、名义频率 50 Hz;未请求转向时曲率必须为 0、功率必须为 0。一个关键细节:控制许可撤销后,带请求位的严格递减功率可作为退出时的降功率路径,不能把它误写成「失能后所有请求位帧必拒」。

    四、自制硬件与插件边界

    车型插件须提交 MEB 代际、DBC、接线位置和 CRC 变体的证据;未经确认时保持只读,不能仅按 ID.4/Born 名称自动授予 TX。感知/定位插件应分别报告 ESC_51 与 ESP_21 的来源、时间戳及健康;控制插件不能自行合并双车速再宣称冗余检查已通过。纵向控制插件应输出带语义的「加速度 / 保持 / 起步 / 坡道释放 / ACC 状态」,由可信任网关负责编码与状态序列,MCU 保留独立的字段检查和失联门。

    HUD、TA 与 KLR 白名单帧缺专属载荷检查,插件网关需给这些帧最小模板、速率和所有权,不能开放任意原始 CAN 写入。验证顺序应包含 Gen1/Gen2 CRC 尾部变异、错长度/计数、双车速失配、按钮沿、失联时保持/释放与功率降阶。现有 test_volkswagen_meb.py 覆盖两代配置、计数/校验、双车速、曲率功率、加速度与停车状态;本机运行该模块 90 项,7 项跳过,其余通过,但它不代替上述 Gen2 前缀/整帧 CRC 尾部变异探针,也未验证目标硬件。

    常见问题

    Gen2 是不是「不校验尾部 CRC」?

    不是。Gen2 替代 CRC 只影响 QFK_01、ESC_51 和 Motor_51,且先按整帧算标准 CRC;只有前缀计算值等于字节 0 时才返回该值,所以标准整帧 CRC 仍可能被接受。QFK_01 前缀长度 28 含校验字节,故前缀算法不保护尾部索引 28..31,这只是一个具体探针结论,不能外推为所有消息都忽略尾部。

    两条车速报文能互相证明冗余吗?

    不能。ESC_51 从四轮原始速度形成主速度,ESP_21 提供独立速度源,曲率 helper 调用 speed_mismatch_check(),但源文档明确不能用任一消息单独证明双源一致。控制插件也不能自行合并双车速再宣称冗余检查已通过。

    失能后所有带请求位的转向帧都会被拒吗?

    不是。源文档指出控制许可撤销后,带请求位的严格递减功率可作为退出时的降功率路径,相邻测试 test_power_without_control() 为此设了专门断言,不能把它误写成「失能后所有请求位帧必拒」。

    按车型名 ID.4/Born 就能自动授予发送权限吗?

    不能。车型插件须提交 MEB 代际、DBC、接线位置和 CRC 变体的证据,未经确认时保持只读;源文档明确不能仅按 ID.4/Born 名称自动授予 TX。网关接线位置还由指纹判断,不能从品牌名推断物理总线。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/VOLKSWAGEN_MEB_PANDA_SAFETY_DEEP_DIVE.md(取用了 MEB 车型资格、RX 格式与使能链、Gen2 替代 CRC 边界、TX 白名单与安全门、硬件插件边界)。

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

    更新记录

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

  • Honda 控制输出:Bosch/Nidec 构帧与 50/10 Hz 节拍

    结论:Honda 控制输出分 Bosch 与 Nidec 两条构帧路径,并叠加三层节拍:转向每轮一条候选、纵向 50 Hz、仪表 10 Hz。Nidec 的正加速并不在 50 Hz 制动帧里,而由 10 Hz ACC_HUD 携带 PCM_SPEED/PCM_GAS,所以仪表链本身就是纵向执行链的一部分。Python 侧的限幅不等于 MCU 的安全保证:CarController 的 STEER_MAX 与变化率处理、panda honda_tx_hook() 的独立检查、继电器故障阻断是不同层次。下述 100/50/10 Hz 均以目标 100 Hz、每轮调用控制器一次且无丢周期为前提,只表示软件名义节拍;未做 DBC 回放、台架或实车验证。

    关键函数与组件

    入口 源码 作用
    CarController.update() carcontroller.py 读取控制目标与车况缓存,限幅、调度并返回候选 CAN 帧和软件回报值。
    compute_gas_brake() carcontroller.py Nidec 把加速度转换成 gas/brake;Bosch 分支当前返回两个零,实际纵向另由 ACC 帧表示。
    actuator_hysteresis() / brake_pump_hysteresis() carcontroller.py Nidec 制动去抖与泵请求;泵计时基于 frame * DT_CTRL。
    CarControllerParams / CanBus values.py、hondacan.py 车型扭矩上限、变化率及 PT/radar/camera/LKAS 路由。
    create_steering_control() / create_acc_commands() hondacan.py 转向和 Bosch 纵向字段编码。
    create_brake_command() / create_acc_hud() hondacan.py Nidec 制动、PCM 加速及仪表字段编码。
    honda_tx_hook() / honda_nidec_fwd_hook() honda.h MCU 独立检查候选帧并决定相机制动帧转发。

    公共转向与回报语义

    每轮先对归一化 actuators.torque 以 STEER_DELTA_UP/DOWN * DT_CTRL 限变化率,再取负号、夹到 [-1, 1] 并乘车型 STEER_MAX 构造 CAN 扭矩。create_steering_control() 始终生成一条 STEERING_CONTROL 候选;CC.latActive 为假时其扭矩和请求位都为零,BOSCH_TJA_CONTROL 还设置 STEER_DOWN_TO_ZERO。这段 Python 限制不替代 MCU 的未使能零转向规则,也不能从 latActive 为真推定车辆接受转向。

    new_actuators 中的 torque 是软件限速后的归一化值,torqueOutputCan 是编码前的整数;accel/gas/brake/speed 有部分字段只在对应分支更新,可能保持上轮值。它们是软件目标回报,不是 panda 发送回执或车辆执行测量。控制器仅在车控存活检查通过时才被调用并发布 sendcan;消息的 valid 使用 CS.canValid,具体总线发送仍需再经过 pandad/panda。

    纵向与按钮分支

    分支 触发条件 生成的候选帧
    原车纵向 未启用自控纵向 偶数轮 BOSCH_SUPPLEMENTAL_1;取消或恢复请求时 SCM_BUTTONS(radarless 发相机总线,其余用 PT 总线)
    自控纵向 Bosch 启用自控纵向 偶数轮 ACC_CONTROL;有雷达版再加 ACC_CONTROL_ON
    自控纵向 Nidec 启用自控纵向 50 Hz BRAKE_COMMAND + 10 Hz ACC_HUD 携带 PCM_SPEED/PCM_GAS

    若未启用自控纵向,偶数轮对非 radarless/CAN FD 平台尝试生成 BOSCH_SUPPLEMENTAL_1,取消或恢复请求则生成 SCM_BUTTONS;radarless 把按钮发到相机总线,其他平台用 PT 总线。源码的 supplemental 条件没有显式限定 Bosch,不能据此推定所有进入该分支的车型都获安全白名单许可。spam_buttons_command() 只生成一条候选,调用处没有独立按钮节流,实际发送还受当前模式的 TX 白名单和按钮安全门制约。

    启用自控纵向时偶数轮生成纵向候选。Bosch 把加速度夹在 BOSCH_ACCEL_MIN/MAX,用独立查表得到 gas;有雷达版同时生成 ACC_CONTROL_ON 与 ACC_CONTROL,无雷达版只生成 ACC_CONTROL。gas_command 在不活动或低于阈值时为 -30000,ACCEL_COMMAND 在不活动时为零;STANDSTILL 由 LongControlState.stopping 连续计数而来,无雷达版 IDLESTOP_ALLOW 要计数超过 200 个 50 Hz 周期。enabled、longActive、stopping 是不同条件,不能合并成单一「已启用」位。

    Nidec 的 compute_gb_honda_nidec() 在低于 2.3 m/s 时引入蠕行制动,再经刹车迟滞、每轮变化率限制、风阻扣减和 NIDEC_BRAKE_MAX - 1 上界量化为 BRAKE_COMMAND。泵请求在制动增大或超过 20 秒后重置计时,正制动下保持至少 0.2 秒。编码器把 AEB 执行相关字段显式置零,FCW 只映射警告和原车 chime。Nidec 正加速不是每个 50 Hz 制动周期的独立油门帧,而是在 10 Hz ACC_HUD 中携带 PCM_SPEED/PCM_GAS,因此 HUD 链也是其纵向执行链的一部分,应分别回放替代与普通分支。

    HUD、总线与 MCU 边界

    每 10 轮:自控纵向才生成 ACC_HUD,所有模式生成一个或两个 LKAS_HUD 候选;有雷达 Bosch 自控纵向还生成 RADAR_HUD,特定 Civic Bosch 补 LEGACY_BRAKE_COMMAND。原车纵向且 BOSCH_EXT_HUD 时 LKAS_HUD_A/B 分开发送。hud_v_cruise 在不可见时取 255,可见时除以 CS.v_cruise_factor,此值依赖 CarState 所选单位。create_acc_hud() 对 Bosch/Nidec 使用不同字段,Nidec 还复制原车 acc_hud 的若干 FCM/图标位;无新鲜度检查时不应将复制成功当作原车功能健康。

    CanBus 不能假定 PT 永远是 bus 0:有雷达 Bosch 的 PT 为 offset+1、radar 为 offset,LKAS 依据自控纵向决定去 PT 还是 radar;radarless/CAN FD 与 Nidec 的 PT 为 offset。panda honda_nidec_init() / honda_bosch_init() 选择地址、总线、长度白名单及 RX 检查;Bosch 自控纵向的 safetyParam 分支仍受 ALLOW_DEBUG 编译门约束,不能因 Python 能构帧就推定量产固件可发。

    honda_tx_hook() 对 Nidec BRAKE_COMMAND 的制动幅度、AEB 位和 ACC_HUD 的速度/gas 作检查;对 Bosch ACC_CONTROL 的加速度、gas 和 AEB 位作检查;未允许控制时转向输出必须为零。诊断地址只允许固定 tester-present 载荷,Bosch 原车纵向的按钮在控制不允许时只能取消。Nidec honda_nidec_fwd_hook() 还按相机原车 AEB 制动力与自控制动请求比较动态转发 0x1FA,不是简单的「总是屏蔽原车制动」。

    插件与硬件验收

    车型输出插件应分别声明转向、纵向、按钮、HUD、诊断保活和原车 ADAS 影响权限;每项绑定 DBC、车型指纹、物理总线、消息周期、数据年龄和 panda safety 模式。只允许插件产生候选控制帧,最终 TX 白名单、力矩/加速度/AEB 约束与超时失效仍在独立 MCU。对 Nidec 要单测 10 Hz ACC_HUD 与 50 Hz 制动的耦合、泵保持和原车 AEB 转发;对 Bosch 要单测雷达/无雷达、原车/自控纵向、ALLOW_DEBUG 配置和 ECU 静默失败。当前没有可用实板、目标车型回放或车辆执行证据,本文不能作为上车许可。

    常见问题

    Nidec 的加速信号在哪条帧里?

    不在 50 Hz 制动帧里。Nidec 正加速是在 10 Hz ACC_HUD 中携带 PCM_SPEED/PCM_GAS,因此 HUD 链也是其纵向执行链的一部分,不能只看 BRAKE_COMMAND。

    Bosch 的 gas 是怎么算出来的?

    自控纵向时 Bosch 把加速度夹在 BOSCH_ACCEL_MIN/MAX,再用独立查表得到 gas;gas_command 在不活动或低于阈值时为 -30000。compute_gas_brake() 的 Bosch 分支当前返回两个零,实际纵向另由 ACC 帧表示。

    PT 总线一定是 bus 0 吗?

    不是。有雷达 Bosch 的 PT 为 offset+1、radar 为 offset,LKAS 依据自控纵向决定去 PT 还是 radar;radarless/CAN FD 与 Nidec 的 PT 为 offset,因此不能假定固定编号。

    Python 的转向限幅算安全保证吗?

    不算。CarController 的 STEER_MAX 与变化率处理只是软件层;MCU 的 honda_tx_hook() 独立检查候选帧,未允许控制时转向输出必须为零,最终还受继电器故障阻断。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/HONDA_CONTROLLER_CAN_OUTPUT_DEEP_DIVE.md(Bosch/Nidec 构帧、50/10 Hz 节拍、HUD 与 MCU 边界)。
    • 项目 02 —— openpilot-research/ 同目录 Honda CarState 与接口专题(输入侧与雷达通信控制作为相邻边界参考)。

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

    更新记录

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