This commit is contained in:
firestar5683
2026-05-31 21:03:42 -05:00
parent 0e7123d632
commit 5d86b7ac5b
14 changed files with 212 additions and 13 deletions
+2
View File
@@ -68,7 +68,9 @@ inline static std::unordered_map<std::string, ParamKeyAttributes> 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}},
+13 -2
View File
@@ -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()
+7 -3
View File
@@ -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,
+9
View File
@@ -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)
+20
View File
@@ -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'])
+15
View File
@@ -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"))
@@ -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():
@@ -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"))
+28 -1
View File
@@ -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<std::tuple<QString, QString, QString, QString, bool>> 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);
}
}
+1
View File
@@ -116,6 +116,7 @@ private:
std::map<std::string, ParamControl*> toggles;
ButtonParamControl *long_personality_setting;
void syncRhdToggle();
void updateToggles();
};
@@ -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) {
</div>
`
} else if (!isGroup) {
rowControl = html`
<input
type="checkbox"
class="ds-toggle"
id="ds-${p.key}"
@change="${() => updateParam(p.key, "checkbox")}" />
`
if (p.key === "IsRHD") {
rowControl = html`
<div style="display:flex; align-items:center; gap:0.75rem;">
<input
type="checkbox"
class="ds-toggle"
id="ds-${p.key}"
@change="${() => updateParam(p.key, "checkbox")}" />
${() => state.values.IsRHDOverride ? html`
<button
class="ds-reset-btn"
@click="${restoreRhdAutoDetection}">Auto</button>
` : ""}
</div>
`
} else {
rowControl = html`
<input
type="checkbox"
class="ds-toggle"
id="ds-${p.key}"
@change="${() => updateParam(p.key, "checkbox")}" />
`
}
}
return html`
@@ -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",
+39
View File
@@ -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):
+7
View File
@@ -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",
},
],
}