diff --git a/common/params_keys.h b/common/params_keys.h index 3df0fc781..7efa9d375 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -415,6 +415,8 @@ inline static std::unordered_map keys = { {"ScreenTimeout", {PERSISTENT, INT, "30", "30", 2}}, {"ScreenTimeoutOnroad", {PERSISTENT, INT, "30", "10", 2}}, {"SecOCKeys", {PERSISTENT | DONT_LOG, STRING, "", "", 0}}, + {"SafeMode", {PERSISTENT, BOOL, "0", "0", 0}}, + {"SafeModeBackup", {PERSISTENT, JSON, "{}", "{}"}}, {"SetSpeedLimit", {PERSISTENT, BOOL, "0", "0", 1}}, {"SetSpeedOffset", {PERSISTENT, FLOAT, "0.0", "0.0", 2}}, {"ShowCEMStatus", {PERSISTENT, BOOL, "1", "0", 2}}, diff --git a/panda/board/obj/body_h7.bin.signed b/panda/board/obj/body_h7.bin.signed index 8fa3269a6..c670e36f3 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 9fe875261..444d2e909 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 f9a8690d8..535ecc2a3 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 92990227f..49f834fbf 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 a85112d1e..857aa66f8 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 2342ed971..c3521d24f 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 0c30b492f..7222db051 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 0c30b492f..7222db051 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 cc53f37d1..d17648a0a 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 2342ed971..c3521d24f 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 ca8040220..d1239251c 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-95107573-DEBUG"; +const uint8_t gitversion[19] = "DEV-863b6289-DEBUG"; diff --git a/panda/board/obj/panda.bin.signed b/panda/board/obj/panda.bin.signed index 88975a49e..f46211ed2 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 abadd630b..1b7284f43 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 53fbe9d4a..1ce14a84d 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 a4b1a01fc..7946b42d9 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 a52925a58..f3ac5413c 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 5e266b696..69102a90b 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 9f55716e9..4a9a6eef8 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 b8a71c25a..6958fc1e1 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 1c9f3ad4e..f2c2f94eb 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 ea5b04ab0..5dd8629e2 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 12ad665e1..dd6a7b0d9 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 f931dd877..6b892078b 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 8d5e7c581..f31fdb509 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 30ffa7819..547a42cc6 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 e9e2a4ce8..b3a09e783 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 8c421cf48..d5753b770 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 31eff1d6c..9afdc7a0e 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 f35b47e8f..bd2421b06 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 26ba0e9e8..11f61fb8a 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 618eec6e3..9e6665da5 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 88361b613..4ddb57d10 100644 --- a/panda/board/obj/version +++ b/panda/board/obj/version @@ -1 +1 @@ -DEV-95107573-DEBUG \ No newline at end of file +DEV-863b6289-DEBUG \ No newline at end of file diff --git a/selfdrive/car/card.py b/selfdrive/car/card.py index 6f5538b77..0fffd3919 100644 --- a/selfdrive/car/card.py +++ b/selfdrive/car/card.py @@ -162,7 +162,8 @@ class Car: self.v_cruise_helper = VCruiseHelper(self.CP) self.is_metric = self.params.get_bool("IsMetric") - self.experimental_mode = self.params.get_bool("ExperimentalMode") + self.safe_mode = self.params.get_bool("SafeMode") + self.experimental_mode = self.params.get_bool("ExperimentalMode") and not self.safe_mode # card is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) @@ -315,8 +316,9 @@ class Car: def params_thread(self, evt): while not evt.is_set(): + self.safe_mode = self.params.get_bool("SafeMode") self.is_metric = self.params.get_bool("IsMetric") - self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl + self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl and not self.safe_mode time.sleep(0.1) def card_thread(self): diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py index 9a18443c0..d185a7b6e 100644 --- a/selfdrive/selfdrived/selfdrived.py +++ b/selfdrive/selfdrived/selfdrived.py @@ -125,7 +125,8 @@ class SelfdriveD: self.logged_comm_issue = None self.not_running_prev = None self.experimental_mode = False - self.personality = self.params.get("LongitudinalPersonality", return_default=True) + self.safe_mode = self.params.get_bool("SafeMode") + self.personality = log.LongitudinalPersonality.relaxed if self.safe_mode else self.params.get("LongitudinalPersonality", return_default=True) self.recalibrating_seen = False self.state_machine = StateMachine() self.rk = Ratekeeper(100, print_delay_threshold=None) @@ -477,7 +478,7 @@ class SelfdriveD: if self.starpilot_toggles.personality_profile_via_lkas: distance_pressed |= any(not be.pressed and be.type == ButtonType.lkas for be in CS.buttonEvents) - if not distance_pressed and self.distance_pressed_previously: + if not distance_pressed and self.distance_pressed_previously and not self.safe_mode: if self.display_timer > 0 or not self.has_menu: self.personality = (self.personality - 1) % 3 self.params.put_nonblocking('LongitudinalPersonality', self.personality) @@ -627,12 +628,15 @@ class SelfdriveD: def params_thread(self, evt): while not evt.is_set(): + self.safe_mode = self.params.get_bool("SafeMode") 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") - if not self.starpilot_toggles.conditional_experimental_mode: + if self.safe_mode: + self.experimental_mode = False + elif not self.starpilot_toggles.conditional_experimental_mode: self.experimental_mode = self.params.get_bool("ExperimentalMode") and self.CP.openpilotLongitudinalControl - self.personality = self.params.get("LongitudinalPersonality", return_default=True) + self.personality = log.LongitudinalPersonality.relaxed if self.safe_mode else self.params.get("LongitudinalPersonality", return_default=True) time.sleep(0.1) def run(self): diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index 7fae2dfd2..b007e7c09 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -54,6 +54,12 @@ class TogglesLayout(Widget): "experimental_white.png", False, ), + "SafeMode": ( + lambda: tr("Safe Mode"), + tr("Temporarily force driving-affecting StarPilot settings back to safe defaults, stock tuning, and the branch default model until disabled."), + "warning.png", + True, + ), "DisengageOnAccelerator": ( lambda: tr("Disengage on Accelerator Pedal"), DESCRIPTIONS["DisengageOnAccelerator"], @@ -153,6 +159,14 @@ class TogglesLayout(Widget): def _update_toggles(self): ui_state.update_params() + safe_mode = self._params.get_bool("SafeMode") + if safe_mode: + if self._params.get_bool("ExperimentalMode"): + self._params.put_bool("ExperimentalMode", False) + if self._params.get("LongitudinalPersonality", return_default=True) != int(log.LongitudinalPersonality.relaxed): + self._params.put_int("LongitudinalPersonality", int(log.LongitudinalPersonality.relaxed)) + self._toggles["ExperimentalMode"].action_item.set_state(False) + self._long_personality_setting.action_item.set_selected_button(int(log.LongitudinalPersonality.relaxed)) e2e_description = tr( "openpilot defaults to driving in chill mode. Experimental mode enables alpha-level features that aren't ready for chill mode. " + @@ -168,9 +182,9 @@ class TogglesLayout(Widget): if ui_state.CP is not None: if ui_state.has_longitudinal_control: - self._toggles["ExperimentalMode"].action_item.set_enabled(True) + self._toggles["ExperimentalMode"].action_item.set_enabled(not safe_mode) self._toggles["ExperimentalMode"].set_description(e2e_description) - self._long_personality_setting.action_item.set_enabled(True) + self._long_personality_setting.action_item.set_enabled(not safe_mode) else: # no long for now self._toggles["ExperimentalMode"].action_item.set_enabled(False) diff --git a/selfdrive/ui/mici/layouts/home.py b/selfdrive/ui/mici/layouts/home.py index 83c0dd146..177dbcfd9 100644 --- a/selfdrive/ui/mici/layouts/home.py +++ b/selfdrive/ui/mici/layouts/home.py @@ -94,6 +94,7 @@ class MiciHomeLayout(Widget): self._version_text = None self._experimental_mode = False + self._safe_mode = False self._current_model_name = "default" self._settings_txt = gui_app.texture("icons_mici/settings.png", 48, 48) @@ -128,7 +129,8 @@ class MiciHomeLayout(Widget): self._update_params() def _update_params(self): - self._experimental_mode = ui_state.params.get_bool("ExperimentalMode") + self._safe_mode = ui_state.params.get_bool("SafeMode") + self._experimental_mode = ui_state.params.get_bool("ExperimentalMode") and not self._safe_mode def _clean_name(value: str) -> str: return re.sub(r"[πŸ—ΊοΈπŸ‘€πŸ“‘]", "", value).replace("(Default)", "").strip() @@ -215,7 +217,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: + 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) self._mouse_down_t = None diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index b847fe74b..e638e365c 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -18,6 +18,7 @@ class TogglesLayoutMici(NavWidget): self.set_back_callback(back_callback) self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"]) + self._safe_mode_btn = BigParamControl("safe mode", "SafeMode", toggle_callback=restart_needed_callback) self._experimental_btn = BigParamControl("experimental mode", "ExperimentalMode") is_metric_toggle = BigParamControl("use metric units", "IsMetric") ldw_toggle = BigParamControl("lane departure warnings", "IsLdwEnabled") @@ -28,6 +29,7 @@ class TogglesLayoutMici(NavWidget): self._scroller = Scroller([ self._personality_toggle, + self._safe_mode_btn, self._experimental_btn, is_metric_toggle, ldw_toggle, @@ -40,6 +42,7 @@ class TogglesLayoutMici(NavWidget): # Toggle lists self._refresh_toggles = ( ("ExperimentalMode", self._experimental_btn), + ("SafeMode", self._safe_mode_btn), ("IsMetric", is_metric_toggle), ("IsLdwEnabled", ldw_toggle), ("AlwaysOnDM", always_on_dm_toggle), @@ -74,6 +77,16 @@ class TogglesLayoutMici(NavWidget): def _update_toggles(self): ui_state.update_params() + safe_mode = ui_state.params.get_bool("SafeMode") + self._experimental_btn.set_enabled(not safe_mode) + self._personality_toggle.set_enabled(not safe_mode) + if safe_mode: + if ui_state.params.get_bool("ExperimentalMode"): + ui_state.params.put_bool("ExperimentalMode", False) + if ui_state.params.get("LongitudinalPersonality", return_default=True) != int(log.LongitudinalPersonality.relaxed): + ui_state.params.put_int("LongitudinalPersonality", int(log.LongitudinalPersonality.relaxed)) + self._experimental_btn.set_checked(False) + self._personality_toggle.set_value("relaxed") # CP gating for experimental mode if ui_state.CP is not None: @@ -85,6 +98,8 @@ class TogglesLayoutMici(NavWidget): self._experimental_btn.set_visible(False) self._experimental_btn.set_checked(False) self._personality_toggle.set_visible(False) + self._experimental_btn.set_enabled(False) + self._personality_toggle.set_enabled(False) ui_state.params.remove("ExperimentalMode") # Refresh toggles from params to mirror external changes diff --git a/selfdrive/ui/onroad/exp_button.py b/selfdrive/ui/onroad/exp_button.py index e5d817141..18b82c29d 100644 --- a/selfdrive/ui/onroad/exp_button.py +++ b/selfdrive/ui/onroad/exp_button.py @@ -63,6 +63,9 @@ class ExpButton(Widget): return self._experimental_mode def _is_toggle_allowed(self): + if self._params.get_bool("SafeMode"): + return False + if not self._params.get_bool("ExperimentalModeConfirmed"): return False diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 23f292a83..f19559ca0 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -41,6 +41,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/icons/experimental_white.svg", false, }, + { + "SafeMode", + tr("Safe Mode"), + tr("Temporarily force driving-affecting StarPilot settings back to safe defaults, stock tuning, and the branch default model until disabled."), + "../assets/icons/warning.png", + true, + }, { "DisengageOnAccelerator", tr("Disengage on Accelerator Pedal"), @@ -164,6 +171,15 @@ void TogglesPanel::showEvent(QShowEvent *event) { void TogglesPanel::updateToggles() { const bool showAllToggles = params.getBool("ShowAllToggles"); + const bool safe_mode = params.getBool("SafeMode"); + if (safe_mode) { + if (params.getBool("ExperimentalMode")) { + params.putBool("ExperimentalMode", false); + } + if (params.getInt("LongitudinalPersonality") != static_cast(cereal::LongitudinalPersonality::RELAXED)) { + params.putInt("LongitudinalPersonality", static_cast(cereal::LongitudinalPersonality::RELAXED)); + } + } auto experimental_mode_toggle = toggles["ExperimentalMode"]; const QString e2e_description = QString("%1
" "

%2


" @@ -187,9 +203,12 @@ void TogglesPanel::updateToggles() { if (hasLongitudinalControl(CP) || showAllToggles) { // normal description and toggle - experimental_mode_toggle->setEnabled(true); + experimental_mode_toggle->setEnabled(!safe_mode); experimental_mode_toggle->setDescription(e2e_description); - long_personality_setting->setEnabled(true); + long_personality_setting->setEnabled(!safe_mode); + if (safe_mode) { + long_personality_setting->setCheckedButton(static_cast(cereal::LongitudinalPersonality::RELAXED)); + } } else { // no long for now experimental_mode_toggle->setEnabled(false); @@ -215,6 +234,12 @@ void TogglesPanel::updateToggles() { experimental_mode_toggle->setDescription(e2e_description); } + if (safe_mode) { + experimental_mode_toggle->setEnabled(false); + long_personality_setting->setEnabled(false); + long_personality_setting->setCheckedButton(static_cast(cereal::LongitudinalPersonality::RELAXED)); + } + StarPilotUIState &fs = *starpilotUIState(); StarPilotUIScene &starpilot_scene = fs.starpilot_scene; QJsonObject &starpilot_toggles = starpilot_scene.starpilot_toggles; diff --git a/selfdrive/ui/qt/onroad/buttons.cc b/selfdrive/ui/qt/onroad/buttons.cc index 329c2acc7..99bce3f25 100644 --- a/selfdrive/ui/qt/onroad/buttons.cc +++ b/selfdrive/ui/qt/onroad/buttons.cc @@ -32,6 +32,9 @@ ExperimentalButton::ExperimentalButton(QWidget *parent) : experimental_mode(fals void ExperimentalButton::changeMode() { const auto cp = (*uiState()->sm)["carParams"].getCarParams(); + if (params.getBool("SafeMode")) { + return; + } bool can_change = hasLongitudinalControl(cp) && params.getBool("ExperimentalModeConfirmed"); if (can_change) { if (starpilot_toggles.value("conditional_experimental_mode").toBool()) { diff --git a/selfdrive/ui/ui b/selfdrive/ui/ui index 5bdf08e6e..f906d04d6 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 faa3bf877..ebe5b4e17 100644 --- a/selfdrive/ui/widgets/exp_mode_button.py +++ b/selfdrive/ui/widgets/exp_mode_button.py @@ -14,13 +14,13 @@ class ExperimentalModeButton(Widget): self.button_height = 125 self.params = Params() - self.experimental_mode = self.params.get_bool("ExperimentalMode") + self.experimental_mode = self.params.get_bool("ExperimentalMode") and not self.params.get_bool("SafeMode") 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") + self.experimental_mode = self.params.get_bool("ExperimentalMode") and not self.params.get_bool("SafeMode") def _get_gradient_colors(self): alpha = 0xCC if self.is_pressed else 0xFF diff --git a/starpilot/common/safe_mode.py b/starpilot/common/safe_mode.py new file mode 100644 index 000000000..207eb028d --- /dev/null +++ b/starpilot/common/safe_mode.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python3 +from __future__ import annotations + +from cereal import log + +from openpilot.common.params import Params + +SAFE_MODE_PARAM = "SafeMode" +SAFE_MODE_BACKUP_PARAM = "SafeModeBackup" +SAFE_MODE_ENFORCE_FRAMES = 20 + +# Driving-affecting settings that Safe Mode forces back to safe branch/stock behavior. +SAFE_MODE_MANAGED_KEYS = ( + "ExperimentalMode", + "LongitudinalPersonality", + "Model", + "DrivingModel", + "DrivingModelName", + "ModelVersion", + "DrivingModelVersion", + "ModelRandomizer", + "DisableOpenpilotLongitudinal", + "ForceFingerprint", + "ClusterOffset", + "LateralTune", + "AdvancedLateralTune", + "ForceAutoTune", + "ForceAutoTuneOff", + "ForceTorqueController", + "SteerDelay", + "SteerFriction", + "SteerKP", + "SteerLatAccel", + "SteerRatio", + "LaneChanges", + "LaneChangeTime", + "LaneDetectionWidth", + "MinimumLaneChangeSpeed", + "NudgelessLaneChange", + "OneLaneChange", + "NNFF", + "NNFFLite", + "TurnDesires", + "QOLLateral", + "PauseLateralSpeed", + "PauseLateralOnSignal", + "LongitudinalTune", + "AdvancedLongitudinalTune", + "EVTuning", + "TruckTuning", + "LongitudinalActuatorDelay", + "MaxDesiredAcceleration", + "StartAccel", + "StopAccel", + "StoppingDecelRate", + "VEgoStarting", + "VEgoStopping", + "AccelerationProfile", + "DecelerationProfile", + "HumanAcceleration", + "HumanFollowing", + "HumanLaneChanges", + "LeadDetectionThreshold", + "RecoveryPower", + "StopDistance", + "TacoTune", + "QOLLongitudinal", + "ForceStops", + "IncreasedStoppedDistance", + "MapGears", + "MapAcceleration", + "MapDeceleration", + "ReverseCruise", + "SetSpeedOffset", + "WeatherPresets", + "IncreaseFollowingLowVisibility", + "IncreaseFollowingRain", + "IncreaseFollowingRainStorm", + "IncreaseFollowingSnow", + "IncreasedStoppedDistanceLowVisibility", + "IncreasedStoppedDistanceRain", + "IncreasedStoppedDistanceRainStorm", + "IncreasedStoppedDistanceSnow", + "ReduceAccelerationLowVisibility", + "ReduceAccelerationRain", + "ReduceAccelerationRainStorm", + "ReduceAccelerationSnow", + "ReduceLateralAccelerationLowVisibility", + "ReduceLateralAccelerationRain", + "ReduceLateralAccelerationRainStorm", + "ReduceLateralAccelerationSnow", + "ConditionalExperimental", + "CECurves", + "CECurvesLead", + "CELead", + "CESlowerLead", + "CEStoppedLead", + "CESpeed", + "CESpeedLead", + "CEModelStopTime", + "CEStopLights", + "CESignalSpeed", + "CESignalLaneDetection", + "CurveSpeedController", + "SpeedLimitController", + "SetSpeedLimit", + "SLCFallback", + "SLCMapboxFiller", + "SLCOverride", + "SLCConfirmation", + "SLCConfirmationHigher", + "SLCConfirmationLower", + "SLCLookaheadHigher", + "SLCLookaheadLower", + "SLCPriority1", + "SLCPriority2", + "Offset1", + "Offset2", + "Offset3", + "Offset4", + "Offset5", + "Offset6", + "Offset7", + "SpeedLimitFiller", + "VisionSpeedLimitDetection", + "CustomPersonalities", + "TrafficPersonalityProfile", + "AggressivePersonalityProfile", + "StandardPersonalityProfile", + "RelaxedPersonalityProfile", + "TrafficFollow", + "TrafficJerkAcceleration", + "TrafficJerkDeceleration", + "TrafficJerkDanger", + "TrafficJerkSpeed", + "TrafficJerkSpeedDecrease", + "AggressiveFollow", + "AggressiveFollowHigh", + "AggressiveJerkAcceleration", + "AggressiveJerkDeceleration", + "AggressiveJerkDanger", + "AggressiveJerkSpeed", + "AggressiveJerkSpeedDecrease", + "StandardFollow", + "StandardFollowHigh", + "StandardJerkAcceleration", + "StandardJerkDeceleration", + "StandardJerkDanger", + "StandardJerkSpeed", + "StandardJerkSpeedDecrease", + "RelaxedFollow", + "RelaxedFollowHigh", + "RelaxedJerkAcceleration", + "RelaxedJerkDeceleration", + "RelaxedJerkDanger", + "RelaxedJerkSpeed", + "RelaxedJerkSpeedDecrease", + "FrogsGoMoosTweak", + "SNGHack", + "SubaruSNG", + "VoltSNG", + "GMPedalLongitudinal", + "LongPitch", + "TacoTuneHacks", +) + +SAFE_MODE_FIXED_VALUES = { + "ExperimentalMode": False, + "LongitudinalPersonality": int(log.LongitudinalPersonality.relaxed), +} + +SAFE_MODE_STOCK_PARAM_MAP = { + "SteerDelay": "SteerDelayStock", + "SteerFriction": "SteerFrictionStock", + "SteerKP": "SteerKPStock", + "SteerLatAccel": "SteerLatAccelStock", + "SteerRatio": "SteerRatioStock", + "LongitudinalActuatorDelay": "LongitudinalActuatorDelayStock", + "StartAccel": "StartAccelStock", + "StopAccel": "StopAccelStock", + "StoppingDecelRate": "StoppingDecelRateStock", + "VEgoStarting": "VEgoStartingStock", + "VEgoStopping": "VEgoStoppingStock", +} + +SAFE_MODE_MEMORY_VALUES = { + "CEStatus": 0, +} + + +def safe_mode_enabled(params_raw: Params | None = None) -> bool: + params_raw = params_raw or Params() + return params_raw.get_bool(SAFE_MODE_PARAM) + + +def _load_backup(params_raw: Params) -> dict[str, dict]: + backup = params_raw.get(SAFE_MODE_BACKUP_PARAM) + return backup if isinstance(backup, dict) else {} + + +def _current_entry(params_raw: Params, key: str) -> dict[str, object]: + value = params_raw.get(key) + return {"present": value is not None, "value": value} + + +def _safe_value(params: Params, key: str): + if key in SAFE_MODE_FIXED_VALUES: + return SAFE_MODE_FIXED_VALUES[key] + + stock_param = SAFE_MODE_STOCK_PARAM_MAP.get(key) + if stock_param is not None: + stock_value = params.get(stock_param) + if stock_value is not None: + return stock_value + + return params.get_stock_value(key) + + +def _apply_value(params_raw: Params, key: str, value) -> bool: + current = params_raw.get(key) + if value is None: + if current is None: + return False + params_raw.remove(key) + return True + + if current == value: + return False + + params_raw.put(key, value) + return True + + +def _mark_toggle_update(params_memory: Params | None) -> None: + if params_memory is None: + return + params_memory.put_bool("StarPilotTogglesUpdated", True) + + +def apply_safe_mode(params: Params, params_raw: Params, params_memory: Params | None = None, *, + ensure_backup: bool = True) -> bool: + changed = False + + if ensure_backup: + backup = _load_backup(params_raw) + missing_backup_keys = [key for key in SAFE_MODE_MANAGED_KEYS if key not in backup] + if missing_backup_keys: + backup = dict(backup) + for key in missing_backup_keys: + backup[key] = _current_entry(params_raw, key) + params_raw.put(SAFE_MODE_BACKUP_PARAM, backup) + changed = True + + for key in SAFE_MODE_MANAGED_KEYS: + changed |= _apply_value(params_raw, key, _safe_value(params, key)) + + if params_memory is not None: + for key, value in SAFE_MODE_MEMORY_VALUES.items(): + if params_memory.get(key) != value: + params_memory.put(key, value) + changed = True + + if changed: + params_raw.put_bool("OnroadCycleRequested", True) + _mark_toggle_update(params_memory) + + return changed + + +def restore_safe_mode(params_raw: Params, params_memory: Params | None = None) -> bool: + changed = False + backup = _load_backup(params_raw) + + if not backup: + if params_raw.get(SAFE_MODE_BACKUP_PARAM) is not None: + params_raw.remove(SAFE_MODE_BACKUP_PARAM) + changed = True + + if params_memory is not None: + for key, value in SAFE_MODE_MEMORY_VALUES.items(): + if params_memory.get(key) != value: + params_memory.put(key, value) + changed = True + + if changed: + params_raw.put_bool("OnroadCycleRequested", True) + _mark_toggle_update(params_memory) + return changed + + restore_keys = dict.fromkeys((*SAFE_MODE_MANAGED_KEYS, *backup.keys())) + for key in restore_keys: + entry = backup.get(key, {"present": False, "value": None}) + restore_value = entry.get("value") if entry.get("present") else None + changed |= _apply_value(params_raw, key, restore_value) + + if params_raw.get(SAFE_MODE_BACKUP_PARAM) is not None: + params_raw.remove(SAFE_MODE_BACKUP_PARAM) + changed = True + + if params_memory is not None: + for key, value in SAFE_MODE_MEMORY_VALUES.items(): + if params_memory.get(key) != value: + params_memory.put(key, value) + changed = True + + if changed: + params_raw.put_bool("OnroadCycleRequested", True) + _mark_toggle_update(params_memory) + + return changed diff --git a/starpilot/common/starpilot_variables.py b/starpilot/common/starpilot_variables.py index 334b8208f..6ca0e4c68 100644 --- a/starpilot/common/starpilot_variables.py +++ b/starpilot/common/starpilot_variables.py @@ -500,6 +500,7 @@ class StarPilotVariables: toggle.debug_mode = self.params.get_bool("DebugMode") toggle.force_offroad = self.params.get_bool("ForceOffroad") toggle.force_onroad = self.params.get_bool("ForceOnroad") + toggle.safe_mode = self.params.get_bool("SafeMode") toggle.is_metric = self.params.get_bool("IsMetric") distance_conversion = 1 if toggle.is_metric else CV.FOOT_TO_METER diff --git a/starpilot/controls/starpilot_card.py b/starpilot/controls/starpilot_card.py index 828e3ae28..e2a135271 100644 --- a/starpilot/controls/starpilot_card.py +++ b/starpilot/controls/starpilot_card.py @@ -51,6 +51,9 @@ class StarPilotCard: self.traffic_mode_enabled = not self.traffic_mode_enabled def handle_experimental_mode(self, sm, starpilot_toggles): + if getattr(starpilot_toggles, "safe_mode", False): + 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"] diff --git a/starpilot/starpilot_process.py b/starpilot/starpilot_process.py index 2e25e31e4..0a6513270 100644 --- a/starpilot/starpilot_process.py +++ b/starpilot/starpilot_process.py @@ -15,6 +15,12 @@ from openpilot.system.athena.registration import UNREGISTERED_DONGLE_ID from openpilot.starpilot.assets.model_manager import MODEL_DOWNLOAD_ALL_PARAM, MODEL_DOWNLOAD_PARAM, ModelManager from openpilot.starpilot.assets.theme_manager import THEME_COMPONENT_PARAMS, ThemeManager from openpilot.starpilot.common.starpilot_functions import update_maps, update_openpilot +from openpilot.starpilot.common.safe_mode import ( + SAFE_MODE_ENFORCE_FRAMES, + apply_safe_mode, + restore_safe_mode, + safe_mode_enabled, +) from openpilot.starpilot.common.starpilot_utilities import ThreadManager, flash_panda, is_url_pingable, lock_doors, use_konik_server from openpilot.starpilot.common.starpilot_variables import ERROR_LOGS_PATH, StarPilotVariables from openpilot.starpilot.controls.starpilot_planner import StarPilotPlanner @@ -144,6 +150,7 @@ def starpilot_thread(): poll="modelV2") params = Params(return_defaults=True) + params_raw = Params() params_memory = Params(memory=True) starpilot_variables = StarPilotVariables() @@ -157,6 +164,7 @@ def starpilot_thread(): next_drive_stats_sync = 0.0 run_update_checks = False + safe_mode_active = safe_mode_enabled(params_raw) started_previously = False time_validated = False @@ -164,6 +172,9 @@ def starpilot_thread(): if error_log.is_file(): error_log.unlink() + if safe_mode_active: + apply_safe_mode(params, params_raw, params_memory) + while True: sm.update() @@ -208,6 +219,17 @@ def starpilot_thread(): if rate_keeper.frame % ASSET_CHECK_RATE == 0: check_assets(now, model_manager, theme_manager, thread_manager, params, params_memory, starpilot_toggles) + current_safe_mode = safe_mode_enabled(params_raw) + safe_mode_changed = current_safe_mode != safe_mode_active + if safe_mode_changed: + if current_safe_mode: + apply_safe_mode(params, params_raw, params_memory) + else: + restore_safe_mode(params_raw, params_memory) + safe_mode_active = current_safe_mode + elif current_safe_mode and (params_memory.get_bool("StarPilotTogglesUpdated") or rate_keeper.frame % SAFE_MODE_ENFORCE_FRAMES == 0): + apply_safe_mode(params, params_raw, params_memory, ensure_backup=False) + if params_memory.get_bool("StarPilotTogglesUpdated") or theme_manager.theme_updated: starpilot_toggles = update_toggles(starpilot_variables, started, theme_manager, thread_manager, time_validated, params, starpilot_toggles) diff --git a/starpilot/system/speed_limit_vision.py b/starpilot/system/speed_limit_vision.py index ea37f3a45..2e3b2c276 100644 --- a/starpilot/system/speed_limit_vision.py +++ b/starpilot/system/speed_limit_vision.py @@ -14,8 +14,8 @@ import numpy as np from openpilot.common.constants import CV -INFERENCE_INTERVAL = 0.4 -FOLLOWUP_INFERENCE_INTERVAL = 0.2 +INFERENCE_INTERVAL = 0.2 +FOLLOWUP_INFERENCE_INTERVAL = 0.1 FOLLOWUP_WINDOW_SECONDS = 1.5 MIN_DETECTION_CONFIDENCE = 0.2 STRONG_DETECTION_CONFIDENCE = 0.72