From 2d1376cd869c8edf427fc14b3967a5f2d5d0b81b Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Tue, 7 Apr 2026 02:15:45 -0400 Subject: [PATCH] BigUI WIP: Cleanup --- .../ui/layouts/settings/starpilot/lateral.py | 152 ++++++++++-- .../settings/starpilot/longitudinal.py | 221 +++++++++++++----- .../ui/layouts/settings/starpilot/panel.py | 46 +++- .../ui/layouts/settings/starpilot/vehicle.py | 160 ++++++------- 4 files changed, 415 insertions(+), 164 deletions(-) diff --git a/selfdrive/ui/layouts/settings/starpilot/lateral.py b/selfdrive/ui/layouts/settings/starpilot/lateral.py index abe79f22..654b62c2 100644 --- a/selfdrive/ui/layouts/settings/starpilot/lateral.py +++ b/selfdrive/ui/layouts/settings/starpilot/lateral.py @@ -1,11 +1,11 @@ from __future__ import annotations +from openpilot.system.hardware import HARDWARE from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.lib.multilang import tr, tr_noop from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog -from openpilot.system.ui.widgets.selection_dialog import SelectionDialog -from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel, create_tile_panel +from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel, create_master_toggle_panel, create_tile_panel from openpilot.selfdrive.ui.layouts.settings.starpilot.tabbed_panel import TabSectionSpec, TabbedSectionHost from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import AetherSliderDialog @@ -13,18 +13,93 @@ class StarPilotAdvancedLateralLayout(StarPilotPanel): def __init__(self): super().__init__() self.CATEGORIES = [ - {"title": tr_noop("Advanced Lateral Tuning"), "type": "toggle", "get_state": lambda: self._params.get_bool("AdvancedLateralTune"), "set_state": lambda x: self._params.put_bool("AdvancedLateralTune", x), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Actuator Delay"), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerDelay'):.2f}s", "on_click": lambda: self._show_float_selector("SteerDelay", 0.0, 0.5, 0.01, "s"), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Friction"), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerFriction'):.3f}", "on_click": lambda: self._show_float_selector("SteerFriction", 0.0, 0.5, 0.005), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Kp Factor"), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerKP'):.2f}", "on_click": lambda: self._show_float_selector("SteerKP", 0.5, 2.5, 0.01), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Lateral Accel"), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerLatAccel'):.2f}", "on_click": lambda: self._show_float_selector("SteerLatAccel", 0.5, 5.0, 0.01), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Steer Ratio"), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerRatio'):.2f}", "on_click": lambda: self._show_float_selector("SteerRatio", 5.0, 25.0, 0.01), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Force Auto-Tune On"), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceAutoTune"), "set_state": lambda x: self._params.put_bool("ForceAutoTune", x), "icon": "toggle_icons/icon_tuning.png", "color": "#597497", "visible": lambda: self._params.get_bool("AdvancedLateralTune")}, - {"title": tr_noop("Force Auto-Tune Off"), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceAutoTuneOff"), "set_state": lambda x: self._params.put_bool("ForceAutoTuneOff", x), "icon": "toggle_icons/icon_tuning.png", "color": "#597497", "visible": lambda: self._params.get_bool("AdvancedLateralTune")}, - {"title": tr_noop("Force Torque Controller"), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceTorqueController"), "set_state": lambda x: self._on_reboot_toggle("ForceTorqueController", x), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497", "visible": lambda: self._params.get_bool("AdvancedLateralTune")}, + {"title": tr_noop("Actuator Delay"), "desc": tr_noop("The time between openpilot's steering command and the vehicle's response."), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerDelay'):.2f}s", "on_click": lambda: self._show_float_selector("SteerDelay", 0.01, 1.0, 0.01, "s"), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497", "visible": self._show_steer_delay}, + {"title": tr_noop("Friction"), "desc": tr_noop("Compensates for steering friction around center."), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerFriction'):.3f}", "on_click": self._show_steer_friction_selector, "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497", "visible": self._show_steer_friction}, + {"title": tr_noop("Kp Factor"), "desc": tr_noop("How strongly openpilot corrects lateral position."), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerKP'):.2f}", "on_click": self._show_steer_kp_selector, "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497", "visible": self._show_steer_kp}, + {"title": tr_noop("Lateral Acceleration"), "desc": tr_noop("Maps steering torque to turning response."), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerLatAccel'):.2f}", "on_click": self._show_steer_lat_accel_selector, "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497", "visible": self._show_steer_lat_accel}, + {"title": tr_noop("Steer Ratio"), "desc": tr_noop("Adjust the relationship between steering wheel input and road-wheel angle."), "type": "value", "get_value": lambda: f"{self._params.get_float('SteerRatio'):.2f}", "on_click": self._show_steer_ratio_selector, "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497", "visible": self._show_steer_ratio}, + {"title": tr_noop("Force Auto-Tune On"), "desc": tr_noop("Force-enable live auto-tuning for friction and lateral acceleration."), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceAutoTune"), "set_state": self._set_force_auto_tune, "icon": "toggle_icons/icon_tuning.png", "color": "#597497", "visible": self._show_force_auto_tune}, + {"title": tr_noop("Force Auto-Tune Off"), "desc": tr_noop("Force-disable live auto-tuning and use your set values instead."), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceAutoTuneOff"), "set_state": self._set_force_auto_tune_off, "icon": "toggle_icons/icon_tuning.png", "color": "#597497", "visible": self._show_force_auto_tune_off}, + {"title": tr_noop("Force Torque Controller"), "desc": tr_noop("Use torque-based steering control instead of the stock steering mode when supported."), "type": "toggle", "get_state": lambda: self._params.get_bool("ForceTorqueController"), "set_state": lambda x: self._on_reboot_toggle("ForceTorqueController", x), "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497", "visible": self._show_force_torque_controller}, ] self._rebuild_grid() + def _advanced_enabled(self): + return self._params.get_bool("AdvancedLateralTune") + + def _using_nnff(self): + return starpilot_state.car_state.hasNNFFLog and self._params.get_bool("LateralTune") and self._params.get_bool("NNFF") + + def _forcing_auto_tune(self): + return not starpilot_state.car_state.hasAutoTune and self._params.get_bool("ForceAutoTune") + + def _forcing_auto_tune_off(self): + return starpilot_state.car_state.hasAutoTune and self._params.get_bool("ForceAutoTuneOff") + + def _forcing_torque_controller(self): + return not starpilot_state.car_state.isAngleCar and self._params.get_bool("ForceTorqueController") + + def _torque_tuning_active(self): + return starpilot_state.car_state.isTorqueCar or self._forcing_torque_controller() or self._using_nnff() + + def _manual_tuning_values_enabled(self): + if starpilot_state.car_state.hasAutoTune: + return self._forcing_auto_tune_off() + return not self._forcing_auto_tune() + + def _show_steer_delay(self): + return self._advanced_enabled() and starpilot_state.car_state.steerActuatorDelay != 0 + + def _show_steer_friction(self): + return (self._advanced_enabled() and starpilot_state.car_state.friction != 0 and self._torque_tuning_active() + and not self._using_nnff() and self._manual_tuning_values_enabled()) + + def _show_steer_kp(self): + return (self._advanced_enabled() and starpilot_state.car_state.steerKp != 0 and self._torque_tuning_active() + and not starpilot_state.car_state.isAngleCar) + + def _show_steer_lat_accel(self): + return (self._advanced_enabled() and starpilot_state.car_state.latAccelFactor != 0 and self._torque_tuning_active() + and not self._using_nnff() and self._manual_tuning_values_enabled()) + + def _show_steer_ratio(self): + return self._advanced_enabled() and starpilot_state.car_state.steerRatio != 0 and self._manual_tuning_values_enabled() + + def _show_force_auto_tune(self): + return (self._advanced_enabled() and not starpilot_state.car_state.hasAutoTune and not starpilot_state.car_state.isAngleCar + and self._torque_tuning_active()) + + def _show_force_auto_tune_off(self): + return self._advanced_enabled() and starpilot_state.car_state.hasAutoTune and not starpilot_state.car_state.isAngleCar + + def _show_force_torque_controller(self): + return self._advanced_enabled() and not starpilot_state.car_state.isAngleCar and not starpilot_state.car_state.isTorqueCar + + def _set_force_auto_tune(self, state): + self._params.put_bool("ForceAutoTune", state) + if state: + self._params.put_bool("ForceAutoTuneOff", False) + + def _set_force_auto_tune_off(self, state): + self._params.put_bool("ForceAutoTuneOff", state) + if state: + self._params.put_bool("ForceAutoTune", False) + + def _show_steer_friction_selector(self): + self._show_float_selector("SteerFriction", 0.0, max(1.0, starpilot_state.car_state.friction * 1.5), 0.01) + + def _show_steer_kp_selector(self): + base = max(0.01, starpilot_state.car_state.steerKp) + self._show_float_selector("SteerKP", base * 0.5, base * 1.5, 0.01) + + def _show_steer_lat_accel_selector(self): + base = max(0.01, starpilot_state.car_state.latAccelFactor) + self._show_float_selector("SteerLatAccel", base * 0.5, base * 1.5, 0.01) + + def _show_steer_ratio_selector(self): + base = max(0.01, starpilot_state.car_state.steerRatio) + self._show_float_selector("SteerRatio", base * 0.5, base * 1.5, 0.01) + def _show_float_selector(self, key, min_v, max_v, step, unit=""): def on_close(res, val): if res == DialogResult.CONFIRM: @@ -68,9 +143,9 @@ class StarPilotLaneChangesLayout(StarPilotPanel): self.CATEGORIES = [ {"title": tr_noop("Lane Changes"), "type": "toggle", "get_state": lambda: self._params.get_bool("LaneChanges"), "set_state": lambda s: self._params.put_bool("LaneChanges", s), "icon": "toggle_icons/icon_lane.png", "color": "#597497"}, {"title": tr_noop("Automatic Lane Changes"), "type": "toggle", "get_state": lambda: self._params.get_bool("NudgelessLaneChange"), "set_state": lambda s: self._params.put_bool("NudgelessLaneChange", s), "icon": "toggle_icons/icon_lane.png", "color": "#597497", "visible": lambda: self._params.get_bool("LaneChanges")}, - {"title": tr_noop("Lane Change Delay"), "type": "value", "get_value": lambda: f"{self._params.get_float('LaneChangeTime'):.1f}s", "on_click": lambda: self._show_float_selector("LaneChangeTime", 0.0, 5.0, 0.1, "s"), "icon": "toggle_icons/icon_lane.png", "color": "#597497", "visible": lambda: self._params.get_bool("LaneChanges")}, + {"title": tr_noop("Lane Change Delay"), "type": "value", "get_value": lambda: f"{self._params.get_float('LaneChangeTime'):.1f}s", "on_click": lambda: self._show_float_selector("LaneChangeTime", 0.0, 5.0, 0.1, "s"), "icon": "toggle_icons/icon_lane.png", "color": "#597497", "visible": lambda: self._params.get_bool("LaneChanges") and self._params.get_bool("NudgelessLaneChange")}, {"title": tr_noop("Min Lane Change Speed"), "type": "value", "get_value": lambda: f"{self._params.get_int('MinimumLaneChangeSpeed')} mph", "on_click": lambda: self._show_speed_selector("MinimumLaneChangeSpeed"), "icon": "toggle_icons/icon_lane.png", "color": "#597497", "visible": lambda: self._params.get_bool("LaneChanges")}, - {"title": tr_noop("Minimum Lane Width"), "type": "value", "get_value": lambda: f"{self._params.get_float('LaneDetectionWidth'):.1f} ft", "on_click": lambda: self._show_float_selector("LaneDetectionWidth", 0.0, 15.0, 0.1, " ft"), "icon": "toggle_icons/icon_lane.png", "color": "#597497", "visible": lambda: self._params.get_bool("LaneChanges")}, + {"title": tr_noop("Minimum Lane Width"), "type": "value", "get_value": lambda: f"{self._params.get_float('LaneDetectionWidth'):.1f} ft", "on_click": lambda: self._show_float_selector("LaneDetectionWidth", 0.0, 15.0, 0.1, " ft"), "icon": "toggle_icons/icon_lane.png", "color": "#597497", "visible": lambda: self._params.get_bool("LaneChanges") and self._params.get_bool("NudgelessLaneChange")}, {"title": tr_noop("One Lane Change Per Signal"), "type": "toggle", "get_state": lambda: self._params.get_bool("OneLaneChange"), "set_state": lambda s: self._params.put_bool("OneLaneChange", s), "icon": "toggle_icons/icon_lane.png", "color": "#597497", "visible": lambda: self._params.get_bool("LaneChanges")}, ] self._rebuild_grid() @@ -93,13 +168,26 @@ class StarPilotLateralTuneLayout(StarPilotPanel): def __init__(self): super().__init__() self.CATEGORIES = [ - {"title": tr_noop("Lateral Tuning"), "type": "toggle", "get_state": lambda: self._params.get_bool("LateralTune"), "set_state": lambda x: self._params.put_bool("LateralTune", x), "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Force Turn Desires"), "type": "toggle", "get_state": lambda: self._params.get_bool("TurnDesires"), "set_state": lambda x: self._params.put_bool("TurnDesires", x), "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497", "visible": lambda: self._params.get_bool("LateralTune")}, - {"title": tr_noop("NNFF"), "type": "toggle", "get_state": lambda: self._params.get_bool("NNFF"), "set_state": lambda x: self._on_reboot_toggle("NNFF", x), "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497", "visible": lambda: self._params.get_bool("LateralTune")}, - {"title": tr_noop("NNFF Lite"), "type": "toggle", "get_state": lambda: self._params.get_bool("NNFFLite"), "set_state": lambda x: self._on_reboot_toggle("NNFFLite", x), "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497", "visible": lambda: self._params.get_bool("LateralTune")}, + {"title": tr_noop("Force Turn Desires Below Lane Change Speed"), "desc": tr_noop("Allow openpilot to follow turn intent below the minimum lane change speed when signaling."), "type": "toggle", "get_state": lambda: self._params.get_bool("TurnDesires"), "set_state": lambda x: self._params.put_bool("TurnDesires", x), "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497", "visible": self._lateral_enabled}, + {"title": tr_noop("Neural Network Feedforward (NNFF)"), "desc": tr_noop("Use the full neural-network feedforward steering controller when available."), "type": "toggle", "get_state": lambda: self._params.get_bool("NNFF"), "set_state": self._set_nnff, "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497", "visible": self._show_nnff}, + {"title": tr_noop("Neural Network Feedforward (NNFF) Lite"), "desc": tr_noop("Use the lightweight NNFF steering logic when the full model is off."), "type": "toggle", "get_state": lambda: self._params.get_bool("NNFFLite"), "set_state": lambda x: self._on_reboot_toggle("NNFFLite", x), "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497", "visible": self._show_nnff_lite}, ] self._rebuild_grid() + def _lateral_enabled(self): + return self._params.get_bool("LateralTune") + + def _show_nnff(self): + return self._lateral_enabled() and starpilot_state.car_state.hasNNFFLog and not starpilot_state.car_state.isAngleCar + + def _show_nnff_lite(self): + return self._lateral_enabled() and not self._params.get_bool("NNFF") and not starpilot_state.car_state.isAngleCar + + def _set_nnff(self, state): + self._params.put_bool("NNFF", state) + if state: + self._params.put_bool("NNFFLite", False) + def _on_reboot_toggle(self, key, state): self._params.put_bool(key, state) from openpilot.selfdrive.ui.ui_state import ui_state @@ -132,9 +220,33 @@ class StarPilotLateralLayout(StarPilotPanel): "always_on_lateral": StarPilotAlwaysOnLateralLayout(), "qol": StarPilotLateralQOLLayout(), }) - tune_panel = create_tile_panel([ - {"title": tr_noop("Advanced Tuning"), "panel": "advanced_lateral", "icon": "toggle_icons/icon_advanced_lateral_tune.png", "color": "#597497"}, - {"title": tr_noop("Lateral Tuning"), "panel": "lateral_tune", "icon": "toggle_icons/icon_lateral_tune.png", "color": "#597497"}, + tune_panel = create_master_toggle_panel([ + { + "title": tr_noop("Advanced Lateral Tuning"), + "desc": tr_noop("Advanced steering control changes to fine-tune how openpilot drives."), + "manage_title": tr_noop("Advanced Settings"), + "manage_desc": tr_noop("Adjust steering response, torque controller behavior, and auto-tuning controls."), + "manage_label": tr_noop("Configure"), + "disabled_label": tr_noop("Enable First"), + "panel": "advanced_lateral", + "get_state": lambda: self._params.get_bool("AdvancedLateralTune"), + "set_state": lambda x: self._params.put_bool("AdvancedLateralTune", x), + "icon": "toggle_icons/icon_advanced_lateral_tune.png", + "color": "#597497", + }, + { + "title": tr_noop("Lateral Tuning"), + "desc": tr_noop("Miscellaneous steering control changes such as turn desires and NNFF modes."), + "manage_title": tr_noop("Lateral Settings"), + "manage_desc": tr_noop("Open turn-intent and neural-network feedforward controls."), + "manage_label": tr_noop("Configure"), + "disabled_label": tr_noop("Enable First"), + "panel": "lateral_tune", + "get_state": lambda: self._params.get_bool("LateralTune"), + "set_state": lambda x: self._params.put_bool("LateralTune", x), + "icon": "toggle_icons/icon_lateral_tune.png", + "color": "#597497", + }, ], { "advanced_lateral": StarPilotAdvancedLateralLayout(), "lateral_tune": StarPilotLateralTuneLayout(), diff --git a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py index 416455e8..db6882db 100644 --- a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py +++ b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py @@ -6,27 +6,80 @@ from openpilot.system.ui.widgets import DialogResult from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog from openpilot.system.ui.widgets.selection_dialog import SelectionDialog from openpilot.system.ui.widgets.input_dialog import InputDialog -from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel, create_tile_panel +from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel, create_master_toggle_panel, create_tile_panel from openpilot.selfdrive.ui.layouts.settings.starpilot.tabbed_panel import TabSectionSpec, TabbedSectionHost from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import AetherSliderDialog +from openpilot.starpilot.common.accel_profile import ( + ACCELERATION_PROFILES, + DECELERATION_PROFILES, + normalize_acceleration_profile, + normalize_deceleration_profile, +) + + +ACCELERATION_PROFILE_OPTIONS = [ + (ACCELERATION_PROFILES["STANDARD"], "Standard"), + (ACCELERATION_PROFILES["ECO"], "Eco"), + (ACCELERATION_PROFILES["SPORT"], "Sport"), + (ACCELERATION_PROFILES["SPORT_PLUS"], "Sport+"), +] + +DECELERATION_PROFILE_OPTIONS = [ + (DECELERATION_PROFILES["STANDARD"], "Standard"), + (DECELERATION_PROFILES["ECO"], "Eco"), + (DECELERATION_PROFILES["SPORT"], "Sport"), +] class StarPilotLongitudinalLayout(StarPilotPanel): def __init__(self): super().__init__() - tune_panel = create_tile_panel([ - {"title": tr_noop("Longitudinal Tuning"), "panel": "tuning", "icon": "toggle_icons/icon_longitudinal_tune.png", "color": "#597497"}, - {"title": tr_noop("Advanced Tuning"), "panel": "advanced", "icon": "toggle_icons/icon_advanced_longitudinal_tune.png", "color": "#597497"}, - {"title": tr_noop("Driving Personalities"), "panel": "personalities", "icon": "toggle_icons/icon_personality.png", "color": "#597497"}, + longitudinal_tune_panel = StarPilotLongitudinalTuneLayout() + advanced_longitudinal_panel = StarPilotAdvancedLongitudinalLayout() + tune_panel = create_master_toggle_panel([ + { + "title": tr_noop("Longitudinal Tuning"), + "desc": tr_noop("Acceleration and braking control changes to fine-tune how openpilot drives."), + "manage_title": tr_noop("Longitudinal Settings"), + "manage_desc": tr_noop("Open acceleration profiles, human-like behavior, lead detection, and turn-speed controls."), + "manage_label": tr_noop("Configure"), + "disabled_label": tr_noop("Enable First"), + "panel": "tuning", + "get_state": lambda: self._params.get_bool("LongitudinalTune"), + "set_state": lambda s: self._params.put_bool("LongitudinalTune", s), + "icon": "toggle_icons/icon_longitudinal_tune.png", + "color": "#597497", + }, + { + "title": tr_noop("Advanced Longitudinal Tuning"), + "desc": tr_noop("Advanced acceleration and braking changes for refining launch, stopping, and actuator response."), + "manage_title": tr_noop("Advanced Settings"), + "manage_desc": tr_noop("Adjust actuator delay, launch and stop behavior, and powertrain-specific tuning options."), + "manage_label": tr_noop("Configure"), + "disabled_label": tr_noop("Enable First"), + "panel": "advanced", + "get_state": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "set_state": lambda s: self._params.put_bool("AdvancedLongitudinalTune", s), + "icon": "toggle_icons/icon_advanced_longitudinal_tune.png", + "color": "#597497", + }, ], { - "tuning": StarPilotLongitudinalTuneLayout(), - "advanced": StarPilotAdvancedLongitudinalLayout(), + "tuning": longitudinal_tune_panel, + "advanced": advanced_longitudinal_panel, "personalities": StarPilotPersonalitiesLayout(), "traffic_personality": StarPilotPersonalityProfileLayout("Traffic"), "aggressive_personality": StarPilotPersonalityProfileLayout("Aggressive"), "standard_personality": StarPilotPersonalityProfileLayout("Standard"), "relaxed_personality": StarPilotPersonalityProfileLayout("Relaxed"), - }) + }, extra_categories=[ + { + "title": tr_noop("Driving Personalities"), + "desc": tr_noop("Customize the Traffic, Aggressive, Standard, and Relaxed profiles to match your driving style."), + "panel": "personalities", + "icon": "toggle_icons/icon_personality.png", + "color": "#597497", + }, + ]) adaptive_panel = create_tile_panel([ {"title": tr_noop("Conditional Experimental"), "panel": "conditional", "icon": "toggle_icons/icon_conditional.png", "color": "#597497"}, @@ -73,89 +126,113 @@ class StarPilotAdvancedLongitudinalLayout(StarPilotPanel): def __init__(self): super().__init__() self.CATEGORIES = [ - { - "title": tr_noop("Advanced Longitudinal Tuning"), - "type": "toggle", - "get_state": lambda: self._params.get_bool("AdvancedLongitudinalTune"), - "set_state": lambda s: self._params.put_bool("AdvancedLongitudinalTune", s), - "icon": "toggle_icons/icon_advanced_longitudinal_tune.png", - "color": "#597497", - }, { "title": tr_noop("EV Tuning"), + "desc": tr_noop("Use acceleration tuning intended for EV and direct-drive vehicles."), "type": "toggle", "get_state": lambda: self._params.get_bool("EVTuning"), - "set_state": lambda s: self._params.put_bool("EVTuning", s), + "set_state": self._set_ev_tuning, "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "is_enabled": lambda: not self._params.get_bool("TruckTuning"), + "disabled_label": tr_noop("Truck Active"), + "visible": self._advanced_enabled, }, { "title": tr_noop("Truck Tuning"), + "desc": tr_noop("Use stronger launch and acceleration behavior intended for heavier vehicles."), "type": "toggle", "get_state": lambda: self._params.get_bool("TruckTuning"), - "set_state": lambda s: self._params.put_bool("TruckTuning", s), + "set_state": self._set_truck_tuning, "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "is_enabled": lambda: not self._params.get_bool("EVTuning"), + "disabled_label": tr_noop("EV Active"), + "visible": self._advanced_enabled, }, { "title": tr_noop("Actuator Delay"), + "desc": tr_noop("The time between an acceleration or brake command and the vehicle's response."), "type": "value", "get_value": lambda: f"{self._params.get_float('LongitudinalActuatorDelay'):.2f}s", "on_click": lambda: self._show_float_selector("LongitudinalActuatorDelay", 0.0, 1.0, 0.01, "s"), "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "visible": self._advanced_enabled, }, { - "title": tr_noop("Max Acceleration"), + "title": tr_noop("Maximum Acceleration"), + "desc": tr_noop("Limit the strongest acceleration openpilot is allowed to command."), "type": "value", "get_value": lambda: f"{self._params.get_float('MaxDesiredAcceleration'):.1f}m/s²", "on_click": lambda: self._show_float_selector("MaxDesiredAcceleration", 0.1, 4.0, 0.1, "m/s²"), "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "visible": self._advanced_enabled, }, { - "title": tr_noop("Start Accel"), + "title": tr_noop("Start Acceleration"), + "desc": tr_noop("Extra acceleration applied when moving away from a stop."), "type": "value", "get_value": lambda: f"{self._params.get_float('StartAccel'):.2f}m/s²", "on_click": lambda: self._show_float_selector("StartAccel", 0.0, 4.0, 0.01, "m/s²"), "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "visible": lambda: self._advanced_enabled() and not self._using_human_acceleration(), }, { - "title": tr_noop("Stop Accel"), + "title": tr_noop("Stop Acceleration"), + "desc": tr_noop("Brake force used to hold the vehicle at a complete stop."), "type": "value", "get_value": lambda: f"{self._params.get_float('StopAccel'):.2f}m/s²", "on_click": lambda: self._show_float_selector("StopAccel", -4.0, 0.0, 0.01, "m/s²"), "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "visible": self._advanced_enabled, }, { "title": tr_noop("Stopping Rate"), + "desc": tr_noop("How quickly braking ramps up when openpilot is bringing the car to a stop."), "type": "value", "get_value": lambda: f"{self._params.get_float('StoppingDecelRate'):.3f}m/s²", "on_click": lambda: self._show_float_selector("StoppingDecelRate", 0.001, 1.0, 0.001, "m/s²"), "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "visible": self._show_stop_tuning_values, }, { - "title": tr_noop("VEgo Starting"), + "title": tr_noop("Start Speed"), + "desc": tr_noop("The speed where openpilot transitions out of the stopped state."), "type": "value", "get_value": lambda: f"{self._params.get_float('VEgoStarting'):.2f}m/s", "on_click": lambda: self._show_float_selector("VEgoStarting", 0.01, 1.0, 0.01, "m/s"), "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "visible": self._show_stop_tuning_values, }, { - "title": tr_noop("VEgo Stopping"), + "title": tr_noop("Stop Speed"), + "desc": tr_noop("The speed where openpilot considers the vehicle fully stopped."), "type": "value", "get_value": lambda: f"{self._params.get_float('VEgoStopping'):.2f}m/s", "on_click": lambda: self._show_float_selector("VEgoStopping", 0.01, 1.0, 0.01, "m/s"), "color": "#597497", - "visible": lambda: self._params.get_bool("AdvancedLongitudinalTune"), + "visible": self._show_stop_tuning_values, }, ] self._rebuild_grid() + def _advanced_enabled(self): + return self._params.get_bool("AdvancedLongitudinalTune") + + def _using_human_acceleration(self): + return self._params.get_bool("LongitudinalTune") and self._params.get_bool("HumanAcceleration") + + def _show_stop_tuning_values(self): + return self._advanced_enabled() and not (starpilot_state.car_state.isToyota and self._params.get_bool("FrogsGoMoosTweak")) + + def _set_ev_tuning(self, state: bool): + self._params.put_bool("EVTuning", state) + if state: + self._params.put_bool("TruckTuning", False) + + def _set_truck_tuning(self, state: bool): + self._params.put_bool("TruckTuning", state) + if state: + self._params.put_bool("EVTuning", False) + def _show_float_selector(self, key, min_v, max_v, step, unit=""): def on_close(res, val): if res == DialogResult.CONFIRM: @@ -470,80 +547,106 @@ class StarPilotLongitudinalTuneLayout(StarPilotPanel): def __init__(self): super().__init__() self.CATEGORIES = [ - { - "title": tr_noop("Longitudinal Tuning"), - "type": "toggle", - "get_state": lambda: self._params.get_bool("LongitudinalTune"), - "set_state": lambda s: self._params.put_bool("LongitudinalTune", s), - "icon": "toggle_icons/icon_longitudinal_tune.png", - "color": "#597497", - }, { "title": tr_noop("Acceleration Profile"), + "desc": tr_noop("Choose how quickly openpilot speeds up."), "type": "value", - "get_value": lambda: self._params.get("AccelerationProfile", encoding='utf-8') or "Standard", - "on_click": lambda: self._show_selection("AccelerationProfile", ["Standard", "Eco", "Sport", "Sport+"]), + "get_value": self._get_acceleration_profile_label, + "on_click": self._show_acceleration_profile_selector, "color": "#597497", - "visible": lambda: self._params.get_bool("LongitudinalTune"), + "visible": self._longitudinal_enabled, }, { "title": tr_noop("Deceleration Profile"), + "desc": tr_noop("Choose how firmly openpilot slows the car down."), "type": "value", - "get_value": lambda: self._params.get("DecelerationProfile", encoding='utf-8') or "Standard", - "on_click": lambda: self._show_selection("DecelerationProfile", ["Standard", "Eco", "Sport"]), + "get_value": self._get_deceleration_profile_label, + "on_click": self._show_deceleration_profile_selector, "color": "#597497", - "visible": lambda: self._params.get_bool("LongitudinalTune"), + "visible": self._longitudinal_enabled, }, { - "title": tr_noop("Human Acceleration"), + "title": tr_noop("Human-Like Acceleration"), + "desc": tr_noop("Smooth throttle behavior at low speed with stronger takeoff from a stop."), "type": "toggle", "get_state": lambda: self._params.get_bool("HumanAcceleration"), "set_state": lambda s: self._params.put_bool("HumanAcceleration", s), "color": "#597497", - "visible": lambda: self._params.get_bool("LongitudinalTune"), + "visible": self._longitudinal_enabled, }, { - "title": tr_noop("Human Following"), + "title": tr_noop("Human-Like Following"), + "desc": tr_noop("Adjust following behavior to feel more natural behind other vehicles."), "type": "toggle", "get_state": lambda: self._params.get_bool("HumanFollowing"), "set_state": lambda s: self._params.put_bool("HumanFollowing", s), "color": "#597497", - "visible": lambda: self._params.get_bool("LongitudinalTune"), + "visible": self._longitudinal_enabled, }, { - "title": tr_noop("Human Lane Changes"), + "title": tr_noop("Human-Like Lane Changes"), + "desc": tr_noop("Use radar-informed behavior during lane changes when radar support is available."), "type": "toggle", "get_state": lambda: self._params.get_bool("HumanLaneChanges"), "set_state": lambda s: self._params.put_bool("HumanLaneChanges", s), "color": "#597497", - "visible": lambda: self._params.get_bool("LongitudinalTune"), + "visible": lambda: self._longitudinal_enabled() and starpilot_state.car_state.hasRadar, }, { - "title": tr_noop("Lead Detection"), + "title": tr_noop("Lead Detection Sensitivity"), + "desc": tr_noop("Control how aggressively openpilot detects and reacts to vehicles ahead."), "type": "value", "get_value": lambda: f"{self._params.get_int('LeadDetectionThreshold')}%", "on_click": lambda: self._show_int_selector("LeadDetectionThreshold", 25, 50, "%"), "color": "#597497", - "visible": lambda: self._params.get_bool("LongitudinalTune"), + "visible": self._longitudinal_enabled, }, { - "title": tr_noop("Taco Tune"), + "title": tr_noop("\"Taco Bell Run\" Turn Speed Hack"), + "desc": tr_noop("Slow down more assertively for turns."), "type": "toggle", "get_state": lambda: self._params.get_bool("TacoTune"), "set_state": lambda s: self._params.put_bool("TacoTune", s), "color": "#597497", - "visible": lambda: self._params.get_bool("LongitudinalTune"), + "visible": self._longitudinal_enabled, }, ] self._rebuild_grid() - def _show_selection(self, key, options): + def _longitudinal_enabled(self): + return self._params.get_bool("LongitudinalTune") + + def _get_acceleration_profile_label(self): + value = normalize_acceleration_profile(self._params.get("AccelerationProfile", encoding='utf-8')) + return self._profile_label_for_value(value, ACCELERATION_PROFILE_OPTIONS) + + def _get_deceleration_profile_label(self): + value = normalize_deceleration_profile(self._params.get("DecelerationProfile", encoding='utf-8')) + return self._profile_label_for_value(value, DECELERATION_PROFILE_OPTIONS) + + def _show_acceleration_profile_selector(self): + self._show_selection("Acceleration Profile", "AccelerationProfile", ACCELERATION_PROFILE_OPTIONS, normalize_acceleration_profile(self._params.get("AccelerationProfile", encoding='utf-8'))) + + def _show_deceleration_profile_selector(self): + self._show_selection("Deceleration Profile", "DecelerationProfile", DECELERATION_PROFILE_OPTIONS, normalize_deceleration_profile(self._params.get("DecelerationProfile", encoding='utf-8'))) + + def _profile_label_for_value(self, value, options): + for option_value, option_label in options: + if option_value == value: + return tr(option_label) + return tr(options[0][1]) + + def _show_selection(self, title, key, options, current_value): + option_labels = [tr(option_label) for _, option_label in options] + label_to_value = {tr(option_label): option_value for option_value, option_label in options} + default_option = next((tr(option_label) for option_value, option_label in options if option_value == current_value), option_labels[0]) + def on_select(res, val): if res == DialogResult.CONFIRM: - self._params.put(key, val) + self._params.put_int(key, label_to_value[val]) self._rebuild_grid() - gui_app.set_modal_overlay(SelectionDialog(tr(key), options, self._params.get(key, encoding='utf-8') or "Standard", on_close=on_select)) + gui_app.set_modal_overlay(SelectionDialog(tr(title), option_labels, default_option, on_close=on_select)) def _show_int_selector(self, key, min_v, max_v, unit=""): def on_close(res, val): diff --git a/selfdrive/ui/layouts/settings/starpilot/panel.py b/selfdrive/ui/layouts/settings/starpilot/panel.py index b3311da3..e0e64f24 100644 --- a/selfdrive/ui/layouts/settings/starpilot/panel.py +++ b/selfdrive/ui/layouts/settings/starpilot/panel.py @@ -89,7 +89,13 @@ class StarPilotPanel(Widget): bg_color=cat.get("color"), ) elif tile_type == "toggle": - tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=cat["set_state"], icon_path=cat.get("icon"), bg_color=cat.get("color"), desc=tr(cat.get("desc", "")), is_enabled=cat.get("is_enabled"), disabled_label=cat.get("disabled_label", "")) + raw_set_state = cat["set_state"] + + def on_toggle(state: bool, setter=raw_set_state): + setter(state) + self._rebuild_grid() + + tile = ToggleTile(title=tr(cat["title"]), get_state=cat["get_state"], set_state=on_toggle, icon_path=cat.get("icon"), bg_color=cat.get("color"), desc=tr(cat.get("desc", "")), is_enabled=cat.get("is_enabled"), disabled_label=cat.get("disabled_label", "")) elif tile_type == "value": tile = ValueTile(title=tr(cat["title"]), get_value=cat["get_value"], on_click=cat["on_click"], icon_path=cat.get("icon"), bg_color=cat.get("color"), is_enabled=cat.get("is_enabled"), desc=tr(cat.get("desc", ""))) else: @@ -138,3 +144,41 @@ def create_tile_panel(categories: list[dict], sub_panels: dict[str, Widget] | No panel._rebuild_grid() return panel + + +def create_master_toggle_panel(toggle_specs: list[dict], sub_panels: dict[str, Widget] | None = None, + extra_categories: list[dict] | None = None) -> StarPilotPanel: + panel = create_tile_panel([], sub_panels) + categories: list[dict] = [] + + for spec in toggle_specs: + get_state = spec["get_state"] + visible = spec.get("visible") + manage_enabled = spec.get("manage_enabled", get_state) + + categories.append({ + "title": spec["title"], + "desc": spec.get("desc", ""), + "type": "toggle", + "get_state": get_state, + "set_state": spec["set_state"], + "icon": spec.get("icon"), + "color": spec.get("color"), + "visible": visible, + }) + + categories.append({ + "title": spec.get("manage_title", "Settings"), + "desc": spec.get("manage_desc", ""), + "type": "value", + "get_value": lambda enabled=get_state, active_label=spec.get("manage_label", "Manage"), inactive_label=spec.get("disabled_label", "Enable First"): tr(active_label) if enabled() else tr(inactive_label), + "on_click": lambda sub_panel=spec["panel"]: panel._navigate_to(sub_panel), + "is_enabled": manage_enabled, + "icon": spec.get("manage_icon", spec.get("icon")), + "color": spec.get("color"), + "visible": visible, + }) + + panel.CATEGORIES = categories + list(extra_categories or []) + panel._rebuild_grid() + return panel diff --git a/selfdrive/ui/layouts/settings/starpilot/vehicle.py b/selfdrive/ui/layouts/settings/starpilot/vehicle.py index 97bd0474..ccf4af26 100644 --- a/selfdrive/ui/layouts/settings/starpilot/vehicle.py +++ b/selfdrive/ui/layouts/settings/starpilot/vehicle.py @@ -1,7 +1,4 @@ from __future__ import annotations -import os -import re -from pathlib import Path from openpilot.system.hardware import HARDWARE from openpilot.system.ui.lib.application import gui_app @@ -12,73 +9,11 @@ from openpilot.system.ui.widgets.selection_dialog import SelectionDialog from openpilot.selfdrive.ui.layouts.settings.starpilot.panel import StarPilotPanel from openpilot.selfdrive.ui.layouts.settings.starpilot.aethergrid import AetherSliderDialog, TileGrid, HubTile, ToggleTile, ValueTile from openpilot.selfdrive.ui.lib.starpilot_state import starpilot_state - -MAKE_TO_FOLDER = { - "acura": "honda", - "audi": "volkswagen", - "buick": "gm", - "cadillac": "gm", - "chevrolet": "gm", - "chrysler": "chrysler", - "cupra": "volkswagen", - "dodge": "chrysler", - "ford": "ford", - "genesis": "hyundai", - "gmc": "gm", - "holden": "gm", - "honda": "honda", - "hyundai": "hyundai", - "jeep": "chrysler", - "kia": "hyundai", - "lexus": "toyota", - "lincoln": "ford", - "man": "volkswagen", - "mazda": "mazda", - "nissan": "nissan", - "peugeot": "psa", - "ram": "chrysler", - "rivian": "rivian", - "seat": "volkswagen", - "škoda": "volkswagen", - "subaru": "subaru", - "tesla": "tesla", - "toyota": "toyota", - "volkswagen": "volkswagen", -} - - -def get_car_names(car_make: str): - folder = MAKE_TO_FOLDER.get(car_make.lower()) - if not folder: - return [], {} - - # Path to values.py in opendbc - values_path = Path(__file__).parents[4] / "opendbc" / "car" / folder / "values.py" - if not values_path.exists(): - return [], {} - - with open(values_path, "r") as f: - content = f.read() - - # Clean comments - content = re.sub(r'#.*', '', content) - - # Find platforms and car names - platforms = re.findall(r'(\w+)\s*=\s*\w+\s*\(', content) - car_models = {} - car_names = [] - - # This is a simplified version of the C++ regex logic - # In values.py, CarDocs often appears as CarDocs("Name", ...) - matches = re.finditer(r'CarDocs\w*\s*\(\s*"([^"]+)"', content) - for match in matches: - name = match.group(1) - if name.lower().startswith(car_make.lower()): - # Find the platform name by looking backwards for the nearest platform assignment - # For now, we'll just store the name - car_names.append(name) - - return sorted(list(set(car_names))), car_models +from openpilot.selfdrive.ui.mici.layouts.settings.fingerprint_catalog import ( + FingerprintModelOption, + get_fingerprint_catalog, + shorten_model_label, +) def _lock_doors_timer_labels(): @@ -91,6 +26,7 @@ def _lock_doors_timer_labels(): class StarPilotVehicleSettingsLayout(StarPilotPanel): def __init__(self): super().__init__() + self._make_options, self._models_by_make, self._models_by_value, self._make_by_model = get_fingerprint_catalog() self._sub_panels = { "gm": StarPilotGMVehicleLayout(), "hkg": StarPilotHKGVehicleLayout(), @@ -103,14 +39,14 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel): { "title": tr_noop("Car Make"), "type": "value", - "get_value": lambda: self._params.get("CarMake", encoding='utf-8') or tr("None"), + "get_value": self._get_display_make, "on_click": self._on_select_make, "color": "#64748B", }, { "title": tr_noop("Car Model"), "type": "value", - "get_value": lambda: self._params.get("CarModelName", encoding='utf-8') or tr("None"), + "get_value": self._get_display_model, "on_click": self._on_select_model, "color": "#64748B", }, @@ -189,37 +125,93 @@ class StarPilotVehicleSettingsLayout(StarPilotPanel): self._tile_grid.add_tile(tile) + def _get_display_make(self): + make = self._params.get("CarMake", encoding='utf-8') or "" + if make: + return make + + model = self._params.get("CarModel", encoding='utf-8') or "" + if model: + return self._make_by_model.get(model, tr("None")) + return tr("None") + + def _get_selected_model_option(self) -> FingerprintModelOption | None: + model = self._params.get("CarModel", encoding='utf-8') or "" + if not model: + return None + + model_name = self._params.get("CarModelName", encoding='utf-8') or "" + make = self._params.get("CarMake", encoding='utf-8') or self._make_by_model.get(model, "") + if make and model_name: + for option in self._models_by_make.get(make, ()): + if option.value == model and option.label == model_name: + return option + + return self._models_by_value.get(model) + + def _get_display_model(self): + selected_option = self._get_selected_model_option() + if selected_option is not None: + return selected_option.button_label + + model = self._params.get("CarModel", encoding='utf-8') or "" + model_name = self._params.get("CarModelName", encoding='utf-8') or "" + make = self._params.get("CarMake", encoding='utf-8') or self._make_by_model.get(model, "") + + if model_name: + return shorten_model_label(make, model_name) if make else model_name + if model and model in self._models_by_value: + return self._models_by_value[model].button_label + return tr("None") + def _on_select_make(self): - makes = sorted(list(MAKE_TO_FOLDER.keys())) - makes = [m.capitalize() for m in makes] + makes = list(self._make_options) + if not makes: + gui_app.set_modal_overlay(ConfirmDialog(tr("No fingerprint list available."), tr("OK"), on_close=lambda r: None)) + return def on_select(res, val): if res == DialogResult.CONFIRM: self._params.put("CarMake", val) - self._params.remove("CarModel") - self._params.remove("CarModelName") + current_model = self._params.get("CarModel", encoding='utf-8') or "" + available_models = {option.value for option in self._models_by_make.get(val, ())} + if current_model not in available_models: + self._params.remove("CarModel") + self._params.remove("CarModelName") self._rebuild_grid() - gui_app.set_modal_overlay(SelectionDialog(tr("Select Make"), makes, self._params.get("CarMake", encoding='utf-8') or "", on_close=on_select)) + current_make = self._params.get("CarMake", encoding='utf-8') or "" + default_make = current_make if current_make in makes else makes[0] + gui_app.set_modal_overlay(SelectionDialog(tr("Select Make"), makes, default_make, on_close=on_select)) def _on_select_model(self): - make = self._params.get("CarMake", encoding='utf-8') + make = self._params.get("CarMake", encoding='utf-8') or "" if not make: gui_app.set_modal_overlay(ConfirmDialog(tr("Please select a Car Make first!"), tr("OK"), on_close=lambda r: None)) return - models, _ = get_car_names(make) - if not models: - gui_app.set_modal_overlay(ConfirmDialog(tr("No models found for this make."), tr("OK"), on_close=lambda r: None)) + model_options = self._models_by_make.get(make, ()) + if not model_options: + gui_app.set_modal_overlay(ConfirmDialog(tr("No models available for this make."), tr("OK"), on_close=lambda r: None)) return + option_labels = [option.option_label for option in model_options] + selected_by_label = {option.option_label: option for option in model_options} + current_model = self._params.get("CarModel", encoding='utf-8') or "" + current_model_name = self._params.get("CarModelName", encoding='utf-8') or "" + default_option = next((option.option_label for option in model_options if option.value == current_model and option.label == current_model_name), None) + if default_option is None: + default_option = next((option.option_label for option in model_options if option.value == current_model), option_labels[0]) + def on_select(res, val): if res == DialogResult.CONFIRM: - self._params.put("CarModelName", val) - # In a real build we'd map name to platform code here + selected_option = selected_by_label[val] + self._params.put("CarModel", selected_option.value) + self._params.put("CarModelName", selected_option.label) + self._params.put("CarMake", make) self._rebuild_grid() - gui_app.set_modal_overlay(SelectionDialog(tr("Select Model"), models, self._params.get("CarModelName", encoding='utf-8') or "", on_close=on_select)) + gui_app.set_modal_overlay(SelectionDialog(tr("Select Model"), option_labels, default_option, on_close=on_select)) def _on_disable_long(self, state): if state: