diff --git a/common/libcommon.a b/common/libcommon.a index 22312efff..c19133a70 100644 Binary files a/common/libcommon.a and b/common/libcommon.a differ diff --git a/common/params_keys.h b/common/params_keys.h index c4a3a7b8a..641f91e60 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -42,6 +42,8 @@ inline static std::unordered_map keys = { {"ExperimentalLongitudinalEnabled", {PERSISTENT, BOOL}}, {"ExperimentalMode", {PERSISTENT, BOOL}}, {"ExperimentalModeConfirmed", {PERSISTENT, BOOL}}, + {"PersistExperimentalState", {PERSISTENT, BOOL, "0", "0", 1}}, + {"PersistedCEStatus", {PERSISTENT, INT, "0", "0"}}, {"FirmwareQueryDone", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, {"ForcePowerDown", {PERSISTENT, BOOL}}, {"GitBranch", {PERSISTENT, STRING}}, diff --git a/common/params_pyx.so b/common/params_pyx.so index 1d7c4ae2c..74bc8c788 100755 Binary files a/common/params_pyx.so and b/common/params_pyx.so differ diff --git a/panda/board/obj/body_h7.bin.signed b/panda/board/obj/body_h7.bin.signed index ce98b80bd..eef0a8395 100644 Binary files a/panda/board/obj/body_h7.bin.signed and b/panda/board/obj/body_h7.bin.signed differ diff --git a/panda/board/obj/body_h7/bootstub.elf b/panda/board/obj/body_h7/bootstub.elf index 3e110e1e6..992175ad7 100755 Binary files a/panda/board/obj/body_h7/bootstub.elf and b/panda/board/obj/body_h7/bootstub.elf differ diff --git a/panda/board/obj/body_h7/main.bin b/panda/board/obj/body_h7/main.bin index e12db6dda..101c49196 100755 Binary files a/panda/board/obj/body_h7/main.bin and b/panda/board/obj/body_h7/main.bin differ diff --git a/panda/board/obj/body_h7/main.elf b/panda/board/obj/body_h7/main.elf index 34cbb277f..3a966c784 100755 Binary files a/panda/board/obj/body_h7/main.elf and b/panda/board/obj/body_h7/main.elf differ diff --git a/panda/board/obj/bootstub.body_h7.bin b/panda/board/obj/bootstub.body_h7.bin index 5e90c3238..e6d48b5aa 100755 Binary files a/panda/board/obj/bootstub.body_h7.bin and b/panda/board/obj/bootstub.body_h7.bin differ diff --git a/panda/board/obj/bootstub.panda.bin b/panda/board/obj/bootstub.panda.bin index d68ff5d68..1e2e82392 100755 Binary files a/panda/board/obj/bootstub.panda.bin and b/panda/board/obj/bootstub.panda.bin differ diff --git a/panda/board/obj/bootstub.panda_h7.bin b/panda/board/obj/bootstub.panda_h7.bin index 4b0852e58..2342ae913 100755 Binary files a/panda/board/obj/bootstub.panda_h7.bin and b/panda/board/obj/bootstub.panda_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_h7_remote.bin b/panda/board/obj/bootstub.panda_h7_remote.bin index 4b0852e58..2342ae913 100755 Binary files a/panda/board/obj/bootstub.panda_h7_remote.bin and b/panda/board/obj/bootstub.panda_h7_remote.bin differ diff --git a/panda/board/obj/bootstub.panda_jungle_h7.bin b/panda/board/obj/bootstub.panda_jungle_h7.bin index 2a2ca36a0..2acb47d9e 100755 Binary files a/panda/board/obj/bootstub.panda_jungle_h7.bin and b/panda/board/obj/bootstub.panda_jungle_h7.bin differ diff --git a/panda/board/obj/bootstub.panda_remote.bin b/panda/board/obj/bootstub.panda_remote.bin index d68ff5d68..1e2e82392 100755 Binary files a/panda/board/obj/bootstub.panda_remote.bin and b/panda/board/obj/bootstub.panda_remote.bin differ diff --git a/panda/board/obj/gitversion.h b/panda/board/obj/gitversion.h index 0014137d7..51e472007 100644 --- a/panda/board/obj/gitversion.h +++ b/panda/board/obj/gitversion.h @@ -1,2 +1,2 @@ extern const uint8_t gitversion[19]; -const uint8_t gitversion[19] = "DEV-b5546f24-DEBUG"; +const uint8_t gitversion[19] = "DEV-08efd8f8-DEBUG"; diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index 039afded6..ae692f3c2 100644 Binary files a/panda/board/obj/panda.bin.signed and b/panda/board/obj/panda.bin.signed differ diff --git a/panda/board/obj/panda/bootstub.elf b/panda/board/obj/panda/bootstub.elf index 330eb7cb8..c494a815c 100755 Binary files a/panda/board/obj/panda/bootstub.elf and b/panda/board/obj/panda/bootstub.elf differ diff --git a/panda/board/obj/panda/main.bin b/panda/board/obj/panda/main.bin index f5f44437d..75d8d2619 100755 Binary files a/panda/board/obj/panda/main.bin and b/panda/board/obj/panda/main.bin differ diff --git a/panda/board/obj/panda/main.elf b/panda/board/obj/panda/main.elf index 1c8c9c0bb..42808b0cc 100755 Binary files a/panda/board/obj/panda/main.elf and b/panda/board/obj/panda/main.elf differ diff --git a/panda/board/obj/panda_h7.bin.signed b/panda/board/obj/panda_h7.bin.signed index 1545a7ea9..3876043cb 100644 Binary files a/panda/board/obj/panda_h7.bin.signed and b/panda/board/obj/panda_h7.bin.signed differ diff --git a/panda/board/obj/panda_h7/bootstub.elf b/panda/board/obj/panda_h7/bootstub.elf index 786d1fa58..78f44cd46 100755 Binary files a/panda/board/obj/panda_h7/bootstub.elf and b/panda/board/obj/panda_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_h7/main.bin b/panda/board/obj/panda_h7/main.bin index b5c28db7a..6d70908e0 100755 Binary files a/panda/board/obj/panda_h7/main.bin and b/panda/board/obj/panda_h7/main.bin differ diff --git a/panda/board/obj/panda_h7/main.elf b/panda/board/obj/panda_h7/main.elf index 6bd58b557..810f97a95 100755 Binary files a/panda/board/obj/panda_h7/main.elf and b/panda/board/obj/panda_h7/main.elf differ diff --git a/panda/board/obj/panda_h7_remote.bin.signed b/panda/board/obj/panda_h7_remote.bin.signed index 2c19e7dfd..a9d526b38 100644 Binary files a/panda/board/obj/panda_h7_remote.bin.signed and b/panda/board/obj/panda_h7_remote.bin.signed differ diff --git a/panda/board/obj/panda_h7_remote/bootstub.elf b/panda/board/obj/panda_h7_remote/bootstub.elf index e26961601..e62c96b38 100755 Binary files a/panda/board/obj/panda_h7_remote/bootstub.elf and b/panda/board/obj/panda_h7_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_h7_remote/main.bin b/panda/board/obj/panda_h7_remote/main.bin index e9ac603b8..acbbcb6d8 100755 Binary files a/panda/board/obj/panda_h7_remote/main.bin and b/panda/board/obj/panda_h7_remote/main.bin differ diff --git a/panda/board/obj/panda_h7_remote/main.elf b/panda/board/obj/panda_h7_remote/main.elf index a28b8d610..8e43065e1 100755 Binary files a/panda/board/obj/panda_h7_remote/main.elf and b/panda/board/obj/panda_h7_remote/main.elf differ diff --git a/panda/board/obj/panda_jungle_h7.bin.signed b/panda/board/obj/panda_jungle_h7.bin.signed index a5fd36bc9..60f29bb73 100644 Binary files a/panda/board/obj/panda_jungle_h7.bin.signed and b/panda/board/obj/panda_jungle_h7.bin.signed differ diff --git a/panda/board/obj/panda_jungle_h7/bootstub.elf b/panda/board/obj/panda_jungle_h7/bootstub.elf index e6d10af62..ef771e51e 100755 Binary files a/panda/board/obj/panda_jungle_h7/bootstub.elf and b/panda/board/obj/panda_jungle_h7/bootstub.elf differ diff --git a/panda/board/obj/panda_jungle_h7/main.bin b/panda/board/obj/panda_jungle_h7/main.bin index 2da8b0de5..996b83772 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.bin and b/panda/board/obj/panda_jungle_h7/main.bin differ diff --git a/panda/board/obj/panda_jungle_h7/main.elf b/panda/board/obj/panda_jungle_h7/main.elf index 8bf0cb8d4..582ce8ea7 100755 Binary files a/panda/board/obj/panda_jungle_h7/main.elf and b/panda/board/obj/panda_jungle_h7/main.elf differ diff --git a/panda/board/obj/panda_remote.bin.signed b/panda/board/obj/panda_remote.bin.signed index a0e53b308..c922dbddb 100644 Binary files a/panda/board/obj/panda_remote.bin.signed and b/panda/board/obj/panda_remote.bin.signed differ diff --git a/panda/board/obj/panda_remote/bootstub.elf b/panda/board/obj/panda_remote/bootstub.elf index 83e94c89a..2f0d81747 100755 Binary files a/panda/board/obj/panda_remote/bootstub.elf and b/panda/board/obj/panda_remote/bootstub.elf differ diff --git a/panda/board/obj/panda_remote/main.bin b/panda/board/obj/panda_remote/main.bin index 8ec65f51d..0674ce9f0 100755 Binary files a/panda/board/obj/panda_remote/main.bin and b/panda/board/obj/panda_remote/main.bin differ diff --git a/panda/board/obj/panda_remote/main.elf b/panda/board/obj/panda_remote/main.elf index 967e94c9c..3f2ed467a 100755 Binary files a/panda/board/obj/panda_remote/main.elf and b/panda/board/obj/panda_remote/main.elf differ diff --git a/panda/board/obj/version b/panda/board/obj/version index 13c7dfe1b..f0728639c 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-b5546f24-DEBUG \ No newline at end of file +DEV-08efd8f8-DEBUG \ No newline at end of file diff --git a/selfdrive/pandad/pandad b/selfdrive/pandad/pandad index 92b4faad1..cf2b287f1 100755 Binary files a/selfdrive/pandad/pandad and b/selfdrive/pandad/pandad differ diff --git a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py index db6882db0..0fd3dd665 100644 --- a/selfdrive/ui/layouts/settings/starpilot/longitudinal.py +++ b/selfdrive/ui/layouts/settings/starpilot/longitudinal.py @@ -15,6 +15,7 @@ from openpilot.starpilot.common.accel_profile import ( normalize_acceleration_profile, normalize_deceleration_profile, ) +from openpilot.starpilot.common.experimental_state import sync_persist_experimental_state ACCELERATION_PROFILE_OPTIONS = [ @@ -254,6 +255,15 @@ class StarPilotConditionalExperimentalLayout(StarPilotPanel): "icon": "toggle_icons/icon_conditional.png", "color": "#597497", }, + { + "title": tr_noop("Persist Experimental State"), + "desc": tr_noop("Keep your manual Conditional Experimental override through reboots until you manually clear it."), + "type": "toggle", + "get_state": lambda: self._params.get_bool("PersistExperimentalState"), + "set_state": self._set_persist_experimental_state, + "color": "#597497", + "visible": lambda: self._params.get_bool("ConditionalExperimental"), + }, { "title": tr_noop("Below Speed"), "type": "value", @@ -353,6 +363,9 @@ class StarPilotConditionalExperimentalLayout(StarPilotPanel): ] self._rebuild_grid() + def _set_persist_experimental_state(self, state: bool): + sync_persist_experimental_state(self._params, self._params_memory, state) + def _show_speed_selector(self, key): def on_close(res, val): if res == DialogResult.CONFIRM: diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 177dbcfd9..7fc023771 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -8,6 +8,12 @@ from openpilot.system.ui.widgets.label import gui_label, MiciLabel, UnifiedLabel from openpilot.system.ui.widgets import Widget from openpilot.system.ui.lib.application import gui_app, FontWeight, DEFAULT_TEXT_COLOR, MousePos from openpilot.starpilot.common.starpilot_variables import MODELS_PATH +from openpilot.starpilot.common.experimental_state import ( + CEStatus, + next_manual_ce_status, + requested_experimental_mode, + sync_manual_ce_state, +) from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.text import wrap_text from openpilot.system.version import training_version, RELEASE_BRANCHES @@ -130,7 +136,7 @@ class MiciHomeLayout(Widget): def _update_params(self): self._safe_mode = ui_state.params.get_bool("SafeMode") - self._experimental_mode = ui_state.params.get_bool("ExperimentalMode") and not self._safe_mode + self._experimental_mode = requested_experimental_mode(ui_state.params, ui_state.params_memory) def _clean_name(value: str) -> str: return re.sub(r"[πŸ—ΊοΈπŸ‘€πŸ“‘]", "", value).replace("(Default)", "").strip() @@ -218,8 +224,15 @@ class MiciHomeLayout(Widget): 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 not self._safe_mode: - self._experimental_mode = not self._experimental_mode - ui_state.params.put("ExperimentalMode", self._experimental_mode) + if ui_state.params.get_bool("ConditionalExperimental"): + current_status = ui_state.params_memory.get_int("CEStatus", default=CEStatus["OFF"]) + override_value = next_manual_ce_status(current_status, self._experimental_mode) + ui_state.params_memory.put_int("CEStatus", override_value) + sync_manual_ce_state(ui_state.params, override_value) + self._experimental_mode = override_value == CEStatus["USER_OVERRIDDEN"] + else: + self._experimental_mode = not self._experimental_mode + ui_state.params.put_bool("ExperimentalMode", self._experimental_mode) self._mouse_down_t = None self._did_long_press = True diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index 18b82c29d..2dd3caf7b 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -4,6 +4,11 @@ from openpilot.common.params import Params from openpilot.selfdrive.ui.ui_state import ui_state from openpilot.system.ui.lib.application import gui_app from openpilot.system.ui.widgets import Widget +from openpilot.starpilot.common.experimental_state import ( + CEStatus, + next_manual_ce_status, + sync_manual_ce_state, +) class ExpButton(Widget): @@ -35,12 +40,20 @@ class ExpButton(Widget): def _handle_mouse_release(self, _): super()._handle_mouse_release(_) if self._is_toggle_allowed(): - new_mode = not self._experimental_mode - self._params.put_bool("ExperimentalMode", new_mode) + if self._params.get_bool("ConditionalExperimental"): + current_status = ui_state.params_memory.get_int("CEStatus", default=CEStatus["OFF"]) + override_value = next_manual_ce_status(current_status, self._experimental_mode) + ui_state.params_memory.put_int("CEStatus", override_value) + sync_manual_ce_state(self._params, override_value) + self._held_mode = None + self._hold_end_time = None + else: + new_mode = not self._experimental_mode + self._params.put_bool("ExperimentalMode", new_mode) - # Hold new state temporarily - self._held_mode = new_mode - self._hold_end_time = time.monotonic() + self._hold_duration + # Hold new state temporarily + self._held_mode = new_mode + self._hold_end_time = time.monotonic() + self._hold_duration def _render(self, rect: rl.Rectangle) -> None: center_x = int(self._rect.x + self._rect.width // 2) diff --git a/selfdrive/ui/qt/offroad/experimental_mode.cc b/selfdrive/ui/qt/offroad/experimental_mode.cc index e255073f3..759cc611d 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.cc +++ b/selfdrive/ui/qt/offroad/experimental_mode.cc @@ -70,7 +70,15 @@ void ExperimentalModeButton::paintEvent(QPaintEvent *event) { } void ExperimentalModeButton::showEvent(QShowEvent *event) { - experimental_mode = params.getBool("ExperimentalMode"); + if (params.getBool("ConditionalExperimental")) { + int status = params_memory.getInt("CEStatus"); + if ((status != 1 && status != 2) && params.getBool("PersistExperimentalState")) { + status = params.getInt("PersistedCEStatus"); + } + experimental_mode = !params.getBool("SafeMode") && status == 2; + } else { + experimental_mode = params.getBool("ExperimentalMode") && !params.getBool("SafeMode"); + } mode_icon->setPixmap(experimental_mode ? experimental_pixmap : chill_pixmap); mode_label->setText(experimental_mode ? tr("EXPERIMENTAL MODE ON") : tr("CHILL MODE ON")); } diff --git a/selfdrive/ui/qt/offroad/experimental_mode.h b/selfdrive/ui/qt/offroad/experimental_mode.h index bfb7638bb..6b15f52dd 100644 --- a/selfdrive/ui/qt/offroad/experimental_mode.h +++ b/selfdrive/ui/qt/offroad/experimental_mode.h @@ -18,6 +18,7 @@ private: void showEvent(QShowEvent *event) override; Params params; + Params params_memory{"", true}; bool experimental_mode; int img_width = 100; int horizontal_padding = 30; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index 37fb599cf..a9dd36f30 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -40,6 +40,7 @@ void ExperimentalButton::changeMode() { if (starpilot_toggles.value("conditional_experimental_mode").toBool()) { int override_value = (starpilot_scene.conditional_status == 1 || starpilot_scene.conditional_status == 2) ? 0 : experimental_mode ? 1 : 2; params_memory.putInt("CEStatus", override_value); + params.putInt("PersistedCEStatus", params.getBool("PersistExperimentalState") ? override_value : 0); } else { params.putBool("ExperimentalMode", !experimental_mode); } diff --git a/selfdrive/ui/qt/widgets/controls.cc b/selfdrive/ui/qt/widgets/controls.cc index 992fe7407..ceb78e012 100644 --- a/selfdrive/ui/qt/widgets/controls.cc +++ b/selfdrive/ui/qt/widgets/controls.cc @@ -139,6 +139,15 @@ void ParamControl::toggleClicked(bool state) { if (!confirm || confirmed || !state || do_confirm()) { if (store_confirm && state) params.putBool(key + "Confirmed", true); params.putBool(key, state); + if (key == "PersistExperimentalState") { + static Params params_memory{"", true}; + int persisted_status = 0; + if (state) { + int current_status = params_memory.getInt("CEStatus"); + persisted_status = (current_status == 1 || current_status == 2) ? current_status : 0; + } + params.putInt("PersistedCEStatus", persisted_status); + } setIcon(state); } else { toggle.togglePosition(); diff --git a/selfdrive/ui/ui b/selfdrive/ui/ui index 69d9fc763..bd6617d95 100755 Binary files a/selfdrive/ui/ui and b/selfdrive/ui/ui differ diff --git a/selfdrive/ui/widgets/exp_mode_button.py b/selfdrive/ui/widgets/exp_mode_button.py index ebe5b4e17..d9e85c7b8 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -3,6 +3,8 @@ 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 +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.starpilot.common.experimental_state import requested_experimental_mode class ExperimentalModeButton(Widget): @@ -14,13 +16,13 @@ class ExperimentalModeButton(Widget): self.button_height = 125 self.params = Params() - self.experimental_mode = self.params.get_bool("ExperimentalMode") and not self.params.get_bool("SafeMode") + self.experimental_mode = requested_experimental_mode(self.params, ui_state.params_memory) 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): - self.experimental_mode = self.params.get_bool("ExperimentalMode") and not self.params.get_bool("SafeMode") + self.experimental_mode = requested_experimental_mode(self.params, ui_state.params_memory) def _get_gradient_colors(self): alpha = 0xCC if self.is_pressed else 0xFF diff --git a/starpilot/common/experimental_state.py b/starpilot/common/experimental_state.py new file mode 100644 index 000000000..590fca26f --- /dev/null +++ b/starpilot/common/experimental_state.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +from openpilot.common.params import Params + +PERSIST_EXPERIMENTAL_STATE_PARAM = "PersistExperimentalState" +PERSISTED_CE_STATUS_PARAM = "PersistedCEStatus" +CE_STATUS_PARAM = "CEStatus" + +CEStatus = { + "OFF": 0, + "USER_DISABLED": 1, + "USER_OVERRIDDEN": 2, + "CURVATURE": 3, + "LEAD": 4, + "SIGNAL": 5, + "SPEED": 6, + "SPEED_LIMIT": 7, + "STOP_LIGHT": 8, +} + +MANUAL_CE_STATUSES = { + CEStatus["USER_DISABLED"], + CEStatus["USER_OVERRIDDEN"], +} + + +def is_manual_ce_status(status: int) -> bool: + return int(status) in MANUAL_CE_STATUSES + + +def normalize_persisted_ce_status(status: int) -> int: + status = int(status) + return status if status in MANUAL_CE_STATUSES else CEStatus["OFF"] + + +def get_persisted_ce_status(params: Params) -> int: + return normalize_persisted_ce_status(params.get_int(PERSISTED_CE_STATUS_PARAM, default=CEStatus["OFF"])) + + +def set_persisted_ce_status(params: Params, status: int) -> int: + normalized = normalize_persisted_ce_status(status) + params.put_int(PERSISTED_CE_STATUS_PARAM, normalized) + return normalized + + +def clear_persisted_ce_status(params: Params) -> None: + params.put_int(PERSISTED_CE_STATUS_PARAM, CEStatus["OFF"]) + + +def sync_persist_experimental_state(params: Params, params_memory: Params | None, enabled: bool) -> None: + params.put_bool(PERSIST_EXPERIMENTAL_STATE_PARAM, enabled) + if enabled: + current_status = params_memory.get_int(CE_STATUS_PARAM, default=CEStatus["OFF"]) if params_memory is not None else CEStatus["OFF"] + set_persisted_ce_status(params, current_status) + else: + clear_persisted_ce_status(params) + + +def sync_manual_ce_state(params: Params, status: int) -> int: + return set_persisted_ce_status(params, status) if params.get_bool(PERSIST_EXPERIMENTAL_STATE_PARAM) else clear_and_return_off(params) + + +def clear_and_return_off(params: Params) -> int: + clear_persisted_ce_status(params) + return CEStatus["OFF"] + + +def next_manual_ce_status(current_status: int, experimental_mode: bool) -> int: + if is_manual_ce_status(current_status): + return CEStatus["OFF"] + return CEStatus["USER_DISABLED"] if experimental_mode else CEStatus["USER_OVERRIDDEN"] + + +def requested_experimental_mode(params: Params, params_memory: Params | None = None) -> bool: + if params.get_bool("SafeMode"): + return False + + if params.get_bool("ConditionalExperimental"): + status = params_memory.get_int(CE_STATUS_PARAM, default=CEStatus["OFF"]) if params_memory is not None else CEStatus["OFF"] + if not is_manual_ce_status(status): + status = get_persisted_ce_status(params) + return status == CEStatus["USER_OVERRIDDEN"] + + return params.get_bool("ExperimentalMode") + + +def restore_persisted_ce_state(params: Params, params_memory: Params) -> int: + current_status = params_memory.get_int(CE_STATUS_PARAM, default=CEStatus["OFF"]) + if is_manual_ce_status(current_status): + sync_manual_ce_state(params, current_status) + return current_status + + if not params.get_bool(PERSIST_EXPERIMENTAL_STATE_PARAM): + return current_status + + restored_status = get_persisted_ce_status(params) + if restored_status != CEStatus["OFF"]: + params_memory.put_int(CE_STATUS_PARAM, restored_status) + return restored_status + + return current_status diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 835a6df22..1a73cb9e1 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -206,6 +206,7 @@ EXCLUDED_KEYS = { "openpilotMinutes", "OverpassRequests", "PandaSignatures", + "PersistedCEStatus", "SpeedLimits", "SpeedLimitsFiltered", "UpdateFailedCount", diff --git a/starpilot/controls/lib/conditional_experimental_mode.py b/starpilot/controls/lib/conditional_experimental_mode.py index 0721bf457..ff58833f5 100644 --- a/starpilot/controls/lib/conditional_experimental_mode.py +++ b/starpilot/controls/lib/conditional_experimental_mode.py @@ -6,19 +6,12 @@ from openpilot.common.filter_simple import FirstOrderFilter from openpilot.common.realtime import DT_MDL from openpilot.common.constants import CV -from openpilot.starpilot.common.starpilot_variables import CRUISING_SPEED, THRESHOLD, params_memory - -CEStatus = { - "OFF": 0, # Off - "USER_DISABLED": 1, # "Experimental Mode" disabled by user - "USER_OVERRIDDEN": 2, # "Experimental Mode" enabled by user - "CURVATURE": 3, # Road curvature condition - "LEAD": 4, # Slower lead vehicle condition - "SIGNAL": 5, # Turn signal condition - "SPEED": 6, # Speed condition - "SPEED_LIMIT": 7, # Speed limit controller condition - "STOP_LIGHT": 8 # Stop light or sign condition -} +from openpilot.starpilot.common.experimental_state import ( + CEStatus, + is_manual_ce_status, + restore_persisted_ce_state, +) +from openpilot.starpilot.common.starpilot_variables import CRUISING_SPEED, THRESHOLD def interp(x, xp, fp): return float(np.interp(x, xp, fp)) @@ -66,6 +59,8 @@ class ConditionalExperimentalMode: def __init__(self, StarPilotPlanner): self.starpilot_planner = StarPilotPlanner + self.params = self.starpilot_planner.params + self.params_memory = self.starpilot_planner.params_memory # Faster filters with hysteresis for better responsiveness self.curvature_filter = FirstOrderFilter(0, self.FILTER_TIME_CURVE, DT_MDL) @@ -83,12 +78,9 @@ class ConditionalExperimentalMode: def update(self, v_ego, sm, starpilot_toggles): now = time.monotonic() - if starpilot_toggles.experimental_mode_via_press: - self.status_value = params_memory.get_int("CEStatus") - else: - self.status_value = CEStatus["OFF"] + self.status_value = CEStatus["OFF"] if self.params.get_bool("SafeMode") else restore_persisted_ce_state(self.params, self.params_memory) - if self.status_value not in (CEStatus["USER_DISABLED"], CEStatus["USER_OVERRIDDEN"]) and not sm["carState"].standstill: + if not is_manual_ce_status(self.status_value) and not sm["carState"].standstill: self.update_conditions(v_ego, sm, starpilot_toggles) triggered = self.check_conditions(v_ego, sm, starpilot_toggles) @@ -103,13 +95,13 @@ class ConditionalExperimentalMode: self.experimental_mode = triggered or hold_active or transition_buffer_active self.prev_experimental_mode = self.experimental_mode - params_memory.put_int("CEStatus", self.status_value if self.experimental_mode else CEStatus["OFF"]) + self.params_memory.put_int("CEStatus", self.status_value if self.experimental_mode else CEStatus["OFF"]) else: self.mode_hold_until = 0.0 self.mode_false_since = 0.0 self.experimental_mode = (self.status_value == CEStatus["USER_OVERRIDDEN"] or (sm["carState"].standstill and self.experimental_mode and self.starpilot_planner.model_stopped)) - self.stop_light_detected &= self.status_value not in (CEStatus["USER_DISABLED"], CEStatus["USER_OVERRIDDEN"]) + self.stop_light_detected &= not is_manual_ce_status(self.status_value) self.stop_light_filter.x = 0 def check_conditions(self, v_ego, sm, starpilot_toggles): diff --git a/starpilot/controls/starpilot_card.py b/starpilot/controls/starpilot_card.py index e2a135271..a8d14815e 100644 --- a/starpilot/controls/starpilot_card.py +++ b/starpilot/controls/starpilot_card.py @@ -4,9 +4,13 @@ from openpilot.common.params import Params from openpilot.selfdrive.car.cruise import CRUISE_LONG_PRESS, ButtonType from openpilot.selfdrive.selfdrived.events import ET +from openpilot.starpilot.common.experimental_state import ( + CEStatus, + next_manual_ce_status, + sync_manual_ce_state, +) from openpilot.starpilot.common.starpilot_utilities import is_FrogsGoMoo from openpilot.starpilot.common.starpilot_variables import ERROR_LOGS_PATH, GearShifter, NON_DRIVING_GEARS -from openpilot.starpilot.controls.lib.conditional_experimental_mode import CEStatus class StarPilotCard: def __init__(self, CP, FPCP): @@ -55,14 +59,10 @@ class StarPilotCard: return if starpilot_toggles.conditional_experimental_mode: - if self.params_memory.get_int("CEStatus") in (CEStatus["USER_DISABLED"], CEStatus["USER_OVERRIDDEN"]): - override_value = CEStatus["OFF"] - elif sm["selfdriveState"].experimentalMode: - override_value = CEStatus["USER_DISABLED"] - else: - override_value = CEStatus["USER_OVERRIDDEN"] - + current_status = self.params_memory.get_int("CEStatus", default=CEStatus["OFF"]) + override_value = next_manual_ce_status(current_status, sm["selfdriveState"].experimentalMode) self.params_memory.put_int("CEStatus", override_value) + sync_manual_ce_state(self.params, override_value) else: self.params.put_bool_nonblocking("ExperimentalMode", not sm["selfdriveState"].experimentalMode) diff --git a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json index ccd510485..3c0772990 100644 --- a/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json +++ b/starpilot/system/the_pond/assets/components/tools/device_settings_layout.json @@ -438,6 +438,14 @@ "ui_type": "toggle", "is_parent_toggle": true }, + { + "key": "PersistExperimentalState", + "label": "Persist Experimental State", + "description": "Keep your manual Conditional Experimental override through reboots until you manually clear it.", + "data_type": "bool", + "ui_type": "toggle", + "parent_key": "ConditionalExperimental" + }, { "key": "CESpeed", "label": "Below", diff --git a/starpilot/system/the_pond/the_pond.py b/starpilot/system/the_pond/the_pond.py index 6619369c4..f7152c24a 100644 --- a/starpilot/system/the_pond/the_pond.py +++ b/starpilot/system/the_pond/the_pond.py @@ -60,6 +60,7 @@ from openpilot.starpilot.common.maps_catalog import ( schedule_label, schedule_param_value, ) +from openpilot.starpilot.common.experimental_state import sync_persist_experimental_state from openpilot.starpilot.common.starpilot_utilities import delete_file, get_lock_status, run_cmd from openpilot.starpilot.common.starpilot_variables import ACTIVE_THEME_PATH, ERROR_LOGS_PATH, EXCLUDED_KEYS, LEGACY_STARPILOT_PARAM_RENAMES, MAPS_PATH, MODELS_PATH, RESOURCES_REPO, SCREEN_RECORDINGS_PATH, STOCK_THEME_PATH, THEME_SAVE_PATH,\ default_ev_tuning_enabled, update_starpilot_toggles @@ -3496,6 +3497,18 @@ def setup(app): "updated": updated, }), 200 + if key == "PersistExperimentalState": + enabled = str_val.strip() in ("1", "true", "True") + sync_persist_experimental_state(params, params_memory, enabled) + update_starpilot_toggles() + return jsonify({ + "message": f"Parameter '{key}' updated successfully.", + "updated": { + "PersistExperimentalState": enabled, + "PersistedCEStatus": params.get_int("PersistedCEStatus", default=0), + }, + }), 200 + if key == "CarMake": catalog = _get_fingerprint_catalog() normalized_make = _normalize_fingerprint_make_key(str_val) diff --git a/starpilot/ui/qt/offroad/longitudinal_settings.cc b/starpilot/ui/qt/offroad/longitudinal_settings.cc index 2ef85ca4d..b030dcd8f 100644 --- a/starpilot/ui/qt/offroad/longitudinal_settings.cc +++ b/starpilot/ui/qt/offroad/longitudinal_settings.cc @@ -87,6 +87,7 @@ StarPilotLongitudinalPanel::StarPilotLongitudinalPanel(StarPilotSettingsWindow * {"VEgoStopping", parent->vEgoStopping != 0 ? QString(tr("Stop Speed (Default: %1)")).arg(QString::number(parent->vEgoStopping, 'f', 2)) : tr("Stop Speed"), tr("The speed at which openpilot considers the vehicle stopped. Increase to brake earlier and stop smoothly; decrease to wait longer but risk overshooting."), ""}, {"ConditionalExperimental", tr("Conditional Experimental Mode"), tr("Automatically switch to \"Experimental Mode\" when set conditions are met. Allows the model to handle challenging situations with smarter decision making."), "../../starpilot/assets/toggle_icons/icon_conditional.png"}, + {"PersistExperimentalState", tr("Persist Experimental State"), tr("Keep your manual Conditional Experimental override through reboots until you manually clear it."), ""}, {"CESpeed", tr("Below"), tr("Switch to \"Experimental Mode\" when driving below this speed without a lead to help openpilot handle low-speed situations more smoothly."), ""}, {"CECurves", tr("Curve Detected Ahead"), tr("Switch to \"Experimental Mode\" when a curve is detected to allow the model to set an appropriate speed for the curve."), ""}, {"CEStopLights", tr("\"Detected\" Stop Lights/Signs"), tr("Switch to \"Experimental Mode\" whenever the driving model \"detects\" a red light or stop sign.

Disclaimer: openpilot does not explicitly detect traffic lights or stop signs. In \"Experimental Mode\", openpilot makes end-to-end driving decisions from camera input, which means it may stop even when there's no clear reason!"), ""}, diff --git a/starpilot/ui/qt/offroad/longitudinal_settings.h b/starpilot/ui/qt/offroad/longitudinal_settings.h index 00e1f3ee4..86b280f8c 100644 --- a/starpilot/ui/qt/offroad/longitudinal_settings.h +++ b/starpilot/ui/qt/offroad/longitudinal_settings.h @@ -30,7 +30,7 @@ private: QSet advancedLongitudinalTuneKeys = {"EVTuning", "TruckTuning", "LongitudinalActuatorDelay", "MaxDesiredAcceleration", "StartAccel", "StopAccel", "StoppingDecelRate", "VEgoStarting", "VEgoStopping"}; QSet aggressivePersonalityKeys = {"AggressiveFollow", "AggressiveFollowHigh", "AggressiveJerkAcceleration", "AggressiveJerkDeceleration", "AggressiveJerkDanger", "AggressiveJerkSpeed", "AggressiveJerkSpeedDecrease", "ResetAggressivePersonality"}; - QSet conditionalExperimentalKeys = {"CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"}; + QSet conditionalExperimentalKeys = {"PersistExperimentalState", "CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"}; QSet curveSpeedKeys = {"CalibratedLateralAcceleration", "CalibrationProgress", "ResetCurveData", "ShowCSCStatus"}; QSet customDrivingPersonalityKeys = {"AggressivePersonalityProfile", "RelaxedPersonalityProfile", "StandardPersonalityProfile", "TrafficPersonalityProfile"}; QSet longitudinalTuneKeys = {"AccelerationProfile", "DecelerationProfile", "HumanAcceleration", "HumanFollowing", "HumanLaneChanges", "LeadDetectionThreshold", "TacoTune"}; diff --git a/system/camerad/camerad b/system/camerad/camerad index 9aafefcf8..1f9e195a7 100755 Binary files a/system/camerad/camerad and b/system/camerad/camerad differ diff --git a/system/loggerd/bootlog b/system/loggerd/bootlog index 20c1cb45a..19767b66d 100755 Binary files a/system/loggerd/bootlog and b/system/loggerd/bootlog differ diff --git a/system/loggerd/encoderd b/system/loggerd/encoderd index 04bf98f51..5a55e2175 100755 Binary files a/system/loggerd/encoderd and b/system/loggerd/encoderd differ diff --git a/system/loggerd/loggerd b/system/loggerd/loggerd index 168ebd9f7..b8f763c5a 100755 Binary files a/system/loggerd/loggerd and b/system/loggerd/loggerd differ