Persist Exp State

This commit is contained in:
firestar5683
2026-04-09 13:00:49 -05:00
parent 08efd8f877
commit 4b5ec507be
57 changed files with 221 additions and 42 deletions
Binary file not shown.
+2
View File
@@ -42,6 +42,8 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> 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}},
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -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";
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
DEV-b5546f24-DEBUG
DEV-08efd8f8-DEBUG
Binary file not shown.
@@ -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:
+16 -3
View File
@@ -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
+18 -5
View File
@@ -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)
+9 -1
View File
@@ -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"));
}
@@ -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;
+1
View File
@@ -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);
}
+9
View File
@@ -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();
BIN
View File
Binary file not shown.
+4 -2
View File
@@ -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
+102
View File
@@ -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
+1
View File
@@ -206,6 +206,7 @@ EXCLUDED_KEYS = {
"openpilotMinutes",
"OverpassRequests",
"PandaSignatures",
"PersistedCEStatus",
"SpeedLimits",
"SpeedLimitsFiltered",
"UpdateFailedCount",
@@ -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):
+8 -8
View File
@@ -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)
@@ -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",
+13
View File
@@ -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)
@@ -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("<b>The speed at which openpilot considers the vehicle stopped.</b> Increase to brake earlier and stop smoothly; decrease to wait longer but risk overshooting."), ""},
{"ConditionalExperimental", tr("Conditional Experimental Mode"), tr("<b>Automatically switch to \"Experimental Mode\" when set conditions are met.</b> Allows the model to handle challenging situations with smarter decision making."), "../../starpilot/assets/toggle_icons/icon_conditional.png"},
{"PersistExperimentalState", tr("Persist Experimental State"), tr("<b>Keep your manual Conditional Experimental override through reboots</b> until you manually clear it."), ""},
{"CESpeed", tr("Below"), tr("<b>Switch to \"Experimental Mode\" when driving below this speed without a lead</b> to help openpilot handle low-speed situations more smoothly."), ""},
{"CECurves", tr("Curve Detected Ahead"), tr("<b>Switch to \"Experimental Mode\" when a curve is detected</b> to allow the model to set an appropriate speed for the curve."), ""},
{"CEStopLights", tr("\"Detected\" Stop Lights/Signs"), tr("<b>Switch to \"Experimental Mode\" whenever the driving model \"detects\" a red light or stop sign.</b><br><br><i><b>Disclaimer</b>: 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!</i>"), ""},
@@ -30,7 +30,7 @@ private:
QSet<QString> advancedLongitudinalTuneKeys = {"EVTuning", "TruckTuning", "LongitudinalActuatorDelay", "MaxDesiredAcceleration", "StartAccel", "StopAccel", "StoppingDecelRate", "VEgoStarting", "VEgoStopping"};
QSet<QString> aggressivePersonalityKeys = {"AggressiveFollow", "AggressiveFollowHigh", "AggressiveJerkAcceleration", "AggressiveJerkDeceleration", "AggressiveJerkDanger", "AggressiveJerkSpeed", "AggressiveJerkSpeedDecrease", "ResetAggressivePersonality"};
QSet<QString> conditionalExperimentalKeys = {"CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"};
QSet<QString> conditionalExperimentalKeys = {"PersistExperimentalState", "CESpeed", "CESpeedLead", "CECurves", "CELead", "CEModelStopTime", "CESignalSpeed", "CEStopLights", "ShowCEMStatus"};
QSet<QString> curveSpeedKeys = {"CalibratedLateralAcceleration", "CalibrationProgress", "ResetCurveData", "ShowCSCStatus"};
QSet<QString> customDrivingPersonalityKeys = {"AggressivePersonalityProfile", "RelaxedPersonalityProfile", "StandardPersonalityProfile", "TrafficPersonalityProfile"};
QSet<QString> longitudinalTuneKeys = {"AccelerationProfile", "DecelerationProfile", "HumanAcceleration", "HumanFollowing", "HumanLaneChanges", "LeadDetectionThreshold", "TacoTune"};
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.