标签: 车辆状态

  • 1.52 米偏置与 3 m/s 门:雷达轨迹与视觉前车的融合边界

    结论:radard 把硬件雷达轨迹与视觉前车融合成两个前车,但它的匹配是「先选最高分、再验合理性门」:一旦最高分目标被距离或速度门拒绝,不会改试次高分目标,即使后者能通过。它依赖三个固定几何/速度常量——距离减 1.52 m 的相机-雷达偏置、距离差门 max(视觉距离×0.25, 5 m)、速度门「差 <10 m/s 或雷达前车绝对速度 >3 m/s」。自制安装位姿或坐标符号不同时,不能只改 CAN 解码而不重验这些常量。以下基于源码快照 c8fb906,未做回放或台架测试;这些阈值是该提交的实现,不等于传感器硬件规格。

    消息链与调度:20 Hz 与可能的缓存复用

    card 在雷达接口返回数据时发布 radarTracks;若雷达错误结构有任一真值,顶层 valid=False,但错误与点仍在消息内。radard.main() 等待 CarParams,以 modelV2 为 SubMaster 轮询主题,读取 radarTracks/carState 后调用 RadarD.update() 与 publish()。这里有个细节:循环没有再用 sm.updated['modelV2'] 包住更新,因此轮询超时也可能沿用缓存数据再发 radarState,不能把每条输出都当作一帧新视觉预测。

    组件与关键函数

    组件/函数 实现功能 边界
    KalmanParams 根据固定表插值 KF1D 增益,建立速度/加速度模型 构造要求雷达步长严格在 (0.01,0.2) s;实际传入 DT_MDL,不是逐帧读取雷达硬件时间差
    Track.update() 保存相对距离/横向位置/相对速度,并更新前车绝对速度及加速度滤波 新 track 第一帧不做 kf.update();track ID 消失即删除对象,重现会重置滤波历史
    Track.potential_low_speed_lead() 判定低速近距离同车道候选 要求 vEgo<4 m/s、|yRel|<1 m、0.75<dRel<25 m;不依赖视觉概率
    match_vision_to_track() 对每条雷达轨迹算纵向/横向/速度拉普拉斯评分并取最大者 选中后还查距离与速度合理性;最高分也可能被拒绝
    get_lead() 优先匹配雷达;高视觉概率但无匹配时回退视觉;可再被低速雷达目标覆盖 leadOne 启用低速覆盖,leadTwo 不启用;视觉入门为滤波概率 >0.5
    RadarD.update() 对齐车速、维护轨迹 ID、平滑视觉概率、组装两个前车和错误 即使输入 sm.all_checks() 不通过,仍执行轨迹/前车组包

    几何、速度与来源选择

    RadarD.__init__() 用 CP.radarDelay 构造自车速度历史队列;RadarD.update() 仅在 carState 接收帧变化时推进队列,以历史首元素加雷达相对速度得到前车速度。原类方法探针确认:0.1 s 延迟配置下队列在启动时先含 0,未收到新 carState 帧即使当前对象的 vEgo 改变也不推进;第三个不同接收帧才取到此前的非零车速。这是按接收帧计数的延迟,不是按每个雷达点的硬件时间戳做精确插值。radarTracks.points 按 trackId 转字典,本轮未出现的 ID 被删;若上游对同一物体频繁换 ID,速度/加速度估计会反复从初值开始。

    match_vision_to_track() 把视觉距离减固定 RADAR_TO_CAMERA=1.52 m,横向位置取负号,速度比较 vRel+vEgo 与视觉预测 lead.v[0]。评分尺度使用模型给的 xStd/yStd/vStd,但最终还要求距离差小于 max(视觉距离×0.25, 5 m),速度差 <10 m/s 或雷达前车绝对速度 >3 m/s。探针确认:速度差即使很大,只要雷达前车绝对速度严格大于 3 m/s,速度合理性门仍放行;且它先选最高分再验门,最高分目标被拒时不会改试次高分目标。

    常量/门 数值 作用
    RADAR_TO_CAMERA 1.52 m 视觉距离减去的固定相机-雷达偏置
    距离合理性门 max(视觉距离×0.25, 5 m) 限制视觉与雷达的距离不一致幅度
    速度合理性门 差 <10 m/s 或绝对速度 >3 m/s 后者会大幅放行不匹配的速度差
    低速覆盖门槛 vEgo<4 m/s、|yRel|<1 m、0.75<dRel<25 m 仅 leadOne 启用,等距不覆盖

    有效性、故障与下游

    RadarD.update() 的顶层有效位取 sm.all_checks(),包含 modelV2、carState、radarTracks 的存活/频率/消息有效检查。ready 只取 sm.seen['modelV2'],不是「当前模型仍新鲜有效」的证明;radarErrors 直接来自上游。探针确认:输入检查失败时仍能组装 leadOne.present=True,而 publish() 将顶层 valid=False 随前车和错误一起交给发布器——硬件错误不应靠 present 掩盖。

    模型概率采用上升立即、下降经一阶滤波的非对称策略:下降按 dt=0.05 s、rc=0.2 s 逐次衰减,探针从 0.8 突降到 0 时观察到 0.64→0.512→0.4096,因此低于原始视觉阈值后的两轮仍可保留视觉前车。若 leadsV3 少于两条,原函数会跳过两个前车字段的赋值。下游 selfdrived 在允许纵向控制的车型上,把 canError、临时不可用与其它雷达错误分别映射为事件;LongitudinalPlanner.update() 把 radarState 交给 MPC,计划输出的 hasLead 使用首个前车的 present。雷达数据最终仍要过计划、使能状态机、车辆控制限幅与独立执行网关——radard 不直接发 CAN。

    插件化时应保留什么

    雷达输入插件应先只读地影子发布候选轨迹,保留唯一且不复用的 ID、硬件时间戳、雷达延迟、车辆坐标与安装偏置、单位及四种错误位。权威 radarTracks 与 radarState 各只能有一个生产者;输入边界须拒绝或隔离 NaN/Inf,缺席、无效或过期轨迹须明确失效,不得用最近一次 present=True 无限延续。ID 连续性会影响滤波历史,候选排序与合理性门的组合也会影响回退,因此设备跟踪策略与前车选择语义都属于插件契约。应按路段回放、误报/漏报故障注入与目标车型 SIL/HIL 顺序验证,再考虑上车。

    常见问题

    视觉前车和雷达前车匹配不上时会怎样?

    get_lead() 优先匹配硬件雷达;高视觉概率但无匹配时回退视觉前车,标 radar=False、radarTrackId=-1,之后还可能被低速雷达目标覆盖。但仅 leadOne 启用低速覆盖,leadTwo 不启用。

    为什么最高分匹配目标被拒绝后,不会换下一个?

    因为函数先对每条轨迹评分并取最大者,再验证距离与速度合理性门;一旦最高分目标被拒,实现不会改试次高分目标,即使它能通过门。这是该提交的确定性行为。

    radarState 的顶层 valid 和前车 present 是一回事吗?

    不是。valid 取输入检查的聚合结果,present 与 radarErrors 各自独立。探针确认输入检查失败时仍能组装 leadOne.present=True,由发布器带着 valid=False 一起发出。

    低速下第一个前车一定会被雷达覆盖吗?

    不一定。低速候选要求 vEgo<4 m/s、|yRel|<1 m、0.75<dRel<25 m。首个前车即使 ready=False 也可被选中,但已有前车时只允许更近的低速点覆盖,等距不覆盖,此时 modelProb 会回到默认 0。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/RADARD_LEAD_FUSION_DEEP_DIVE.md(雷达轨迹与视觉前车融合,含源码行号索引)
    • 项目 02 —— openpilot-research/probes/test_radar_lead_selection_contract.py(坐标/速度匹配与回退的隔离探针)
    • 项目 02 —— openpilot-research/PLUGIN_REFACTOR.md(源文档指向的插件化蓝图)

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

    更新记录

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

  • 车速 >1 m/s、转角 <45°:车辆参数在线学习的九维 EKF 与观测门

    结论:车辆参数在线学习是一台九维卡尔曼滤波器,但九个状态里只有横向速度与偏航率两个有显式微分方程,其余七个在状态转移中保持不变。学习被三道门约束:车速 >1 m/s、转向角绝对值 <45°、且非倒挡;此外偏航率与路面滚转观测只在「位姿有效」时才写入。它的输出是在线估计值,不是车辆认证参数,也不能替代独立的执行器安全限制。

    九维状态与两条微分方程

    滤波器状态依次为:轮胎刚度因子、转向比、慢零偏、快零偏、二维车速(纵向/横向)、偏航率、转向角与路面滚转。动力学按线性化单轨横向模型生成:每步用一阶欧拉积分 x_next = x + dt·x_dot 推进,而代码只给横向速度与偏航率指定了导数,其余七个状态只是保持原值。

    状态 含义 是否有显式微分方程
    STIFFNESS 轮胎侧偏刚度因子 否
    STEER_RATIO 转向比 否(出现在动力学分母)
    慢 / 快 ANGLE_OFFSET 转向零偏(慢、快两档) 否(慢零偏无直接观测方程)
    VELOCITY(二维) 纵向 / 横向车速 仅横向速度有方程
    YAW_RATE 偏航率 是
    STEER_ANGLE 转向角 否
    ROAD_ROLL 路面滚转 否

    横向速度与偏航率的导数中,纵向速度与转向比都出现在分母;而学习门只在外部输入层面检查车速与转角,并未对滤波内部状态每次预测时的分母做形式化保证。实现也没有把车身侧倾、路面超高、轮胎非线性或高阶积分显式放进这套方程,因此不能把它外推为全工况动力学模型。

    什么条件下才允许学习

    只有在车辆运动且转向不极端时才启用学习:车速大于 1 m/s、转向角绝对值小于 45°、且不在倒挡。偏航率观测要求角速度有效、标准差在 0 到 10 rad/s 之间、偏航率绝对值小于 1 rad/s,否则以 0 和 10 度标准差替代;滚转观测要求标准差小于 1.5°、数值严格落在 ±10° 内且传感器状态正常,否则同样回退,并在进入滤波器前受单步限速。若「位姿有效」为假,则偏航率与滚转观测不写入,但快零偏以及刚度/转向比的弱约束观测仍会继续。

    输入 启用条件 不满足时
    车况(学习使能) vEgo > 1 m/s、|转向角| < 45°、非倒挡 暂停学习,设置滤波时间
    偏航率观测 角速度有效、0 < std < 10 rad/s、|yaw| < 1 rad/s 以 0 与 10° 标准差替代
    滚转观测 标准差 < 1.5°、数值严格在 ±10° 内、传感器正常 以 0 与 10° 标准差替代
    位姿门 位姿有效为真 不写偏航率/滚转观测,弱约束仍执行

    观测码与噪声:默认表不等于实际传入

    滤波器为每个观测码准备了默认噪声,但调用方可以显式覆盖。例如偏航率与横向速度的默认项本身「未定义」,由运行时代码显式传入方差;滚转的默认项也会被显式覆盖。这意味着「默认表」只是兜底,实际噪声要按调用点判断。

    观测码 直接观测的状态 默认噪声项
    ROAD_FRAME_YAW_RATE 偏航率 未定义,由调用方显式传方差
    ROAD_FRAME_X_SPEED 纵向车速 0.1²
    STEER_ANGLE 转向角 rad(0.05)²
    ANGLE_OFFSET_FAST 快零偏 rad(10)²
    STEER_RATIO 转向比 5²
    STIFFNESS 刚度因子 0.5²
    ROAD_ROLL 路面滚转 rad(1)²(调用时显式覆盖)

    有效位分层:一个「healthy」不够用

    这套输出的有效位是分层的,不能折叠成一个布尔值:顶层「消息有效」直接采用输入的通信检查结果;内层「参数有效」是慢/总零偏、滚转范围与标准差、刚度及转向比范围的合取;「传感器有效」只在活动且车速大于 10 m/s 时检查车速与偏航率差的量级是否小于 4 m/s²,且不进入内层有效的合取;「位姿有效」在本快照恒为真,因此不能当作上游位姿状态的透传。输出值还对角度与滚转各做每帧 1° 的限速。

    下游消费同样要注意边界:控制侧直接读取刚度、转向比、零偏与滚转,其中前两者仅以「与 0.1 取较大值」设下限后更新车辆模型,并没有用内层有效位包住这次更新。安全效果依赖上层状态机、通信故障处理与独立网关的组合,不能把某一个有效位当成单点安全证明。

    持久化与插件边界

    正常运行会周期性写入持久缓存,并另有一个由车型流程写入的「上一行程车型参数」键用于车型核对。恢复时要求两个键都存在,且只明确校验车型指纹相同、历史转向比落在相对范围内;缺任一键就回退默认值且不清除另一个键,车型不符或越界时只删除估计缓存键、保留车型键。写缓存的节拍按帧号取模,「约一分钟」取决于循环节拍,不是墙上计时保证。插件化时,估计器可作为单写者、版本化、可回放的候选,但应先做影子输出比对,不直接抢占正式主题;输入契约须固定时间戳、坐标系、角度单位、车型参数版本与噪声标准差,输出契约要保留顶层通信有效位、内层参数有效位、分项有效位与数据年龄。

    常见问题

    在线估计出的车辆参数能当认证参数用吗?

    不能。它是在线估计值,依赖行驶激励与观测门;低速、倒挡或转向极端时学习会暂停,慢零偏甚至没有直接观测方程。它不能替代车辆认证参数,也不能替代独立的执行器安全限制。

    停车时滤波器还在更新吗?

    不会照常更新。车况门要求车速大于 1 m/s 且转角绝对值小于 45° 才启用学习,停车后不再产生新的学习观测,并会停止协方差的时间增长与回绕。

    为什么同一个参数会有好几个「有效位」?

    因为它们语义不同:顶层有效位来自输入通信检查,内层有效位是参数范围与不确定度的合取,传感器有效位只做横向一致性检查且不参与内层合取,位姿有效位在本快照恒为真。折叠成一个 healthy 会丢掉这些区别。

    倒车时会参与参数学习吗?

    不会。学习门明确排除倒挡:只有车速大于 1 m/s、转向角绝对值小于 45° 且非倒挡时才启用学习。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/PARAMSD_VEHICLE_LEARNING_DEEP_DIVE.md(九维 EKF 方程、观测码与噪声、学习门、有效位分层、持久化与插件边界)

    说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有状态维度、阈值与配置均出自源文档,标注为未验证的结论保留其原有边界;参数均为在线估计值,不代表任何车型的认证参数。

    更新记录

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

  • 变道意图与 LDW:20 mph 与 31 mph 两道阈值各管什么

    结论:变道意图和车道偏离预警是两条不同的辅助链路。变道意图靠转向灯新沿、驾驶员同向扭矩与盲区位,在车速不低于 20 mph 时从 off 进入准备态;车道偏离预警则要求车速严格大于 31 mph、近 5 秒无转向灯且横向未激活。两者都只产生提示或候选,不直接授权转向或制动执行器。本文为固定源码快照 c8fb906 的静态分析。

    消息与时序:本帧状态、下一帧脉冲

    modeld.main() 在本帧推理前读取上一次 DH.desire,编码到 desire_pulse;ModelState.run() 只将 desire 从低到高的变化送入模型。推理后才从本帧 desireState 左右变道项之和计算 lane_change_prob,调用 DH.update(),并把更新后的状态/方向写入本帧 modelV2.meta。因此状态提示可在本帧出现,新意图要到下一帧才进入模型;不能把它们当同一时刻的闭环输出。

    plannerd.main() 以 modelV2 轮询,只有该主题更新时计算 LDW 并发布 driverAssistance;消息顶层 valid=sm.all_checks(),所以计算出的左右布尔值与可供消费的有效性是两回事。服务表配置 driverAssistance 为 20 Hz,这不是端到端时延保证。desirePrediction 是 LDW 使用的另一组模型值,不能与 desireState 混写。

    组件/入口 主要逻辑 下游或边界
    DesireHelper.get_lane_change_direction() 从左转向灯决定左,否则决定右;调用者先要求仅单侧灯亮。 准备态方向可随灯变化。
    DesireHelper.update() 转向灯新沿、速度、横向活动、驾驶员同向扭矩、盲区和模型完成概率推进状态。 只设置意图/状态,不直接写转向执行器。
    ModelState.run() 在推理输入中把非零 desire 的上升沿转为脉冲。 持续保持同一意图不会每帧重发脉冲。
    LaneDepartureWarning.update() 根据车速、灯冷却、横向活动、线可见度/距离及预测概率设置左右偏离。 仅产生布尔结果,不制动或转向。
    Controls.state_control() laneChangeState!=off 时设置 carControl 转向灯请求,按有效位设置 HUD 偏离方向。 车型是否实现灯控需逐车型检查。

    变道意图的状态门

    DesireHelper.update() 只有横向激活、单侧转向灯出现新沿、速度不低于 20 mph,才从 off 进入 preLaneChange。准备态要求 steeringPressed 且扭矩方向与转向灯一致,并且该侧无盲区报警,才进入 laneChangeStarting。熄灯或降到门槛以下会关闭准备态;已启动后源码不再以盲区位直接撤销,而是依赖模型概率和超时等分支,因此不能把盲区位当成持续的安全许可。

    完成门要求启动至少 0.5 s 且左右变道 desireState 概率和严格小于 0.02,之后若灯仍亮回准备态,否则关闭。横向失活或累计计时超过 10 s 关闭。laneChangeFinishing 虽在 log.capnp 中定义、selfdrived 也接受,本版本 helper 没有赋值该状态。五项原 DesireHelper 类假消息枚举探针确认:20 mph 恰好允许新沿进入准备态;横向失活会把上一灯沿记忆清零,再激活时即使灯一直亮也可视为新沿;同向扭矩在盲区亮时不能启动,但进入 laneChangeStarting 后熄灯/盲区亮且模型完成概率仍高时,意图继续输出。十秒上限在每次调用开头检查,首次越过十秒的该帧仍处于启动态,下一次调用才复位。

    车道偏离预警的判定条件

    LaneDepartureWarning.update() 要求车速严格大于 31 mph、按模型帧估算的近 5 s 内没有转向灯、carControl.latActive=False,且 desirePrediction 非空。左/右车道线各自概率大于 0.5;以相机横向偏移 0.04 m 修正后的近线条件分别是左侧 y[0]>-1.12 m、右侧 y[0]<1.04 m;对应方向的模型变道预测概率还须大于 0.1。last_blinker_frame 初值为零,冷却按 SubMaster.frame * DT_MDL 估算,不是单独的墙钟计时。四项原 LDW 类假模型字段探针确认:初始帧号 99 仍在五秒冷却内,帧号 100 恰好结束;31 mph 等值不允许 LDW,模型概率/线可见度/近线距离的等值也不触发;空 desirePrediction 会清左右状态,但非空且短于所需索引的向量直接抛 IndexError。

    维度 变道意图 车道偏离预警
    车速门槛 不低于 20 mph 才能进入准备态。 严格大于 31 mph。
    转向灯 需要单侧灯的新沿。 近 5 s 内没有转向灯。
    横向活动 要求横向激活。 要求 latActive=False。
    驾驶员输入 要求 steeringPressed 且扭矩同向。 不依赖驾驶员扭矩,依赖线可见度与模型预测。
    输出 变道状态/方向、转向灯请求与提示。 左右偏离布尔值、HUD 方向与声音提示。

    提示与执行边界

    selfdrived 仅在 IsLdwEnabled 和 driverAssistance 有效时生成 ldw;开关由参数轮询更新。该事件是 PERMANENT 小型提示及声音,不是 disengage 或执行器指令。准备变道、盲区阻挡和变道中分别是 WARNING 提示;其中盲区阻挡带提示音。controlsd 也会在 driverAssistance 有效时把左右偏离送入 HUD。

    两项原消费者条件组合探针确认了旧值消费的边界:先收 driverAssistance.valid=True,随后假时钟推进到超出 20 Hz 服务的十倍存活窗口且无新消息时,updated=False、alive=False,但 valid=True 与旧左右字段保留,隔离执行 SelfdriveD.update_events() 的 LDW 分支仍会添加 ldw;若收到新消息且外层 valid=False,两个分支都跳过。转向灯、驾驶员扭矩/按压、盲区都来自车型解码,不是模型自己「看见了」即可替代,新硬件接口必须逐车型证明同向扭矩和盲区位的确切含义。

    常见问题

    变道是车辆自己判断安全后完成的吗?

    不是。这里的变道是驾驶员参与的 L2 辅助行为,DesireHelper 只设置意图/状态,不直接写转向执行器;车型是否实现灯控还需逐车型检查。它不自动判断周围交通安全后自行变道。

    为什么变道用 20 mph、LDW 用 31 mph?

    两条链路的门槛是独立定义的:DesireHelper.update() 要求速度不低于 20 mph 才进入准备态;LaneDepartureWarning.update() 要求车速严格大于 31 mph。源码没有把两者合并为同一策略函数。

    盲区位会一直阻止变道吗?

    不会。准备态要求该侧无盲区报警,但一旦进入 laneChangeStarting,源码不再以盲区位直接撤销,而依赖模型概率和超时等分支,因此不能把盲区位当成持续的安全许可。

    LDW 的 5 秒冷却按墙钟算吗?

    不是。冷却按 SubMaster.frame * DT_MDL 估算,last_blinker_frame 初值为零;探针确认灯亮后的第 99 帧仍冷却、第 100 帧可再次触发。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/LANE_CHANGE_LDW_DEEP_DIVE.md(DesireHelper 状态机、LDW 阈值、消息发布与提示边界)。
    • 项目 02 —— 同目录探针记录:五项 DesireHelper 状态、四项 LDW 阈值、两项 plannerd 发布、两项消费者分支隔离探针结论。

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

    更新记录

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

  • 纵向 MPC 候选合成:10 秒 12 段时域与最小加速度选择

    结论:纵向规划器每帧把 MPC 目标加速度、巡航候选和实验模式的 e2e 候选放在一起,取最小加速度作为 aTarget,但 shouldStop 是对全部候选求或——所以两者可能来自不同候选,不能按单一来源重构。最终还要裁到车型接口常量 ACCEL_MIN=-3.5、ACCEL_MAX=2.0,再交给 LongControl。本文为固定源码快照 c8fb906 的静态分析,数学建模说明不等于数值收敛或车辆级认证。

    进程与消息链

    manager 进程表在非纵向机动实验的行车模式运行 plannerd。main() 等待 CarParams,以 modelV2 轮询;只有本轮模型主题更新才调用纵向规划并发布 longitudinalPlan,然后运行车道偏离检测、发布 driverAssistance。两个输出各自写 sm.all_checks() 作为顶层有效位,规划计算本身没有被该检查提前短路。longitudinalPlan 名义频率为 20 Hz,真实时延受输入消息年龄和求解耗时影响。

    输入包括 modelV2 模型动作与油门概率、radarState 两个前车、carState 车速/巡航速度/转角、controlsState 纵向状态与强制减速、vehicleParameters 转向零偏,以及 selfdriveState 模式与驾驶风格。MPC 本体把自车建模为位置、速度、加速度三维状态,以 jerk 为控制量。

    候选是怎么合成的

    LongitudinalPlanner.update() 同步车况、MPC、巡航和可选 e2e 候选,选择目标并推进期望车速滤波。它在 controlsState.forceDecel 时把巡航目标速度置零;纵向控制关闭、巡航速度未初始化或非自控纵向且尚未使能时重置内部目标。carControl.orientationNED 可用时以俯仰估计滑行加速度,否则以 ACCEL_MAX 作缺省值;vehicleParameters.angleOffsetDeg 从转角中扣除,用于非实验巡航候选的横向加速度预算。

    get_cruise_accel() 的巡航候选按车速限制正加速度,并在非实验模式下用 sqrt(max(a_total²-a_y²,0)) 限制弯中纵向余量;若 allow_throttle=False,还把正加速度上限向坡度估得的滑行加速度收紧。allow_throttle 来自模型 gasPressProbs[1] > 0.4 或车速 <= 2.5 m/s;若数组不足两项,代码回退概率 1.0。它的计算顺序是先将速度误差裁到巡航/弯道/滑行候选范围,再按上一帧 a_cruise 和 j_cruise * dt 限变化率。

    四项原函数 NumPy 探针确认了这种「先裁范围、后限变化率」的瞬态:10 m/s、上一帧 2.0 m/s² 时返回 1.94 m/s²,高于该速度的 1.2 m/s² 巡航上限;20 m/s 且弯道预算降为零时仍返回约 0.453 m/s²;禁油门滑行上限为 -0.3 m/s² 时可暂时返回 -0.07 m/s²。这是平滑历史带来的候选瞬态,不等于最终 aTarget 或车辆执行值必然越界。

    最终选择上,MPC 轨迹经 get_accel_from_plan() 按车型纵向延迟插值得到当前目标;再与巡航候选比较,实验模式增加 modelV2.action.desiredAcceleration 候选。代码取最小加速度,再裁到车型接口常量 ACCEL_MIN=-3.5、ACCEL_MAX=2.0;shouldStop 却对全部候选求或。因此 aTarget 与 shouldStop 可来自不同候选。controlsd controlsd 将两者交给 LongControl.update(),后者按 off/stopping/pid 状态与车型 PID 限值给执行器目标;不能把 aTarget 当作直接 CAN 帧。

    字段 来源 相位与边界
    aTarget MPC、巡航与实验模式 e2e 候选取最小加速度后再裁到车型常量。 不是直接 CAN 帧;需经 LongControl。
    shouldStop 对全部候选求或。 可与 aTarget 来自不同候选。
    allowThrottle 模型 gasPressProbs[1] > 0.4 或车速 <= 2.5 m/s。 只提示与巡航候选约束;controlsd 未把它当独立布尔门。
    消息 valid publish() 时写 sm.all_checks()。 与候选计算相分离,计算本身不被它短路。
    函数/组件 作用 关键边界
    get_cruise_accel() 按巡航速度差给目标加速度,非实验模式考虑弯道横向与滑行/油门约束,再做 jerk 限幅。 后置 jerk 限幅可使返回值暂时高于前面算出的候选上限。
    LongitudinalPlanner.update() 同步车况、MPC、巡航与可选 e2e 候选,选择目标并推进期望车速滤波。 允许在输入检查失败时计算;valid 只在发布时反映 sm.all_checks()。
    LongitudinalPlanner.publish() 发布速度/加速度/jerk 轨迹、aTarget、shouldStop、FCW、来源与油门提示。 hasLead 取 leadOne.present,不表达雷达消息有效性。
    get_T_FOLLOW() 选择跟车时距:宽松 1.75 s、标准 1.45 s、激进 1.25 s。 个性化只改变优化参数,不改变独立安全网关策略。

    MPC 时域与跟车时距

    gen_long_model() 在构建期由 CasADi/acados 生成求解器,运行时不是 Python 手工积分器。gen_long_ocp() 建立 12 段、10 秒非均匀预测时域,设置距离/jerk 成本与速度、加速度、危险距离软约束;约束与权重在运行时更新,不能只看静态模型推断车辆一定在物理极限内。get_T_FOLLOW() 按驾驶风格选择 1.75 s、1.45 s 或 1.25 s 的跟车时距。

    process_lead() 处理前车状态、裁剪异常值并外推轨迹;无前车时注入虚拟高速前车。六项原 MPC 前车方法隔离探针确认:无目标时首点为前方 50 m、车速比自车高 10 m/s 的虚拟前车;近目标距离会被可刹距离公式抬高,输入加速度被裁到 [-10,5] m/s²。LongitudinalMpc.update() 把两个前车换算成等效静止障碍,逐节点选择更近约束,运行求解并累计 FCW 近碰撞帧;source 取第零节点最近障碍,后续节点可换前车,FCW 只用 leadOne 且要求 modelProb>0.9。探针确认即使后续节点的逐时刻最小障碍已切换到另一前车,source 只由第零节点决定。set_cur_state() 与上次速度差严格大于 2 m/s 时重设所有 solver 节点状态,等于 2 m/s 不重设。

    求解失败与 FCW 边界

    LongitudinalMpc.run() 在求解状态非零时先读取本次解,再按单调时钟判断是否记录警告并调用 reset();LongitudinalPlanner.update() 随后仍插值当前(已清零的)速度/加速度/jerk 轨迹并参与候选选择,没有专门的「求解失败」消息位。reset() 同时将 solve_time、solution_status、crash_cnt 与 last_cloudlog_t 置零;因此告警时间戳不跨失败保存,当单调时钟已超过 5 秒时,连续失败可每次记录,而非真正跨失败限频。发布的求解耗时为清零后的值,原始失败状态没有发布。

    FCW 侧,LongitudinalMpc.update() 在 5 秒预测窗口内存在距离小于 0.25 m 且首个前车模型概率 >0.9 时累加 crash_cnt;规划器要求计数 >2 且车辆未静止才发布 FCW。selfdrived 还会结合另一条模型硬制动预测生成 FCW 事件,两者不能混为单一碰撞检测器。探针还确认:第一前车模型概率从 0.91 降到严格边界 0.9 时计数清零;dRel=NaN 可继续形成 NaN 位置轨迹,当前纯输入处理层没有有限性校验。

    常见问题

    为什么 aTarget 和 shouldStop 可能来自不同的候选?

    因为最终 aTarget 是「取最小加速度」,而 shouldStop 是对全部候选「求或」。探针确认:MPC 的 -0.5 m/s² 目标获选时,较大的 e2e 候选仍可提供 shouldStop=True。

    跟车距离有哪几档?

    get_T_FOLLOW() 提供宽松 1.75 s、标准 1.45 s、激进 1.25 s 三档;个性化只改变 MPC 的优化参数,不改变独立的安全网关策略。

    MPC 求解失败会怎样?

    run() 遇非零求解状态会调用 reset(),清零轨迹、计数和状态;规划器随后仍插值已清零的轨迹参与候选选择,且没有专门的「求解失败」消息位,发布的求解耗时也是清零后的值。

    allowThrottle=False 是不是就等于禁止加速?

    不完全是。allowThrottle 被发布供 UI 调整显示;本快照 controlsd.state_control() 只显式消费 aTarget/shouldStop,未把 allowThrottle 作为独立布尔门传给 LongControl。它对正常巡航候选的限幅有作用,但不能据此断言所有候选都被禁止正加速度。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/LONGITUDINAL_PLANNER_MPC_DEEP_DIVE.md(plannerd 消息链、候选合成、MPC 时域与求解失败边界)。
    • 项目 02 —— 同目录探针记录:四项巡航数值、四项规划器候选、六项 MPC 前车、四项求解器生命周期隔离探针结论。

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

    更新记录

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

  • Toyota CarState:bus_timeout 为什么不等于信号健康

    结论:在 Toyota 输入链里,can_valid 与 bus_timeout 是两种不同的健康语义。can_valid 只对已注册报文做活性检查;bus_timeout 只代表总线流量空窗,而且 CANParser.update() 对该总线的任何帧都会刷新 last_nonempty_nanos,即使不是已注册地址。因此「总线有流量」不等于「每个必需信号都健康」,单看 vl 值也无法区分「未收到有效样本」和「物理零」。

    入口与关键函数

    Toyota 的输入链从 card 解码 CAN 帧开始,经车型接口把信号归一成 CarState,再向下游发布与消费。

    组件/函数 源码 作用
    CarInterface 绑定 interface.py 指定 Toyota CarState/CarController/雷达接口;参数构建决定车型标志、角度模式和纵向能力。
    card.state_update() card.py 接收 can,转为帧列表,再调用车型接口和雷达接口。
    CarInterfaceBase.update() interfaces.py 先更新所有 CANParser,后调用 CarState.update(),再汇总 canValid/canTimeout、速度显示迟滞和按钮启用。
    CarState.get_can_parsers() carstate.py 为动力总线 0 和相机总线 2 创建解析器,仅预注册 BLINKERS_STATE。
    parse_wheel_speeds() interfaces.py 四轮均值乘单位及车型系数,再进入 KF1D 得到 vEgo/aEgo。

    总线注册与健康的准确含义

    Toyota 的动力总线只预注册 BLINKERS_STATE,频率传入 NaN;NaN 在 _add_message() 中令该报文 ignore_alive=True,所以转向灯报文本身不参与超时失效,并非所有 Toyota 输入都忽略超时。其余报文是在 CarState.update() 第一次读 cp.vl[...] 时由 VLDict 动态注册;首次读发生在本轮 cp.update() 之后,即该轮此前收到的报文尚未由新注册状态解码。

    普通动态报文先以 1 Hz 假定频率、10 秒超时阈值注册,收到足够样本后解析器估计频率并改为约十个周期的阈值。这里有两个容易混淆的点:其一,CANParser.update() 对该总线的任何帧都会刷新 last_nonempty_nanos,所以 bus_timeout 只代表总线流量空窗;其二,MessageState.parse() 遇 DBC checksum 或 counter 错误不更新缓存值与时间戳。最终 CarInterfaceBase.update() 对两路解析器取 all(can_valid) 和 any(bus_timeout)。

    还有一个细节:转角偏移更新分支内部也读取 cp.can_valid,接口汇总时又读取一次;当前 can_valid 属性读取会更新 can_invalid_cnt,不是纯查询。因此不能把它解释为严格的「连续五次 update 失败后失效」。

    信号到 CarState 的映射

    类别 来源与条件 输出/状态
    车身与踏板 动力总线 BODY_CONTROL_STATE、BRAKE_MODULE、ESP_CONTROL;SecOC 用 GAS_PEDAL,其他用 PCM_CRUISE.GAS_RELEASED doorOpen、安全带、驻车制动、制动/保持、gasPressed。
    档位与车速 SecOC 用 GEAR_PACKET_HYBRID,其他用 GEAR_PACKET;四轮 WHEEL_SPEEDS 枚举映射档位;轮速先得 vEgoRaw,再滤波为 vEgo/aEgo;vEgoCluster 初始设为 vEgo * 1.015。
    转向 STEER_ANGLE_SENSOR、STEER_TORQUE_SENSOR、EPS_STATUS 转角/角速度、扭矩、驾驶员转向判定、LKA/LTA 临时与永久故障。
    巡航 UNSUPPORTED_DSU 用 DSU_CRUISE 与 PCM_CRUISE_ALT;其他用 PCM_CRUISE_2 与 PCM_CRUISE_SM;运行态来自 PCM_CRUISE 主开关、设定车速、仪表设定车速、启用/停止/非自适应状态和故障。
    按键/HUD 相机 LKAS_HUD;部分平台的 ACC_CONTROL.DISTANCE LKAS 边沿合成按钮事件、gap 调整事件;相机 HUD 缓存供控制器构帧。

    其中 stockAeb 仅在非 SecOC、未禁雷达路径读取,且要求 PRECOLLISION_ACTIVE 与负 FORCE;accFaulted 的低速 lockout 附加判据仅在源码指定的 TSS2/ACC 类型和 openpilot 纵向控制条件下执行,不应泛化为所有 Toyota 车型。

    来源选择也是常被误解的点:cp_acc 在 TSS2 且非 RADAR_ACC 时指相机解析器,否则指动力解析器;这是 ACC/PCS 的信号来源选择,不是对两路数据作一致性比对。相机解析器初始没有注册报文,全部依赖动态注册;原车 ADAS 的 stockAeb、stockFcw 与左右盲区还受 TSS2、HAS_BSM、DISABLE_RADAR 条件控制,ACC_CONTROL.ACC_TYPE 影响低速 lockout 分支。

    转角标定、故障与按钮时序

    STEER_TORQUE_SENSOR.STEER_ANGLE 在非零、未初始化时才让 accurate_steer_angle_seen 永久锁存为真。此后仅在原角度小于 90 度、角速度小于 100 度/秒且 cp.can_valid 为真时,才更新 60 秒时间常数的 FirstOrderFilter 偏移;首次合格样本直接初始化。偏移一旦初始化,后续即便本轮不再更新,也会继续用于 torque_sensor_angle - offset。这里没有单独的偏移年龄或置信度字段。

    steeringPressed 是驾驶员力矩绝对值直接大于 STEER_THRESHOLD,不是调用基类的去抖函数。EPS_STATUS.LKA_STATE 总是参与故障映射,仅角度控制模式再并入 LTA_STATE;代码列出的临时故障为 0/9/11/21/25,永久故障为 3/17,这只是软件分类。TSS2 相机 LDA_ON_MESSAGE 从非零值切换到不同值时,代码合成一次 LKAS 按下和释放事件。lkas_hud 与 SecOC 同步字段是从解析器字典浅拷贝的最近缓存,没有在此单独检查样本年龄。

    消费与插件设计约束

    card 将 CS.canValid 作为 carState 外层 valid,也用于 sendcan 消息的外层有效位;后者是否真正被 MCU 拒绝仍取决于车端传输和 safety 链,不能把外层位等同于物理禁发。CarEvents 把 vehicleSensorsInvalid、accFaulted、转向故障、驾驶员施力等转成事件。重构成车型输入插件时,输出至少应为逐信号的来源总线、DBC/车型版本、原始采样时间、年龄、有效/缺失/故障状态及标准物理单位。

    常见问题

    can_valid 和 bus_timeout 到底有什么区别?

    can_valid 对已注册报文做活性检查,并把计数器达到失败阈值视为无效;bus_timeout 只代表总线流量空窗,任何帧(即使未注册)都会刷新 last_nonempty_nanos。所以有总线流量仍可能缺少某个必需信号。

    为什么转向灯报文不参与超时失效?

    因为动力总线只预注册 BLINKERS_STATE,其频率传入 NaN,在 _add_message() 中令该报文 ignore_alive=True。这只针对转向灯报文本身,并非所有 Toyota 输入都忽略超时。

    动态注册的报文首轮会被解码吗?

    不会。其他 cp.vl[...] 首次读取时 VLDict 才动态注册,而首次读发生在该轮 cp.update() 之后,因此该轮此前收到的报文尚未由新注册状态解码,初值零可能只是「未收到有效样本」。

    can_valid 的读取会改变状态吗?

    会。当前 can_valid 属性读取会更新 can_invalid_cnt,不是纯查询;而且转角偏移分支与接口汇总都会读取它。因此不能把它当成「连续五次失败后才失效」的严格计数语义。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/TOYOTA_CARSTATE_INPUT_HEALTH_CONTRACT_DEEP_DIVE.md(取用了总线注册与健康语义、信号到 CarState 映射、转角标定与按钮时序、消费与插件约束)。
    • 项目 02 —— openpilot-research/TOYOTA_SECOC_KEY_FRESHNESS_SAFETY_BOUNDARY_DEEP_DIVE.md(仅用于确认 SecOC 同步 MAC 与 freshness 的上游验证边界)。

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

    更新记录

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

  • Mazda 52/45 迟滞与 Nissan 12 帧平均:三品牌横控差异

    结论:Chrysler、Nissan、Mazda 三家都保留了原车的纵向巡航控制,但横向控制、取消策略与安全模式差异显著,不能抽象成同一套报文插件。Nissan 用后轮速度做安全侧转角限制、驾驶员扭矩按 12 帧平均;Mazda 的最低价速度门用 52/45 km/h 上/下行迟滞并等待 LKAS_BLOCK 清除;Chrysler 的 Ram HD 与 CUSW 被标为 dashcamOnly,CUSW 还使用独立的 chryslerCusw 模式且固件注册仅在 ALLOW_DEBUG。本文为对本地 source/ 与固定版本 opendbc_repo 的静态阅读,未运行测试、未连接实板、未验证任何年款。

    Chrysler:多平台 EPS 电机扭矩

    CarInterface._get_params() 根据 Pacifica/Jeep、Ram DT、Ram HD、CUSW 选择安全模式与参数;Ram HD、CUSW 标为 dashcamOnly,CUSW 使用独立的 chryslerCusw 模式,其固件注册仅存在于 ALLOW_DEBUG 编译块。此快照中雷达解析接口虽存在,但始终 radarUnavailable=True。EPS 固件/车型还会影响最低转向速度。

    CarState.update() 与 update_cusw() 使用不同的状态映射,Ram 的 ACC 状态来自相机总线。CarController.update() 在软件侧按 EPS 测得扭矩限幅,按平台采用最低转向速度迟滞,并在 LKAS 控制位下降后等待一段帧数再重新使能,以避免 EPS 故障。chryslercan.py 里的构帧函数各自编码仪表、转向与巡航按钮,车型报文与 checksum 不能仅靠品牌名复用。

    层 关键入口 已核对行为
    参数 CarInterface._get_params() 按 Pacifica/Jeep、Ram DT、Ram HD、CUSW 选安全模式;Ram HD、CUSW 标 dashcamOnly;CUSW 注册仅 ALLOW_DEBUG
    输入 CarState.update() / update_cusw() 主流平台与 CUSW 用不同状态映射;Ram 的 ACC 状态来自相机总线
    输出 CarController.update() 按 EPS 测得扭矩限幅,采用最低转向速度迟滞,LKAS 位下降后等一段帧数再重新使能
    构帧 chryslercan.py create_lkas_hud()、create_lkas_command()、create_cruise_buttons() 分别编码
    固件 RX/TX chrysler_rx_hook() / chrysler_tx_hook() / chrysler_init() Pacifica/Jeep 与 Ram DT/HD 分别设扭矩、变化率与电机扭矩误差限值;按钮只允许取消或恢复
    CUSW 固件 chrysler_cusw_init() / rx() / tx() 另套地址、RX 表、扭矩限值与按钮位定义;注释仍记载转向故障待解决

    验证入口是 Chrysler 与 CUSW 的安全测试以及通用车型接口测试;这三品牌各自目录下尚无独立车型测试套件,本次也未运行。CUSW 即使源码可编译,其安全模式注册也仅存在于调试固件,注释仍记载转向故障待解决,不可视为量产可用。

    Nissan:转角控制与车型专属取消

    Nissan 侧选择 nissan 安全模式,采用转角控制与原车纵向;Altima 通过安全参数切换 EPS 及相关状态报文总线。此快照中雷达不可用,但不能据此认定原车 ADAS 的内部感知已被软件接管。

    CarState.update() 里,Altima 与其他车型的 PT/相机 bus 对调;后轮速度用于安全侧转角限制的原始速度,四轮均值用于平滑估计,驾驶员扭矩按 12 帧平均以避免误报。CarController.update() 先用一阶滤波平滑低速大转角指令,再按速度/实际角度限制目标,驾驶员施力时降低 EPS 可用扭矩上限。

    取消策略是 Nissan 最不能包装成通用插件的部分:Rogue/X-Trail/Altima 发送 CRUISE_THROTTLE 取消,Leaf 则用 create_cancel_msg() 把 CANCEL_SEATBELT 置位,让 ProPILOT 将其解释为安全带未系而退出,源码说明会伴随蜂鸣和警告——这是明确的 HMI/安全语义。

    层 关键入口 已核对行为
    参数 CarInterface._get_params() 选 nissan 安全模式、转角控制、原车纵向;Altima 切换 EPS 相关报文总线
    输入 CarState.update() / get_can_parsers() Altima 与其余车型 PT/相机 bus 对调;后轮速度用于安全侧转角限制;驾驶员扭矩按 12 帧平均
    输出 CarController.update() 低速大转角先一阶滤波,再按速度/实际角度限幅;驾驶员施力时降低 EPS 可用扭矩上限
    Leaf 取消 create_cancel_msg() 置 CANCEL_SEATBELT 使 ProPILOT 解释为安全带未系退出,源码说明伴随蜂鸣与警告
    固件 RX/TX nissan_rx_hook() / nissan_tx_hook() / nissan_init() 转角幅度与速度相关变化率由固件检查;0x20b 按钮帧只允许取消位;Leaf 0x280 在白名单但无逐位附加约束

    Nissan 的 RX 表显式忽略 checksum/counter,因此不能声称该路径有独立的报文完整性检验。此外,MCU 的 RX 变体锁定、Altima 总线、0x20B 按钮掩码与 Leaf 0x280 载荷边界都需按具体 DBC 与测试核对,不能只凭品牌推断。

    Mazda:扭矩控制与速度迟滞

    Mazda 选择 mazda 安全模式与扭矩调校;此快照仅 CX-5 2022、CX-9 2021 不标 dashcamOnly,部分车型有最低转向车速门。CarState.update() 解析四轮速度、原车巡航、踏板、驾驶员/EPS 扭矩及相机 LKAS 状态。

    最低速度门使用 52/45 km/h 上/下行迟滞,并等待 LKAS_BLOCK 清除;低速告警与临时转向故障由状态逻辑产生。CarController.update() 按驾驶员扭矩限幅 LKAS,踩刹车后延迟发取消以规避与原车巡航主开关的竞态,停车恢复按间隔发 Resume。mazdacan.py 的构帧函数使用不同来源与计数器,需要保持相机/主总线消息时序。

    层 关键入口 已核对行为
    参数 CarInterface._get_params() 选 mazda 安全模式与扭矩调校;仅 CX-5 2022、CX-9 2021 不标 dashcamOnly
    输入 CarState.update() 最低速度门用 52/45 km/h 上/下行迟滞,并等待 LKAS_BLOCK 清除
    输出 CarController.update() 按驾驶员扭矩限幅 LKAS;踩刹车后延迟发取消;停车恢复按间隔发 Resume
    构帧 mazdacan.py create_steering_control()、create_alert_command()、create_button_cmd() 来源与计数器各异
    固件 RX/TX mazda_rx_hook() / mazda_tx_hook() / mazda_init() LKAS 扭矩受最大值、速率、驾驶员反向扭矩约束;未使能时按钮帧只能是取消

    Mazda 的 RX 表忽略 checksum/counter/质量位,同样不能声称有独立报文完整性检验。

    三者共用同一套通用边界:指纹 DBC、CarState、CarController 与 panda 的 RX/TX 钩子,但具体报文与状态各自实现。验证入口包括 Chrysler、CUSW、Nissan、Mazda 各自的 panda 安全测试以及通用车型接口测试;这些测试覆盖按钮、平台扭矩与部分总线变体,本次均未运行。

    对硬件与插件化的含义

    1. 车型契约必须区分 Pacifica/Ram/CUSW、Altima/Leaf、CX-5/CX-9 的报文地址、总线、控制类型、计数器与发布状态,不能只给出 brand 和 canfd 两个字段。
    2. 调试固件的 Chrysler CUSW 与 Ram HD 路径、Nissan Leaf 的安全带模拟取消、Mazda 的刹车后取消延迟,都应列为需要人机交互与实车故障分析的高风险策略,不开放为通用插件选项。
    3. 软件限幅、倒车/低速/停用状态和原车辅助系统冲突,必须与 panda 的 TX 白名单和 RX 状态独立核对;缺少 checksum/counter 的 RX 路径还需额外评估错误帧、重放与总线时序失效。
    4. 目标车型应先做只读采集、DBC/总线回放、错误帧注入、台架与封闭场地验证;本篇不是上车许可。

    常见问题

    三个品牌能共用一套转向控制插件吗?

    不能。三者的报文地址、总线、控制类型(Chrysler 为 EPS 电机扭矩、Nissan 为转角、Mazda 为扭矩)与计数器都不同,且取消策略各异,因此不能抽象成同一套报文插件。

    Nissan Leaf 是怎么取消巡航的?

    它不用普通取消按钮,而是用 create_cancel_msg() 把 CANCEL_SEATBELT 置位,让 ProPILOT 将其解释为安全带未系而退出,源码说明会伴随蜂鸣和警告,具有明确 HMI/安全语义。

    Mazda 的最低转向速度门是多少?

    源文档给出的最低速度门使用 52/45 km/h 上/下行迟滞,并等待 LKAS_BLOCK 清除后才允许。该阈值出自源文档,本文未在实车验证。

    为什么说这些品牌的 RX 路径没有独立完整性校验?

    Nissan 与 Mazda 的 RX 表都显式忽略 checksum/counter(Mazda 还忽略质量位),Chrysler 的 CUSW 也是另套定义。缺少这些字段意味着错误帧、重放与总线时序失效需要额外评估。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CHRYSLER_NISSAN_MAZDA_DEEP_DIVE.md(三品牌参数、输入/输出、构帧与固件 RX/TX 对照)
    • 项目 02 —— openpilot-research/CHRYSLER_PANDA_SAFETY_MODE_DEEP_DIVE.md(源文档指向的 Chrysler panda 安全函数级专题)
    • 项目 02 —— openpilot-research/PANDA_BOARD_DEEP_DIVE.md(源文档指向的板级执行专题)

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

    更新记录

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

  • selfdrived 事件生产者:53 个直接事件与两道提前返回

    结论:SelfdriveD.update_events() 在同一帧内直接引用 53 个 EventName,但它们只是在「生产者」一侧被加入;加入事件之后仍要经过状态门与告警仲裁,才能变成驾驶员听到的告警或执行器退出。两道提前返回(未初始化、CP.passive)会让后续平台健康检查整体跳过,而 cruiseMismatch/noGps 在事件类型映射中为空,不能凭名字推断会触发禁入或退出。以下基于固定源码快照 c8fb906 的静态分析,未运行端到端时序或实车验证。

    每帧入口与两道提前返回

    update_events() 每帧先执行 clear():它保留静态事件并更新计数,随后处理调试、大模型与一次性启动提示。若 initialized=False,函数添加 selfdriveInitializing 后立即返回,此时不运行后续的驾驶员监测、车况、硬件与通信故障检查。也就是说「初始化中」这一种状态会掩盖其他所有平台健康判定。

    初始化本身在 data_sample() 中等待 canValid && sm.all_checks(),或约 6 秒超时;超时只结束等待,不保证订阅数据有效。初始化完成后可以处理书签,但 CP.passive 又会使函数提前返回。carUnrecognized/dashcamMode 是构造时添加的静态事件,startupNoCar/startupNoControl 等则通过 startup_event 变量在首轮间接加入,所以不能从 53 个直接名称的计数推断它们缺席。

    53 个直接事件按来源分组

    下表按各来源首次直接引用的顺序,列出相应的 EventName 与生产条件的中文摘要。同一帧可能追加多个事件,Events 的有序插入并不等于去重。

    来源 直接 EventName(摘要) 条件及边界
    调试与大模型 joystickDebug、bigModelLoading、bigModelFailed、lateralManeuver、longitudinalManeuver、selfdriveInitializing 大模型失败是有条件的软退出(失败/失联/外设消失边沿),非每帧无条件触发;机动实验按接收帧选择,横向优先
    书签与巡航恢复 userBookmark、userBookmarkNotPaired、resumeBlocked 非 PCM 巡航、设定速度 vCruise > 250 且加速/恢复按键出现时阻止恢复
    驾驶员监测 tooDistracted、driverDistracted1~3、driverUnresponsive1~3 非 notCar 才检查;lockout 禁止使能;alertLevel 1/2/3 与 activePolicy 是否 vision 决定两套提示
    CAN 与踏板 pcmEnable、pedalPressed CS.canValid 后才转发 CarEvents;油门上升沿需开启 DisengageOnAccelerator
    设备健康 overheat、outOfSpace、lowMemory、fanMalfunction 热状态达 overheated;非仿真空闲空间 <7% 或内存 >90%;风扇见下文数值门
    标定与车道偏离 calibrationIncomplete、calibrationRecalibrating、calibrationInvalid、ldw 标定状态非 calibrated 时三选一;IsLdwEnabled 且左右偏离任一成立才产生 ldw
    panda 安全 controlsMismatch、relayMalfunction 与 safetyConfigs 逐项比较;额外 panda 必须 silent/noOutput;mismatch_counter >= 200 可直接触发
    系统/总线/通信 processNotRunning、cameraMalfunction、canBusMissing、canError、commIssue 等 进程故障优先并跳过相机检查;总通信问题仅在 all_checks() 失败且大模型不在缓冲期时产生
    定位/参数/传感器 posenetInvalid、locationdTemporaryError、paramsdTemporaryError、sensorDataInvalid 主题曾被收到才看有效位;加速度计或陀螺仪任一主题距最近接收帧超过约 10 秒
    提示与副作用 fcw、steerSaturated、noGps、modeldLagging、personalityChanged FCW 只生产提醒不下发制动;转向饱和看目标/实际横向加速度比值;驾驶风格在按键释放时循环

    车辆侧的更多事件由 CarEvents.update() 转发,它还会产生车门、安全带、挡位、定速、转向和品牌专属事件,并由 CS.canValid 门控;车辆接口上报的 CarState 信号真假、车型安全钩子和线束电气行为不由这一层证明。

    几个容易被名字误导的事件值得单列。excessiveActuation 只在非 notCar 且有校准姿态时调用 ExcessiveActuationCheck.update(),首次触发会写驻车告警并把内存标志置真,之后才持续添加,构造时还会从已有的 Offroad_ExcessiveActuation 恢复该标志。变道提示同理:模型处于预变道且对应方向盲区被占用时阻止提示,进入开始或结束阶段时才发 laneChange——这些是模型状态消费,不代表真实盲区已被验证。

    数值门与告警边界

    这些条件的触发都绑定具体数值门,且多数在「严格超过某段时间对应的帧数」之后才成立。下表汇总关键阈值。

    项目 阈值 / 时间门
    空闲空间 / 内存 非仿真下空闲空间 <7%,或内存使用 >90%
    风扇故障 已知 panda、风扇目标 >50% 且缓存转速 <500 rpm,严格超过 15 秒对应帧数;注释称「5 秒」,数值实现为 15 秒
    未初始化超时 约 6 秒(只结束等待,不保证数据有效)
    panda 配置不匹配 严格超过 10 秒对应帧数;或 safetyRxChecksInvalid / mismatch_counter >= 200 直接触发
    传感器新鲜度 加速度计或陀螺仪任一主题距最近接收帧超过约 10 秒
    转向饱和 横控活动且最近约 2 秒未检测到驾驶员转向,并满足比值与控制器 saturated
    原车巡航一致性 / GPS cruiseMismatch 约 6 秒;noGps 约 2 秒且累计无 GPS 行驶距离 >1500 m(映射为空)
    模型频率 modelV2.frameDropPerc > 1 即添加 modeldLagging

    需要特别记住两处:风扇反馈故障「注释 5 秒、实现 15 秒」,并且不检查反馈新鲜度;cruiseMismatch 与 noGps 在本快照的 EVENTS 映射中为空,而 Events.contains() 只看类型映射,所以它们不会据此生成禁入或退出类型。

    生产条件不等于告警或退出

    update_events() 对某些系统故障有意设置了优先级与缓冲:总通信问题仅在 all_checks() 失败、无其他新增系统禁入/退出故障、且大模型不在加载或就绪后 5 秒缓冲期时才产生;进程故障优先,alive 优先于平均频率。这些都是「生产条件」层面的取舍,不是最终告警时刻。

    系统与通信故障内部也排了优先级:manager 指出应运行而未运行的进程时先报进程故障并跳过相机检查,recv_frame=0 的首次消息不通过该门,缓存故障可以持续触发事件;车况的 canTimeout 优先于 !canValid,且只有 openpilot 纵控才检查雷达错误,雷达 CAN 错误归入 canError。

    因此插件化时,传感器、模型与车型插件必须提供时间戳、数据有效位和失联证据,核心才保留事件归一化、状态机与 panda 安全约束。下一步应逐事件追到上游生产消息及回调告警,配合进程回放、SIL/HIL 和封闭场地验证;本文没有运行测试或控制车辆。

    常见问题

    未初始化时为什么很多故障检查根本不运行?

    因为 clear() 后若 initialized=False,函数会添加 selfdriveInitializing 并立即返回,后续的驾驶员监测、车况、硬件与通信故障检查全部被跳过。

    这 53 个直接事件就是全部事件吗?

    不是。这只是 update_events() 中直接引用的名称;启动事件通过变量间接加入、车辆事件由 CarEvents 转发,全部 99 个名称与类型组合另见静态事件索引。

    风扇故障到底是 5 秒还是 15 秒?

    注释称「5 秒」,但数值实现为 15 秒对应帧数;并且该检查不核对反馈新鲜度,只按「从最后一次健康处理帧起严格超过 15 秒」成立。

    cruiseMismatch 或 noGps 触发后会退出吗?

    不能这样推断。这两个 EventName 在本快照的 EVENTS 映射中为空,而 Events.contains() 只看类型映射,所以不会据此生成告警/退出类型。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SELFDRIVE_EVENT_PRODUCERS_DEEP_DIVE.md(53 个直接事件的生产条件、两道提前返回与数值门)
    • 项目 02 —— openpilot-research/SELFDRIVE_EVENT_CATALOG.md(全部 99 个名称与类型组合的静态事件索引)

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

    更新记录

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

  • CarEvents 判定分两级:1 组通用条件加 5 个品牌分支

    结论:CarEvents 是「车况 → 使能/退出事件」的适配层:它只把 CarState、上一帧状态与最近一次控制请求翻译成事件名,本身不发 CAN,也不替代 panda 固件安全钩子。它的判定分两级——先跑一组通用条件,再进入 5 个显式品牌分支;并且只在 CS.canValid 为真时被调用,CAN 无效时走的是另一条独立故障路径。以下为固定源码快照 c8fb906 的静态追踪。

    CarEvents 在链路里的位置

    card.state_update() 经车型 CarInterface.update() 形成 carState;selfdrived.update_events() 只在 CS.canValid 时调用 self.car_events.update(CS, CS_prev, sm['carControl']) 并合并返回的事件。若 CAN 无效,selfdrived 走 canError/canBusMissing 等独立故障路径,因此不能把「CarEvents 忽略无效报文」误解为此时仍可使能。

    CarEvents 的输入有三层时间语义:当前 CS 是车辆观察,上一帧 CS_prev 用于巡航与转向故障等边沿判断,CC 是最近被消费的控制请求,并不代表车端执行器已按该请求动作。update() 对 body/mock 直接返回空容器,其余品牌先过通用条件,再进入本文件显式实现的品牌分支;未被显式实现的品牌并非不支持,而是使用通用条件与车型自身的 CarState/CarParams/接口规则。

    通用条件 create_common_events() 覆盖什么

    create_common_events() 创建一个新的 Events 容器,从车型接口的 DRIVABLE_GEARS、CP.pcmCruise 与品牌判断中决定可行驶挡位与 PCM 使能方式。它覆盖四类信息:乘员与挡位、原车辅助与模式、速度/制动/准备状态、以及用户覆盖与请求。倒挡是一个典型边界——当倒挡不属于车型 DRIVABLE_GEARS 时,源码会同时产生 wrongGear 与 reverseGear 两个事件。

    速度与准备类条件里,vEgo > MAX_CTRL_SPEED 产生超速事件,brakeHoldActive、parkingBrake、accFaulted、carNotReady 各有对应事件;其中制动保持只在 openpilot 纵向控制时才加入。传感器与转向类则把 vehicleSensorsInvalid、invalidLkasSetting、lowSpeedAlert 与 steerFaultTemporary/Permanent 写成事件。

    条件组 代表字段与 EventName 状态机含义
    乘员与挡位 doorOpen、seatbeltUnlatched、wrongGear、reverseGear 门/带/挡位常映射 NO_ENTRY 与软退出;倒挡另有用户退出。
    原车辅助/模式 cruiseState.available、nonAdaptive、espDisabled/espActive、stockFcw/stockAeb/stockLkas 原车巡航/稳定控制状态与原车碰撞/车道功能被映射为提示或禁止使能。
    速度/制动/准备 vEgo > MAX_CTRL_SPEED、brakeHoldActive、parkingBrake、accFaulted、carNotReady 分别产生超速、制动保持、驻车制动、ACC 故障与未就绪事件。
    用户覆盖/请求 steeringPressed、steeringDisengage 上升沿、gasPressed、buttonEnable、cancel 按钮 转向/油门覆盖、用户退出与启用请求分别走不同类型。
    传感器/转向 vehicleSensorsInvalid、invalidLkasSetting、lowSpeedAlert、steerFaultTemporary/Permanent 加入传感器与转向故障事件,暂时故障另有提示强度选择。

    PCM 启用门与边沿语义

    通用 PCM 启用门的条件是 CP.pcmCruise and brand != 'honda'。原车巡航 enabled 出现上升沿且 blockPcmEnable=False 时产生 pcmEnable,未启用时产生 pcmDisable;非 PCM 车型通常由 CarState.buttonEnable 产生 buttonEnable。这些事件分别被映射到 ENABLE 或 USER_DISABLE,最终由下游状态机裁决。

    这里有一个容易误判的细节:Hyundai 的 cancel 按钮只有在非 PCM 巡航时才会被本层转为 buttonCancel,因为源码明确避免把某些车的暂停/恢复复用键误作取消。同理,steeringDisengage 只在上升沿加入,不会在持续按下的每一帧重复产生。

    临时转向故障为什么不是布尔映射

    steerFaultTemporary 不是简单地把某个位映射成事件。steering_unpressed 记录未按方向盘的帧数,no_steer_warning 与 silent_steer_warning 决定究竟发静默的 steerTempUnavailableSilent 还是更强的 steerTempUnavailable。近 1.5 秒内有手动覆盖、静止或仍处静默阶段时偏向静默提醒;永久转向故障另发 steerUnavailable。

    隔离探针给出一个可复现的数量关系:在本快照 DT_CTRL=0.01 下,静默判定对应 150 帧,即 int(1.5/DT_CTRL)。这些计数取决于控制循环节奏与上游 CarState 更新节奏,不能从车型 CAN 原始位直接推出屏幕告警强度,这个 150 也不是所有车型的告警延迟保证。

    5 个品牌补充规则

    品牌分支处理的是通用条件表达不了的低速与边沿特例。Chrysler 用 0.5 m/s 迟滞区间避免速度边界抖动;Honda 在品牌分支内单独处理 PCM 低速与巡航退出;Toyota 的低速判断只在 OP 纵向启用时评估;GM 在「静止且踩刹车并且网络位置为前视摄像头」时跳过低速使能事件;Volkswagen 则在控制请求使能后才额外加入 speedTooLow。

    品牌 源码条件 产生的事件/目的
    Chrysler vEgo < minSteerSpeed + 0.5 置提醒,vEgo > minSteerSpeed + 1.0 清除 belowSteerSpeed;0.5 m/s 区间避免边界抖动。
    Honda PCM 巡航时低于 minEnableSpeed;巡航启用上升沿;巡航丢失且控制请求不在继续制动 belowEngageSpeed、pcmEnable、speedTooLow/cruiseDisabled;保留低速继续制动特例。
    Toyota 仅 OP 纵向;standstill 且未踩刹车、控制请求 resume 或混动标志;低于最低使能速度 resumeRequired、belowEngageSpeed;请求加速度大于 0.3 时 speedTooLow。
    GM 低于 minEnableSpeed,但 standstill && brakePressed && networkLocation=fwdCamera 例外 通常 belowEngageSpeed;前述条件允许驻车踩刹车时不产生该事件。
    Volkswagen 仅 OP 纵向;vEgo < minEnableSpeed + 0.5,且控制使能时 vEgo < minEnableSpeed belowEngageSpeed 和 speedTooLow。

    要强调:以上只是 CarEvents 中的品牌分支,不是这些品牌完整的 CAN/DBC/安全策略。事件容器本身不负责禁用执行器,最终状态由 StateMachine.update() 决定,执行活动由 controlsd 决定,panda 固件仍负责独立发送限制。

    常见问题

    CarEvents 会直接下发 CAN 或让车退出吗?

    不会。它只产生事件名交给下游:gasPressedOverride、steerOverride、buttonCancel、wrongGear 等分别被 events.py 映射到覆盖、用户退出或软退出,然后由状态机、controlsd 与 panda 固件分层决定最终行为。

    为什么倒挡会同时出现两个事件?

    因为当倒挡不属于车型的 DRIVABLE_GEARS 时,代码会同时产生 wrongGear 与 reverseGear。隔离探针确认了这个组合,具体结果还要看车型的 DRIVABLE_GEARS 定义。

    PCM 车和非 PCM 车的启用事件一样吗?

    不一样。通用 PCM 启用门是 CP.pcmCruise and brand != 'honda',PCM 车靠巡航 enabled 上升沿产生 pcmEnable;Honda 在品牌分支单独处理;非 PCM 车通常由 CarState.buttonEnable 产生 buttonEnable。

    1.5 秒的静默提醒延时对每辆车都成立吗?

    不成立。150 帧来自本快照 DT_CTRL=0.01 与 int(1.5/DT_CTRL),计数还取决于控制循环与上游 CarState 的更新节奏,不是所有车型实际告警延迟的保证。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CAR_EVENTS_GATEWAY_DEEP_DIVE.md(CarEvents 的通用条件、PCM 门、临时转向故障与 5 个品牌分支)。
    • 项目 02 —— openpilot-research/ 同目录探针记录:五项通用条件与六项品牌契约隔离探针结论。

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

    更新记录

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

  • calibrationd 的 5 块门槛:500 个合格样本与名义 10 次平滑

    结论:calibrationd 的「已标定」不是一次测量结果,而是要凑满 5 个有效分块,即至少 500 次被接受的样本。样本门只认严格的高速直行与低不确定度:车速与视觉前向速度都要高于 15 mph 对应值、偏航率低于 2°/s;前 5 块会放宽不确定度门,但不放宽速度门。持久快照能让进程重启快速回到名义状态,但那不是新行程重新测得 500 个独立样本。

    主循环与输入有效性

    main() 读取 CarParams,以 cameraOdometry 作为轮询主题,接收 carState,并创建 Calibrator(param_put=True)。每当收到更新的相机里程计,就先把当前 carState.vEgo 交给 handle_v_ego(),再把视觉平移、旋转、广角外参、标准差和路面高度输入 handle_cam_odom()。服务表给 cameraOdometry 配置名义 20 Hz,extrinsicsCalibration 为 4 Hz。

    循环每 5 次 SubMaster.update() 调用一次 send_data(),消息顶层 valid 取 sm.all_checks()。但样本更新段仅判断 sm.updated['cameraOdometry'],没有在进入 handle_cam_odom() 前显式判断该消息的 valid;输出有效位和内部估计更新是两条不同的门。主循环相位探针还确认:仅 carState 更新不触发估计;相机更新时若没有新的车况,就复用上次 carState.vEgo。因此样本门使用的车速没有独立的新鲜度检查。

    还有一个节拍细节:SubMaster.update_msgs() 每次调用都会推进 frame,包括 poll 超时后无新消息;因此源码注释中的「4 Hz driven by cameraOdometry」是正常输入下的名义速率,不是无消息时严格不发布。隔离探针确认:首次更新后 frame=0 即发布,此后 frame=5 再发布,且无相机更新仍可发布 valid=False 的输出。

    样本接受门

    handle_cam_odom() 先将旧姿态平滑权重按每次调用 1/SMOOTH_CYCLES 递减,再执行下列判定。任一条不满足即丢弃本次样本。

    条件 源码阈值 结果
    直行高速 carState.vEgo > MIN_SPEED_FILTER 且 trans[0] > MIN_SPEED_FILTER、abs(rot[2]) < MAX_YAW_RATE_FILTER 车速与视觉前向速度都严格高于 15 mph 对应的 m/s,偏航率严格低于 2°/s;任一不满足即丢本次样本。
    方向估计 atan2(trans_std[1], trans[0]) < MAX_VEL_ANGLE_STD 横向平移不确定度角严格低于 0.25°;源码用 trans_std[1],不是三轴方差的综合门。
    高度估计 若 roadTransformTransStd 长度为 3,则第 3 项 < exp(-3.5) 约 0.030 的长度单位;若列表长度不是 3,代码把此项视为通过,而非拒绝。
    初始放宽 (rpy_certain && height_certain) || valid_blocks < INPUTS_NEEDED 前 5 个有效分块可绕过后两项不确定度门,但仍需满足直行高速。

    sanity_clip() 将 NaN 姿态回退初值,并把 pitch/yaw 限在有效范围外扩 0.005 弧度的区间;它是数值夹取,不是「校准有效」的判定。PITCH_LIMITS 在 mici 上与其他设备不同,YAW_LIMITS 共用。

    统计窗口、状态转换与平滑

    moving_avg_with_linear_decay() 在当前 100 样本块内更新姿态、广角角度与高度;只在接受样本后前进索引,满块时递增 valid_blocks 并让 block_idx 在 50 块窗口内环绕。因此「完成 5 块」表示至少 500 次被接受的样本,并非固定 25 秒或任意 500 帧。get_valid_idxs() 会从统计中排除当前正在写的块。

    状态 判定 边界
    uncalibrated 已完成块不足 5 块 原先处于 recalibrating 则保留该状态。
    calibrated 达到 5 块且平均 pitch/yaw 在严格限制内 否则为 invalid。
    recalibrating 当前为 calibrated 且 pitch 跨度 > 4° 或 yaw 跨度 > 2° 以最近块重置为 1 块、保存旧姿态,按 SMOOTH_CYCLES=10 次调用递减权重平滑;高度跨度目前只留 TODO。

    平滑权重在样本拒绝之前递减。从 1.0 开始以二进制浮点每次减 0.1,第 10 次后仍有约 1.39e-16 的正残值,第 11 次才由 max(0, ...) 精确归零。所以「10 次平滑」是名义次数,不代表 10 个被接受的样本或严格固定时间。

    持久状态与消费者

    Calibrator.__init__() 读取持久的 CalibrationParams,但仅在 param_put=True 时解析旧 rpyCalib、validBlocks、广角角度和高度;解析失败则保留默认值。update_status() 在 idx==0 且 block_idx % 10 == 5 时写入 get_msg(True).to_bytes()——这是分块节拍上的 Params 写入,使用 True 构造持久消息,并非当次 sm.all_checks() 的快照。此外,CP.notCar 时输出被覆盖为已标定、100%、零姿态,不能以仿真/非汽车输出证明真实传感器已标定。

    下游有三个消费者,且有效性门并不一致:modeld 在外参主题更新时更新模型 warp;AugmentedRoadView 还要求消息有效及状态已标定才更新叠加矩阵;selfdrived 根据标定状态产生禁止进入/软退出事件。接入新相机时,不能用其中一个界面的「显示正常」替代端到端验证。

    测试范围与硬件插件要求

    test_calibrationd.py 覆盖持久状态读取、低速/大偏航拒样、高不确定度在前 5 块后拒样、pitch/yaw 分散度触发重标定;原版测试本次因缺少 libparams_c.dylib 在收集阶段未执行用例,而三组隔离探针(快照边界五项、筛样五项、主循环相位四项)通过。自制相机/IMU 插件要明确输出坐标、量纲、时间戳、标准差与安装几何;插件不得直接伪造 calStatus=calibrated 或持久 CalibrationParams 来绕过测量链。

    常见问题

    calibrationd 的「完成 5 块」到底代表什么?

    代表至少 500 次被接受的样本(每块 100 个合格样本)。它不是固定 25 秒,也不是任意 500 帧;只有通过样本门、被真正接受的样本才会让块内索引前进。

    为什么说车速没有独立的新鲜度检查?

    因为样本更新段只判断 sm.updated['cameraOdometry'];相机更新到达时若没有新的车况,就复用上次的 carState.vEgo。能否接受仍由 handle_cam_odom() 的速度与不确定度门决定,而不是车速本身是否新鲜。

    高度标准差为空的列表为什么不会拒样?

    源码只在 roadTransformTransStd 长度等于 3 时检查第 3 项 < exp(-3.5);长度不为 3 时该分支直接把高度判为「确定」,空列表亦可接受。这是实现细节,不代表数据缺失被主动识别。

    重启后立即「已标定」是重新测出来的吗?

    不是。持久快照(CalibrationParams)能让进程重启时迅速恢复名义标定状态,但那不是新行程重新测得 500 个独立样本;对持久快照的异常形状也应单独核查。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/CALIBRATIOND_STATE_PERSISTENCE_DEEP_DIVE.md(取用了样本接受门、统计窗口与状态转换、持久状态与三个消费者,以及测试范围)。
    • 项目 02 —— openpilot-research/GEOMETRY_TRANSFORMS_HARDWARE_DEEP_DIVE.md(仅用于确认相机/IMU 坐标定义与外参几何前提)。

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

    更新记录

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