test: remove ExperimentalMode UI/param usage (stage 3d)

This commit is contained in:
1okko
2026-10-21 08:41:29 +08:00
parent 4ad15623e6
commit ff2c2a7afa
17 changed files with 74 additions and 343 deletions
+3 -3
View File
@@ -178,7 +178,7 @@ class Car:
self.v_cruise_helper = VCruiseHelper(self.CP, self.CP_SP)
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode")
self.initial_cruise_speed = self.params.get("InitialCruiseSpeedKph", return_default=True)
# card is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None)
@@ -211,7 +211,7 @@ class Car:
self.v_cruise_helper.update_v_cruise(CS, self.sm['carControl'].enabled, self.is_metric)
if self.sm['carControl'].enabled and not self.CC_prev.enabled:
# Use CarState w/ buttons from the step selfdrived enables on
self.v_cruise_helper.initialize_v_cruise(self.CS_prev, self.experimental_mode)
self.v_cruise_helper.initialize_v_cruise(self.CS_prev, self.initial_cruise_speed)
# TODO: mirror the carState.cruiseState struct?
CS.vCruise = float(self.v_cruise_helper.v_cruise_kph)
@@ -296,7 +296,7 @@ class Car:
def params_thread(self, evt):
while not evt.is_set():
self.is_metric = self.params.get_bool("IsMetric")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.initial_cruise_speed = self.params.get("InitialCruiseSpeedKph", return_default=True)
# sunnypilot
self.v_cruise_helper.read_custom_set_speed_params()
+2 -5
View File
@@ -13,7 +13,6 @@ V_CRUISE_MIN = 8
V_CRUISE_MAX = 145
V_CRUISE_UNSET = 255
V_CRUISE_INITIAL = 40
V_CRUISE_INITIAL_EXPERIMENTAL_MODE = 105
IMPERIAL_INCREMENT = round(CV.MPH_TO_KPH, 1) # round here to avoid rounding errors incrementing set speed
ButtonEvent = car.CarState.ButtonEvent
@@ -138,16 +137,14 @@ class VCruiseHelper(VCruiseHelperSP):
self.button_timers[b.type.raw] = 1 if b.pressed else 0
self.button_change_states[b.type.raw] = {"standstill": CS.cruiseState.standstill, "enabled": enabled}
def initialize_v_cruise(self, CS, experimental_mode: bool) -> None:
def initialize_v_cruise(self, CS, initial_speed: int) -> None:
# initializing is handled by the PCM
if self.CP.pcmCruise:
return
initial = V_CRUISE_INITIAL_EXPERIMENTAL_MODE if experimental_mode else V_CRUISE_INITIAL
if any(b.type in (ButtonType.accelCruise, ButtonType.resumeCruise) for b in CS.buttonEvents) and self.v_cruise_initialized:
self.v_cruise_kph = self.v_cruise_kph_last
else:
self.v_cruise_kph = int(round(np.clip(CS.vEgo * CV.MS_TO_KPH, initial, V_CRUISE_MAX)))
self.v_cruise_kph = int(round(np.clip(max(CS.vEgo * CV.MS_TO_KPH, initial_speed), V_CRUISE_INITIAL, V_CRUISE_MAX)))
self.v_cruise_cluster_kph = self.v_cruise_kph
@@ -14,7 +14,7 @@ ButtonEvent = car.CarState.ButtonEvent
ButtonType = car.CarState.ButtonEvent.Type
def run_cruise_simulation(cruise, e2e, personality, t_end=20.):
def run_cruise_simulation(cruise, personality, t_end=20.):
man = Maneuver(
'',
duration=t_end,
@@ -24,7 +24,6 @@ def run_cruise_simulation(cruise, e2e, personality, t_end=20.):
cruise_values=[cruise],
prob_lead_values=[0.0],
breakpoints=[0.],
e2e=e2e,
personality=personality,
)
valid, output = man.evaluate()
@@ -32,8 +31,7 @@ def run_cruise_simulation(cruise, e2e, personality, t_end=20.):
return output[-1, 3]
@parameterized_class(("e2e", "personality", "speed"), itertools.product(
[True, False], # e2e
@parameterized_class(("personality", "speed"), itertools.product(
log.LongitudinalPersonality.schema.enumerants, # personality
[5,35])) # speed
class TestCruiseSpeed(OpenpilotTestCase):
@@ -41,7 +39,7 @@ class TestCruiseSpeed(OpenpilotTestCase):
print(f'Testing {self.speed} m/s')
cruise_speed = float(self.speed)
simulation_steady_state = run_cruise_simulation(cruise_speed, self.e2e, self.personality)
simulation_steady_state = run_cruise_simulation(cruise_speed, self.personality)
self.assertAlmostEqual(simulation_steady_state, cruise_speed, delta=.01, msg=f'Did not reach {self.speed} m/s')
@@ -59,16 +57,16 @@ class TestVCruiseHelper(OpenpilotTestCase):
for _ in range(2):
self.v_cruise_helper.update_v_cruise(car.CarState(cruiseState={"available": False}), enabled=False, is_metric=False)
def enable(self, v_ego, experimental_mode):
def enable(self, v_ego):
# Simulates user pressing set with a current speed
self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego), experimental_mode)
self.v_cruise_helper.initialize_v_cruise(car.CarState(vEgo=v_ego), V_CRUISE_INITIAL)
def test_adjust_speed(self):
"""
Asserts speed changes on falling edges of buttons.
"""
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS)
for btn in (ButtonType.accelCruise, ButtonType.decelCruise):
for pressed in (True, False):
@@ -92,7 +90,7 @@ class TestVCruiseHelper(OpenpilotTestCase):
CS.buttonEvents = [ButtonEvent(type=ButtonType.decelCruise, pressed=pressed)]
self.v_cruise_helper.update_v_cruise(CS, enabled=enabled, is_metric=False)
if pressed:
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS)
# Expected diff on enabling. Speed should not change on falling edge of pressed
assert (not pressed) == (self.v_cruise_helper.v_cruise_kph == self.v_cruise_helper.v_cruise_kph_last)
@@ -102,7 +100,7 @@ class TestVCruiseHelper(OpenpilotTestCase):
Asserts we don't increment set speed if user presses resume/accel to exit cruise standstill.
"""
self.enable(0, False)
self.enable(0)
for standstill in (True, False):
for pressed in (True, False):
@@ -122,7 +120,7 @@ class TestVCruiseHelper(OpenpilotTestCase):
for v_ego in np.linspace(0, 100, 101):
self.reset_cruise_speed_state()
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS)
# first decrement speed, then perform gas pressed logic
expected_v_cruise_kph = self.v_cruise_helper.v_cruise_kph - IMPERIAL_INCREMENT
@@ -143,11 +141,10 @@ class TestVCruiseHelper(OpenpilotTestCase):
Asserts allowed cruise speeds on enabling with SET.
"""
for experimental_mode in (True, False):
for v_ego in np.linspace(0, 100, 101):
self.reset_cruise_speed_state()
assert not self.v_cruise_helper.v_cruise_initialized
for v_ego in np.linspace(0, 100, 101):
self.reset_cruise_speed_state()
assert not self.v_cruise_helper.v_cruise_initialized
self.enable(float(v_ego), experimental_mode)
assert V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX
assert self.v_cruise_helper.v_cruise_initialized
self.enable(float(v_ego))
assert V_CRUISE_INITIAL <= self.v_cruise_helper.v_cruise_kph <= V_CRUISE_MAX
assert self.v_cruise_helper.v_cruise_initialized
+4 -15
View File
@@ -29,7 +29,6 @@ from openpilot.common.hardware import HARDWARE
from openpilot.sunnypilot.mads.mads import ModularAssistiveDrivingSystem
from openpilot.sunnypilot import get_sanitize_int_param
from openpilot.sunnypilot.selfdrive.car.car_specific import CarSpecificEventsSP
from openpilot.sunnypilot.selfdrive.car.cruise_helpers import CruiseHelper
from openpilot.sunnypilot.selfdrive.car.intelligent_cruise_button_management.controller import IntelligentCruiseButtonManagement
from openpilot.sunnypilot.selfdrive.selfdrived.button_state_tracker import ButtonStateTracker
from openpilot.sunnypilot.selfdrive.selfdrived.events import EventsSP
@@ -55,7 +54,7 @@ TurnDirection = custom.ModelDataV2SP.TurnDirection
IGNORED_SAFETY_MODES = (SafetyModel.silent, SafetyModel.noOutput)
class SelfdriveD(CruiseHelper):
class SelfdriveD:
def __init__(self, CP=None, CP_SP=None):
self.params = Params()
@@ -123,8 +122,6 @@ class SelfdriveD(CruiseHelper):
# cleanup old params
if not self.CP.alphaLongitudinalAvailable:
self.params.remove("AlphaLongitudinalEnabled")
if not self.CP.openpilotLongitudinalControl:
self.params.remove("ExperimentalMode")
self.CS_prev = car.CarState.new_message()
self.AM = AlertManager()
@@ -141,7 +138,6 @@ class SelfdriveD(CruiseHelper):
self.events_prev = []
self.logged_comm_issue = None
self.not_running_prev = None
self.experimental_mode = False
self.personality = get_sanitize_int_param(
"LongitudinalPersonality",
min(log.LongitudinalPersonality.schema.enumerants.values()),
@@ -183,7 +179,6 @@ class SelfdriveD(CruiseHelper):
self.car_events_sp = CarSpecificEventsSP(self.CP, self.CP_SP)
CruiseHelper.__init__(self, self.CP)
self.button_state_tracker = ButtonStateTracker()
def update_events(self, CS):
@@ -514,16 +509,12 @@ class SelfdriveD(CruiseHelper):
if CS.gearShifter == car.CarState.GearShifter.park and self.mads.enabled:
self.events.remove(EventName.canBusMissing)
CruiseHelper.update(self, CS, self.events_sp, self.experimental_mode)
# decrement personality on distance button press
if self.CP.openpilotLongitudinalControl:
if any(not be.pressed and be.type == ButtonType.gapAdjustCruise for be in CS.buttonEvents):
if not self.experimental_mode_switched:
self.personality = (self.personality - 1) % 3
self.params.put('LongitudinalPersonality', self.personality)
self.events.add(EventName.personalityChanged)
self.experimental_mode_switched = False
self.personality = (self.personality - 1) % 3
self.params.put('LongitudinalPersonality', self.personality)
self.events.add(EventName.personalityChanged)
self.icbm.run(CS, self.sm['carControl'], self.sm['longitudinalPlanSP'], self.is_metric)
@@ -600,7 +591,6 @@ class SelfdriveD(CruiseHelper):
ss.active = self.active
ss.state = self.state_machine.state
ss.engageable = not self.events.contains(ET.NO_ENTRY)
ss.experimentalMode = self.experimental_mode
ss.personality = self.personality
ss.alertText1 = self.AM.current_alert.alert_text_1
@@ -667,7 +657,6 @@ class SelfdriveD(CruiseHelper):
self.is_metric = self.params.get_bool("IsMetric")
self.is_ldw_enabled = self.params.get_bool("IsLdwEnabled")
self.disengage_on_accelerator = self.params.get_bool("DisengageOnAccelerator")
self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl
self.personality = self.params.get("LongitudinalPersonality", return_default=True)
self.mads.read_params()
+1 -25
View File
@@ -1,10 +1,8 @@
import time
import pyray as rl
from collections.abc import Callable
from enum import IntEnum
from openpilot.common.params import Params
from openpilot.selfdrive.ui.widgets.offroad_alerts import UpdateAlert, OffroadAlert
from openpilot.selfdrive.ui.widgets.exp_mode_button import ExperimentalModeButton
from openpilot.selfdrive.ui.widgets.prime import PrimeWidget
from openpilot.selfdrive.ui.widgets.setup import SetupWidget
from openpilot.system.ui.lib.text_measure import measure_text_cached
@@ -39,7 +37,6 @@ class HomeLayout(Widget):
self.current_state = HomeLayoutState.HOME
self.last_refresh = 0
self.settings_callback: Callable[[], None] | None = None
self.update_available = False
self.alert_count = 0
@@ -60,29 +57,20 @@ class HomeLayout(Widget):
self._prime_widget = PrimeWidget()
self._setup_widget = SetupWidget()
self._exp_mode_button = ExperimentalModeButton()
self._setup_callbacks()
def show_event(self):
super().show_event()
self._exp_mode_button.show_event()
self.last_refresh = time.monotonic()
self._refresh()
def _setup_callbacks(self):
self.update_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME))
self.offroad_alert.set_dismiss_callback(lambda: self._set_state(HomeLayoutState.HOME))
self._exp_mode_button.set_click_callback(lambda: self.settings_callback() if self.settings_callback else None)
def set_settings_callback(self, callback: Callable):
self.settings_callback = callback
def _set_state(self, state: HomeLayoutState):
# propagate show/hide events
if state != self.current_state:
if state == HomeLayoutState.HOME:
self._exp_mode_button.show_event()
if state in self._layout_widgets:
self._layout_widgets[state].show_event()
if self.current_state in self._layout_widgets:
@@ -235,19 +223,7 @@ class HomeLayout(Widget):
self._prime_widget.render(self.left_column_rect)
def _render_right_column(self):
exp_height = 125
exp_rect = rl.Rectangle(
self.right_column_rect.x, self.right_column_rect.y, self.right_column_rect.width, exp_height
)
self._exp_mode_button.render(exp_rect)
setup_rect = rl.Rectangle(
self.right_column_rect.x,
self.right_column_rect.y + exp_height + SPACING,
self.right_column_rect.width,
self.right_column_rect.height - exp_height - SPACING,
)
self._setup_widget.render(setup_rect)
self._setup_widget.render(self.right_column_rect)
def _refresh(self):
self._version_text = self._get_version_text()
-1
View File
@@ -63,7 +63,6 @@ class MainLayout(Widget):
on_flag=self._on_bookmark_clicked,
open_settings=lambda: self.open_settings(PanelType.TOGGLES))
self._layouts[MainState.HOME]._setup_widget.set_open_settings_callback(lambda: self.open_settings(PanelType.FIREHOSE))
self._layouts[MainState.HOME].set_settings_callback(lambda: self.open_settings(PanelType.TOGGLES))
self._layouts[MainState.SETTINGS].set_callbacks(on_close=self._set_mode_for_state)
for layout in (self._layouts[MainState.ONROAD], self._home_body_layout):
@@ -178,7 +178,6 @@ class DeveloperLayout(Widget):
def _on_lat_maneuver_mode(self, state: bool):
self._params.put_bool("LateralManeuverMode", state, block=True)
self._params.put_bool("ExperimentalMode", False, block=True)
self._params.put_bool("JoystickDebugMode", False, block=True)
self._joystick_toggle.action_item.set_state(False)
self._params.put_bool("LongitudinalManeuverMode", False, block=True)
@@ -46,13 +46,6 @@ class TogglesLayout(Widget):
# param, title, desc, icon, needs_restart
self._toggle_defs = {
"ExperimentalMode": (
lambda: tr("Experimental Mode"),
"",
"experimental_white.png",
False,
),
"IsLdwEnabled": (
lambda: tr("Enable Lane Departure Warnings"),
DESCRIPTIONS["IsLdwEnabled"],
@@ -109,11 +102,7 @@ class TogglesLayout(Widget):
self._toggles[param] = toggle
# insert longitudinal personality after NDOG toggle
if param == "ExperimentalMode":
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
self._update_experimental_mode_icon()
self._toggles["LongitudinalPersonality"] = self._long_personality_setting
self._scroller = Scroller(list(self._toggles.values()), line_separator=True, spacing=0)
ui_state.add_engaged_transition_callback(self._update_toggles)
@@ -133,45 +122,8 @@ class TogglesLayout(Widget):
def _update_toggles(self):
ui_state.update_params()
e2e_description = tr(
"sunnypilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " +
"Experimental features are listed below:<br>" +
"<h4>End-to-End Longitudinal Control</h4><br>" +
"Let the driving model control the gas and brakes. sunnypilot will drive as it thinks a human would, including stopping for red lights and stop signs. " +
"Since the driving model decides the speed to drive, the set speed will only act as an upper bound. This is an alpha quality feature; " +
"mistakes should be expected.<br>" +
"<h4>New Driving Visualization</h4><br>" +
"The driving visualization will transition to the road-facing wide-angle camera at low speeds to better show some turns. " +
"The Experimental mode logo will also be shown in the top right corner."
)
if ui_state.CP is not None:
if ui_state.has_longitudinal_control:
self._toggles["ExperimentalMode"].action_item.set_enabled(True)
self._toggles["ExperimentalMode"].set_description(e2e_description)
self._long_personality_setting.action_item.set_enabled(True)
else:
# no long for now
self._toggles["ExperimentalMode"].action_item.set_enabled(False)
self._toggles["ExperimentalMode"].action_item.set_state(False)
self._long_personality_setting.action_item.set_enabled(False)
self._params.remove("ExperimentalMode")
unavailable = tr("Experimental mode is currently unavailable on this car since the car's stock ACC is used for longitudinal control.")
long_desc = unavailable + " " + tr("sunnypilot longitudinal control may come in a future update.")
if ui_state.CP.alphaLongitudinalAvailable:
if self._is_release:
long_desc = unavailable + " " + tr("An alpha version of sunnypilot longitudinal control can be tested, along with " +
"Experimental mode, on non-release branches.")
else:
long_desc = tr("Enable the sunnypilot longitudinal control (alpha) toggle to allow Experimental mode.")
self._toggles["ExperimentalMode"].set_description("<b>" + long_desc + "</b><br><br>" + e2e_description)
else:
self._toggles["ExperimentalMode"].set_description(e2e_description)
self._update_experimental_mode_icon()
self._long_personality_setting.action_item.set_enabled(ui_state.has_longitudinal_control)
# TODO: make a param control list item so we don't need to manage internal state as much here
# refresh toggles from params to mirror external changes
@@ -186,35 +138,7 @@ class TogglesLayout(Widget):
def _render(self, rect):
self._scroller.render(rect)
def _update_experimental_mode_icon(self):
icon = "experimental.png" if self._toggles["ExperimentalMode"].action_item.get_state() else "experimental_white.png"
self._toggles["ExperimentalMode"].set_icon(icon)
def _handle_experimental_mode_toggle(self, state: bool):
confirmed = self._params.get_bool("ExperimentalModeConfirmed")
if state and not confirmed:
def confirm_callback(result: DialogResult):
if result == DialogResult.CONFIRM:
self._params.put_bool("ExperimentalMode", True, block=True)
self._params.put_bool("ExperimentalModeConfirmed", True, block=True)
else:
self._toggles["ExperimentalMode"].action_item.set_state(False)
self._update_experimental_mode_icon()
# show confirmation dialog
content = (f"<h1>{self._toggles['ExperimentalMode'].title}</h1><br>" +
f"<p>{self._toggles['ExperimentalMode'].description}</p>")
dlg = ConfirmDialog(content, tr("Enable"), rich=True, callback=confirm_callback)
gui_app.push_widget(dlg)
else:
self._update_experimental_mode_icon()
self._params.put_bool("ExperimentalMode", state, block=True)
def _toggle_callback(self, state: bool, param: str):
if param == "ExperimentalMode":
self._handle_experimental_mode_toggle(state)
return
self._params.put_bool(param, state, block=True)
if self._toggle_defs[param][3]:
self._params.put_bool("OnroadCycleRequested", True, block=True)
+7 -34
View File
@@ -2,7 +2,6 @@ from __future__ import annotations
import datetime
import math
import time
from openpilot.cereal import log
import pyray as rl
@@ -127,13 +126,8 @@ class MiciHomeLayout(Widget):
self._on_alerts_click: Callable | None = None
self._alert_count_callback: Callable[[], int] | None = None
self._mouse_down_t: None | float = None
self._did_long_press = False
self._is_pressed_prev = False
self._version_text = self._get_version_text()
self._experimental_icon = IconWidget("icons_mici/experimental_mode.png", (48, 48))
self._usb_icon = IconWidget("icons_mici/usb.png", (62, 40))
self._chestnut_icon = IconWidget("icons_mici/chestnut_green.png", (54, 40))
self._chestnut_loading_icon = IconWidget("icons_mici/chestnut.png", (68, 40))
@@ -146,7 +140,6 @@ class MiciHomeLayout(Widget):
self._status_bar_layout = HBoxLayout([
IconWidget("icons_mici/settings.png", (48, 48), opacity=0.9),
NetworkIcon(),
self._experimental_icon,
self._usb_icon,
self._chestnut_icon,
self._chestnut_loading_icon,
@@ -162,23 +155,6 @@ class MiciHomeLayout(Widget):
self._branch_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, scroll=True)
self._version_commit_label = UnifiedLabel("", font_size=36, text_color=rl.GRAY, font_weight=FontWeight.ROMAN, max_width=480, wrap_text=False)
def _update_state(self):
if self.is_pressed and not self._is_pressed_prev:
self._mouse_down_t = time.monotonic()
elif not self.is_pressed and self._is_pressed_prev:
self._mouse_down_t = None
self._did_long_press = False
self._is_pressed_prev = self.is_pressed
if self._mouse_down_t is not None:
if time.monotonic() - self._mouse_down_t > 0.5:
# long gating for experimental mode - only allow toggle if longitudinal control is available
if ui_state.has_longitudinal_control and ui_state.experimental_mode_confirmed:
ui_state.experimental_mode = not ui_state.experimental_mode
ui_state.params.put("ExperimentalMode", ui_state.experimental_mode, block=True)
self._mouse_down_t = None
self._did_long_press = True
def set_callbacks(self, on_settings: Callable | None = None, on_alerts: Callable | None = None,
alert_count_callback: Callable[[], int] | None = None,
alert_icon_callback: Callable[[], rl.Texture | None] | None = None):
@@ -188,15 +164,13 @@ class MiciHomeLayout(Widget):
self._alerts_pill.set_alert_count_callback(alert_count_callback, alert_icon_callback)
def _handle_mouse_release(self, mouse_pos: MousePos):
if not self._did_long_press:
relative_x = mouse_pos.x - self.rect.x
has_alerts = self._alert_count_callback and self._alert_count_callback() > 0
if has_alerts and relative_x > self.rect.width - ALERTS_ZONE_WIDTH:
if self._on_alerts_click:
self._on_alerts_click()
elif self._on_settings_click:
self._on_settings_click()
self._did_long_press = False
relative_x = mouse_pos.x - self.rect.x
has_alerts = self._alert_count_callback and self._alert_count_callback() > 0
if has_alerts and relative_x > self.rect.width - ALERTS_ZONE_WIDTH:
if self._on_alerts_click:
self._on_alerts_click()
elif self._on_settings_click:
self._on_settings_click()
def _get_version_text(self) -> tuple[str, str, str, str] | None:
version = ui_state.params.get("Version")
@@ -249,7 +223,6 @@ class MiciHomeLayout(Widget):
usb_connected = ui_state.usb_connected
usb_unknown = ui_state.usb_unknown
chestnut_state = ui_state.chestnut_state
self._experimental_icon.set_visible(ui_state.experimental_mode)
if gui_app.sunnypilot_ui():
self._set_chestnut_visibility()
else:
@@ -83,6 +83,8 @@ class DeveloperLayoutMici(NavScroller):
toggle_callback=self._on_alpha_long_enabled,
description="Use alpha openpilot longitudinal control instead of stock ACC. This may disable Automatic Emergency " +
"Braking (AEB).")
self._navigation_toggle = BigParamControl("navigation (alpha)", "NavigationEnabled",
description="Turn-by-turn navigation with a route map. Set a destination from comma connect.")
self._debug_mode_toggle = BigParamControl("ui debug mode", "ShowDebugInfo",
toggle_callback=lambda checked: (gui_app.set_show_touches(checked), gui_app.set_show_fps(checked)),
description="Show touch locations and the UI frame rate.")
@@ -96,6 +98,7 @@ class DeveloperLayoutMici(NavScroller):
self._lat_maneuver_toggle,
self._alpha_long_toggle,
self._debug_mode_toggle,
self._navigation_toggle,
])
# Toggle lists
@@ -107,9 +110,10 @@ class DeveloperLayoutMici(NavScroller):
("LateralManeuverMode", self._lat_maneuver_toggle),
("AlphaLongitudinalEnabled", self._alpha_long_toggle),
("ShowDebugInfo", self._debug_mode_toggle),
("NavigationEnabled", self._navigation_toggle),
)
onroad_blocked_toggles = (self._adb_toggle, self._joystick_toggle)
release_blocked_toggles = (self._joystick_toggle, self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle)
release_blocked_toggles = (self._joystick_toggle, self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle, self._navigation_toggle)
engaged_blocked_toggles = (self._long_maneuver_toggle, self._lat_maneuver_toggle, self._alpha_long_toggle)
# Hide non-release toggles on release builds
@@ -180,7 +184,6 @@ class DeveloperLayoutMici(NavScroller):
def _on_lat_maneuver_mode(self, state: bool):
ui_state.params.put_bool("LateralManeuverMode", state, block=True)
ui_state.params.put_bool("ExperimentalMode", False, block=True)
ui_state.params.put_bool("JoystickDebugMode", False, block=True)
self._joystick_toggle.set_checked(False)
ui_state.params.put_bool("LongitudinalManeuverMode", False, block=True)
@@ -1,59 +1,27 @@
from collections.abc import Callable
from openpilot.cereal import log
from openpilot.system.ui.widgets.scroller import NavScroller
from openpilot.selfdrive.ui.mici.widgets.button import BigParamControl, BigMultiParamToggle, BigToggle, GreyBigButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationCircleButton
from openpilot.selfdrive.ui.mici.widgets.button import BigToggle, BigParamControl, BigMultiParamToggle
from openpilot.system.ui.lib.application import gui_app
from openpilot.selfdrive.ui.layouts.settings.common import restart_needed_callback
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.mici.layouts.settings.initial_cruise_speed import InitialCruiseSpeedButton
PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants
class ExperimentalModeConfirmPage(NavScroller):
def __init__(self, on_confirm: Callable[[], None]):
super().__init__()
accept = BigConfirmationCircleButton("enable\nexperimental mode",
gui_app.texture("icons_mici/setup/driver_monitoring/dm_check.png", 64, 64),
lambda: self.dismiss(on_confirm))
self._scroller.add_widgets([
GreyBigButton("enabling\nexperimental mode", "scroll to continue",
gui_app.texture("icons_mici/setup/warning.png", 64, 64)),
GreyBigButton("", "openpilot defaults to driving in chill mode."),
GreyBigButton("", "Experimental mode enables alpha-level features that aren't ready for chill mode."),
GreyBigButton("End-to-End Longitudinal Control"),
GreyBigButton("", "Let the driving model control the gas and brakes."),
GreyBigButton("", "openpilot will drive as it thinks a human would, including stopping for red lights and stop signs."),
GreyBigButton("", "The set speed will only act as an upper bound."),
GreyBigButton("", "This is an alpha quality feature; mistakes should be expected."),
GreyBigButton("New Driving Visualization"),
GreyBigButton("", "The path will change colors to communicate acceleration intent."),
GreyBigButton("", "Red for braking, green for acceleration, and gray for coasting."),
accept,
])
class TogglesLayoutMici(NavScroller):
def __init__(self):
super().__init__()
self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"],
self._personality_toggle = BigMultiParamToggle("following distance", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"],
description="Standard is recommended.\n" +
"Aggressive follows closer, with firmer gas and braking.\n" +
"Relaxed leaves more space.\n" +
"Use the steering wheel distance button on supported cars.")
self._experimental_btn = BigToggle("experimental mode", description_icon=gui_app.texture("icons_mici/experimental_mode.png", 64, 64),
initial_state=ui_state.params.get_bool("ExperimentalMode"), toggle_callback=self._on_experimental_mode,
description="Let the driving model control gas and brakes.\n" +
"Includes stopping for red lights and stop signs.\n" +
"Set speed is a maximum, not a target.\n" +
"These are alpha features. Expect mistakes.\n" +
"The path colors show acceleration and braking.")
is_metric_toggle = BigParamControl("use metric units", "IsMetric")
self._initial_cruise_speed = InitialCruiseSpeedButton()
self._initial_cruise_speed.set_visible(lambda: ui_state.CP is not None and ui_state.has_longitudinal_control and not ui_state.CP.pcmCruise)
is_metric_toggle = BigToggle("use metric units", initial_state=ui_state.params.get_bool("IsMetric"), toggle_callback=self._on_metric_toggle)
ldw_toggle = BigParamControl("lane departure warnings", "IsLdwEnabled",
description="Warn when you drift across a detected lane line.\n" +
"Only above 31 mph (50 km/h), with no turn signal.")
@@ -72,7 +40,7 @@ class TogglesLayoutMici(NavScroller):
self._scroller.add_widgets([
self._personality_toggle,
self._experimental_btn,
self._initial_cruise_speed,
is_metric_toggle,
ldw_toggle,
always_on_dm_toggle,
@@ -83,7 +51,6 @@ class TogglesLayoutMici(NavScroller):
# Toggle lists
self._refresh_toggles = (
("ExperimentalMode", self._experimental_btn),
("IsMetric", is_metric_toggle),
("IsLdwEnabled", ldw_toggle),
("AlwaysOnDM", always_on_dm_toggle),
@@ -115,35 +82,17 @@ class TogglesLayoutMici(NavScroller):
super().show_event()
self._update_toggles()
def _on_metric_toggle(self, state: bool):
ui_state.params.put_bool("IsMetric", state, block=True)
self._update_toggles()
def _update_toggles(self):
ui_state.update_params()
self._initial_cruise_speed.refresh()
# CP gating for experimental mode
if ui_state.CP is not None:
if ui_state.has_longitudinal_control:
self._experimental_btn.set_visible(True)
self._personality_toggle.set_visible(True)
else:
# no long for now
self._experimental_btn.set_visible(False)
self._experimental_btn.set_checked(False)
self._personality_toggle.set_visible(False)
ui_state.params.remove("ExperimentalMode")
self._personality_toggle.set_visible(ui_state.has_longitudinal_control)
# Refresh toggles from params to mirror external changes
for key, item in self._refresh_toggles:
item.set_checked(ui_state.params.get_bool(key))
def _on_experimental_mode(self, state: bool):
if state and not ui_state.params.get_bool("ExperimentalModeConfirmed"):
# Don't show enabled state until confirm
self._experimental_btn.set_checked(False)
def on_confirm():
ui_state.params.put_bool("ExperimentalModeConfirmed", True)
ui_state.params.put_bool("ExperimentalMode", True)
self._experimental_btn.set_checked(True)
gui_app.push_widget(ExperimentalModeConfirmPage(on_confirm))
else:
ui_state.params.put_bool("ExperimentalMode", state)
@@ -1,7 +1,6 @@
import pyray as rl
from dataclasses import dataclass
from openpilot.common.constants import CV
from openpilot.selfdrive.ui.onroad.exp_button import ExpButton
from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus
from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.multilang import tr
@@ -18,11 +17,9 @@ CRUISE_DISABLED_CHAR = '–'
class UIConfig:
header_height: int = 300
border_size: int = 30
button_size: int = 192
set_speed_width_metric: int = 200
set_speed_width_imperial: int = 172
set_speed_height: int = 204
wheel_icon_size: int = 144
@dataclass(frozen=True)
@@ -71,7 +68,6 @@ class HudRenderer(Widget):
self._font_medium: rl.Font = gui_app.font(FontWeight.MEDIUM)
self._font_speed: rl.Font = gui_app.high_res_speed_font()
self._exp_button: ExpButton = ExpButton(UI_CONFIG.button_size, UI_CONFIG.wheel_icon_size)
def _update_state(self) -> None:
"""Update HUD state based on car state and controls state."""
@@ -118,13 +114,6 @@ class HudRenderer(Widget):
self._draw_current_speed(rect)
button_x = rect.x + rect.width - UI_CONFIG.border_size - UI_CONFIG.button_size
button_y = rect.y + UI_CONFIG.border_size
self._exp_button.render(rl.Rectangle(button_x, button_y, UI_CONFIG.button_size, UI_CONFIG.button_size))
def user_interacting(self) -> bool:
return self._exp_button.is_pressed
def _draw_set_speed(self, rect: rl.Rectangle) -> None:
"""Draw the MAX speed indicator box."""
set_speed_width = UI_CONFIG.set_speed_width_metric if ui_state.is_metric else UI_CONFIG.set_speed_width_imperial
@@ -85,7 +85,6 @@ class CruiseLayout(Widget):
)
items = [
self._toggles_layout._toggles["ExperimentalMode"],
self._toggles_layout._toggles["LongitudinalPersonality"],
self._toggles_layout._toggles["IsMetric"],
self.scc_v_toggle,
@@ -8,6 +8,7 @@ from enum import Enum
from openpilot.cereal import messaging, log, custom
from opendbc.car.structs import car
from opendbc.sunnypilot.car.interfaces import has_bsm
from openpilot.common.params import Params
from openpilot.selfdrive.ui.sunnypilot.layouts.settings.display import OnroadBrightness
from openpilot.sunnypilot.models.helpers import ACTIVE_BUNDLE_KEYS, get_active_source
@@ -35,7 +36,7 @@ class UIStateSP:
self.is_sp_release: bool = self.params.get_bool("IsReleaseSpBranch")
self.sm_services_ext = [
"modelManagerSP", "selfdriveStateSP", "longitudinalPlanSP", "backupManagerSP",
"gpsLocation", "lateralTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP", "lateralDelay"
"lateralTorqueParameters", "carStateSP", "liveMapDataSP", "carParamsSP", "lateralDelay"
]
self.sunnylink_state = SunnylinkState()
@@ -212,7 +213,7 @@ class UIStateSP:
self.params.remove("AlphaLongitudinalEnabled")
# BSM not available: clear BSM-dependent settings
if not CP.enableBsm:
if not has_bsm(CP, self.CP_SP):
self.params.remove("AutoLaneChangeBsmDelay")
else:
# No CarParams: clear all car-dependent params as safety default
@@ -221,10 +222,6 @@ class UIStateSP:
self.params.remove("LateralJerkTorqueController")
self.params.remove("AlphaLongitudinalEnabled")
# No longitudinal control: no experimental mode
if not has_long:
self.params.remove("ExperimentalMode")
# ICBM: clear if not available or if full longitudinal control is active
if self.CP_SP is not None:
if not self.CP_SP.intelligentCruiseButtonManagementAvailable or has_long:
+18 -12
View File
@@ -12,8 +12,9 @@ from openpilot.common.swaglog import cloudlog
from openpilot.selfdrive.ui.lib.prime_state import PrimeState
from openpilot.system.ui.lib.application import gui_app
from openpilot.common.hardware import HARDWARE, PC
from openpilot.common.hardware.usb import TYPEC_CC_ORIENTATION_PATH, get_usb_state, is_chestnut_usb_id, read_int
from openpilot.common.hardware.usb import cable_connected, get_usb_state, is_chestnut_usb_id
from openpilot.selfdrive.modeld.helpers import chestnut_compiled
from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS
from openpilot.selfdrive.ui.sunnypilot.ui_state import UIStateSP, DeviceSP
@@ -69,6 +70,8 @@ class UIState(UIStateSP):
"selfdriveState",
"longitudinalPlan",
"gpsLocationExternal",
"gpsLocation",
"navInstruction",
"carOutput",
"carControl",
"vehicleParameters",
@@ -88,14 +91,16 @@ class UIState(UIStateSP):
# Core state variables
self.is_metric: bool = self.params.get_bool("IsMetric")
self.initial_cruise_speed: int = self.params.get("InitialCruiseSpeedKph", return_default=True)
self.is_release = False # self.params.get_bool("IsReleaseBranch")
self.always_on_dm: bool = self.params.get_bool("AlwaysOnDM")
self.experimental_mode: bool = self.params.get_bool("ExperimentalMode")
self.experimental_mode_confirmed: bool = self.params.get_bool("ExperimentalModeConfirmed")
self.navigation_enabled: bool = self.params.get_bool("NavigationEnabled")
self.nav_destination_set: bool = self.params.get("NavDestination") is not None
self.chestnut_present: bool = False
self.chestnut_compiled: bool = chestnut_compiled()
self.chestnut_active: bool | None = None
self.chestnut_loading: bool = False
self.chestnut_alert: bool = False
self.usb_connected: bool = False
self.usb_connected_ts: float | None = None
self.usb_disconnected_ts: float | None = None
@@ -210,7 +215,7 @@ class UIState(UIStateSP):
self.status = UIStatus.DISENGAGED
self.started_frame = self.sm.frame
self.started_time = time.monotonic()
self.chestnut_present = self.sm["deviceState"].chestnutPresent
self.chestnut_present = self.sm["deviceState"].chestnutPresent or (self.chestnut_compiled and self.usb_connected)
for callback in self._offroad_transition_callbacks:
callback()
@@ -221,7 +226,8 @@ class UIState(UIStateSP):
detected = self.sm["deviceState"].chestnutPresent
if not self.started:
self.chestnut_present = detected
self.chestnut_state = (ChestnutState.READY if detected and self.chestnut_compiled else
self.chestnut_state = (ChestnutState.FAILED if self.chestnut_alert else
ChestnutState.READY if detected and self.chestnut_compiled else
ChestnutState.UNCOMPILED if detected else ChestnutState.DISCONNECTED)
return
@@ -230,10 +236,10 @@ class UIState(UIStateSP):
self.chestnut_state = ChestnutState.DISCONNECTED
elif not self.chestnut_compiled:
self.chestnut_state = ChestnutState.UNCOMPILED
elif self.chestnut_state == ChestnutState.FAILED or not detected or (model_seen and (not self.sm.alive["modelV2"] or not self.sm["modelV2"].big)):
self.chestnut_state = ChestnutState.FAILED
elif self.chestnut_loading or not model_seen:
self.chestnut_state = ChestnutState.LOADING
elif self.chestnut_state == ChestnutState.FAILED or not detected or (model_seen and (not self.sm.alive["modelV2"] or not self.sm["modelV2"].big)):
self.chestnut_state = ChestnutState.FAILED
elif self.chestnut_active is False:
self.chestnut_state = ChestnutState.FAILED
else:
@@ -252,15 +258,15 @@ class UIState(UIStateSP):
self.recording_audio = self.params.get_bool("RecordAudio") and self.started
self.is_metric = self.params.get_bool("IsMetric")
self.initial_cruise_speed = self.params.get("InitialCruiseSpeedKph", return_default=True)
self.always_on_dm = self.params.get_bool("AlwaysOnDM")
self.experimental_mode = self.params.get_bool("ExperimentalMode")
self.experimental_mode_confirmed = self.params.get_bool("ExperimentalModeConfirmed")
if not self.chestnut_compiled:
self.chestnut_compiled = chestnut_compiled()
self.navigation_enabled = self.params.get_bool("NavigationEnabled")
self.nav_destination_set = self.params.get("NavDestination") is not None
self.chestnut_active = self.params.get("ChestnutActive")
self.chestnut_loading = self.params.get_bool("ChestnutLoading")
self.chestnut_alert = any(self.params.get(k) is not None for k, a in OFFROAD_ALERTS.items() if k.startswith("Offroad_Chestnut") and a["severity"] >= 0)
now = time.monotonic()
if read_int(TYPEC_CC_ORIENTATION_PATH) != 0:
if cable_connected():
self.usb_disconnected_ts = None
if not self.usb_connected:
self.usb_connected = True
@@ -1,65 +0,0 @@
import pyray as rl
from openpilot.common.params import Params
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
class ExperimentalModeButton(Widget):
def __init__(self):
super().__init__()
self.img_width = 80
self.horizontal_padding = 25
self.button_height = 125
self.params = Params()
self.experimental_mode = self.params.get_bool("ExperimentalMode")
self.chill_pixmap = gui_app.texture("icons/couch.png", self.img_width, self.img_width)
self.experimental_pixmap = gui_app.texture("icons/experimental_grey.png", self.img_width, self.img_width)
def show_event(self):
super().show_event()
self.experimental_mode = self.params.get_bool("ExperimentalMode")
def _get_gradient_colors(self):
alpha = 0xCC if self.is_pressed else 0xFF
if self.experimental_mode:
return rl.Color(255, 155, 63, alpha), rl.Color(219, 56, 34, alpha)
else:
return rl.Color(20, 255, 171, alpha), rl.Color(35, 149, 255, alpha)
def _draw_gradient_background(self, rect):
start_color, end_color = self._get_gradient_colors()
rl.draw_rectangle_gradient_h(int(rect.x), int(rect.y), int(rect.width), int(rect.height),
start_color, end_color)
def _render(self, rect):
rl.begin_scissor_mode(int(rect.x), int(rect.y), int(rect.width), int(rect.height))
self._draw_gradient_background(rect)
rl.draw_rectangle_rounded_lines_ex(self._rect, 0.19, 10, 5, rl.BLACK)
rl.end_scissor_mode()
# Draw vertical separator line
line_x = rect.x + rect.width - self.img_width - (2 * self.horizontal_padding)
separator_color = rl.Color(0, 0, 0, 77) # 0x4d = 77
rl.draw_line_ex(rl.Vector2(line_x, rect.y), rl.Vector2(line_x, rect.y + rect.height), 3, separator_color)
# Draw text label (left aligned)
text = tr("EXPERIMENTAL MODE ON") if self.experimental_mode else tr("CHILL MODE ON")
text_x = rect.x + self.horizontal_padding
text_y = rect.y + rect.height / 2 - 45 * FONT_SCALE // 2 # Center vertically
rl.draw_text_ex(gui_app.font(FontWeight.NORMAL), text, rl.Vector2(int(text_x), int(text_y)), 45, 0, rl.BLACK)
# Draw icon (right aligned)
icon_x = rect.x + rect.width - self.horizontal_padding - self.img_width
icon_y = rect.y + (rect.height - self.img_width) / 2
icon_rect = rl.Rectangle(icon_x, icon_y, self.img_width, self.img_width)
# Draw current mode icon
current_icon = self.experimental_pixmap if self.experimental_mode else self.chill_pixmap
source_rect = rl.Rectangle(0, 0, current_icon.width, current_icon.height)
rl.draw_texture_pro(current_icon, source_rect, icon_rect, rl.Vector2(0, 0), 0, rl.WHITE)
+1 -2
View File
@@ -4,7 +4,7 @@
#include <chrono>
#include <utility>
#include "tools/replay/filereader.h"
#include "tools/replay/py_downloader.h"
#include "tools/replay/py_tools.h"
#include "tools/replay/util.h"
#include "common/util.h"
@@ -142,7 +142,6 @@ void LogReader::migrateOldEvents() {
new_state.setAlertType(old_dep.getAlertType());
new_state.setEnabled(old_dep.getEnabled());
new_state.setEngageable(old_dep.getEngageable());
new_state.setExperimentalMode(old_dep.getExperimentalMode());
new_state.setPersonality(old_dep.getPersonality());
new_state.setState(old_dep.getState());