UIState.update() 在绘制之后:告警时延与 5 Hz 刷新

作者:

在

结论:车载 UI 是呈现与交互层,不裁决自动驾驶使能,权威状态来自 hardwared、selfdrived、card 与模型进程。决定告警时延的是一条容易被忽略的顺序:GuiApplication.render() 的生成器在绘制顶层 Widget 之后才结束本帧绘制,本轮 UIState.update() 因此发生在绘制之后,新消息通常在下一帧才用于 Widget——所以 uiDebug.frameTimeMillis 不能当成「消息发布到屏幕显示」的端到端时延。本文为对本地固定源码快照的静态阅读,逐个像素、全部语言与 GPU 合成均未验证。

入口、数据与帧顺序

ui.main() 初始化窗口后,按屏幕尺寸创建 MainLayout 或 MiciMainLayout,每次从 GuiApplication.render() 的生成器取得帧状态再调用 UIState.update()。屏幕关闭时生成器仍周期性 yield False,让 UIState 有机会更新并重新唤醒屏幕。触摸事件经 MouseState 由顶层 Widget 分发到子 Widget 或导航栈。

组件/关键函数 实现作用
UIState._initialize() 建立单例与大量 cereal 主题订阅,初始化 started、status、车型、实验模式和 Chestnut 状态
UIState.update() 非阻塞 SubMaster.update(0),刷新状态/告警枚举,再调用屏幕设备管理;约 5 Hz 后台线程读取较慢的 Params
UIState._update_state() / _update_status() 从 pandaStates、deviceState、selfdriveState 派生点火/行车/使能状态并通知边沿回调;started 要求 deviceState.started 且自身缓存点火为真
UIState._update_chestnut_state() 根据设备发现、模型编译产物、新鲜度与 Params 显示断开/加载/激活/失败,与固件刷写状态不是同一状态机
Device._update_brightness() 行车时按相机曝光估算环境亮度并平滑,后台线程设置背光;触摸、点火、交互超时控制屏幕电源
Widget.render() / _process_mouse_events() 先更新状态再绘制,然后处理触摸;只接受从自身命中区域开始的触摸,长按达 0.45 秒后清除点击跟踪

需要单独记住的是:UIState.update() 里的非阻塞订阅只刷新枚举与屏幕设备管理,较慢的参数项由约 5 Hz 的后台线程承担,这两者的节奏并不相同。

页面与关键交互

MainLayout._set_mode_for_state() 管理首页、设置、行车三态;行车切换会自动进入摄像头画面,侧边栏可由行车画面触摸切换。SettingsLayout 分为 Device、Network、Toggles、Software、Firehose、Developer 六面板,其中 TogglesLayout._update_toggles() 会根据车型纵向能力、分支和 engaged 状态限制开关。

页面/组件 关键入口 行为
大屏主导航 MainLayout._set_mode_for_state() 首页、设置、行车三态;行车切换自动进入摄像头画面,body/notCar 特殊处理
大屏首页/侧边栏 HomeLayout._update_state() / Sidebar._update_state() 显示更新/驻车告警、网络/温度/连接/设备状态,可进入设置或发布书签
大屏设置 SettingsLayout Device、Network、Toggles、Software、Firehose、Developer 六面板,开关受车型与状态限制
首次引导 OnboardingWindow 及其 mici 版本 条款版本与培训版本写 Params;mici 另延长交互超时并将亮度设为 100,退出后恢复
mici 主导航 MiciMainLayout._handle_transitions() 横向滚动驻车告警、首页、行车/Body;点火后约 2.5 秒切换行车,静止时返回行车

行车画面与告警

AugmentedRoadView._render() 先绘制摄像头,再按顺序叠加模型路径、HUD、告警和驾驶员状态,且仅在 ui_state.started 时执行。CameraView._render() 非阻塞读取 VisionIPC:没有新帧时保留上一帧,连接丢失时清空并绘制占位;comma 硬件走 EGL 直接缓冲,PC 走 Y/UV 纹理拷贝。ModelRenderer._render() 要求模型和标定至少在本次行车之后收到过。

相机画面的连接管理也不简单:_ensure_connection() 对重连做了节流,_handle_switch() 要等目标流有帧才完成宽/窄切换,而宽/窄流的选择取决于实验模式与速度门限;_update_calibration() 只消费有效标定矩阵。屏幕上的路径只是视觉呈现,不是控制输出的独立证明。

告警侧的两个时间门值得单列:AlertRenderer.get_alert() 在 10 秒未收到首次行车 selfdriveState 时显示等待;在 comma 硬件上,已收过状态但断流超过 5 秒时先显示接管/系统失联,再显示重启提示;大屏实现还会拒绝前一行车周期的旧告警。mici 的 AlertRenderer.get_alert() 有相同超时分支,但正常告警路径没有大屏的 recv_frame < started_frame 过滤——这是一个需要回放验证的旧告警可见性风险,不是已实测问题。语音失联告警由 soundd 负责,不能由普通 UI 插件替代。

硬件与插件化边界

显示平台适配需提供可靠触摸、屏幕电源、背光、GPU/EGL 或纹理路径和相机图像零拷贝能力,并监测触摸到反馈、消息到危险告警、屏幕唤醒与画面丢帧的真实时延。装饰性页面与指标可以插件化,但接管告警、失联占位、条款/培训、驾驶员监测提示及设置权限应由受信任核心约束;插件不能覆盖 selfdriveState 权威主题,也不能在未完成车型和安全校验时直接开启控制选项。

现有 test_raylib_ui 只检查 UI 进程初始化;截图差异回放覆盖若干页面/告警视觉状态,但本次未运行。两种屏幕、触摸竞争、前后帧时延、长期断流与各语言文本溢出均尚未实测。

因此插件化要区分「装饰」与「权威」:装饰性页面与指标可自由替换,而告警呈现、失联占位、条款培训与设置权限必须留在受信任核心;插件也不得在未完成车型与安全校验时开启任何控制选项。

常见问题

uiDebug.frameTimeMillis 是端到端显示时延吗?

不是。生成器在绘制顶层 Widget 之后才结束本帧,本轮 UIState.update() 发生在绘制之后,新消息通常在下一帧才用于 Widget;因此该指标只是帧数据,不等于从消息发布到屏幕显示的端到端时延。

UIState 的状态刷新是实时的吗?

不是。状态订阅用非阻塞 SubMaster.update(0),而较慢的 Params 项由约 5 Hz 的后台线程刷新;屏幕关闭时生成器仍周期性 yield False 让 UIState 有机会更新并重新唤醒屏幕。

UI 的 started 状态是怎么定的?

UIState._update_state() 要求 deviceState.started 为真且自身缓存的点火为真,同时从 pandaStates/deviceState/selfdriveState 派生状态并通知边沿回调。

行车告警的等待与失联阈值是多少?

AlertRenderer.get_alert() 在 10 秒未收到首次行车 selfdriveState 时显示等待;comma 硬件上已收过状态但断流超过 5 秒时先显示接管/系统失联,再显示重启提示。mici 缺少大屏那层旧告警过滤,尚需回放验证。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/UI_RUNTIME_DEEP_DIVE.md(UI 入口、UIState 聚合、页面导航与行车告警时延)
  • 项目 02 —— openpilot-research/CHESTNUT_USB_FIRMWARE_DEEP_DIVE.md(源文档指向的 Chestnut 固件刷写状态专题)
  • 项目 02 —— openpilot-research/UI_SIDEBAR_STATUS_FRESHNESS_ACTION_CONTRACT_DEEP_DIVE.md(源文档指向的侧栏状态新鲜度与动作门专题)

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

更新记录

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

评论

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注