车载 UI 控件:可点击不等于授权,滑动确认不重检车况

作者:

在

结论:界面控件「可点击、可见、已滑动确认」都不是车辆控制授权,也不构成功能安全保证。Toggle 先翻转内存状态再调用回调,set_state() 甚至不触发业务回调;MultiOptionDialog 的「显示层选择」与「持久化」是两步;滑动确认只减少偶发触碰,并不自动重检当前车辆状态。本文为固定源码快照的静态阅读,失败模式是源码边界推断,不是实车复现。

基础输入与弹窗

Button 复用基础 Widget 的点击回调和命中规则,_update_state() 根据按压与禁用选择背景/文字颜色,_render() 绘制按钮及文字。ButtonRadio 在释放后切换自身选中状态;IconButton 用纹理和透明度滤波表示状态。视觉上的 DANGER 风格只改变样式,不增加确认门槛。

控件 关键行为 边界
Toggle _handle_mouse_release() 先翻转内存状态、再调用可选回调 set_state() 不调用业务回调,也不代表 Params 已更新
ConfirmDialog 确认或取消时先 pop_widget(),再以 DialogResult 调业务回调 接受 Enter/Escape 物理键;若当作不可绕过安全门需另作状态约束
单按钮 alert_dialog() 隐藏取消按钮 底层仍支持 Escape 取消
MultiOptionDialog 先在列表中暂存 selection,仅当当前选项与原值不同才启用 Select _set_result() 出栈后由调用方决定是否写参数;显示与持久化是两步

两种屏幕的设置写入路径

大屏与 mici 不是仅换了皮肤的同一套业务状态机,新增设置时必须逐屏追踪写入和刷新路径。

路径/关键函数 参数与门控
TogglesLayout._update_toggles() 大屏按 CarParams 的纵向控制能力显示/禁用实验模式和驾驶风格;从 Params 重新同步开关;需重启的开关在 engaged 时禁用
TogglesLayout._handle_experimental_mode_toggle() 首次开启实验模式弹出富文本确认;确认后写 ExperimentalMode 与 ExperimentalModeConfirmed,取消时回滚 UI 开关
TogglesLayout._toggle_callback() 非实验模式开关同步写布尔参数;需重启的选项追加 OnroadCycleRequested
TogglesLayoutMici._on_experimental_mode() 首次开启跳到确认页,取消时不写开启参数;完成滑动确认后才写入确认与模式参数
BigParamControl._handle_mouse_release() 先执行 BigToggle 的状态翻转/可选回调,再写对应 Params;restart_needed_callback() 只请求行车周期重启

mici 多选项控件的回调与参数顺序

BigMultiToggle._handle_mouse_release() 先调用 BigToggle 父类:最底层 Widget._handle_mouse_release() 执行可选通用点击回调,然后 BigToggle 翻转 _checked 并执行 toggle_callback(布尔值);返回后才用 options.index(value) 循环到下一选项,执行 select_callback(新字符串)。BigMultiParamToggle._handle_mouse_release() 再把新选项索引写入 Params。因此若调用方同时注册这些回调,观察顺序是点击 → 布尔翻转 → 选项变化 → 参数写入,前面的回调不能假定持久参数已更新。父类 BigToggle 的 _checked 在 BigMultiToggle._draw_content() 中不参与选项 pill 绘制,布尔回调值与屏幕当前选项语义无关。

BigMultiParamToggle._load_value() 用 Params.get(param) or 0 直接索引选项列表。LongitudinalPersonality 在参数 schema 中是整数,log.capnp 枚举的 aggressive/standard/relaxed 顺序与当前 UI 选项一致。缺参回到 0;越界正值会在构造时抛 IndexError,负值按 Python 负索引显示非预期选项。本类不校验外部修改后的参数范围,也没有在写入失败时回滚已轮转的画面值。

插件化时多选设置应显式建模为一个枚举状态和一次提交结果,不把内部隐藏布尔态暴露为第二个业务状态;宿主应在显示与提交两处检查选项范围、车辆状态和参数写入回执。硬件台架要测物理/触摸重复释放、滚动撤销、外部方向盘按钮并发改 personality、畸形持久值及写入失败后的 UI 一致性。

滑动确认如何生效

SliderBase._handle_mouse_event() 只在圆形手柄区域开始拖动,并根据释放时的位置设置已确认时间;_update_state() 等手柄动画到终点且至少经过 0.2 秒后仅调用一次确认回调。LargerSlider 用于 mici 安装流程,BigSlider/RedBigSlider 用于 BigConfirmationDialog,后者可在确认后先动画退出,再调用业务动作。滑动减少偶发触碰,但并不自动重检当前车辆状态。

还需注意释放路径的守卫条件:基础 Widget._process_mouse_events() 在收到 release 时先调用子类 _handle_mouse_event(),再检查此前是否按下并在命中区内;而 SliderBase._handle_mouse_event() 的确认判断只看手柄位置,未显式要求本次仍在拖动手柄。若 UI 帧停顿、手势取消或其他释放事件与残留位置交错,需验证不会误确认——这是静态推断的手势竞争测试点,不是实测误触结论。

硬件与插件化边界

硬件验收应覆盖触摸抖动、按下后移出、长按、快速滑动、休眠首触、物理键 Enter/Escape、页面弹出时触点未释放,以及确认期间 engaged/车型能力变化。控制类写入应在最终业务回调再次核对当前状态,不能只靠按钮 enabled、警示颜色或滑块门槛。插件可提供非关键 UI 控件和设置说明,但宿主须掌握关键参数允许列表、写入权限、确认流程及行车冻结规则;涉及 OpenpilotEnabledToggle、实验模式或硬件/网络状态的写入不得由任意插件直接提交。

常见问题

把开关拨到「开」就代表参数已写入吗?

不一定。Toggle._handle_mouse_release() 先翻转内存状态再调用可选回调,set_state() 甚至不调用业务回调;显示层的翻转与 Params 持久化是不同的两步。

滑动确认能替代对车辆状态的检查吗?

不能。滑动只在手柄到达终点且至少经过 0.2 秒后调用一次确认回调,用于减少偶发触碰,但不会自动重检当前车辆状态;控制类写入仍需在最终业务回调再次核对。

mici 多选项控件的回调顺序是什么?

若调用方同时注册回调,观察顺序是点击 → 布尔翻转 → 选项变化 → 参数写入,前面的回调不能假定持久参数已更新,因为参数写入发生在最后一步。

为什么负值的持久化参数会显示意外选项?

BigMultiParamToggle._load_value() 用 Params.get(param) or 0 直接索引选项列表,负值会按 Python 负索引取到非预期选项,越界正值则在构造时抛 IndexError,本类不校验外部修改后的参数范围。

延伸阅读

参考资料

  • 项目 02 —— openpilot-research/UI_CONTROLS_CONFIRMATION_DEEP_DIVE.md(取用了基础输入与弹窗、两屏设置写入路径、mici 多选项控件、滑动确认与硬件/插件边界)。
  • 项目 02 —— openpilot-research/DYNAMIC_ALERT_CALLBACKS_DEEP_DIVE.md(仅用于确认「可见状态不等于授权」的相邻表述)。

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

更新记录

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

评论

发表回复

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