标签: 安全边界

  • Params 生命周期:注册键、清理时机与未上抛的写失败

    结论:Params 是低频配置与状态的持久层,不是高频消息总线,也不是独立的车辆安全授权。每个合法键都有注册类型、位标志和可选默认值;同名相近的键(CarParamsCache / CarParamsPersistent / CarParamsPrevRoute)用途与新鲜度完全不同。写入路径存在一个明确的缺口:文件系统返回的负错误码没有在 Python put() 中被检查,所以关键安全配置不能仅凭 put(..., block=True) 没抛异常就宣称持久化成功。固定快照基线为 c8fb906,本地归档缺编译后的 libparams_c,未运行原生参数测试或断电故障注入。

    注册、读写与类型

    params_keys.h 与 ParamKeyAttributes 规定每个合法键有类型、位标志、可选默认值。例如 CarParams 是 bytes 且在 manager 启动 / 上路时清除,CarParamsPersistent 则持久保存。动态插件不能只在自己进程里声明任意新键;必须设计命名空间、类型、迁移与生命周期,并纳入可信配置注册机制。

    Params.check_key() 让 Python 拒绝未注册键及含 NUL 的键;put() 仅接受与注册类型匹配的 Python 类型,get() 按声明反序列化。Python ParamKeyType 支持 string、bool、int、float、time、JSON、bytes;C++ Params 是字节存储,并不自行验证每个键的业务内容。get_bool() 只把 "1" 视为真,其余值都返回 false,因此它不能用来判断某键是否被明确写入;get() 与 get_bool() 在缺省处理上也不完全相同。

    组件 / 函数 已核对契约 对插件的影响
    params_keys.h / ParamKeyAttributes 每个合法键有类型、位标志、可选默认值;CarParams 启动 / 上路清除,CarParamsPersistent 持久。 插件须设计命名空间、类型、迁移与生命周期。
    check_key() 拒绝未注册键与含 NUL 的键;put() 只接受匹配类型。 不能靠自由字符串文件模拟已登记参数。
    get() / _cpp2python() 非阻塞空字节不请求默认时返回 None,请求默认时用注册表默认值;block=True 得到空字节时抛 KeyboardInterrupt;损坏 JSON 警告一次并尝试默认。 默认值回退可能掩盖原始损坏;空字符串不能区分「缺失」与「已写空值」。
    put() / params_put() 默认走 C++ 异步队列;block=True 直接调用文件写入;负返回码未在 Python 侧检查。 关键配置不能只凭无异常宣称持久化成功。

    存储层:原子替换与异步写队列

    Params::put() 走临时文件写入、fsync、目录锁、原子 rename、目录 fsync;异步写线程按队列逐项执行,析构时等待。单键替换具备原子写入路径,但多键配置不是原子事务;异步写也不是跨进程的严格提交确认。

    参数目录通过参数前缀软链接暴露,OpenpilotPrefix 能让测试进程隔离到单独前缀;C ABI 层负责异常翻译与返回缓冲复制。这两层保证的是「路径与接口」的正确性,并不校验业务内容是否符合调用方预期。

    Params::clearAll() 按位标志删除匹配键,同时删除注册表之外的文件;OpenpilotPrefix 能把测试进程隔离到单独前缀。这意味着未登记的插件键可能在清理时消失,验证环境也必须记录前缀与参数快照。

    manager 的清理时机与实际键例

    参数的新鲜度由 manager 在几个时点统一管理:

    时点 源码动作 示例影响
    manager 初始化 清除 CLEAR_ON_MANAGER_START、上路、下路、点火标志;发布渠道还清 DEVELOPMENT_ONLY,随后给缺失的有默认值键写默认。 CarParams、ControlsReady、FirmwareQueryDone 被清除;OpenpilotEnabledToggle 等持久键保留 / 补默认。
    上路边沿 检测 deviceState.started 从 false 到 true,清 CLEAR_ON_ONROAD_TRANSITION。 车型参数需 card 在新行车周期重新提供,插件不能缓存旧控制授权。
    下路边沿 清 CLEAR_ON_OFFROAD_TRANSITION,并更新 IsOffroad。 调试模式、Chestnut 活动状态不应跨周期视为有效。
    点火边沿 基于 pandaStates 点火值清 CLEAR_ON_IGNITION_ON。 DriverTooDistracted、ChestnutActive 等按点火周期复位。

    安全敏感的 SecOCKey 标为 PERSISTENT | DONT_LOG,而 DONT_LOG 的含义由 logger 实现为保留键名但不写值。它不是加密、访问控制或对所有外部插件日志的自动脱敏。

    失败路径与验证

    params_c.cc 只将抛出的 C++ 异常转成 last_error;params_put() 原样返回 Params::put() 的整数结果,而 Params.put() 丢弃该结果。原方法探针确认:若假 C 函数返回负值而 last_error 为空,_bind() 会把负值交还给 Python,两个写入方法都返回 None 且不抛异常;若 last_error 有值,_bind() 抛 RuntimeError。mkstemp、write、fsync 或 rename 的真实磁盘故障尚未复现。

    Params::clearAll() 对删除操作不检查每个 unlink 的返回值;FileLock 锁打开或加锁失败时记录错误,但构造并未抛异常。配置冻结与清理的失败检测不能仅依赖调用返回。现有参数测试覆盖同步 / 异步读写、未知键、默认值与按标志清理,日志测试检查 DONT_LOG 值被遮蔽;本次未运行。新插件参数还应测试断电、磁盘满、跨进程竞争、旧版本迁移、上路 / 下路与点火边沿,以及记录中是否泄露密钥。插件注册器应在 offroad 冻结配置版本、哈希及权限;行车进程读到的配置与 panda 固件安全模式必须可互相校验,失败时保持被动模式。

    常见问题

    put(block=True) 没报错,是否说明参数已经落盘?

    不能这样推断。文件系统调用返回的负错误码没有在 Python put() 中检查;探针确认假 C 函数返回负值且 last_error 为空时,写入方法仍返回 None 且不抛异常。关键安全配置需读回、版本校验及故障注入。

    CarParams 和 CarParamsPersistent 有什么区别?

    用途与新鲜度不同。CarParams 是 bytes 且在 manager 启动 / 上路时被清除,需要 card 在新行车周期重新提供;CarParamsPersistent 与 CarParamsPrevRoute 是持久键。名称相似但不能互相替代。

    DONT_LOG 会自动脱敏日志吗?

    不会。DONT_LOG 由 logger 实现为保留键名但不写值;它不是加密、访问控制,也不是对所有外部插件日志的自动脱敏。安全敏感的 SecOCKey 只是被标为 PERSISTENT | DONT_LOG。

    插件能自己声明新的参数键吗?

    不能只在自己进程里声明。合法键必须在注册表里有类型、位标志与默认值;check_key() 会拒绝未注册键。clearAll() 还会删除注册表之外的文件,所以未登记的插件键可能在清理时消失。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PARAMS_LIFECYCLE_DEEP_DIVE.md(取用了键注册、读写类型、存储原子性、manager 清理时机与写失败路径分析)。

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

    更新记录

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

  • panda Python 设备 API:连接副作用、CAN 传输与健康协议

    结论:panda 的 Python API 不是一组只读查询函数,而是一条会改变设备状态的维护接口:Panda() 构造时立即调用 connect(),连接成功后会读取健康与协议版本、重置 CAN 通信、关闭三路总线的 CAN FD 自动切换并写入默认速率。CAN 传输层用六字节头加 XOR 校验组织报文,返回帧把总线号加 128、被拒帧加 192。XOR 与版本检查只检测传输格式与意外损坏,不是对报文来源的密码学认证,也不能替代 panda MCU 的车型 TX 约束。本文为固定源码快照 c8fb906 的静态阅读,未连接设备、未发送 CAN。

    连接不是只读操作

    Panda.__init__() 可按序列号或命令行选择设备,并立即调用 connect()。连接先试 USB 后试 SPI:USB 侧匹配 VID/PID、读取序列号并可能独占接口;SPI 侧检查 bootstub 状态与协议版本。没有设备时 connect() 抛异常,wait=True 会重试。close() 关闭句柄及 USB context,reset() 发送设备复位请求并可重连。

    连接成功后,驱动会读取 MCU 的健康与 CAN 包版本;默认 disable_checks=True 会禁用心跳检查和省电,随后重置 CAN 通信、关闭三个总线的 CAN FD 自动切换并写入默认 CAN 速率。因此普通 Panda() 本身就带有设备状态副作用,不适合作为插件的「探测对象」。正式 pandad 的 Python 包装器只在健康检查与固件维护时临时打开设备,运行时收发由 C++ pandad 承担。

    get_packets_versions() 读取两个协议版本,短响应时返回 (0,0);health()、can_health() 和 CAN 收发入口都用装饰器对比本地与设备版本,不匹配则拒绝解释数据或发送。这意味着自制 MCU 固件不能只实现同名端点,还必须保持头格式、健康结构布局与版本 hash 一致。

    CAN 包格式与传输

    pack_can_buffer() 只接受 DLC_TO_LEN 中列出的长度;头部编码 DLC、总线、FD、地址及 XOR 校验。USB 写入时按约 256 字节分块,SPI 不走这条分块开关。unpack_can_buffer() 验证校验和,跨传输的不完整帧会留在 can_rx_overflow_buffer 等下次拼接;返回帧把 bus 加 128,被拒绝帧加 192,不能误认作真实物理总线编号。

    can_send_many() 经版本检查后调用底层 bulkWrite,在短写时重复提交剩余字节;默认写超时为 10 ms,源码注明 timeout=0 意味无限。can_recv() 读取后重组帧,对 USB IO 与 overflow 错误会无限重试并休眠,函数自身没有总重试上限。发送函数没有处理 bulkWrite 返回零但不抛异常的情况,按控制流可能不前进,是否发生需用具体传输实现验证。

    环节 实现事实 边界
    pack_can_buffer() 头部含 DLC、总线、FD、地址与 XOR 未对车型允许地址、速率或驾驶状态裁决
    USB 写入 按约 256 字节分块 SPI 不走这条分块开关
    unpack_can_buffer() 验证校验和,残帧留待下次拼接 返回帧 bus 加 128,被拒帧加 192
    can_recv() IO/overflow 错误无限重试并休眠 函数本身没有总重试上限

    健康与配置 API

    health() 解码点火、controls_allowed、安全模式与参数、心跳丢失、RX 校验无效、缓冲溢出、故障和温度等,它是点时状态,不证明下一帧仍允许发送,也不能只看 controls_allowed 而忽略 safety mode 与 fault。can_health() 报告每总线的 bus-off、错误被动、收发丢帧、校验错误与速率/CAN FD 信息,其计数器是硬件/固件视角,不能代替车型信号新鲜度。

    配置类方法与维护类方法有明确的能力差别:set_safety_mode()、set_alternative_experience() 向 MCU 发送安全模式与体验标志请求,但 Python 方法本身不做车型许可判断;send_heartbeat() 会重新开启心跳检查。get_signature() 读取设备签名字节并与本地固件尾部比对,属于版本一致性比较,并不是单凭 Python 层就完成的固件可信启动验证。

    函数簇 返回/动作 信任边界
    health() 点火、控制许可、安全模式、心跳、溢出、故障、温度 点时状态,不能只看 controls_allowed
    can_health() 每总线 bus-off、错误被动、丢帧、校验错误、速率 计数器是硬件/固件视角,协议版本须一致
    set_safety_mode() 发送安全模式与体验标志控制请求 Python 方法未做车型许可,插件不应直接获得
    set_obd() 与 CAN 配置 切 OBD 复用、收发器使能、CAN/CAN FD 速率 对布线与收帧可见性有副作用
    get_signature() 读设备签名并与本地固件尾部比对 版本一致性比较,不等于固件可信启动

    面向自制硬件与插件的约束

    源码给出的设计建议(不是现有代码已实现)是:把设备接口分成枚举与只读健康、接收 CAN、发送 CAN、改写总线与安全模式、固件维护五级能力,只有受信任的单一网关持有发送、心跳和 safety mode 权限。自制网关至少要复现包格式、残帧处理、返回/拒绝标记、版本握手、错误计数和独立的 MCU TX 安全策略,并在异常时由硬件默认停止自动控制,而不是依赖 Python 进程及时恢复。

    还需要注意,pandad.py 的 Python 包装器会在固件签名不同或处于 bootstub 状态时调用 flash/recover,之后再启动 C++ pandad;它是会改写固件的恢复链,因此研究时不应把实例化 Panda 或运行包装器视为无害操作。协议 ABI、线束拓扑、CAN 速率及固件镜像应绑定到具体硬件版本,设备发现不得自动刷写或切换安全模式。

    常见问题

    为什么说 Panda() 一实例化就有副作用?

    因为它按序列号或 CLI 选设备后立即调用 connect();连接成功会读取运动健康与协议版本,默认 disable_checks=True 时禁用心跳检查与省电,并重置 CAN 通信、写入默认 CAN 速率。所以它不适合作为插件的「只读探测」入口。

    can_send_many() 与 can_recv() 会无限重试吗?

    can_recv() 对 USB IO 与 overflow 错误会无限重试并休眠,函数自身没有总重试上限;can_send_many() 在短写时重复提交剩余字节,默认写超时 10 ms,源码注明 timeout=0 表示无限。因此需要调用方自己设定超时与失败处理。

    CAN 包的 XOR 校验能证明报文来源可信吗?

    不能。XOR 与包版本检查用于检测传输格式错误或意外损坏,不是对 CAN 报文来源的密码学认证,也不能替代 panda MCU 的车型 TX 约束。pack_can_buffer() 本身不对车型允许地址、速率或驾驶状态做裁决。

    自制 MCU 固件只实现同名端点就够了吗?

    不够。Python 层用装饰器对比本地与设备协议版本,不匹配就拒绝解释或发送,所以固件还须保持头格式、健康结构布局及版本 hash 一致;此外还应复现残帧处理、返回/拒绝标记、错误计数与独立的 MCU TX 安全策略。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PANDA_PYTHON_DEVICE_API_DEEP_DIVE.md(取用了连接副作用、CAN 包格式与传输、健康与配置 API 分层、自制硬件约束)。
    • 项目 02 —— openpilot-research/PANDA_BOARD_DEEP_DIVE.md(关联的板级安全链专题,用于区分 Python 层能力与 MCU 执行门,本文未展开其函数细节)。

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

    更新记录

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

  • Hyundai 接口的 CAN FD 判别、能力位与 ECU 静默恢复边界

    结论:Hyundai 车型接口的能力位是软件决策,不等于目标 ECU 已按请求改变状态。接口按指纹与固件把平台分成 CAN FD 与经典 CAN 两条判别路径,再由相机总线上是否出现 0x50 或 0x110 决定 LKA 还是 LFA 转向,并据此交换 A-CAN / E-CAN;纵向 ECU 静默只在「自控纵向且非相机 SCC」路径发起,且通用 disable_ecu() 的返回布尔值没有在 Hyundai init() 中被汇总,card 在其返回后直接写 ControlsReady。固定快照基线为 c8fb906,未执行 UDS 请求或实车验证。

    入口与函数链

    CarInterfaceBase.get_params() 先装载平台规格和静态 flags,再调用 Hyundai _get_params(),最后计算车辆惯量与轮胎刚度。Hyundai _get_params() 负责分 CAN FD / 经典 CAN,由指纹与固件推导动态标志、纵向能力和 safety 配置。HyundaiFlags 记录车型 / 检测特征,HyundaiSafetyFlags 编码给 MCU 的控制策略,二者不可互换。

    组件 职责
    CarInterfaceBase.get_params() 先装载平台规格与静态 flags,再调用 Hyundai _get_params(),最后算惯量与轮胎刚度。
    CarInterface._get_params() 分 CAN FD / 经典 CAN,由指纹与固件推导动态标志、纵向能力与 safety 配置。
    CanBus.__init__() / CanBusBase.__init__() LKA / LFA 使 A-CAN / E-CAN 互换;安全配置数量决定四路总线偏移。
    CarInterface.init() / deinit() 条件性发 UDS 通信控制禁发 / 恢复请求。
    disable_ecu() 先进入扩展会话,再请求通信控制;返回布尔值,但上层 Hyundai 接口未消费。
    card.controls_update() 首次进入控制时调用接口 init(),其返回后写 ControlsReady。

    CAN FD:相机地址驱动的分支

    _get_params() 先由 CanBus(None, fingerprint).CAM 找相机总线;该总线上出现 0x50 或 0x110 时判为 LKA 转向,否则判为 LFA。随后 CanBus(None, fingerprint, lka_steering) 重新计算 A-CAN / E-CAN:LKA 平台与 LFA 平台交换两条总线。

    CanBusBase 在指纹阶段按非空 bus 最大编号推导四路偏移;运行阶段 CanBus(CP) 则按 safetyConfigs 数量算偏移。因此识别出的拓扑必须与实际 panda / 线束配置一致,不能由车型名称单独推断 E-CAN 号。CAN FD 的 0x1ba、0xFA、0x1cf、0x130、0x40 等地址参与 BSM、混动、备用按钮和档位报文推断。

    LKA 平台若相机有 0x110,再加 ALT LKA 标志;非 LKA 平台在没有 CANFD_RADAR_SCC 静态标志时,推导相机 SCC。CANFD_NO_RADAR_DISABLE 会关掉实验纵向可用性;LKA 平台若 car_fw 未列出 ADAS ECU,也会关掉。这些是保守能力判据,但空固件列表与「已证明车辆无 ADAS ECU」不能画等号。CAN FD 默认以 hyundaiCanfd 作为执行 safety;若推导出的 E-CAN 编号至少为 4,则在安全配置列表前插入一个 noOutput,由后一个配置承载 Hyundai safety 参数。

    经典 CAN、共同能力与雷达状态

    经典 CAN 在动力总线 0 / 相机总线 2 上按地址判断 BSM、LFA 图标、FCA 报文及 LDA 按钮。LEGACY 平台选 hyundaiLegacy safety,因为旧报文缺计数 / 校验;其他经典平台选 hyundai,相机 SCC 再追加相应 safety 参数。LEGACY 或 UNSUPPORTED_LONGITUDINAL 会使实验纵向不可用。

    两条路径共同设置 0.1 秒转向执行延迟、0.4 秒限制计时器、扭矩标定,以及 ALT_LIMITS / ALT_LIMITS_2 安全标志;后者还使车型 dashcamOnly。特定平台型号另改延迟或设 dashcamOnly。

    概念 边界说明
    openpilotLongitudinalControl 等于 alpha_long and alphaLongitudinalAvailable,pcmCruise 取其反值;只有真正启用时才给 MCU 设 LONG。
    radarUnavailable 仅在 CAN 指纹 bus 1 有雷达起始地址且平台 DBC 提供 Bus.radar 时为假;不是实时雷达在线或诊断静默结果。
    被动回退 card 可因开关关闭、无控制器或 dashcamOnly 把接口设为被动并把安全模式改为 noOutput。
    动力类型 EV / HEV / FCEV 映射为安全参数,但不改变上述能力判定层级。

    车型参数里的 LONG 不是最终上路授权:card 仍可能把接口设为被动并把安全模式改为 noOutput。

    ECU 静默与恢复的实际边界

    CarInterface.init() 仅在自控纵向且非相机 SCC 路径静默纵向 ECU:经典 CAN 及普通 CAN FD 目标为 0x7d0,LKA 转向 CAN FD 改为 E-CAN 的 0x730。默认通信控制请求带 UDS 0x28 的 suppress-positive-response 位;它表示请求 ECU 不发送正响应,不是已经收到确认。若 CANFD_ENABLE_BLINKERS 置位,接口另对 0x7b1 发独立请求;该 flag 在本快照的静态平台项中未见设置。

    通用 disable_ecu() 先要求扩展会话的有效前缀响应,随后以空期望前缀调用通信控制查询并丢弃查询结果;只要后者没有抛异常,就可记录成功。因此其返回真也不等于已验证 0x28 正响应或实际 ECU 停发。Hyundai init() 又没有返回 / 汇总这个布尔值;card 在它返回后直接写 ControlsReady。这是一条源码确认的状态回执缺口,不是对特定车辆雷达或 ADAS ECU 实际行为的断言。

    deinit() 构造启用收发请求并复用 init() 的条件与地址。在本快照的主运行链未检出 CI.deinit() 调用,所以不能说正常退出一定主动恢复 ECU。控制器每 100 帧的 tester-present 也仅在自控纵向、非 CAN FD 相机 SCC 时形成。禁用雷达可能使原车 AEB 不可用,必须逐硬件架构独立验证。

    面向新硬件与插件化的接口建议

    车型插件只应给出期望能力、指纹 / 固件证据、DBC 和拓扑;受信任 ECU 管理服务独占诊断写权限,并单独报告请求已发送、预期正响应或可观测行为、持续存活和恢复结果。不同车型的相机 SCC 与雷达 / ADAS ECU 静默对原车 AEB 的影响,不得合并为一个通用开关。非上车验证矩阵应覆盖 LKA / LFA、相机 / 雷达 SCC、双 panda 总线偏移、备用按钮 / 档位、缺失 ADAS 固件、CANFD_NO_RADAR_DISABLE、负 UDS 响应、超时、掉电恢复及 AEB / FCW。

    常见问题

    为什么不能用车型名直接推断 E-CAN 号?

    因为 E-CAN 号由拓扑推导:指纹阶段 CanBusBase 按非空 bus 最大编号算四路偏移,运行阶段 CanBus(CP) 按 safetyConfigs 数量算偏移;而 LKA 与 LFA 平台会交换 A-CAN / E-CAN。识别出的拓扑必须与实际 panda / 线束配置一致。

    车型参数里 LONG 为真就代表能上纵向控制吗?

    不代表。只有 openpilotLongitudinalControl(即 alpha_long and alphaLongitudinalAvailable)真正启用时才给 MCU 设 LONG;card 还可能因开关关闭、无控制器或 dashcamOnly 把接口设为被动并把安全模式改为 noOutput。

    接口静默雷达 ECU 成功了吗?

    无法从返回值判断。默认请求带 UDS 0x28 suppress-positive-response 位,只是让 ECU 不回正响应;disable_ecu() 丢弃查询结果,返回真也不等于已验证 0x28 正响应或 ECU 实际停发。Hyundai init() 更没有汇总该布尔值。

    退出时会自动恢复被静默的 ECU 吗?

    不能这样假设。deinit() 会构造恢复请求并复用 init() 的条件与地址,但在本快照的主运行链未检出 CI.deinit() 调用。恢复是否发生需按具体运行路径与硬件架构单独验证。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/HYUNDAI_INTERFACE_CAPABILITY_ECU_LIFECYCLE_DEEP_DIVE.md(取用了 CAN FD / 经典 CAN 判别、能力位推导、ECU 静默与恢复边界分析)。

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

    更新记录

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

  • Tesla 与 Rivian 不能共用一套插件:转角控制 vs 动态扭矩的两条链路

    结论:Tesla 与 Rivian 都属于电动车,但不能共用一套「电动车插件」:Tesla 是转角控制加选择性原车报文转发,Rivian 是动态扭矩加原车 ACC 状态取消,两者在总线拓扑、原车辅助系统与安全策略上均不同。两品牌的纵向参数读取都受 ALLOW_DEBUG 限制,车型/固件识别是执行前提;未识别、无法确认固件或未接线时保持被动。本文为源码静态分析,没有实车、线束、固件构建或 HIL 验证。

    Tesla:转角控制与选择性转发

    CarInterface._get_params() 选择 tesla 安全模式和转角控制,并通过指纹识别缺失 DAS_settings、新 3-bit DAS_steeringControlType 固件;Model X 或旧固件条件可标为 dashcamOnly。雷达支持需要特定 Continental 雷达、DBC 与物理接入 bus 1,不能只凭车型名推断。alpha_long 可写纵向安全参数,但固件实际纵向启用受 ALLOW_DEBUG 限制。

    CarState.update() 与 update_autopark_state() 从 party 与 autopilot-party 两条总线解析车速、踏板、EPS 手握级别/接管、巡航、Autopark、原车 AEB/LKAS,并保留 DAS_control 副本用于取消报文;状态机只在未巡航的上升沿锁存 Autopark。输出侧,CarController.update() 在 hands_on_level >= 3 时停用横向,每 2 帧按速度和车辆模型限制目标转角并定期发送允许转向报文;纵向启用时发送加速度,否则仅构造符合取消语义的 DAS_control。get_safety_CP() 以 Model Y 参数匹配固件转角限制。

    雷达接口读取 40 对 A/B 目标消息,要求索引一致,按状态删除旧目标并报告 CAN、遮挡及传感器故障;无物理雷达/未接线时退回基础接口。tesla_checksum() 实现发送帧校验,但编码器不是发送许可。

    层 关键入口 已核对行为
    安全 RX tesla_rx_hook() 从 EPAS、DI、ESP 采样转角、双车速、油门/制动、巡航与手动接管,识别原车 AEB、LKAS 与 Autopark
    安全 TX tesla_tx_hook() Autopark 锁存时拒绝所有候选;转向检查角度/速率/控制类型;原车 LKAS 运行时禁止主机转向
    纵向限制 tesla_tx_hook() 禁止主机 AEB 事件;原车 AEB 运行时拒绝纵向/取消帧;非纵向模式只允许无执行量的取消
    原车转发 tesla_fwd_hook() 按 Autopark、原车 LKAS、原车 AEB 选择性屏蔽相机/Autopilot 总线的转向与纵向报文
    初始化 tesla_init() 构造不同纵向白名单;先假定 Autopark/Summon 激活,直到状态消息澄清

    需要强调的是,tesla_fwd_hook() 的转发是根据状态选择性屏蔽,不能把「拦截原车控制帧」写成一条恒定规则。tesla_init() 启动先假定 Autopark/Summon 激活,是为降低上电时与原车系统争用而设的保守起点。

    Rivian:动态扭矩与原车 ACC 状态取消

    CarInterface._get_params() 选择 rivian 模式,以 0x321 消息是否存在区分 Gen2,并配置扭矩控制。本快照设 radarUnavailable=True;Gen2 自控纵向默认不开放,docs 可影响能力字段。即使软件选择纵向,固件白名单也仅在 ALLOW_DEBUG 接受参数。输入侧 CarState.update() 从 PT、相机、ADAS 三路解析车速、踏板、转向、巡航、AEB 和故障;Gen2 缺部分车门/安全带 CAN 信号,源码说明依赖原车 ACC 处理,不能等同软件已观测到这些状态。另外 cruiseState.speed 的注释说明当前是检测到的限速替代值,不是已核对的目标巡航速度。

    Rivian 会复制 ACM_lkaHbaCmd、Gen1 触摸方向盘帧,并保留每次 VDM_AdasSts 更新;后者可能一轮收到两帧,所以控制器逐帧重发以保留时序。输出侧,CarController.update() 根据速度插值得到扭矩上限,再按驾驶员扭矩限幅;Gen1 可发送触摸方向盘消息。自控纵向生成加速度帧;原车纵向取消时先发送数帧 available 再发 unavailable 状态,让 ACM 接受切换。

    层 关键入口 已核对行为
    参数 _get_params() 以 0x321 区分 Gen2;本快照设雷达不可用
    编码 riviancan.py create_lka_steering()、create_wheel_touch()、create_longitudinal()、create_adas_status() 复用原车字段并重算校验
    雷达 RadarInterface.update() 有目标解析器,但参数层总设不可用,通常走基础接口;只保留 LRR 或 SRR+LRR 模式有效目标
    安全 RX rivian_rx_hook() 从 ESP 与 VDM 双车速检查失配;CRC/质量位函数对特定地址独立验证
    安全 TX rivian_tx_hook() 转向扭矩设速度相关上限、驾驶员抵消及变化率;纵向加速度另设限值
    模式选择 rivian_init() 原车 ACC 与自控纵向使用不同 TX 白名单;纵向选择仅在 ALLOW_DEBUG 编译块读取

    对硬件与插件化重构的四条约束

    1. 车型/固件识别是执行前提。Tesla 的 steering-control 字段代际、Rivian Gen1/Gen2、雷达物理接入和发布/调试固件选择必须进入受审计的能力清单;不能把 alphaLongitudinalAvailable 直接映射为「允许发送」。
    2. 原车系统仲裁不能外包给普通插件。Tesla 的 Autopark、原车 LKAS/AEB 转发优先级和 Rivian 的 ACC 状态取消时序应与板级安全门、主机状态机共同验证,在不明状态下不放行控制。
    3. 传感器数据必须携带来源与有效性。Tesla/Rivian 的双车速、CAN 质量位和雷达模式不是普通数值字段;新硬件要保留总线、时间戳、计数器/校验及故障标志,防止旧值伪装成实时观测。
    4. 发布固件单独验收。这两个模式本身可在注册表中找到,但纵向参数读取受 ALLOW_DEBUG;不能用调试固件跑通的纵向测试宣称发布版可用。

    把两条链路放在一起看,可以发现共性:软件层「能构造帧」与硬件层「允许发送」始终是两件事,而原车辅助系统(Autopark、LKAS、AEB、ACC)的在场状态会随时改变这条边界。跨品牌流程可参考车型协议总览,板级执行可参考 panda 专题。

    常见问题

    Tesla 与 Rivian 都是电动车,为什么不能共用一套插件?

    因为两者控制类型不同:Tesla 采用转角控制并选择性转发原车报文,Rivian 采用动态扭矩并依赖原车 ACC 状态取消。它们的总线拓扑、原车辅助系统与安全策略都不同,源文档明确「不能共用一套电动车插件」,必须按平台分别声明能力。

    为什么代码里有纵向控制,实车却不一定能启用?

    因为两品牌的纵向参数读取都受 ALLOW_DEBUG 限制:Tesla 的 tesla_longitudinal 仅在 ALLOW_DEBUG 读取安全参数,Rivian 的纵向选择也只在 ALLOW_DEBUG 编译块读取。因此不能用调试固件跑通的纵向测试宣称发布版可用。

    Tesla 雷达支持能否只凭车型名判断?

    不能。源文档指出雷达支持需要特定 Continental 雷达、DBC 与物理接入 bus 1,不能只凭车型名推断;Rivian 侧参数层在本快照总设 radarUnavailable=True,即使代码有目标解析器,也不会据此宣称实车雷达链已启用。

    Rivian 的原车纵向取消为什么要先发 available 再发 unavailable?

    源文档说明这是为了让 ACM 接受切换:取消时先发送数帧 available 再发 unavailable 状态。这属于原车系统仲裁时序,应与板级安全门、主机状态机共同验证,而不是当作普通的功能开关。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TESLA_RIVIAN_DEEP_DIVE.md(取用了 Tesla/Rivian 的参数、输入、输出、安全 RX/TX、转发与初始化行为,以及四条插件化约束)。
    • 项目 02 —— openpilot-research/CAR_PROTOCOL_IMPLEMENTATIONS.md(仅用于确认两品牌目录与跨品牌安全主链路的总体位置)。

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

    更新记录

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

  • 界面告警、驻车更新与远程服务:不在控制闭环内的进程边界

    结论:界面告警、驻车更新、云端连接与设备诊断这些进程大多不在控制闭环内,但它们决定驾驶员能否看见告警、设备能否更新、以及远程能否观测。源文档给出的一条清晰边界是:ui 只做呈现、soundd 只把上游状态转成音频、updated 只在驻车条件下由 manager 启动、webrtcd 默认只监听 127.0.0.1:5001 且 HTTP handler 本身没有展示独立认证检查。因此「替换显示或音频硬件」「开放本地端口」都不只是 UI 问题,而是部署安全边界。

    一、谁在控制闭环之外

    源文档用一张关系图概括了这些服务的输入输出:selfdriveState / onroadEvents 同时喂给 ui + UIState 与 soundd;deviceState / pandaStates 喂给 ui 和 updated;athenad 的云端连接与 webrtcd 的本地流媒体相连;camerad / encoderd 向 webrtcd 供流;proclogd / journald / tombstoned 汇入日志与上传;GNSS 侧 qcomgpsd 或 pigeond + ubloxd 共同供 timed。

    这些进程的启停条件写在 process_config.py:updated 使用 only_offroad,webrtcd 使用直播或 notCar 条件,soundd 使用驾驶员视角条件。具体启停仍由 manager 动态判定,配置声明不等于恒久运行的保证。

    二、UI 与声音告警

    组件 关键入口 作用与失效边界
    渲染进程 ui.main() 选择大屏 MainLayout 或小屏 MiciMainLayout,每轮刷新 ui_state 并发布 uiDebug。
    UI 数据聚合 UIState.update() 非阻塞收取模型、车况、标定、设备与自驾状态;刷新设备插拔与屏幕管理。
    行车/驻车切换 _update_state()、_update_status() deviceState.started 与点火共同决定 UI 的 started;selfdriveState 决定已使能/覆盖显示。
    声音输入 Mic.callback()、Mic.update() 采集原始音频、估计近似 A 加权声压并发布 rawAudioData;主循环发布 soundPressure。
    声音输出 Soundd.soundd_thread() 按 selfdriveState.alertSound 播放告警,关键警报逐步升音量。
    失联警报 check_selfdrive_timeout_alert() 此前处于使能状态而 selfdriveState 短时失联时发出立即警告。

    三层职责必须分清:ui 是呈现层,不是使能状态的裁决者;soundd 不负责决定警告事件,只把上游状态转成音频;micd 发布的是未校准的近似声压,用于自适应音量,不应当作精密声级计。替换显示或音频硬件时,应保留视觉/声音告警时延、失联提醒和看门狗行为,不能因插件崩溃而静默隐藏关键警告。

    三、驻车更新路径

    入口 源码行为
    updated.main() DisableUpdates 时退出;加排他锁,建立 staging overlay,循环检查/拉取,失败记录并退避。
    WaitTimeHelper SIGHUP 触发立即拉取,SIGUSR1 只做检查;定时等待避免高频网络请求。
    Updater.check_for_update() 查询远端分支,比较目标分支与当前提交;目标分支还受设备类型映射影响。
    Updater.fetch_update() 将 staging overlay 标成不一致,fetch 目标分支并更新子模块,必要时更新 AGNOS 后 finalize。
    set_consistent_flag() 用 .overlay_consistent 表示已完成可切换镜像;拉取前清除,完成后由 finalize 路径设置。

    需要特别记住:更新代码里的 checkout --force、reset --hard、clean -xdff 是应用到 staging overlay 的安装逻辑,不应拿来操作本地研究目录。updated 只在驻车条件下由 manager 启动;按流量计费的网络会推迟普通拉取,但长时间未更新或用户请求仍可进入拉取路径。安全相关插件包应同样采用可回退、版本一致、签名与发布管控的更新路径,不能在行车中热替换控制算法。

    四、云端连接与 WebRTC

    athenad.main() 用设备 token 建立 WebSocket,失败后指数退避重连,守护进程跨 manager 重启;jsonrpc_handler() 从接收队列取请求,按 dispatcher 执行 RPC。startStream() 在驻车时设置 IsLiveStreaming 唤醒相关进程,再向本地 webrtcd 提交 SDP,默认回传车况和设备状态。

    webrtcd 默认绑定 127.0.0.1:5001,以 HTTP 提供 stream/schema/notify;handle_get_stream() 中新会话替换旧会话,已有连接时预热请求返回 busy。StreamSession.message_handler() 只接受本会话声明的传入服务类型,再转成 cereal 主题;notCar 直播路径可声明 testJoystick。

    这里必须写明风险边界:webrtcd 默认仅监听本机,但 HTTP handler 本身没有展示独立的认证检查,而 /stream 的请求体含可声明的传入桥接服务。因此本地访问权限、端口暴露和上游 RPC 授权是部署边界,不能把此类桥接列表视为通用车辆控制插件 API;车载产品必须限制网络暴露与可桥接主题。此处是基于静态路径的风险提示,并非端到端安全审计结论。

    五、时间、GNSS、调制解调器与诊断

    timed 发布 clocks,仅在 GNSS 消息新鲜、有定位且时间落入合理范围时调整系统时间,启动阶段无 GNSS 的行为需另行核对。pigeond 负责 u-blox 串口设备的供电、初始化与原始报文接收,硬件 GPIO 和波特率强耦合;ubloxd 做 UBX 帧同步与校验和解析;qcomgpsd 是另一条 GNSS/蜂窝模组诊断路径,与 ubloxd 的选择由设备节点决定。

    modem 管理蜂窝数据连接及状态机,路由、DNS 和失败重试需逐状态分析。proclogd 收集 CPU/内存/进程统计并发布 procLog;journald 将系统 journal 的 JSON 行映射为 operatingSystemLog;tombstoned 收集崩溃报告/栈信息,需另核对隐私与上传边界。这些是当前源码级功能入口,不代表已完成 GNSS 协议、蜂窝状态机或系统日志隐私审计。

    常见问题

    UI 或声音进程崩溃会静默隐藏关键告警吗?

    这正是需要防护的场景。源文档强调 ui 只是呈现层、不是使能裁决者,soundd 只把上游状态转成音频;替换显示或音频硬件时应保留视觉/声音告警时延、失联提醒和看门狗行为,不能因插件崩溃而静默隐藏关键警告。

    更新代码里的 reset –hard 会不会动到本地研究目录?

    不会,前提是它们只作用于 staging overlay。源文档特别说明 checkout --force、reset --hard、clean -xdff 是应用到 staging overlay 的安装逻辑,不应拿来操作本地研究目录;updated 也只在驻车条件下由 manager 启动。

    webrtcd 的桥接列表是通用车辆控制 API 吗?

    不是。StreamSession.message_handler() 只接受本会话声明的传入服务类型,但 HTTP handler 本身没有展示独立认证检查,本地端口暴露与上游 RPC 授权才是部署边界。车载产品必须限制网络暴露与可桥接主题,确保远程消息无法直达 sendcan 或修改 panda 安全模式。

    没有 GNSS 时 timed 会怎样调整系统时间?

    源文档只说明 timed 仅在 GNSS 消息新鲜、有定位且时间落入合理范围时才调整系统时间,并明确「需核对启动阶段无 GNSS 的行为」。因此无 GNSS 时的具体回退行为尚未在源文档给出结论,不能想当然地推断。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/OPERATIONS_SERVICES_DEEP_DIVE.md(取用了服务边界、UI 与声音告警、驻车更新、云端连接与 WebRTC、GNSS 与诊断的服务清单)。

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

    更新记录

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

  • 二维码编码只到版本 20、纠错固定 L:openpilot 编解码两端并不对称

    结论:openpilot 的二维码模块编码端与解码端能力并不对称:编码只覆盖版本 1 到 20、只构造 byte mode,并固定纠错等级 L 与 mask 0;解码端则支持版本 1 到 40 与四个纠错等级,还会尝试相邻版本、alignment 与转置等候选。更重要的产品边界是:二维码只是载体不是授权——配对令牌取不到时会退化成空令牌 URL,而这个 URL 仍能成功编码成图形,所以「屏幕上有二维码」不证明令牌有效;eSIM 扫码的前置检查也只校验字符串形状。以下为固定源码快照 c8fb906 的静态分析与隔离测试结论。

    编码与屏幕呈现

    make_texture() 把输入字符串编码为 UTF-8,按 _capacity() 从版本 1 到 20 选择首个可容纳的码型,超过上限抛 ValueError。_data_codewords() 只构造 QR 的 byte mode,写入 8/16 位长度、终止位与交替填充字节;_codewords() 按 _EC 表分块、计算 GF(256) Reed-Solomon 余数并交织。当前编码固定纠错等级 L 和 mask 0,并不搜索视觉质量最佳的 mask,编码版本范围也比解码范围窄。

    _Qr 绘制 finder/timing/alignment、格式位与版本位,再以两列之字形填入数据。普通模式外加 4 模块 quiet zone,反色模式不加边框;两者都把每个模块放大为 10×10 RGBA 像素后通过 raylib 上传纹理。make_texture() 的注释认为上传会复制像素,因此中间 NumPy 数组无需保持存活,但实际 GPU 驱动资源生命周期仍由 UI 调用方负责。

    项 编码端 解码端
    版本范围 1 到 20 1 到 40
    纠错等级 固定 L 四个等级均支持
    字符模式 仅 byte mode(UTF-8) 数字、字母数字、byte、Kanji 与受支持 ECI
    mask 固定 mask 0,不搜索 从格式位恢复级别/mask

    配对 QR 的展示边界

    大屏 PairingDialog._generate_qr_code() 先释放旧纹理,再把 DongleId 与配对 JWT 拼入 URL 制作新纹理,每 300 秒刷新,失败时记录日志并清空纹理。二维码中可能有配对令牌,视觉截图、投屏与诊断日志应视为敏感路径;生成纹理只负责表示 URL,不能证明 URL 仍有效或账户已授权。

    mici 的 QR 控件用反色纹理,析构时卸载;__init__() 立即生成纹理,失败时 _texture=None,_render() 改画错误标签。显示缩放只按矩形高度计算,不按宽度取较小比例:若插件给非方形且宽度更窄的矩形,二维码可能横向溢出(当前控件默认建为正方形,配对页还会把它设为屏幕高度的正方形,因此这是组件复用边界而非已实测故障)。

    未配对时 PrimeScroller.__init__() 先为 QR 调用一次 _get_pairing_url() 并生成纹理,但 _last_pairing_qr_generation 初始化为负无穷,首次 _render() 会再次请求令牌、释放初始纹理并生成新纹理,之后按 300 秒周期刷新;这意味着首次展示有两份请求/纹理工作,并可能在两个请求之间取到不同令牌。刷新的时间戳即使 _generate_qr_code() 失败也会更新,错误标签将持续到下一次刷新或重建。若令牌 API 抛异常,_get_pairing_url() 返回 https://connect.comma.ai/?pair=;该字符串仍可成功编码为二维码,故有 QR 图形并不证明配对令牌有效。

    图像到文本的解码链

    顶层 decode() 先定位 finder,再尝试估计版本及相邻两个版本、是否使用 alignment、矩阵是否转置;候选均失败返回 None。它仅捕获 QRError,并非对任意形状、任意 dtype 或超大数组都提供「失败返回 None」的保证。二值图与游程/透视运算会分配与输入像素数相关的内存,外部插件传入图像前必须限制形状、大小与处理预算。

    阶段 关键函数与限制
    灰度二值化 _binarize() 分块取样、算局部对比度并参考邻块/全局阈值;边长小于 21 像素抛 QRError,假定二维灰度数组。
    finder 定位 _find_patterns() 以 1:1:3:1:1 横向候选、纵向复查去重,至少约 2 像素/模块;_pick_finders() 从前十个候选中选几何最接近直角等边的三点。
    透视采样 _perspective() 解单应矩阵;_sample() 可用 alignment pattern 修正右下角,采样越出图像抛 QRError。
    矩阵恢复 decode_matrix() 只接受 21..177 且边长满足 4v+17 的方阵;从两份格式位选汉明距离不超过 3 的级别/mask,再反掩码、去交织。
    纠错与文本 _rs_correct() 做 syndrome、Berlekamp-Massey、错误定位/幅值求解并复验;无 ECI 的 byte 段先试 UTF-8,失败退回 Latin-1。

    eSIM 扫码调用与信任边界

    QRScannerDialog 使用座舱 CabinCameraView:显示时打开驾驶员视角并关闭红外,隐藏时反向恢复;它最多约每 0.25 秒启动一次扫描,上一扫描线程仍活跃时不再启动新线程。取帧时只复制 NV12 的 Y 平面有效宽度,避免 VisionIPC 缓冲回收后后台线程读到复用数据,随后在后台调用 qrcode.decode(gray)。扫码 UI 的轮询与相机/线程生命周期不等于二维码算法内置超时。

    识别到文本后,UI 先用 parse_lpa_activation_code() 检查 LPA: 前缀与三个非空 $ 分段,失败展示短时错误;通过则让用户输入昵称,再交给蜂窝管理器下载 profile。这个前置检查只是字符串形状校验,不认证 SM-DP+ 服务器、激活码归属或 eUICC 安装授权。插件化时应把「二维码像素识别」「业务数据格式校验」「服务端授权」分为三层:配对展示插件只接收短时令牌的受控呈现能力,扫码插件只输出有长度上限的候选字符串,不直接触发下载或持有 eUICC/网络权限。

    已有测试与待验证项

    test_qrcode.py 使用独立生成、打包的 .npz 矩阵 fixture,覆盖 1 到 40 版本、四种纠错级别、多种文本模式/ECI、噪声、旋转、镜像、透视、边缘贴码与无二维码图像,还检查自有编码器版本 1 到 20 的矩阵往返。通过隔离脚本执行原测试文件的 13 个测试方法全部通过,其中版本/纠错等级循环含 160 组矩阵与渲染图检查,纹理编码往返含 20 个版本;隔离器仅提供 pyray.Texture 的导入期类型桩。

    但该测试不覆盖 make_texture() 的 raylib 上传,也不验证真实车载相机曝光/畸变、GPU 纹理生命周期、恶意超大图像或 eSIM 服务端授权。新显示硬件需在重复开闭、令牌失败、图形上下文销毁/重建与非方形布局下检查 GPU 纹理数量与 QR 可扫性。

    常见问题

    屏幕上有配对二维码就说明令牌有效吗?

    不能。令牌 API 抛异常时 _get_pairing_url() 返回 https://connect.comma.ai/?pair=,这个空令牌 URL 仍可成功编码成二维码。生成纹理只负责表示 URL,不证明令牌有效或账户已授权。

    为什么首次进入配对页会出现两次令牌请求?

    因为 PrimeScroller.__init__() 已为 QR 调用过一次 _get_pairing_url() 并生成纹理,而 _last_pairing_qr_generation 初始化为负无穷,首次 _render() 会再次请求令牌、释放初始纹理并生成新纹理;两个请求之间可能取到不同令牌。

    扫码识别到 LPA 激活码就等于能装成功吗?

    不等于。parse_lpa_activation_code() 只检查 LPA: 前缀与三个非空 $ 分段,是字符串形状校验,不认证 SM-DP+ 服务器、激活码归属或 eUICC 安装授权。

    编码端和解码端的版本范围一样吗?

    不一样。编码端只覆盖版本 1 到 20、固定纠错等级 L 与 mask 0;解码端支持版本 1 到 40 与四个纠错等级,并会尝试相邻版本、alignment 与转置等候选。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/QR_CODE_PAIRING_ESIM_PIPELINE_DEEP_DIVE.md(编码/解码两端能力、配对 QR 展示边界与 eSIM 扫码信任边界)。
    • 项目 02 —— openpilot-research/ 同目录探针记录:原二维码测试文件的 13 个隔离测试方法与 160 组矩阵检查结论。

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

    更新记录

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

  • Hyundai 与 Honda 车型协议:为什么品牌目录不等于通用协议

    结论:品牌目录不是通用协议。Hyundai 的经典 CAN 可选 hyundai/hyundaiLegacy、CAN FD 用 hyundaiCanfd;Honda 分 Nidec 与 Bosch,Bosch 还要再分雷达版/无雷达版及 CAN FD。它们是不同的能力组合,必须声明精确平台、固件范围与总线拓扑。CarController 的软件限幅不替代 panda 安全钩子,CarState 的解析结果也不替代固件对驾驶员扭矩、制动和巡航状态的独立采样。

    Hyundai:经典 CAN 与 CAN FD

    Hyundai 按 CANFD 分支:用相机和 E-CAN 指纹判别 LKA/LFA、按钮、档位及 SCC 来源,LONG、能源类型和限制参数写入 safetyParam。这里要记住:alphaLongitudinalAvailable 与 openpilotLongitudinalControl 分开,前者不能保证后者启用。

    层 关键入口 已核对的实现
    ECU 生命周期 init()、deinit() 自控纵向且非相机 SCC 时发送 UDS 通信控制,目标地址依平台/转向架构改变;退出尝试恢复。
    输入 CarState.update()、update_canfd() 分别把经典 CAN/CAN FD 的速度、驾驶员输入、巡航、转向、车门等信号映射到统一 CarState。
    输出调度 CarController.update() 经典 CAN 与 CAN FD 分流至 create_can_msgs()/create_canfd_msgs()。
    构帧 hyundaican.py、hyundaicanfd.py 经典 CAN 的 LKAS11/CLU11/SCC 帧与 CAN FD 的转向、取消、ACC、LFA 抑制帧分别构造;CAN FD 的 CanBus 映射 E-CAN、A-CAN、相机总线,不能硬编码单一 bus。
    固件接收/发送 hyundai_rx_hook()、hyundai_tx_hook()、hyundai_canfd_tx_hook() 两条分支检查转向扭矩/请求间隔、加速度及按钮权限,诊断地址仅准许指定 tester-present 载荷。
    模式组装 hyundai_init()、hyundai_legacy_init()、hyundai_canfd_init() Legacy 因旧报文缺计数器/校验而采用不同接收检查,不能视作安全规则全等。

    Honda:Nidec 与 Bosch

    Honda 的 Bosch 用 hondaBosch、Nidec 用 hondaNidec;Bosch 继续区分雷达版、无雷达版及 CAN FD。Bosch CAN FD 在此快照中不开放实验纵向。

    层 关键入口 已核对的实现
    ECU 生命周期 init()、deinit() 有雷达的 Bosch 自控纵向会尝试禁用雷达 ECU 通信,退出时尝试恢复。源码明确警告此操作会禁用原车 AEB。
    输出调度 CarController.update()、compute_gas_brake() 区分 Bosch/Nidec;actuator_hysteresis() 与 brake_pump_hysteresis() 处理 Nidec 制动输出。
    构帧 hondacan.py 按平台总线构帧;CanBus 映射 PT、radar、camera 等总线。
    固件接收 honda_rx_hook() 备用刹车报文 0x1BE 避免把某些车型电脑制动误判为用户踩刹车;Nidec 的原车 AEB 制动与 openpilot 制动比较后决定转发。
    模式与转发 honda_nidec_init()、honda_bosch_init()、honda_nidec_fwd_hook() Nidec 对相机制动帧 0x1FA 进行动态转发;Bosch 实验纵向在固件中受 ALLOW_DEBUG 编译门限制,不能只看 Python 配置推断可用。

    另外两处细节也影响拆分:Hyundai 侧,经典 CAN 可选 hyundai/hyundaiLegacy,CAN FD 转向灯能力还可能涉及额外 ECU;Legacy 因旧报文缺计数器/校验而采用不同接收检查,测试入口覆盖相机 SCC、Legacy、能源类型、纵向、AEB 位、按钮和诊断场景,但不等于覆盖所有实车变种。

    Honda 侧,honda_tx_hook() 对 Nidec 检查制动/HUD,对 Bosch 检查加速度/油门,均阻止主机发送 AEB 执行位;无使能时转向只能为零,Bosch 按钮只能取消,诊断地址仅准许指定 tester-present。备用刹车报文 0x1BE 用于避免把某些车型电脑制动误判为用户踩刹车。

    Hyundai 的固件发送侧还要区分纵向状态:hyundai_canfd_tx_hook() 在非自控纵向时仅允许符合取消语义的 ACC 帧,经典 CAN 则另检查 FCA11/SCC12 的 AEB 执行位。Honda 侧的转发也不对称:honda_nidec_fwd_hook() 对相机制动帧 0x1FA 动态转发,而 Bosch 实验纵向在固件中受 ALLOW_DEBUG 编译门限制,不能只看 Python 配置推断可用。

    为什么必须按能力组合拆分

    同一品牌下,CAN/CAN FD 与 Nidec/Bosch 是不同能力组合,不能互相套用。Honda 的原车 AEB 保留与否依硬件架构和具体纵向方案而变:Nidec 用相机制动帧的动态转发,而 Bosch 雷达禁用方案是另一条路径,不能把 Nidec 转发逻辑套到 Bosch 雷达禁用方案。Hyundai 侧,hyundai_canfd_tx_hook() 中的转向限值还包含驾驶员扭矩抵消、速率与 EPS 转向请求切断容差;这些是固件拒绝候选帧的条件,不应在插件层开放为任意可改配置。

    换句话说,能力组合必须由「平台 + 固件范围 + 总线拓扑 + 传感器架构」共同定义,任何一项缺失都可能把另一套组合的结论错误套用过来;这也解释了为何同品牌不同年款需要各自的证据链。

    车载硬件与插件化含义

    1. 车型插件应声明精确平台、固件范围、总线拓扑、DBC、能力与安全模式参数;同一品牌的 CAN/CAN FD 或 Nidec/Bosch 必须是不同能力组合。不能让插件动态改写 panda 固件的 TX 白名单和物理限制。
    2. ECU 通信控制必须是受控生命周期操作,记录请求、响应、超时及恢复结果。尤其 Honda Bosch 雷达禁用涉及原车 AEB;目标硬件设计应先明确是否允许此工作模式,而不是默认启用。
    3. 硬件网关需要保持总线来源与时间戳、独立看门狗、断电/主机失联回退、板上 RX/TX/转发安全门;E-CAN/PT/camera 总线映射错误是安全风险,不是普通配置瑕疵。
    4. 每个目标车型建立指纹和固件识别、DBC 解析/打包、安全测试、日志回放、断线与错误帧注入、SIL/HIL、封闭场地验证的证据链。未知或模糊匹配保持被动,不以品牌名放行执行。

    常见问题

    为什么同一品牌要按经典 CAN 与 CAN FD 分开?

    因为两者有不同的输入映射、构帧函数与安全模式:Hyundai 的经典 CAN 可选 hyundai/hyundaiLegacy、CAN FD 用 hyundaiCanfd;Legacy 还因旧报文缺计数器/校验而采用不同接收检查。它们是不同能力组合,不能视作同一套安全规则。

    Honda 禁用雷达 ECU 通信有什么代价?

    源码明确警告:有雷达的 Bosch 自控纵向禁用雷达 ECU 通信的操作会禁用原车 AEB。因此不能把它当作无副作用的模式切换,目标硬件设计应先明确是否允许此工作模式。

    Bosch 的实验纵向能在固件里开放吗?

    在固件中受 ALLOW_DEBUG 编译门限制,不能只看 Python 配置推断可用;此外 Bosch CAN FD 在此快照中不开放实验纵向。

    Nidec 的相机制动帧转发能套用到 Bosch 吗?

    不能。Honda 的原车 AEB 保留与否依硬件架构和具体纵向方案而变;Nidec 对相机制动帧 0x1FA 的动态转发与 Bosch 雷达禁用方案是不同路径,不能互相套用。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/HYUNDAI_HONDA_DEEP_DIVE.md(取用了 Hyundai 经典/CAN FD 链路、Honda Nidec/Bosch 链路,以及车载硬件与插件化含义)。
    • 项目 02 —— openpilot-research/HYUNDAI_CANFD_PANDA_SAFETY_DEEP_DIVE.md(仅用于确认 CAN FD 逐模式 RX/TX 总线矩阵与纵向构建门的引用位置)。

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

    更新记录

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

  • pandad 的 100 Hz 主循环:connected 不等于链路健康

    结论:原生 pandad 用 100 Hz 主节拍派发约 20/10/2 Hz 的低频任务,但它暴露的「存活证据」必须分层看:进程存活、句柄 connected、CAN 更新、控制读成功、MCU 安全模式读回是五个不同层级。PandaSpiHandle::connected 在构造时为真,而在此 SPI 实现中未见出错后置假,因此 check_connected() 不是可靠的链路探针;comms_healthy 则在 bulk 负返回时锁存为假,即使随后成功也不自动恢复。本文为对本地 source/ 快照 c8fb906 的静态追踪,未启动进程、未接入实板、未发送车辆控制帧。

    构建、进程所有权与运行开关

    SConscript 在非 Darwin 平台构建原生程序;但 SCons 构建出二进制不等于 manager 直接管理它——进程表把原生 _pandad 设为 enabled=False,常驻的是 Python pandad 包装器。包装器通过签名门后 Popen(["./pandad"]),等待其退出再恢复,原生进程的退出码不作为包装器的成功判据。macOS 不构建此目录的原生程序,但进程表仍定义 Python 包装器,两者是独立的构建/运行条件。

    开关 源码入口 对运行态的影响
    BOARDD_LOOPBACK connect() 只要变量存在就请求 panda CAN 回环,不是只读诊断
    BOARDD_SKIP_FW_CHECK connect() 跳过原生 up_to_date() 签名门;不跳过外层 Python 包装器的刷写/最终签名检查
    FAKESEND pandad_run() 阻止发送线程调 Panda::can_send(),但主循环仍可下发安全模式、心跳、风扇与 IR 命令
    STARTED send_panda_states() 在本地 health 副本中强制点火线位,影响点火/省电/NO_OUTPUT 分支与发布值,不改变真实引脚
    NO_FAN_CONTROL process_peripheral_state() 跳过风扇目标下发,仍处理 IR 与其他设备命令
    PANDAD_MAXOUT can_receive() 收 CAN 后追加一次向 0xab 端点的丢弃缓冲读取,返回值不单独作为健康判据
    SPI_ERR_PROB lltransfer() 正值时随机改 SPI 长度、TX 或 RX 数据,属实传输注错开关,不可在行车部署

    这些开关不是可分配给普通插件的能力:FAKESEND 只约束候选 CAN 发送,STARTED 与回环/注错会改变网关控制流或真实总线行为。设备配置应由受信任启动器使用固定清单生成,并在上车前核对实际进程环境。

    调度和观测语义

    主循环先收 CAN,然后每 5 帧处理风扇/IR,每 10 帧更新 selfdriveState、deviceState 并处理 panda 健康/心跳/安全模式,每 50 帧发布外设状态;每轮还持续消费 MCU 串口日志。RateKeeper 名义基频是 100 Hz,首次 frame=0 即运行低频分支。

    节拍 帧模数 任务
    100 Hz 每帧 先接收 CAN 并发布,逐轮读取串口日志;RateKeeper 调整帧计数与睡眠
    约 20 Hz 每 5 帧 处理风扇目标与 cabinCameraState IR 目标
    约 10 Hz 每 10 帧 更新 selfdriveState、deviceState,处理 MCU/CAN 健康、心跳与 PandaSafety 配置
    约 2 Hz 每 50 帧 发布 peripheralState(电压、电流、风扇)

    若 SPI 交易、串口读取或消息处理超期,实际频率由运行时决定,RateKeeper 不能保证硬实时或补发错过的状态帧。engaged 每个 10 Hz 周期由 selfdriveState 的 alive/valid 与 enabled 联合计算;is_onroad 仅在 deviceState 本次 updated 时更新,否则沿用上次布尔值。因此「设备消息未更新」与「确认仍在行车」不能等同。

    send_panda_states() 对 health 或任一路 CAN health 返回空值时直接跳过发布,process_panda_state() 随之返回,本轮不发心跳,也不执行它的通信故障退出门;主循环随后仍调用 configureSafetyMode(is_onroad)。若读到了结构体但 comms_healthy=false,事件标无效;只在 ignition_local=false 时设置 do_exit,点火真时继续运行并依赖其他安全层。

    失联、退出与故障路径

    PandaSpiHandle::connected 构造时为真,在此 SPI 实现中未见出错后置假,因此 check_connected() 不是可靠的链路探针——进程存活、句柄 connected、CAN 更新、控制读成功、MCU 安全模式读回是不同层级的证据。默认 SPI 交易重试还可能没有总时限。退出主循环后,只有 is_onroad && !engaged && connected 才尝试发 NO_OUTPUT,且控制写结果没有回执;接着 join() 发送线程——若线程正阻塞于无总截止期的 SPI 交易,退出也可能被推迟。

    Python 包装器会在原生进程退出后进入自己的恢复循环,但不能代替 MCU 固件的独立心跳/安全钩子。固件过期异常在 connect() 的构造 try 之外抛出,原生内部 100 ms 重试只覆盖构造失败,外层包装器才处理进程退场。

    自制硬件和插件验收

    应把网关状态拆成 process_alive、transport_available、can_rx_fresh、health_snapshot_complete、mcu_safety_mode_confirmed 和 heartbeat_age,每项带设备 UID、采样时间、错误原因与独立阈值。插件只能订阅这些观测或提交受约束候选帧,不能自行改变心跳/安全模式;退出和断电路径应由 MCU 侧默认安全态兜底,而非依赖主机退出时的一次控制写。

    相邻的 test_pandad_spi.py 在实板注入小概率 SPI 错误,检查 CAN、pandaStates、peripheralState 的数据与平均/最大间隔;它没有覆盖永久 SPI 失败、health 只有短读、点火真假两种退出分支或 join() 被无限交易阻塞。本轮未运行该硬件测试,新平台应先在无车台架做这些故障注入。

    常见问题

    check_connected() 能证明 SPI 链路正常吗?

    不能。PandaSpiHandle::connected 在构造时为真,而在此 SPI 实现中未见出错后置假,所以 check_connected() 不是可靠的链路探针;进程存活、句柄状态、CAN 更新、控制读成功与 MCU 安全模式读回是不同层级的证据。

    comms_healthy 变假后会自动恢复吗?

    不会。comms_healthy 只在 bulk 返回负值时被置假,本句柄内未见恢复为真,即使随后调用成功也不自动恢复;控制读写本身也不更新该位。

    health 读失败时会发心跳吗?

    不会。send_panda_states() 对 health 或任一路 CAN health 返回空值时直接跳过发布,process_panda_state() 随之返回,本轮不发心跳,也不执行它的通信故障退出门。

    pandad 退出时一定会发 NO_OUTPUT 吗?

    不一定。退出主循环后只有 is_onroad && !engaged && connected 同时成立才尝试发 NO_OUTPUT,且控制写结果没有回执;join() 若阻塞在无总截止期的 SPI 交易,退出也可能被推迟。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PANDAD_NATIVE_MAIN_LOOP_LIVENESS_TELEMETRY_DEEP_DIVE.md(原生 pandad 入口、主/发送线程、调度分频与失联降级)
    • 项目 02 —— openpilot-research/PANDAD_NATIVE_SPI_RESPONSE_BOUNDS_RETRY_CONTRACT_DEEP_DIVE.md(源文档指向的原生 SPI 响应边界与重试专题)
    • 项目 02 —— openpilot-research/PROCESS_SUPERVISION_DEEP_DIVE.md(源文档指向的进程监督与设备恢复包装器专题)

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

    更新记录

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

  • Ford panda 安全模式:双车速曲率门与 CAN FD 纵向的编译门

    结论:Ford safety 的核心不是「有没有白名单」,而是双车速源、质量位、曲率/jerk 门与构建变体共同构成的判据。ABS 与 PCM 第二车速名义 50 Hz、yaw 100 Hz,曲率由 yaw 除以经下界保护的车速得到;CAN FD 的自控纵向 TX 白名单只在 ALLOW_DEBUG 且纵向参数位置位时开放。因此官方测试套件默认带 ALLOW_DEBUG,不能用来证明 release 固件开放该能力。

    函数与责任

    函数 输入、状态或约束
    ford_get_counter()、ford_get_checksum()、ford_compute_checksum() 从 ABS 车速与 yaw 帧提取/计算计数和校验;其它 RX 消息靠检查表选择忽略。
    ford_get_quality_flag_valid() ABS、PCM 第二车速和 yaw 的指定质量位必须为 3;其余 RX 帧在配置表中显式忽略质量位。
    ford_rx_hook() ABS 与 PCM 双车速进入共享状态;yaw 除以经过下界保护的车速后形成实测曲率;另更新停车、油门、刹车和原车巡航状态。
    ford_tx_hook() 检查 ACCDATA、方向盘按钮、Lane Assist 和两种 LateralMotionControl;返回 false 拒绝候选帧。
    ford_init() 选择 6 条 RX 健康规则、经典 CAN/CAN FD TX 白名单及调试编译门;没有 Ford 专属转发钩子。

    RX 数据与新鲜度

    总线 0 上,ABS 车速和 PCM 第二车速名义 50 Hz、yaw 100 Hz、刹车/巡航 10 Hz、油门 100 Hz、停车状态 50 Hz。ABS 帧检查校验、4 位计数和质量位;yaw 帧检查校验、8 位计数和质量位;PCM 第二车速帧忽略计数器和校验和,但仍检查质量位,源码注释将此与某些 ECU 的跳计数/短暂校验异常联系起来。其余三类无计数/校验帧在配置中也忽略质量位。

    共享 rx_msg_safety_check() 先更新检查状态;只有当前帧有效且匹配 RX 检查表时,safety_rx_hook() 才调用 ford_rx_hook()。坏校验/质量帧不能按这条调用链更新 Ford 车况,而且有效性失败可撤销 controls_allowed。共享曲率检查中的 speed_mismatch_check() 再比较双速度源;自制网关若合并来源、丢失质量位或用单一速度伪造两路,将破坏这层判据。一个关键细节:yaw 可能先于新车速到达,此时曲率使用的是已保存车速,不能声称两个采样硬件同步。

    ford_init() 选择 6 条 RX 健康规则;FORD_COMMON_TX_MSGS 对两个按钮总线设 check_relay=false,其余列出的发送帧设为 true;共享 safety_fwd_hook() 依据当前 TX 表静态阻断从相机总线向主总线转发的受控地址,因此经典 CAN 与 CAN FD 的阻断集合也随模式变化。这些白名单都含 Steering_Data_FD1 的 0/2 两路和 ACCDATA_3、Lane_Assist_Data1、IPMA_Data 的 0 路;Ford 函数单独调用不能代表完整安全判定。

    TX 模式矩阵与位级门

    选择 ACCDATA 横向报文 依据
    经典 CAN,任意纵向参数位 白名单包含 LateralMotionControl FORD_LONG_TX_MSGS;并非参数位才开放。
    CAN FD,默认或 release 固件 白名单不含 LateralMotionControl2 FORD_CANFD_STOCK_TX_MSGS。
    CAN FD,ALLOW_DEBUG 且纵向参数位置位 白名单包含 LateralMotionControl2 FORD_CANFD_LONG_TX_MSGS 与 ford_init() 的编译门。

    ford_tx_hook() 对 ACCDATA 同时检查请求油门、预测油门、制动加速度及制动预充/减速位;CmbbDeny_B_Actl 为 1 一律拒绝,以免禁止原车 AEB。get_longitudinal_allowed() 还要求使能且驾驶员未踩油门;禁用时仅允许各字段的中性编码。cancel 只在前次原车巡航已接合时放行,resume 只在 controls_allowed 时放行;Lane Assist 的执行动作必须为零。

    两个横向格式分别解析曲率、曲率率、路径角/偏移,后面三项必须保持源码规定的 inactive 原始编码。steer_curvature_cmd_checks() 再检查绝对曲率、随车速变化的横向加速度/jerk、与实测曲率的偏差及非使能中性值;它会维护 desired_last,拒帧时清零,因而更换插件或安全模式时不能把拒帧当作完全无状态事件。另外,ACCDATA_3 与 IPMA_Data 在 Ford TX 钩子中没有专属载荷检查,不能把白名单解释成逐信号验证。

    证据、缺口与插件边界

    test_ford.py 定义经典 CAN 纵向、CAN FD 原车纵向和 CAN FD 自控纵向三类具体测试配置。2026-10-04 在本地用原版 CANPacker、CFFI 和编译的 libsafety 运行 python -m unittest opendbc.safety.tests.test_ford -q:102 项中 91 项通过、11 项跳过。跳过的是三种具体配置均无 _user_regen_msg 的九个通用再生制动用例,以及两个抽象基类的 setUpClass,不是 Ford 曲率或纵向边界失败。

    但要注意:测试库的 _build_libsafety() 默认加 ALLOW_DEBUG,所以这次官方套件不能证明 release 固件开放 CAN FD 自控纵向 TX 白名单。七项帧级离线探针分别编译不带/带 ALLOW_DEBUG 的原 libsafety,确认:界内曲率帧通过;坏 ABS 质量位不更新车速并撤销 controls_allowed;两路合法但相差 5 m/s 的车速在曲率 TX 时触发失配退出。面向「万物即插件」,Ford 车型插件应声明经典/CAN FD、总线拓扑、DBC、纵向能力、固件构建变体和 safetyParam,并把两路车速及 yaw 的来源、质量位、时间戳分别传到受信任安全层。

    常见问题

    为什么 yaw 先到会用到旧车速?

    因为源码注释明确指出 yaw 可能先于新车速到达,此时曲率使用的是已保存车速;因此不能声称两个采样硬件同步。探针也确认,先更新 ABS 车速再更新 yaw 时,实测曲率在新 yaw 到达前保留旧值。

    PCM 第二车速为什么忽略计数器与校验和?

    源码注释将此与某些 ECU 的跳计数/短暂校验异常联系起来。但该帧仍检查质量位,质量位不为 3 时不能更新车速。

    CAN FD 的自控纵向在 release 固件下能开吗?

    默认或 release 固件的白名单不含 ACCDATA;只有 ALLOW_DEBUG 且纵向参数位置位时才使用 FORD_CANFD_LONG_TX_MSGS。官方测试套件默认带 ALLOW_DEBUG,所以不能据此证明 release 固件已开放该能力。

    白名单里出现 ACCDATA,说明每个信号都被校验了吗?

    不是。白名单只核对地址/总线/长度并调用 ford_tx_hook(),而 ACCDATA_3 与 IPMA_Data 在该钩子中没有专属载荷检查。白名单准入不等于逐信号验证,也不等于 ECU 接受。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/FORD_PANDA_SAFETY_MODE_DEEP_DIVE.md(取用了 RX 数据与新鲜度、TX 模式矩阵与位级门、测试证据与缺口,以及插件边界)。
    • 项目 02 —— openpilot-research/PLUGIN_REFACTOR.md(仅用于确认车型插件与安全模式归位的引用位置)。

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

    更新记录

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

  • 大众四平台加斯巴鲁两代:一套安全模式为什么不能按品牌复用

    结论:品牌名不是能力边界。同一家车厂的不同平台在安全模式、报文地址、校验方式和控制类型上差异很大:大众至少要分成 MQB、MEB、MLB、PQ 四套,斯巴鲁至少要分成 Global Gen1/Gen2 与 Preglobal;插件能力必须按平台 + 固件 + 接线位置 + 总线拓扑 + 控制类型声明,不能按品牌名继承。而且该快照把大众 MLB/PQ 与斯巴鲁 Preglobal 放在 ALLOW_DEBUG 条件下,不能因源码实现存在就认定发布固件支持执行。以下基于源码快照 c8fb906 与固定版本 opendbc_repo 的静态阅读,未接目标 ECU、未验证具体年款。

    大众:四套平台不能合并

    参数层 CarInterface._get_params() 会按 PQ、MLB、MEB、默认 MQB 选择安全模式;变速箱、网关/前相机接入、盲区监测等由指纹决定。MEB 使用曲率控制且配置自控纵向;非 MEB 的 alphaLongitudinalAvailable 与 alpha_long 路径须结合实际发布配置判断,不能只看能力字段放行。另外,若 PT bus 编号不在首组,还会前置 noOutput 安全配置。

    层 关键组件 源码行为
    输入 CarState.update() 分发到 update_mqb/pq/meb/mlb();update_hca_state() 与 update_acc_fault() 是故障状态收敛点。
    控制 CarController.update() MEB 将目标曲率与测量曲率差补偿后限幅,控制转向功率逐步升降;其余平台按驾驶员扭矩限制目标扭矩。
    MEB 停车 MebLongStateMachine.update() 协调 ACC 状态、保持/起步/释放坡道与退出斜坡,避免停车保持直接切到无请求;它不是通用纵向 PID。
    雷达 RadarInterface.update() MEB 网关接法读取相机发布的过滤后轨迹,不是原始雷达点;校验消息、状态与重复 ID,维护目标生命周期。

    大众固件模式差异

    把四套平台的固件行为并排看,差异集中在校验、TX 白名单与纵向编译门上:

    模式 初始化、RX、TX 必须保留的区别
    MQB init()、rx()、tx() 原车 ACC 与自控纵向用不同 TX 白名单;纵向固件标志仅在 ALLOW_DEBUG 分支读取。RX 从 ESP、EPS、TSK、油门、刹车和按钮更新状态。
    MEB init()、rx()、tx() Gen2 的替代 CRC 与消息长度单独选择;TX 校验曲率、转向功率、ACC 加速度,并限制停车保持与起步请求。
    MLB init()、rx()、tx() TX 表只含转向、HUD 与原车 ACC 按钮,没有纵向加速/制动帧;六组 RX 全部显式忽略 checksum,但检查计数器。
    PQ init()、rx()、tx() 使用不同地址、XOR 校验和与计数器位置;纵向标志同样有 ALLOW_DEBUG 编译门。

    MLB 与 PQ 的细节很典型:MLB 的 TX 表根本不发纵向帧,且源码保留着校验 TODO,不能据「通用大众模式」推断它的校验能力;PQ 用 XOR 校验和与不同计数器位置,原车 ACC 与自控纵向的使能事件也不同。MEB 的停车保持即使 ACC 未激活仍可能影响车辆,因此必须维持独立固件门。

    斯巴鲁:Global 与 Preglobal

    参数层 CarInterface._get_params() 对 Preglobal 选 subaruPreglobal,其余选 subaru;Global Gen2 会写入安全参数。Preglobal、角度 LKAS、Hybrid 被标为 dashcamOnly,而此快照显式将 alphaLongitudinalAvailable=False,所以普通参数路径不会启用自控纵向——相关代码仍存在,不代表当前可用。

    层 关键组件 源码行为
    输入 CarState.update() 按 Gen2、Hybrid、Preglobal、角度 LKAS 改变刹车、轮速、巡航、转角来源;保留原车 EyeSight 报文副本供控制器改写。
    输出 CarController.update() 软件侧按驾驶员扭矩限幅;STEER_RATE_LIMITED 时在高转向速率下周期性切断请求以避免 EPS 故障。
    构帧 subarucan.py Global 与 Preglobal 的转向、EyeSight Distance、HUD 及校验和函数分开;不能把同名逻辑当作相同总线或相同编码。
    Global 固件 subaru_init()、subaru_rx_hook()、subaru_tx_hook() Gen2 的车速/制动/巡航来自 alt bus,Gen1 来自主总线;ES_Distance 仅准许取消且油门值必须为 inactive。
    Preglobal 固件 subaru_preglobal_init()、rx()、tx() 另一套报文地址与驾驶员扭矩符号,特定车型可反转扭矩方向;RX 对计数器/校验和的忽略有源码 TODO。

    Global 的纵向限值常量虽然存在,但当前 TX 路径没有放行纵向控制;Preglobal 的 TX 白名单也含 ES_Distance,按钮行为需按平台测试核对。ECU 生命周期上,只有 DISABLE_EYESIGHT 标志才会执行 EyeSight 通信控制,这条分支不能当作默认运行路径。

    对硬件与插件化的约束

    1. 插件能力必须按平台、固件、接线位置、总线拓扑与控制类型声明,不按品牌名继承。MEB 曲率控制、MQB/PQ/MLB 扭矩控制以及斯巴鲁 Global/Preglobal 不能共用一套执行器协议配置。
    2. 固件安全模式仍是独立硬件信任边界:插件可选择经审计的模式与参数,但不得改写 TX 白名单、CRC、转向/加速度限值,也不能以主机软件状态替代固件 RX 检查。
    3. 大众 MEB 的停车保持/起步是动作语义,不只是加速度数字;应在插件契约中显式建模,并用故障注入验证失联、错误 CRC、错误 bus、状态迁移及退出斜坡。
    4. 斯巴鲁 Hybrid、角度 LKAS、Preglobal 与大众 PQ/MLB 在源码中有待验证或发布限制,应保持被动,直到目标车的 DBC、帧重放、台架与封闭场地证据齐全。

    常见问题

    能不能写一套代码同时适配大众所有车型?

    不能。大众至少分 MQB、MEB、MLB、PQ 四套平台,安全模式、TX 白名单、校验方式与控制类型都不同。参数层必须按平台选择,MEB 用曲率控制,其余用扭矩控制,不能合并。

    源码里实现了某平台的纵向控制,就代表能用吗?

    不代表。此快照把大众 MLB/PQ 与斯巴鲁 Preglobal 放在 ALLOW_DEBUG 条件下,斯巴鲁还显式将 alphaLongitudinalAvailable=False。源码实现存在不等于发布固件支持执行。

    为什么 MLB 模式不能按「大众通用」假设校验能力?

    因为 MLB 的六组 RX 全部显式忽略 checksum、只检查计数器,源码还保留着校验 TODO。不能据通用大众模式推断它具备同样的完整性校验。

    斯巴鲁 Global 和 Preglobal 的报文可以复用吗?

    不可以。两者的转向、EyeSight Distance、HUD 以及校验和函数分开实现,Preglobal 还使用另一套报文地址与驾驶员扭矩符号、特定车型可反转扭矩方向。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/VW_SUBARU_DEEP_DIVE.md(大众与斯巴鲁车型实现专题,含源码行号索引)
    • 项目 02 —— openpilot-research/VOLKSWAGEN_MEB_PANDA_SAFETY_DEEP_DIVE.md(源文档指向的 MEB 安全钩子逐函数专题)
    • 项目 02 —— openpilot-research/SUBARU_GLOBAL_PANDA_SAFETY_DEEP_DIVE.md(源文档指向的斯巴鲁 Global 安全专题)

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

    更新记录

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