标签: 告警仲裁

  • selfdrived 告警仲裁:99 个 EventName 里只留 1 条上屏

    结论:同一帧里可以同时成立多个故障事件,但最终屏幕文字、声音与 HUD 图像只会呈现一条告警。selfdrived 用「事件容器(有序但不去重)→ 状态机(按类型优先级)→ AlertManager(按 alert_type 每帧留一条)」这三段流水线完成仲裁,任何一段的边界都会让结果和直觉不同。本文为固定源码快照 c8fb906 的静态分析,不等于独立硬件安全保证。

    从车辆状态到消息输出

    SelfdriveD.__init__() 等待或接收 CarParams,创建车辆事件适配、消息订阅、告警管理器和状态机。carState 用独立 socket 接收,以它作为步进输入;data_sample() 再非阻塞更新其余主题。初始化须满足 CS.canValid && sm.all_checks(),或约 6 秒超时进入初始化完成路径;这不是「超时后所有输入都变有效」,后续仍会创建故障事件。

    step() 每次先采样、聚合事件;只有 CP.passive=False 且完成初始化才调用状态机;随后更新告警并发布消息。params_thread() 约每 100 毫秒刷新单位、车道偏离、油门踏板退出、实验模式和驾驶风格。异步参数刷新与控制步进并行,不能假设 UI 写完 Params 后下一条 selfdriveState 已同步使用全部设置。

    publish_selfdriveState() 将 enabled、active、状态、engageable、告警文案/声音/HUD 图像写入 selfdriveState;engageable 的条件是当前没有 NO_ENTRY 事件,并不等于当前已使能。onroadEvents 每约 1 秒或事件名单变化时发布。controlsd 用 enabled/active 再结合车辆故障、静止门和纵向覆盖决定 latActive/longActive;pandad 还要求 selfdriveState 消息 alive/valid 才把 engaged 告知 panda。软件状态机、控制器活动门和 panda 固件安全门是三个不同层次。

    事件容器为什么不去重

    Events 保存当前帧有序事件、静态事件以及每个 EventName 的连续出现计数。clear() 先更新计数再仅保留静态事件,随后 SelfdriveD.update_events() 按来源追加动态事件。contains(ET) 查询事件的类型映射;create_alerts() 只为本状态允许的类型创建 Alert,并用 creation_delay 抑制短暂出现的告警。

    add() / add_from_msg() 用 bisect.insort 排序,但不去重;重复事件会产生重复的 create_alerts() 结果,计数器仍按「该事件名是否出现」每帧只加一次。static=True 会同时加入静态列表和当前列表,连续 clear() 会保留静态项并继续累计它的计数。创建延迟按 DT_CTRL * (之前连续帧数 + 1) 判断当前帧,等号也允许产生告警;回调在延迟判断前调用,异常直接向调用方传播。

    生产路径并非完全没有重复机会:bigModelFailed 可由失败边沿与活动模型故障段同帧添加,canError 可由雷达错误与车辆 CAN 无效同帧添加;这四项分支由隔离探针确认,但不证明实车同帧共现。同一 EventName 还可以有多个类型,例如 controlsMismatch 同时映射 IMMEDIATE_DISABLE 与 NO_ENTRY,cameraMalfunction 有 PERMANENT/SOFT_DISABLE/NO_ENTRY;哪个 Alert 真正参与显示取决于状态机当前的 current_alert_types。

    事件类型 状态意义 典型例子
    ENABLE / NO_ENTRY 启用请求 / 禁止进入 按键或 PCM 启用;系统初始化、车型能力、故障。
    PRE_ENABLE 进入预使能等待 需要条件继续满足才进入 enabled。
    OVERRIDE_LATERAL / OVERRIDE_LONGITUDINAL 驾驶员覆盖某执行维度 overriding 状态。
    SOFT_DISABLE 给驾驶员接管窗口 摄像头故障、进程失联、通信问题。
    IMMEDIATE_DISABLE / USER_DISABLE 立即退出 / 用户主动退出 安全模式不匹配、CAN 总线丢失、踏板/取消。
    WARNING / PERMANENT 活动状态展示 / 始终候选 转向临时不可用、FCW/LDW、启动与硬件提示。

    状态机的优先级

    StateMachine.update() 每步先递减软退出计时器,并始终加入 PERMANENT 告警类型。非 disabled 状态下,IMMEDIATE_DISABLE 优先于 USER_DISABLE,两者优先于软退出或覆盖。disabled 只有在出现 ENABLE 且没有 NO_ENTRY 时才能进入 preEnabled、overriding 或 enabled。ENABLED_STATES 包含 preEnabled、enabled、softDisabling、overriding;ACTIVE_STATES 不含 preEnabled,所以「enabled」与「执行器正在活动」并非同义词。

    当前状态 条件 结果
    enabled SOFT_DISABLE 进入 softDisabling,计时约 3 秒。
    enabled 横/纵覆盖 进入 overriding,继续 enabled=True, active=True。
    softDisabling 故障消失 回 enabled;若持续至计时结束则转 disabled。
    preEnabled PRE_ENABLE 消失 转 enabled;否则停留预使能。
    overriding 覆盖消失 回 enabled;若出现软退出则转 softDisabling。
    任意非 disabled IMMEDIATE_DISABLE 或 USER_DISABLE 直接变成 disabled。

    SOFT_DISABLE_TIME 是按 DT_CTRL 换算的步数,取决于循环是否按预期执行;最坏墙上时间和驾驶员实际听到提醒的延迟需要实测。源码还有两个易误判的边界:处于 disabled 且同帧有 ENABLE + IMMEDIATE_DISABLE、却没有 NO_ENTRY 时,代码只走 disabled 分支、结果进入 enabled;处于 preEnabled 时若 PRE_ENABLE 消失但 SOFT_DISABLE 仍在,会先进入 enabled 且返回 active=True,下一次调用才进入 softDisabling。

    告警仲裁:一帧只留一条

    AlertEntry.active() / just_added() 用包含端点的 frame <= end_frame 判定有效;只有当前帧紧挨上次加入帧且原条目仍有效才保留旧 start_frame。AlertManager.add_many() 以 alert_type 为键保留告警:每次覆盖该槽的负载和 added_frame,结束帧取 max(frame + 1, start_frame + duration),duration 单位是控制帧而非墙上秒。间隔一帧以上的重加即使旧告警还有效,也会重置 start_frame。

    process_alerts() 先把匹配 clear_event_types 的条目 end_frame 设为 -1,再从仍有效的条目按 (priority, start_frame) 最大值选择。完全同分时使用严格 >,字典中先插入的 alert_type 胜出;同一键的不同负载则由最后一次 add_many() 覆盖,并不产生第二个候选。SelfdriveD.update_alerts() 在不活动时清理 WARNING,已使能时清理 NO_ENTRY。因此同一帧可有多个事件,但只有一个当前显示/声音告警。set_offroad_alert() 另把驻车提示写入 Params,属于另一呈现通道,不参加本次 onroad AlertManager 排序。

    常见问题

    同一帧有多个故障,屏幕上为什么只显示一条告警?

    因为 process_alerts() 只从有效条目里按 (priority, start_frame) 最大值选一条;完全同分时用严格 >,字典中先插入的 alert_type 胜出。事件列表可以有多条,最终呈现的告警只有一条。

    enabled=True 是不是就代表执行器已经动了?

    不是。ENABLED_STATES 含 preEnabled,而 ACTIVE_STATES 不含它;enabled 只表示状态机处于使能族。controlsd 还要结合车辆故障、静止门和纵向覆盖决定 latActive/longActive,pandad 另要求 selfdriveState 消息 alive/valid。

    重复添加的事件会被自动去重吗?

    不会。add() / add_from_msg() 用 bisect.insort 排序但不去重,重复事件会产生重复的 create_alerts() 结果,计数仍按「该事件名是否出现」每帧只加一次。插件接入不应依赖容器自动去重。

    软退出(soft disable)给的 3 秒是按墙钟算的吗?

    不是。SOFT_DISABLE_TIME 是按 DT_CTRL 换算的步数,取决于循环是否按预期执行;最坏墙上时间和驾驶员实际听到提醒的延迟需要实测。

    延伸阅读

    参考资料

    • 项目 02 —— openpilot-research/SELFDRIVE_EVENTS_STATE_ALERTS_DEEP_DIVE.md(事件容器、状态机与 AlertManager 的三段仲裁机制与故障边界)。
    • 项目 02 —— 同目录探针记录:五项 Events 容器、五项状态机转移、五项 AlertManager 仲裁隔离探针结论。

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

    更新记录

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