From 5d86b7ac5b50da137b6e2d7a33da24ae6c15124c Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Sun, 31 May 2026 21:03:42 -0500 Subject: [PATCH] IsRHD --- common/params_keys.h | 2 + selfdrive/monitoring/dmonitoringd.py | 15 ++++- selfdrive/monitoring/helpers.py | 10 +++- selfdrive/monitoring/test_monitoring.py | 9 +++ selfdrive/ui/layouts/settings/device.py | 20 +++++++ selfdrive/ui/layouts/settings/toggles.py | 15 +++++ selfdrive/ui/mici/layouts/settings/device.py | 2 + selfdrive/ui/mici/layouts/settings/toggles.py | 13 +++++ selfdrive/ui/qt/offroad/settings.cc | 29 +++++++++- selfdrive/ui/qt/offroad/settings.h | 1 + .../components/tools/device_settings.js | 56 ++++++++++++++++--- .../tools/device_settings_layout.json | 7 +++ starpilot/system/the_pond/the_pond.py | 39 +++++++++++++ tools/StarPilot/generate_pond_layout.py | 7 +++ 14 files changed, 212 insertions(+), 13 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index a3b1d83f7..cac9099d7 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -68,7 +68,9 @@ inline static std::unordered_map keys = { {"IsMetric", {PERSISTENT, BOOL}}, {"IsOffroad", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsOnroad", {PERSISTENT, BOOL}}, + {"IsRHD", {PERSISTENT, BOOL}}, {"IsRhdDetected", {PERSISTENT, BOOL}}, + {"IsRHDOverride", {PERSISTENT, BOOL}}, {"IsReleaseBranch", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsTakingSnapshot", {CLEAR_ON_MANAGER_START, BOOL}}, {"IsTestedBranch", {CLEAR_ON_MANAGER_START, BOOL}}, diff --git a/selfdrive/monitoring/dmonitoringd.py b/selfdrive/monitoring/dmonitoringd.py index 17e03cf3d..19ad18bbb 100644 --- a/selfdrive/monitoring/dmonitoringd.py +++ b/selfdrive/monitoring/dmonitoringd.py @@ -8,6 +8,10 @@ from openpilot.selfdrive.monitoring.helpers import DriverMonitoring GearShifter = structs.CarState.GearShifter +def get_rhd_override(params): + return params.get_bool("IsRHD") if params.get_bool("IsRHDOverride") else None + + def dmonitoringd_thread(): config_realtime_process([0, 1, 2, 3], 5) @@ -15,7 +19,11 @@ def dmonitoringd_thread(): pm = messaging.PubMaster(['driverMonitoringState']) sm = messaging.SubMaster(['driverStateV2', 'liveCalibration', 'carState', 'selfdriveState', 'modelV2'], poll='driverStateV2') - DM = DriverMonitoring(rhd_saved=params.get_bool("IsRhdDetected"), always_on=params.get_bool("AlwaysOnDM")) + DM = DriverMonitoring( + rhd_saved=params.get_bool("IsRhdDetected"), + always_on=params.get_bool("AlwaysOnDM"), + rhd_override=get_rhd_override(params), + ) demo_mode=False sm = sm.extend(['starpilotCarState']) @@ -44,13 +52,16 @@ def dmonitoringd_thread(): # load live always-on toggle if sm['driverStateV2'].frameId % 40 == 1: DM.always_on = params.get_bool("AlwaysOnDM") + DM.wheel_on_right_default = params.get_bool("IsRhdDetected") + DM.wheel_on_right_override = get_rhd_override(params) demo_mode = params.get_bool("IsDriverViewEnabled") and sm["carState"].gearShifter != GearShifter.reverse # save rhd virtual toggle every 5 mins - if (sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and + if (DM.wheel_on_right_override is None and sm['driverStateV2'].frameId % 6000 == 0 and not demo_mode and DM.wheelpos.prob_offseter.filtered_stat.n > DM.settings._WHEELPOS_FILTER_MIN_COUNT and DM.wheel_on_right == (DM.wheelpos.prob_offseter.filtered_stat.M > DM.settings._WHEELPOS_THRESHOLD)): params.put_bool_nonblocking("IsRhdDetected", DM.wheel_on_right) + params.put_bool_nonblocking("IsRHD", DM.wheel_on_right) def main(): dmonitoringd_thread() diff --git a/selfdrive/monitoring/helpers.py b/selfdrive/monitoring/helpers.py index b8cd03147..351914e79 100644 --- a/selfdrive/monitoring/helpers.py +++ b/selfdrive/monitoring/helpers.py @@ -141,7 +141,7 @@ def face_orientation_from_net(angles_desc, pos_desc, rpy_calib): class DriverMonitoring: - def __init__(self, rhd_saved=False, settings=None, always_on=False): + def __init__(self, rhd_saved=False, settings=None, always_on=False, rhd_override=None): # init policy settings self.settings = settings if settings is not None else DRIVER_MONITOR_SETTINGS(device_type=HARDWARE.get_device_type()) @@ -159,6 +159,7 @@ class DriverMonitoring: self.wheel_on_right = False self.wheel_on_right_last = None self.wheel_on_right_default = rhd_saved + self.wheel_on_right_override = rhd_override self.face_detected = False self.terminal_alert_cnt = 0 self.terminal_time = 0 @@ -268,12 +269,15 @@ class DriverMonitoring: self.wheelpos.prob_offseter.push_and_update(rhd_pred) self.wheelpos.prob_calibrated = self.wheelpos.prob_offseter.filtered_stat.n > self.settings._WHEELPOS_FILTER_MIN_COUNT - if self.wheelpos.prob_calibrated or demo_mode: + if self.wheel_on_right_override is not None and not demo_mode: + self.wheel_on_right = self.wheel_on_right_override + elif self.wheelpos.prob_calibrated or demo_mode: self.wheel_on_right = self.wheelpos.prob_offseter.filtered_stat.M > self.settings._WHEELPOS_THRESHOLD else: self.wheel_on_right = self.wheel_on_right_default # use default/saved if calibration is unfinished # make sure no switching when engaged - if op_engaged and self.wheel_on_right_last is not None and self.wheel_on_right_last != self.wheel_on_right and not demo_mode: + if (self.wheel_on_right_override is None and op_engaged and self.wheel_on_right_last is not None and + self.wheel_on_right_last != self.wheel_on_right and not demo_mode): self.wheel_on_right = self.wheel_on_right_last driver_data = driver_state.rightDriverData if self.wheel_on_right else driver_state.leftDriverData if not all(len(x) > 0 for x in (driver_data.faceOrientation, driver_data.facePosition, diff --git a/selfdrive/monitoring/test_monitoring.py b/selfdrive/monitoring/test_monitoring.py index ffbb9e68a..50ef93576 100644 --- a/selfdrive/monitoring/test_monitoring.py +++ b/selfdrive/monitoring/test_monitoring.py @@ -66,6 +66,15 @@ class TestMonitoring: def _assert_no_events(self, events): assert all(not len(e) for e in events) + def test_rhd_manual_override_beats_saved_default(self): + DM = DriverMonitoring(rhd_saved=False, rhd_override=True) + DM._update_states(msg_ATTENTIVE, [0, 0, 0], 0, False, False) + assert DM.wheel_on_right + + DM = DriverMonitoring(rhd_saved=True, rhd_override=False) + DM._update_states(msg_ATTENTIVE, [0, 0, 0], 0, False, False) + assert not DM.wheel_on_right + # engaged, driver is attentive all the time def test_fully_aware_driver(self): events, _ = self._run_seq(always_attentive, always_false, always_true, always_false) diff --git a/selfdrive/ui/layouts/settings/device.py b/selfdrive/ui/layouts/settings/device.py index 00ae6a188..f03e03c69 100644 --- a/selfdrive/ui/layouts/settings/device.py +++ b/selfdrive/ui/layouts/settings/device.py @@ -23,6 +23,7 @@ from openpilot.system.ui.widgets.scroller_tici import Scroller DESCRIPTIONS = { 'pair_device': tr_noop("Pair your device with comma connect (connect.comma.ai) and claim your comma prime offer."), 'driver_camera': tr_noop("Preview the driver facing camera to ensure that driver monitoring has good visibility. (vehicle must be off)"), + 'reset_driver_monitoring': tr_noop("Clears the saved driver monitoring wheel-side calibration and any manual right-hand-driving override."), 'reset_calibration': tr_noop("openpilot requires the device to be mounted within 4° left or right and within 5° up or 9° down."), 'review_guide': tr_noop("Review the rules, features, and limitations of openpilot"), } @@ -61,6 +62,8 @@ class DeviceLayout(Widget): self._pair_device_btn, button_item(lambda: tr("Driver Camera"), lambda: tr("PREVIEW"), lambda: tr(DESCRIPTIONS['driver_camera']), callback=self._show_driver_camera, enabled=ui_state.is_offroad), + button_item(lambda: tr("Reset Driver Monitoring"), lambda: tr("RESET"), lambda: tr(DESCRIPTIONS['reset_driver_monitoring']), + callback=self._reset_driver_monitoring_prompt, enabled=ui_state.is_offroad), self._reset_calib_btn, button_item(lambda: tr("Review Training Guide"), lambda: tr("REVIEW"), lambda: tr(DESCRIPTIONS['review_guide']), self._on_review_training_guide, enabled=ui_state.is_offroad), @@ -119,6 +122,23 @@ class DeviceLayout(Widget): dialog = ConfirmDialog(tr("Are you sure you want to reset calibration?"), tr("Reset")) gui_app.set_modal_overlay(dialog, callback=reset_calibration) + def _reset_driver_monitoring_prompt(self): + if ui_state.engaged: + gui_app.set_modal_overlay(alert_dialog(tr("Disengage to Reset Driver Monitoring"))) + return + + def reset_driver_monitoring(result: int): + if ui_state.engaged or result != DialogResult.CONFIRM: + return + + self._params.remove("IsRhdDetected") + self._params.remove("IsRHD") + self._params.remove("IsRHDOverride") + self._params.put_bool("OnroadCycleRequested", True) + + dialog = ConfirmDialog(tr("Are you sure you want to reset driver monitoring calibration?"), tr("Reset")) + gui_app.set_modal_overlay(dialog, callback=reset_driver_monitoring) + def _update_calib_description(self): desc = tr(DESCRIPTIONS['reset_calibration']) diff --git a/selfdrive/ui/layouts/settings/toggles.py b/selfdrive/ui/layouts/settings/toggles.py index a1fc85eb2..d543634bf 100644 --- a/selfdrive/ui/layouts/settings/toggles.py +++ b/selfdrive/ui/layouts/settings/toggles.py @@ -29,6 +29,7 @@ DESCRIPTIONS = { ), "AlwaysOnDM": tr_noop("Enable driver monitoring even when openpilot is not engaged."), 'RecordFront': tr_noop("Upload data from the driver facing camera and help improve the driver monitoring algorithm."), + "IsRHD": tr_noop("Use right-hand-drive driver monitoring. This follows the auto-detected side until changed manually."), "IsMetric": tr_noop("Display speed in km/h instead of mph."), "RecordAudio": tr_noop("Record and store microphone audio while driving. The audio will be included in the dashcam video in comma connect."), } @@ -38,6 +39,7 @@ class TogglesLayout(Widget): def __init__(self): super().__init__() self._params = Params() + self._sync_rhd_toggle() # param, title, desc, icon, needs_restart self._toggle_defs = { @@ -77,6 +79,12 @@ class TogglesLayout(Widget): "monitoring.png", False, ), + "IsRHD": ( + lambda: tr("Right Hand Driving"), + DESCRIPTIONS["IsRHD"], + "monitoring.png", + False, + ), "RecordFront": ( lambda: tr("Record and Upload Driver Camera"), DESCRIPTIONS["RecordFront"], @@ -158,6 +166,7 @@ class TogglesLayout(Widget): def _update_toggles(self): ui_state.update_params() + self._sync_rhd_toggle() safe_mode = self._params.get_bool("SafeMode") if safe_mode: if self._params.get_bool("ExperimentalMode"): @@ -246,8 +255,14 @@ class TogglesLayout(Widget): return self._params.put_bool(param, state) + if param == "IsRHD": + self._params.put_bool("IsRHDOverride", True) if self._toggle_defs[param][3]: self._params.put_bool("OnroadCycleRequested", True) def _set_longitudinal_personality(self, button_index: int): self._params.put("LongitudinalPersonality", button_index) + + def _sync_rhd_toggle(self): + if not self._params.get_bool("IsRHDOverride"): + self._params.put_bool("IsRHD", self._params.get_bool("IsRhdDetected")) diff --git a/selfdrive/ui/mici/layouts/settings/device.py b/selfdrive/ui/mici/layouts/settings/device.py index 37d19cd03..813079d90 100644 --- a/selfdrive/ui/mici/layouts/settings/device.py +++ b/selfdrive/ui/mici/layouts/settings/device.py @@ -370,6 +370,8 @@ class DeviceLayoutMici(NavScroller): def reset_driver_monitoring_callback(): params = ui_state.params params.remove("IsRhdDetected") + params.remove("IsRHD") + params.remove("IsRHDOverride") params.put_bool("OnroadCycleRequested", True) def uninstall_openpilot_callback(): diff --git a/selfdrive/ui/mici/layouts/settings/toggles.py b/selfdrive/ui/mici/layouts/settings/toggles.py index 21b538597..77134c7c9 100644 --- a/selfdrive/ui/mici/layouts/settings/toggles.py +++ b/selfdrive/ui/mici/layouts/settings/toggles.py @@ -12,6 +12,11 @@ PERSONALITY_TO_INT = log.LongitudinalPersonality.schema.enumerants class TogglesLayoutMici(NavScroller): def __init__(self): super().__init__() + self._sync_rhd_toggle() + + def rhd_toggle_callback(checked: bool): + ui_state.params.put_bool("IsRHD", checked) + ui_state.params.put_bool("IsRHDOverride", True) self._personality_toggle = BigMultiParamToggle("driving personality", "LongitudinalPersonality", ["aggressive", "standard", "relaxed"]) self._safe_mode_btn = BigParamControl("safe mode", "SafeMode", toggle_callback=restart_needed_callback) @@ -19,6 +24,7 @@ class TogglesLayoutMici(NavScroller): is_metric_toggle = BigParamControl("use metric units", "IsMetric") ldw_toggle = BigParamControl("lane departure warnings", "IsLdwEnabled") always_on_dm_toggle = BigParamControl("always-on driver monitor", "AlwaysOnDM") + rhd_toggle = BigParamControl("right hand driving", "IsRHD", toggle_callback=rhd_toggle_callback) record_front = BigParamControl("record & upload driver camera", "RecordFront", toggle_callback=restart_needed_callback) record_mic = BigParamControl("record & upload mic audio", "RecordAudio", toggle_callback=restart_needed_callback) enable_openpilot = BigParamControl("enable openpilot", "OpenpilotEnabledToggle", toggle_callback=restart_needed_callback) @@ -30,6 +36,7 @@ class TogglesLayoutMici(NavScroller): is_metric_toggle, ldw_toggle, always_on_dm_toggle, + rhd_toggle, record_front, record_mic, enable_openpilot, @@ -42,6 +49,7 @@ class TogglesLayoutMici(NavScroller): ("IsMetric", is_metric_toggle), ("IsLdwEnabled", ldw_toggle), ("AlwaysOnDM", always_on_dm_toggle), + ("IsRHD", rhd_toggle), ("RecordFront", record_front), ("RecordAudio", record_mic), ("OpenpilotEnabledToggle", enable_openpilot), @@ -72,6 +80,7 @@ class TogglesLayoutMici(NavScroller): def _update_toggles(self): ui_state.update_params() + self._sync_rhd_toggle() safe_mode = ui_state.params.get_bool("SafeMode") self._experimental_btn.set_enabled(not safe_mode) self._personality_toggle.set_enabled(not safe_mode) @@ -100,3 +109,7 @@ class TogglesLayoutMici(NavScroller): # Refresh toggles from params to mirror external changes for key, item in self._refresh_toggles: item.set_checked(ui_state.params.get_bool(key)) + + def _sync_rhd_toggle(self): + if not ui_state.params.get_bool("IsRHDOverride"): + ui_state.params.put_bool("IsRHD", ui_state.params.get_bool("IsRhdDetected")) diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index ea8017028..0d4272e52 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -25,6 +25,8 @@ #include "starpilot/ui/qt/offroad/starpilot_settings.h" TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { + syncRhdToggle(); + // param, title, desc, icon, restart needed std::vector> toggle_defs{ { @@ -76,6 +78,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { "../assets/icons/monitoring.png", false, }, + { + "IsRHD", + tr("Right Hand Driving"), + tr("Use right-hand-drive driver monitoring. This follows the auto-detected side until changed manually."), + "../assets/icons/monitoring.png", + false, + }, { "RecordFront", tr("Record and Upload Driver Camera"), @@ -136,6 +145,13 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }); } + if (param == "IsRHD") { + QObject::connect(toggle, &ParamControl::toggleFlipped, this, [this](bool state) { + params.putBool("IsRHD", state); + params.putBool("IsRHDOverride", true); + }); + } + addItem(toggle); toggles[param.toStdString()] = toggle; @@ -154,6 +170,12 @@ TogglesPanel::TogglesPanel(SettingsWindow *parent) : ListWidget(parent) { }); } +void TogglesPanel::syncRhdToggle() { + if (!params.getBool("IsRHDOverride")) { + params.putBool("IsRHD", params.getBool("IsRhdDetected")); + } +} + void TogglesPanel::updateState(const UIState &s) { const SubMaster &sm = *(s.sm); @@ -184,6 +206,8 @@ void TogglesPanel::showEvent(QShowEvent *event) { } void TogglesPanel::updateToggles() { + syncRhdToggle(); + const bool showAllToggles = params.getBool("ShowAllToggles"); const bool safe_mode = params.getBool("SafeMode"); const bool simple_mode = params.getBool("SimpleMode"); @@ -266,6 +290,7 @@ void TogglesPanel::updateToggles() { experimental_mode_toggle->setVisible(showAllToggles || !starpilot_toggles.value("conditional_experimental_mode").toBool()); auto record_audio_toggle = toggles["RecordAudio"]; record_audio_toggle->setVisible(showAllToggles || !starpilot_toggles.value("no_logging").toBool()); + toggles["IsRHD"]->refresh(); auto safe_mode_toggle = toggles["SafeMode"]; if (safe_mode_toggle != nullptr) { @@ -410,7 +435,7 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { resetDmCalibBtn = new ButtonControl( tr("Reset Driver Monitoring"), tr("RESET"), - tr("Clears the saved driver monitoring wheel-side calibration if the device thinks you're seated on the wrong side. " + tr("Clears the saved driver monitoring wheel-side calibration and any manual right-hand-driving override if the device thinks you're seated on the wrong side. " "Resetting will restart openpilot if the car is powered on.") ); connect(resetDmCalibBtn, &ButtonControl::clicked, [&]() { @@ -418,6 +443,8 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { if (ConfirmationDialog::confirm(tr("Are you sure you want to reset driver monitoring calibration?"), tr("Reset"), this)) { if (!uiState()->engaged()) { params.remove("IsRhdDetected"); + params.remove("IsRHD"); + params.remove("IsRHDOverride"); params.putBool("OnroadCycleRequested", true); } } diff --git a/selfdrive/ui/qt/offroad/settings.h b/selfdrive/ui/qt/offroad/settings.h index bb048f1e5..14952e448 100644 --- a/selfdrive/ui/qt/offroad/settings.h +++ b/selfdrive/ui/qt/offroad/settings.h @@ -116,6 +116,7 @@ private: std::map toggles; ButtonParamControl *long_personality_setting; + void syncRhdToggle(); void updateToggles(); }; diff --git a/starpilot/system/the_pond/assets/components/tools/device_settings.js b/starpilot/system/the_pond/assets/components/tools/device_settings.js index 6b3e05fae..37a276e55 100644 --- a/starpilot/system/the_pond/assets/components/tools/device_settings.js +++ b/starpilot/system/the_pond/assets/components/tools/device_settings.js @@ -644,6 +644,31 @@ async function updateParam(key, elType) { } } +async function restoreRhdAutoDetection() { + const currentValues = { ...state.values } + try { + const res = await fetch("/api/params", { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ key: "IsRHDOverride", value: false }), + }) + const data = await res.json() + + if (res.ok) { + const updated = (data.updated && typeof data.updated === "object") ? data.updated : {} + state.values = { ...state.values, IsRHDOverride: false, ...updated } + showParamSnackbar(data.message || "Right Hand Driving auto detection restored.") + scheduleSyncInputs() + } else { + state.values = currentValues + showParamSnackbar(data.error || "Failed to restore auto detection", "error") + } + } catch (e) { + state.values = currentValues + showParamSnackbar("Network error — is the device reachable?", "error") + } +} + function revertInput(key, current, elType) { const el = document.getElementById(`ds-${key}`) if (!el) return @@ -847,13 +872,30 @@ function renderSettingRow(p) { ` } else if (!isGroup) { - rowControl = html` - - ` + if (p.key === "IsRHD") { + rowControl = html` +
+ + ${() => state.values.IsRHDOverride ? html` + + ` : ""} +
+ ` + } else { + rowControl = html` + + ` + } } return html` 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 3f73f085c..796002ea5 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 @@ -2187,6 +2187,13 @@ "data_type": "bool", "ui_type": "toggle" }, + { + "key": "IsRHD", + "label": "Right Hand Driving", + "description": "Use right-hand-drive driver monitoring. This follows the auto-detected side until changed manually.", + "data_type": "bool", + "ui_type": "toggle" + }, { "key": "DisableOpenpilotLongitudinal", "label": "Disable openpilot Longitudinal", diff --git a/starpilot/system/the_pond/the_pond.py b/starpilot/system/the_pond/the_pond.py index b99b42787..4905876ec 100644 --- a/starpilot/system/the_pond/the_pond.py +++ b/starpilot/system/the_pond/the_pond.py @@ -2029,6 +2029,8 @@ _cached_param_types = None _cached_default_values = None _cached_static_default_values = None +POND_MANUAL_BOOL_PARAM_KEYS = {"IsRHD", "IsRHDOverride"} + def _get_param_type_info(): global _cached_allowed_keys, _cached_param_types if _cached_allowed_keys is None: @@ -2049,6 +2051,12 @@ def _get_param_type_info(): for k, dt in _get_layout_type_overrides().items(): if k in types and dt in ("int", "float") and types[k] == bool: types[k] = float if dt == "float" else int + elif k in types and dt == "bool": + types[k] = bool + + for k in POND_MANUAL_BOOL_PARAM_KEYS: + if k in _cached_allowed_keys: + types[k] = bool # Keep legacy aliases editable for older payloads/UI clients. alias_to_key = { @@ -2223,6 +2231,9 @@ def _get_current_param_value(key, value_type, defaults_lookup=None): if key == CUSTOM_ACCEL_PROFILE_INITIALIZED_KEY: return _get_custom_accel_profile_initialized() + if key == "IsRHD" and not _safe_params_get_bool("IsRHDOverride"): + return _safe_params_get_bool("IsRhdDetected") + if key in CUSTOM_ACCEL_PROFILE_PARAM_KEYS and not _get_custom_accel_profile_initialized(): if defaults_lookup is None: defaults_lookup = _get_default_param_values() @@ -3790,6 +3801,32 @@ def setup(app): }, }), 200 + if key == "IsRHD": + enabled = str_val.strip() in ("1", "true", "True") + params.put_bool("IsRHD", enabled) + params.put_bool("IsRHDOverride", True) + return jsonify({ + "message": "Right Hand Driving override updated successfully.", + "updated": { + "IsRHD": enabled, + "IsRHDOverride": True, + }, + }), 200 + + if key == "IsRHDOverride": + enabled = str_val.strip() in ("1", "true", "True") + params.put_bool("IsRHDOverride", enabled) + updated = {"IsRHDOverride": enabled} + if not enabled: + auto_rhd = params.get_bool("IsRhdDetected") + params.put_bool("IsRHD", auto_rhd) + updated["IsRHD"] = auto_rhd + + return jsonify({ + "message": "Right Hand Driving auto detection restored." if not enabled else "Right Hand Driving override enabled.", + "updated": updated, + }), 200 + if key == "CarMake": catalog = _get_fingerprint_catalog() normalized_make = _normalize_fingerprint_make_key(str_val) @@ -3933,6 +3970,8 @@ def setup(app): return _serialize_param_write_value(defaults_lookup.get(request_key)), 200 if request_key == CUSTOM_ACCEL_PROFILE_INITIALIZED_KEY: return _serialize_param_write_value(_get_custom_accel_profile_initialized()), 200 + if request_key == "IsRHD" and not params.get_bool("IsRHDOverride"): + return ("1" if params.get_bool("IsRhdDetected") else "0"), 200 value = params.get(request_key) or "" if request_key in ("Model", "DrivingModel"): if isinstance(value, bytes): diff --git a/tools/StarPilot/generate_pond_layout.py b/tools/StarPilot/generate_pond_layout.py index 5f312d270..ed15e7c71 100755 --- a/tools/StarPilot/generate_pond_layout.py +++ b/tools/StarPilot/generate_pond_layout.py @@ -50,6 +50,13 @@ INJECTED_SECTION_PARAMS = { "data_type": "bool", "ui_type": "toggle", }, + { + "key": "IsRHD", + "label": "Right Hand Driving", + "description": "Use right-hand-drive driver monitoring. This follows the auto-detected side until changed manually.", + "data_type": "bool", + "ui_type": "toggle", + }, ], }