结论:在 EVENTS 主映射里共有 49 个回调槽位、22 种回调形式:两个工厂覆盖 27 槽位,20 个直接函数覆盖 22 槽位。回调在 creation_delay 延迟过滤之前执行,即使告警最终未显示也会读取输入、构造文本,且这里没有逐回调异常隔离。更关键的是,回调返回的 ImmediateDisableAlert 只改变文案、声音、视觉标记、优先级与持续时间,是否立刻退出仍由事件类型决定,而不是由告警子类名里有 Immediate 决定。本文为固定源码快照 c8fb906 的静态阅读,未实测墙上时间与声音输出延迟。
调用链:从输入到仲裁
SelfdriveD.update_alerts() 提供六个实参:CP、CS、sm、is_metric、soft_disable_timer 和驾驶风格枚举字符串。Events.create_alerts() 只处理当前状态允许的事件类型:值若是 Alert 实例则直接使用,否则调用回调创建新实例。随后按连续帧计数判断 creation_delay,写入 EventName/类型 键和事件类型。
| 环节 | 行为 | 边界 |
|---|---|---|
update_alerts() |
传入 CP、CS、sm、公英制、软退出计时与驾驶风格 |
输入字段来自当前 SubMaster 消息,跨主题时序需回放确认 |
create_alerts() |
只处理当前状态允许的事件类型;静态 Alert 直接用,否则调用回调 |
回调在 creation_delay 过滤前执行,无逐回调异常隔离 |
AlertManager.add_many() |
以 EventName/类型 键保存告警及结束帧 |
「事件已出现」与「UI 当前展示」可能不同步 |
process_alerts() |
按优先级、起始帧选当前项 | 最终仲裁仍在受控核心,不由插件决定 |
这里有一个容易被忽略的顺序细节:create_alerts() 在判断 creation_delay 之前就已经执行了回调,因此告警文案与动态数值是在过滤之前构造的。若某回调依赖尚未更新的输入,它可能读到上一帧的字段;反过来,一个最终因延迟未显示的告警,其回调副作用——例如读取 sm、构造字符串——仍已发生。由于没有逐回调异常隔离,单个回调抛错会影响同一容器内的处理顺序,这一点在插件化时必须由受控核心兜底。
两类软退出工厂:27 槽位
软退出相关的槽位由两个工厂覆盖,共 27 个。soft_disable_alert() 覆盖 24 个事件,包括 bigModelFailed、cameraMalfunction、cameraFrameRate、locationdTemporaryError、paramsdTemporaryError、steerTempUnavailable、steerTimeLimit、sensorDataInvalid、excessiveActuation、overheat、calibrationInvalid、calibrationIncomplete、calibrationRecalibrating、espDisabled、commIssue、commIssueAvgFreq、selfdrivedLagging、processNotRunning、radarFault、radarTempUnavailable、modeldLagging、posenetInvalid、lowMemory、espActive;user_soft_disable_alert() 覆盖 3 个:wrongGear、doorOpen、seatbeltNotLatched。
| 回调形式 | 槽位数与事件 | 运行结果 |
|---|---|---|
soft_disable_alert() |
24 个(含 overheat、radarFault、modeldLagging、lowMemory 等) |
软退出计数低于 int(0.5/DT_CTRL) 时构造 ImmediateDisableAlert 外观,否则构造 SoftDisableAlert |
user_soft_disable_alert() |
3 个:wrongGear、doorOpen、seatbeltNotLatched |
同样在剩余约 0.5 秒改成紧急外观,之前使用文案较缓和的 UserSoftDisableAlert |
关键区别在于:回调返回的 ImmediateDisableAlert 只改变告警的文案、声音、视觉标记、优先级与持续时间,Events.create_alerts() 仍将 event_type 标为映射键 SOFT_DISABLE。StateMachine.update() 是否立刻退出由事件类型决定,不会因为 Alert 子类名含 Immediate 就改走 IMMEDIATE_DISABLE。计数按 DT_CTRL=0.01 的名义周期换算,墙上时间和声音输出延迟尚未实测。
20 个直接回调:22 槽位
| 回调与主映射事件 | 消费字段和动态内容 |
|---|---|
joystick_alert():joystickDebug.WARNING |
从 carControl.actuators.accel/torque 计算调试文案;不是实车执行反馈 |
longitudinal_maneuver_alert():longitudinalManeuver.WARNING、lateralManeuver.WARNING |
两个事件都读 alertDebug 的两行文案;横向事件由 lateralManeuverPlan 触发但回调仍读 alertDebug |
invalid_lkas_setting_alert():invalidLkasSetting.PERMANENT |
根据 Tesla/Mazda/Nissan 品牌调整原车 LKAS 说明,其余用通用文案 |
paramsd_invalid_alert():paramsdTemporaryError.NO_ENTRY |
按 angleOffsetValid、steerRatioValid、stiffnessFactorValid 顺序挑第一个错误并附数值 |
camera_malfunction_alert():cameraMalfunction.PERMANENT |
对三路摄像头逐个调用 sm.all_checks([service]) 拼出失败名称 |
overheat_alert():overheat.PERMANENT |
取 CPU、GPU、内存温度的最大值显示;空列表用 0 兜底 |
comm_issue_alert():commIssue.NO_ENTRY |
对 sm.data 各主题重新执行 all_checks,只列前四个失败主题 |
personality_changed_alert():personalityChanged.WARNING |
显示当前驾驶风格字符串,持续时间 1.5 秒 |
其余直接回调包括 startup_master_alert()、below_steer_speed_alert()、wrong_car_mode_alert()、out_of_space_alert()、below_engage_speed_alert()、too_distracted_alert()、calibration_invalid_alert()、calibration_incomplete_alert()、process_not_running_alert()、modeld_lagging_alert()、posenet_invalid_alert()、low_memory_alert()。需要强调「槽位」是 EVENTS 中事件名与事件类型的组合,不等于 22 个独立事件,也不等于 49 条屏幕上同时可见的告警。此外 NoEntryAlert 在 mici 上会调换两行文字,EVENTS.update() 还覆写 9 个条目中的告警定义,实际设备呈现必须按设备类型重核。
从这些实现里还能看出一个共性:动态文案普遍消费当前订阅到的消息字段,例如 overheat_alert() 取 CPU/GPU/内存温度最大值、modeld_lagging_alert() 读 modelV2.frameDropPerc、posenet_invalid_alert() 计算 CS.vEgo - modelV2.velocity.x[0]。这些字段来自不同进程、不同采样时刻,回调本身不做新鲜度校验,因此「文案里显示的数值」不宜直接当作同一时刻的联合状态。
仲裁、测试与插件边界
AlertManager.add_many() 以 EventName/类型 键保存告警及结束帧,process_alerts() 按优先级、起始帧选当前项。update_alerts() 在不活动时清理 WARNING,已使能时清理 NO_ENTRY,这可能让「事件已出现」和「UI 当前展示」不同步。selfdriveState 再发布当前文案、声音与 HUD 标记。
测试覆盖存在缺口:test_alerts.py 的静态告警属性检查明确跳过回调值,test_alertmanager.py 主要验证持续帧逻辑,不能证明 20 个直接回调的所有输入边界正确。隔离探针确认回调在 creation_delay 过滤前执行、异常不在容器中隔离。重构成插件时应把输入观测和告警策略分层:插件提供可信字段与新鲜度,受控核心保留事件类型、软退出计时、回调异常处理和最终仲裁;不得让插件仅靠改文案或改告警子类来更改安全状态。
常见问题
回调返回 ImmediateDisableAlert 会让车辆立刻退出吗?
不会。create_alerts() 仍把 event_type 标为 SOFT_DISABLE,是否立刻退出由 StateMachine.update() 按事件类型决定,与告警子类名是否含 Immediate 无关。
回调只在告警最终显示时才执行吗?
不是。回调在 creation_delay 延迟过滤之前执行,因此即使最终未显示也会读取输入并构造文本,且这里没有逐回调异常隔离。
49 个槽位等于 49 条同时可见的告警吗?
不等于。槽位是 EVENTS 中事件名与事件类型的组合,既不是 22 个独立事件,也不是屏幕上同时可见的告警条数;mici 覆写还会改变实际呈现。
「事件已出现」和「UI 当前展示」总是同步吗?
不一定。update_alerts() 在不活动时清理 WARNING、已使能时清理 NO_ENTRY,因此事件状态与 selfdriveState 发布给 UI 的展示可能不同步。
延伸阅读
- 赛道仪表的信息架构:从圈速到告警的设计顺序 —— 告警优先级与呈现顺序的工程视角。
- 车载 UI 按钮、开关与确认控件 —— 另一处「可点击/可见状态不等于授权」的边界。
- 从使能到车轮:执行链上的八道门,以及它们各自不保证什么 —— 解释告警类型与实际退出为何必须分层。
- 车辆消息字段契约:CarState、CarControl、CarOutput 与 CarParams —— 本文多个回调消费的字段来源。
- CAN 报文解析的「健康位」在说什么:校验、计数器与总线空窗 —— 承接
all_checks类故障判据的语义。
参考资料
- 项目 02 ——
openpilot-research/DYNAMIC_ALERT_CALLBACKS_DEEP_DIVE.md(取用了回调槽位统计、两类软退出工厂、20 个直接回调、仲裁与测试缺口、插件边界及隔离探针结论)。 - 项目 02 ——
openpilot-research/CAR_MESSAGES_FIELD_CONTRACT_DEEP_DIVE.md(仅用于确认回调消费字段的消息来源)。
说明:本文为对固定源码快照的静态结构分析,未运行完整测试套件、未连接硬件、未验证目标车型的真实参数组合。文中所有阈值、行号索引与配置均出自源文档,标注为未验证的结论保留其原有边界。
更新记录
首次发布 2026-10-11 最近核对:2026-10-11
发表回复