mirror of
https://github.com/firestar5683/StarPilot.git
synced 2026-11-02 20:13:53 +08:00
Enable Experimental Mode by default without confirmation (#39043)
* Enable Experimental Mode by default without confirmation * Update toggles.py * Fix formatting in End-to-End Longitudinal Control description Original-upstream-commit: f21bfc36be5f423f31b2c2a948d1e2eff91b02e8 Upstream: https://github.com/commaai/openpilot/commit/f21bfc36be5f423f31b2c2a948d1e2eff91b02e8
This commit is contained in:
@@ -183,7 +183,6 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> keys = {
|
||||
{"DriverLockoutCount", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, INT, "0"}},
|
||||
{"AlphaLongitudinalEnabled", {PERSISTENT, BOOL}},
|
||||
{"ExperimentalMode", {PERSISTENT, BOOL}},
|
||||
{"ExperimentalModeConfirmed", {PERSISTENT, BOOL}},
|
||||
{"FirmwareQueryDone", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}},
|
||||
{"ForcePowerDown", {PERSISTENT, BOOL}},
|
||||
{"GitBranch", {PERSISTENT, STRING}},
|
||||
|
||||
@@ -3,10 +3,7 @@ from openpilot.common.params import Params, UnknownKeyName
|
||||
from openpilot.system.ui.widgets import Widget
|
||||
from openpilot.system.ui.widgets.list_view import multiple_button_item, toggle_item
|
||||
from openpilot.system.ui.widgets.scroller_tici import Scroller
|
||||
from openpilot.system.ui.widgets.confirm_dialog import ConfirmDialog
|
||||
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.selfdrive.ui.ui_state import ui_state
|
||||
from openpilot.starpilot.ui.slc_offset_feature import SlcOffsetOwner, native_parked
|
||||
|
||||
@@ -159,15 +156,9 @@ class TogglesLayout(Widget):
|
||||
ui_state.update_params()
|
||||
|
||||
e2e_description = tr(
|
||||
"openpilot 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. openpilot 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."
|
||||
"Since the driving model decides the speed to drive, the set speed will only act as an upper bound. Mistakes should be expected.<br>"
|
||||
)
|
||||
|
||||
if ui_state.CP is not None:
|
||||
@@ -216,27 +207,6 @@ class TogglesLayout(Widget):
|
||||
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)
|
||||
self._toggles["ExperimentalMode"].action_item.set_state(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._params.put_bool("ExperimentalMode", state, block=True)
|
||||
self._toggles["ExperimentalMode"].action_item.set_state(state)
|
||||
self._update_experimental_mode_icon()
|
||||
|
||||
def _toggle_callback(self, state: bool, param: str):
|
||||
if param == "IsMetric":
|
||||
@@ -244,12 +214,10 @@ class TogglesLayout(Widget):
|
||||
self._toggles[param].action_item.set_state(self._params.get_bool(param))
|
||||
self._metric_source = self._slc_offsets._raw("IsMetric")
|
||||
return ok
|
||||
if param == "ExperimentalMode":
|
||||
self._handle_experimental_mode_toggle(state)
|
||||
return True
|
||||
|
||||
self._params.put_bool(param, state, block=True)
|
||||
self._toggles[param].action_item.set_state(state)
|
||||
if param == "ExperimentalMode":
|
||||
self._update_experimental_mode_icon()
|
||||
if self._toggle_defs[param][3]:
|
||||
self._params.put_bool("OnroadCycleRequested", True, block=True)
|
||||
return True
|
||||
|
||||
@@ -173,7 +173,7 @@ class MiciHomeLayout(Widget):
|
||||
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:
|
||||
if ui_state.has_longitudinal_control:
|
||||
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
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
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 BigParamControl, BigMultiParamToggle, BigToggle
|
||||
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
|
||||
@@ -13,29 +10,6 @@ from openpilot.starpilot.ui.slc_offset_feature import SlcOffsetOwner, native_par
|
||||
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):
|
||||
@@ -50,8 +24,8 @@ class TogglesLayoutMici(NavScroller):
|
||||
"Relaxed leaves more space.\n" +
|
||||
"Use the steering wheel distance button on supported cars.")
|
||||
self._safe_mode_btn = BigParamControl("safe mode", "SafeMode", toggle_callback=restart_needed_callback)
|
||||
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,
|
||||
self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode",
|
||||
description_icon=gui_app.texture("icons_mici/experimental_mode.png", 64, 64),
|
||||
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" +
|
||||
@@ -164,19 +138,6 @@ class TogglesLayoutMici(NavScroller):
|
||||
ui_state.params.put_bool("RecordAudio", desired)
|
||||
ui_state.params.put_bool("OnroadCycleRequested", True)
|
||||
|
||||
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)
|
||||
|
||||
def request_personality(self, index: int) -> bool:
|
||||
self._update_toggles()
|
||||
@@ -186,8 +147,9 @@ class TogglesLayoutMici(NavScroller):
|
||||
|
||||
def request_experimental(self) -> bool:
|
||||
self._update_toggles()
|
||||
if (ui_state.CP is None or not ui_state.has_longitudinal_control or not self._experimental_btn.enabled or
|
||||
not ui_state.params.get_bool("ExperimentalModeConfirmed")):
|
||||
if ui_state.CP is None or not ui_state.has_longitudinal_control or not self._experimental_btn.enabled:
|
||||
return False
|
||||
self._on_experimental_mode(not ui_state.sm["selfdriveState"].experimentalMode)
|
||||
desired = not ui_state.sm["selfdriveState"].experimentalMode
|
||||
ui_state.params.put_bool("ExperimentalMode", desired)
|
||||
self._experimental_btn.set_checked(desired)
|
||||
return True
|
||||
|
||||
@@ -38,7 +38,7 @@ class ExpButton(Widget):
|
||||
|
||||
def request_toggle(self) -> bool:
|
||||
self._update_state()
|
||||
if self._is_toggle_allowed():
|
||||
if ui_state.has_longitudinal_control:
|
||||
new_mode = not self._experimental_mode
|
||||
self._params.put_bool("ExperimentalMode", new_mode)
|
||||
|
||||
@@ -67,10 +67,3 @@ class ExpButton(Widget):
|
||||
self._hold_end_time = self._held_mode = None
|
||||
|
||||
return self._experimental_mode
|
||||
|
||||
def _is_toggle_allowed(self):
|
||||
if not self._params.get_bool("ExperimentalModeConfirmed"):
|
||||
return False
|
||||
|
||||
# Mirror exp mode toggle using persistent car params
|
||||
return ui_state.has_longitudinal_control
|
||||
|
||||
@@ -138,7 +138,6 @@ class UIState:
|
||||
self.is_release = 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.chestnut_present: bool = False
|
||||
self._gpu_artifacts = ModelManager()
|
||||
self._gpu_artifacts_at: float | None = None
|
||||
@@ -322,7 +321,6 @@ class UIState:
|
||||
self.is_metric = self.params.get_bool("IsMetric")
|
||||
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")
|
||||
raw_chestnut = self.params.get("ChestnutActive")
|
||||
self.chestnut_active = raw_chestnut if type(raw_chestnut) is bool else (
|
||||
True if raw_chestnut in (b"1", "1") else False if raw_chestnut in (b"0", "0") else None)
|
||||
|
||||
@@ -48,7 +48,6 @@ class TestUiAction(unittest.TestCase):
|
||||
directory = tempfile.TemporaryDirectory()
|
||||
self.addCleanup(directory.cleanup)
|
||||
self.params = Params(directory.name)
|
||||
self.params.put_bool('ExperimentalModeConfirmed', True, block=True)
|
||||
self.params.put('LKASButtonControl', 5, block=True)
|
||||
self.cp = CarInterface.get_non_essential_params(CAR.HONDA_CIVIC)
|
||||
self.planner = LongitudinalPlanner(self.cp, init_v=15.0)
|
||||
@@ -226,7 +225,7 @@ class TestUiAction(unittest.TestCase):
|
||||
setattr(changed.slcAction, field, value)
|
||||
self.assertIsNone(observation(changed, observed))
|
||||
|
||||
def test_release_checks_exact_settings_confirmation_and_changed_context(self):
|
||||
def test_release_checks_exact_settings_and_changed_context(self):
|
||||
stamp = MONO + 50_000_000
|
||||
self.ack(stamp)
|
||||
context = self.ui.context(self.sm, self.cp, now_ns=stamp + 1000)
|
||||
@@ -237,9 +236,11 @@ class TestUiAction(unittest.TestCase):
|
||||
self.params.put_bool('SafeMode', True, block=True)
|
||||
self.assertFalse(self.ui.dispatch(context, self.sm, self.cp, self.publisher, now_ns=stamp + 10_002_000))
|
||||
self.params.remove('SafeMode')
|
||||
self.params.put_bool('ExperimentalModeConfirmed', False, block=True)
|
||||
original = self.params.get('ConditionalModeConfig')
|
||||
self.configure(choice=ModeChoice.STOCK)
|
||||
self.assertFalse(self.ui.dispatch(context, self.sm, self.cp, self.publisher, now_ns=stamp + 10_003_000))
|
||||
self.params.put_bool('ExperimentalModeConfirmed', True, block=True)
|
||||
self.params.put('ConditionalModeConfig', original, block=True)
|
||||
self.choice = ModeChoice.CEM
|
||||
self.ack(stamp + 20_000_000, effective=True)
|
||||
self.assertFalse(self.ui.dispatch(context, self.sm, self.cp, self.publisher, now_ns=stamp + 20_001_000))
|
||||
self.assertEqual(self.publisher.events, [])
|
||||
|
||||
@@ -51,8 +51,7 @@ def current_authority(sm, cp, drive_id: int, now_ns: int) -> bool:
|
||||
def exact_settings(params, snapshot: SettingsSnapshot) -> bool:
|
||||
try:
|
||||
return (read_saved(params, 'ConditionalModeConfig', MAX_DOCUMENT_BYTES) == (snapshot.document_raw, True) and
|
||||
read_saved(params, 'SafeMode', 8) == (snapshot.safe_mode_raw, True) and
|
||||
read_saved(params, 'ExperimentalModeConfirmed', 8) == (b'1', True))
|
||||
read_saved(params, 'SafeMode', 8) == (snapshot.safe_mode_raw, True))
|
||||
except (AttributeError, OSError, TypeError, ValueError):
|
||||
return False
|
||||
|
||||
|
||||
@@ -315,10 +315,9 @@ class StarShellSession:
|
||||
return bool(self._favorite_authority() and ui_state.CP is not None and ui_state.has_longitudinal_control and
|
||||
not ui_state.params.get_bool("SafeMode") and callback())
|
||||
|
||||
confirmed = ui_state.params.get_bool("ExperimentalModeConfirmed")
|
||||
context = (self._conditional_action_owner().context(ui_state.sm, ui_state.CP, now_ns=time.monotonic_ns())
|
||||
if long_available and confirmed and conditional else None)
|
||||
exp_available = bool(long_available and confirmed and (not conditional or context is not None))
|
||||
if long_available and conditional else None)
|
||||
exp_available = bool(long_available and (not conditional or context is not None))
|
||||
def toggle_experimental():
|
||||
if self._conditional_favorite_active() != conditional:
|
||||
return False
|
||||
|
||||
@@ -310,19 +310,18 @@ class NativeSettingsPageTests(unittest.TestCase):
|
||||
personality.callback.assert_not_called()
|
||||
self.assertEqual(personality.selected_button, 1)
|
||||
|
||||
def test_toggles_saved_state_matches_display_after_changes_and_confirmations(self):
|
||||
def test_toggles_saved_state_matches_display_after_changes(self):
|
||||
# Run the real controller methods with only device services isolated.
|
||||
source = Path('openpilot/selfdrive/ui/layouts/settings/toggles.py')
|
||||
layout = next(node for node in ast.parse(source.read_text()).body if isinstance(node, ast.ClassDef) and node.name == 'TogglesLayout')
|
||||
methods = {'_update_toggles', '_update_state', '_toggle_callback', '_handle_experimental_mode_toggle',
|
||||
methods = {'_update_toggles', '_update_state', '_toggle_callback',
|
||||
'request_toggle', 'request_personality', '_set_longitudinal_personality'}
|
||||
layout.bases = []
|
||||
layout.body = [node for node in layout.body if isinstance(node, ast.FunctionDef) and node.name in methods]
|
||||
ui = NS(CP=None, engaged=False, update_params=Mock(), sm=NS(updated={'selfdriveState': False}))
|
||||
app = NS(push_widget=Mock())
|
||||
namespace = ModuleType('toggles_controller_test')
|
||||
namespace.__dict__.update(tr=lambda text: text, ui_state=ui, gui_app=app, DialogResult=DialogResult,
|
||||
ConfirmDialog=lambda *args, **kwargs: NS(callback=kwargs['callback']))
|
||||
namespace.__dict__.update(tr=lambda text: text, ui_state=ui)
|
||||
exec(compile(ast.Module(body=[layout], type_ignores=[]), str(source), 'exec'), namespace.__dict__)
|
||||
owner = namespace.TogglesLayout()
|
||||
saved = {'IsLdwEnabled': False, 'LongitudinalPersonality': 1}
|
||||
@@ -358,15 +357,12 @@ class NativeSettingsPageTests(unittest.TestCase):
|
||||
self.click(pane, 2000)
|
||||
self.assertFalse(saved['IsLdwEnabled'])
|
||||
self.assertEqual(pane.snapshot().rows[0].value, 'Off')
|
||||
for result in (DialogResult.CANCEL, DialogResult.CONFIRM):
|
||||
for desired in (True, False):
|
||||
self.click(pane, 2000, index=2)
|
||||
self.assertFalse(saved.get('ExperimentalMode', False))
|
||||
app.push_widget.call_args.args[0].callback(result)
|
||||
pane._update_state()
|
||||
self.assertEqual(pane.snapshot().rows[2].value, 'On' if result == DialogResult.CONFIRM else 'Off')
|
||||
self.click(pane, 2000, index=2)
|
||||
self.assertFalse(saved['ExperimentalMode'])
|
||||
self.assertEqual(pane.snapshot().rows[2].value, 'Off')
|
||||
self.assertEqual(saved['ExperimentalMode'], desired)
|
||||
self.assertEqual(pane.snapshot().rows[2].value, 'On' if desired else 'Off')
|
||||
app.push_widget.assert_not_called()
|
||||
|
||||
def network_panel(self):
|
||||
manager = Mock(spec=WifiManager, is_connection_saved=lambda ssid: ssid == "Saved", connected_ssid="Saved",
|
||||
|
||||
@@ -307,7 +307,7 @@ class TestOnroadFavorites(unittest.TestCase):
|
||||
bookmark, personality, experimental = Mock(return_value=True), Mock(return_value=True), Mock(return_value=True)
|
||||
native = NS(CP=NS(carFingerprint='car', flags=0, pcmCruise=True), has_longitudinal_control=True, started_frame=42, personality=1,
|
||||
sm={"deviceState": NS(startedMonoTime=42)},
|
||||
params=NS(get_bool=lambda key: key == 'ExperimentalModeConfirmed'))
|
||||
params=NS(get_bool=lambda key: False))
|
||||
with patch.object(runtime_app, 'ui_state', native):
|
||||
actions = session.native_favorite_actions(bookmark, personality, experimental)
|
||||
assert actions[BOOKMARK].invoke()
|
||||
@@ -444,7 +444,7 @@ class TestOnroadFavorites(unittest.TestCase):
|
||||
self.enterContext(patch.object(session, 'conditional_actions', NS(context=Mock(return_value=context), dispatch=Mock(return_value=True)), create=True))
|
||||
experimental = Mock(return_value=True)
|
||||
native = NS(CP=NS(carFingerprint='car', flags=0, pcmCruise=True), has_longitudinal_control=True, started_frame=42,
|
||||
personality=1, sm={"deviceState": NS(startedMonoTime=42)}, params=NS(get_bool=lambda key: key == 'ExperimentalModeConfirmed'))
|
||||
personality=1, sm={"deviceState": NS(startedMonoTime=42)}, params=NS(get_bool=lambda key: False))
|
||||
with patch.object(runtime_app, 'ui_state', native), patch.object(runtime_app, '_slc_action_publisher') as publisher:
|
||||
action = session.native_favorite_actions(Mock(), Mock(), experimental)[EXPERIMENTAL]
|
||||
assert action.available and action.invoke()
|
||||
@@ -457,9 +457,9 @@ class TestOnroadFavorites(unittest.TestCase):
|
||||
session._favorite_authority.return_value = False
|
||||
assert not action.invoke()
|
||||
session._favorite_authority.return_value = True
|
||||
native.params.get_bool = lambda key: key in ('SafeMode', 'ExperimentalModeConfirmed')
|
||||
native.params.get_bool = lambda key: key == 'SafeMode'
|
||||
assert not action.invoke()
|
||||
native.params.get_bool = lambda key: key == 'ExperimentalModeConfirmed'
|
||||
native.params.get_bool = lambda key: False
|
||||
session._conditional_favorite_active.return_value = False
|
||||
assert not action.invoke()
|
||||
assert session.conditional_actions.dispatch.call_count == 1
|
||||
|
||||
Reference in New Issue
Block a user