From 59272ea85cbb648d835e310e3f3a301940ee8707 Mon Sep 17 00:00:00 2001
From: James <91348155+FrogAi@users.noreply.github.com>
Date: Mon, 1 Dec 2025 12:00:00 -0700
Subject: [PATCH] Force Drive State
---
frogpilot/common/frogpilot_variables.py | 6 ++++--
frogpilot/ui/qt/offroad/utilities.cc | 28 +++++++++++++++++++++++++
selfdrive/selfdrived/selfdrived.py | 2 +-
selfdrive/ui/qt/onroad/alerts.cc | 2 +-
selfdrive/ui/ui.cc | 2 ++
system/hardware/hardwared.py | 2 ++
system/manager/manager.py | 2 +-
7 files changed, 39 insertions(+), 5 deletions(-)
mode change 100755 => 100644 system/hardware/hardwared.py
mode change 100644 => 100755 system/manager/manager.py
diff --git a/frogpilot/common/frogpilot_variables.py b/frogpilot/common/frogpilot_variables.py
index 1570fae3..621e781f 100644
--- a/frogpilot/common/frogpilot_variables.py
+++ b/frogpilot/common/frogpilot_variables.py
@@ -306,6 +306,8 @@ class FrogPilotVariables:
toggle.liveValid = False
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.is_metric = self.params.get_bool("IsMetric")
distance_conversion = 1 if toggle.is_metric else CV.FOOT_TO_METER
@@ -475,7 +477,7 @@ class FrogPilotVariables:
toggle.device_shutdown_time = DEVICE_SHUTDOWN_TIMES.get(self.get_value("DeviceShutdown", cast=int, condition=device_management))
toggle.increase_thermal_limits = self.get_value("IncreaseThermalLimits", condition=device_management)
toggle.low_voltage_shutdown = self.get_value("LowVoltageShutdown", cast=float, condition=device_management, min=VBATT_PAUSE_CHARGING, max=12.5)
- toggle.no_logging = self.get_value("NoLogging", condition=device_management and not self.vetting_branch)
+ toggle.no_logging = self.get_value("NoLogging", condition=device_management and not self.vetting_branch) or toggle.force_onroad
toggle.no_uploads = self.get_value("NoUploads", condition=device_management and not self.vetting_branch)
toggle.no_onroad_uploads = self.get_value("DisableOnroadUploads", condition=toggle.no_uploads)
@@ -592,7 +594,7 @@ class FrogPilotVariables:
screen_management = self.get_value("ScreenManagement")
toggle.screen_brightness = max(self.get_value("ScreenBrightness", cast=float, condition=screen_management), 1)
- toggle.screen_brightness_onroad = self.get_value("ScreenBrightnessOnroad", cast=float, condition=(screen_management))
+ toggle.screen_brightness_onroad = self.get_value("ScreenBrightnessOnroad", cast=float, condition=(screen_management and not toggle.force_onroad))
toggle.screen_recorder = self.get_value("ScreenRecorder", condition=screen_management) or toggle.debug_mode
toggle.screen_timeout = self.get_value("ScreenTimeout", cast=float, condition=screen_management)
toggle.screen_timeout_onroad = self.get_value("ScreenTimeoutOnroad", cast=float, condition=screen_management)
diff --git a/frogpilot/ui/qt/offroad/utilities.cc b/frogpilot/ui/qt/offroad/utilities.cc
index 9c86abba..1d74b61a 100644
--- a/frogpilot/ui/qt/offroad/utilities.cc
+++ b/frogpilot/ui/qt/offroad/utilities.cc
@@ -40,6 +40,34 @@ FrogPilotUtilitiesPanel::FrogPilotUtilitiesPanel(FrogPilotSettingsWindow *parent
}
addItem(flashPandaButton);
+ FrogPilotButtonsControl *forceStartedButton = new FrogPilotButtonsControl(tr("Force Drive State"), tr("Force openpilot to be offroad or onroad."), "", {tr("OFFROAD"), tr("ONROAD"), tr("OFF")}, true);
+ QObject::connect(forceStartedButton, &FrogPilotButtonsControl::buttonClicked, [this](int id) {
+ if (id == 0) {
+ params.putBool("ForceOffroad", true);
+ params.putBool("ForceOnroad", false);
+
+ updateFrogPilotToggles();
+ } else if (id == 1) {
+ params.put("CarParams", params.get("CarParamsPersistent"));
+ params.put("FrogPilotCarParams", params.get("FrogPilotCarParamsPersistent"));
+
+ params.putBool("ForceOffroad", false);
+ params.putBool("ForceOnroad", true);
+
+ updateFrogPilotToggles();
+ } else if (id == 2) {
+ params.putBool("ForceOffroad", false);
+ params.putBool("ForceOnroad", false);
+
+ updateFrogPilotToggles();
+ }
+ });
+ forceStartedButton->setCheckedButton(2);
+ if (forceOpenDescriptions) {
+ forceStartedButton->showDescription();
+ }
+ addItem(forceStartedButton);
+
ButtonControl *resetTogglesButton = new ButtonControl(tr("Reset Toggles to Default"), tr("RESET"), tr("Reset all toggles to their default values."));
QObject::connect(resetTogglesButton, &ButtonControl::clicked, [parent, resetTogglesButton, this]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all toggles to their default values?"), tr("Reset"), this)) {
diff --git a/selfdrive/selfdrived/selfdrived.py b/selfdrive/selfdrived/selfdrived.py
index fb31b152..dc3db508 100644
--- a/selfdrive/selfdrived/selfdrived.py
+++ b/selfdrive/selfdrived/selfdrived.py
@@ -344,7 +344,7 @@ class SelfdriveD:
self.events.add(EventName.usbError)
if CS.canTimeout:
self.events.add(EventName.canBusMissing)
- elif not CS.canValid:
+ elif not CS.canValid and not self.frogpilot_toggles.force_onroad:
self.events.add(EventName.canError)
# generic catch-all. ideally, a more specific event should be added above instead
diff --git a/selfdrive/ui/qt/onroad/alerts.cc b/selfdrive/ui/qt/onroad/alerts.cc
index 17c3f83a..456927b6 100644
--- a/selfdrive/ui/qt/onroad/alerts.cc
+++ b/selfdrive/ui/qt/onroad/alerts.cc
@@ -51,7 +51,7 @@ OnroadAlerts::Alert OnroadAlerts::getAlert(const SubMaster &sm, const SubMaster
}
}
- if (!sm.updated("selfdriveState") && (sm.frame - started_frame) > 5 * UI_FREQ) {
+ if (!sm.updated("selfdriveState") && (sm.frame - started_frame) > 5 * UI_FREQ && !frogpilot_toggles.value("force_onroad").toBool()) {
const int SELFDRIVE_STATE_TIMEOUT = 5;
const int ss_missing = (nanos_since_boot() - sm.rcv_time("selfdriveState")) / 1e9;
diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc
index ab8fae0f..2d3b9b4b 100644
--- a/selfdrive/ui/ui.cc
+++ b/selfdrive/ui/ui.cc
@@ -77,6 +77,8 @@ static void update_state(UIState *s, FrogPilotUIState *fs) {
if (scene.started) {
frogpilot_scene.started_timer += 1;
}
+ scene.started |= frogpilot_scene.frogpilot_toggles.value("force_onroad").toBool();
+ scene.started &= !frogpilot_scene.frogpilot_toggles.value("force_offroad").toBool();
}
void ui_update_params(UIState *s) {
diff --git a/system/hardware/hardwared.py b/system/hardware/hardwared.py
old mode 100755
new mode 100644
index 8cc0f6f8..564f90d8
--- a/system/hardware/hardwared.py
+++ b/system/hardware/hardwared.py
@@ -352,6 +352,8 @@ def hardware_thread(end_event, hw_queue) -> None:
should_start = should_start and all(startup_conditions.values())
# FrogPilot variables
+ should_start |= frogpilot_toggles.force_onroad
+ should_start &= not frogpilot_toggles.force_offroad
if should_start != should_start_prev or (count == 0):
params.put_bool("IsEngaged", False)
diff --git a/system/manager/manager.py b/system/manager/manager.py
old mode 100644
new mode 100755
index 01875256..ab33734a
--- a/system/manager/manager.py
+++ b/system/manager/manager.py
@@ -155,7 +155,7 @@ def manager_thread() -> None:
started = sm['deviceState'].started
- if started and not started_prev:
+ if started and not started_prev and not frogpilot_toggles.force_onroad:
params.clear_all(ParamKeyFlag.CLEAR_ON_ONROAD_TRANSITION)
# FrogPilot variables