From c8786d930d3fbe936e184c917769306518eaedeb Mon Sep 17 00:00:00 2001 From: ZwX1616 Date: Fri, 17 Jul 2026 15:27:20 -0700 Subject: [PATCH] DM: reasonable lockout ramp up (#38358) --- openpilot/cereal/log.capnp | 3 +- openpilot/common/params_keys.h | 1 + openpilot/selfdrive/monitoring/policy.py | 30 ++++++++++++------- .../selfdrive/monitoring/test_monitoring.py | 14 ++++----- openpilot/selfdrive/selfdrived/events.py | 6 ++-- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/openpilot/cereal/log.capnp b/openpilot/cereal/log.capnp index 8ce9f868a..e1725d1e4 100644 --- a/openpilot/cereal/log.capnp +++ b/openpilot/cereal/log.capnp @@ -2160,7 +2160,8 @@ struct DriverMonitoringStateDEPRECATED @0xb83cda094a1da284 { struct DriverMonitoringState { lockout @0 :Bool; - lockoutRecoveryPercent @11 :Int8; + lockoutCount @15 :Int8; + lockoutMinutesRemaining @11 :Int8; alert3Count @12 :Int8; noResponseCount @13 :Int8; noResponseForceDecel @14 :Bool; diff --git a/openpilot/common/params_keys.h b/openpilot/common/params_keys.h index 7efa5721d..0615c0270 100644 --- a/openpilot/common/params_keys.h +++ b/openpilot/common/params_keys.h @@ -37,6 +37,7 @@ inline static std::unordered_map keys = { {"DoShutdown", {CLEAR_ON_MANAGER_START, BOOL}}, {"DoUninstall", {CLEAR_ON_MANAGER_START, BOOL}}, {"DriverTooDistracted", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, BOOL}}, + {"DriverLockoutCount", {CLEAR_ON_MANAGER_START | CLEAR_ON_IGNITION_ON, INT, "0"}}, {"AlphaLongitudinalEnabled", {PERSISTENT | DEVELOPMENT_ONLY, BOOL}}, {"ExperimentalMode", {PERSISTENT, BOOL}}, {"ExperimentalModeConfirmed", {PERSISTENT, BOOL}}, diff --git a/openpilot/selfdrive/monitoring/policy.py b/openpilot/selfdrive/monitoring/policy.py index 03db1dc02..1ce9f03b3 100644 --- a/openpilot/selfdrive/monitoring/policy.py +++ b/openpilot/selfdrive/monitoring/policy.py @@ -41,7 +41,7 @@ class DRIVER_MONITOR_SETTINGS: # lockout specs self._MAX_ALERT_3 = 2 self._MAX_NO_RESPONSE = 1 - self._LOCKOUT_TIME = int(1800 / DT_DMON) + self._LOCKOUT_TIMES = [int(60 * n_min / DT_DMON) for n_min in [1, 5, 15, 30]] self._TIMEOUT_RECOVERY_FACTOR_MAX = 5. self._TIMEOUT_RECOVERY_FACTOR_MIN = 1.25 @@ -152,7 +152,10 @@ class DriverMonitoring: self.cnt_since_alert_3 = 0 self.no_response_timeout = int(self.settings._NO_RESPONSE_TIMEOUT / DT_DMON) self.no_response_cnt = 0 - self.lockout_time = 0 + self.lockout_active = Params().get_bool("DriverTooDistracted") + self.lockout_count = Params().get("DriverLockoutCount") or 0 + self.lockout_duration = self.settings._LOCKOUT_TIMES[min(max(self.lockout_count - 1, 0), len(self.settings._LOCKOUT_TIMES) - 1)] + self.lockout_time_elapsed = 0 self.step_change = 0. self.active_policy = MonitoringPolicy.vision self.driver_interacting = False @@ -163,7 +166,6 @@ class DriverMonitoring: self.threshold_alert_2 = 0. self.dcam_uncertain_cnt = 0 self.dcam_reset_cnt = 0 - self.too_distracted = Params().get_bool("DriverTooDistracted") self._reset_awareness() self._set_policy(MonitoringPolicy.vision) @@ -310,16 +312,20 @@ class DriverMonitoring: self.driver_interacting = driver_engaged if self.alert_3_cnt >= self.settings._MAX_ALERT_3 or self.no_response_cnt >= self.settings._MAX_NO_RESPONSE: - self.too_distracted = True + if not self.lockout_active: + self.lockout_count += 1 + self.lockout_duration = self.settings._LOCKOUT_TIMES[min(self.lockout_count - 1, len(self.settings._LOCKOUT_TIMES) - 1)] + Params().put("DriverLockoutCount", self.lockout_count) + self.lockout_active = True - if self.too_distracted: - self.lockout_time += 1 - if self.lockout_time > self.settings._LOCKOUT_TIME: - self.too_distracted = False + if self.lockout_active: + self.lockout_time_elapsed += 1 + if self.lockout_time_elapsed > self.lockout_duration: + self.lockout_active = False self.alert_3_cnt = 0 self.cnt_since_alert_3 = 0 self.no_response_cnt = 0 - self.lockout_time = 0 + self.lockout_time_elapsed = 0 always_on_valid = self.always_on and not wrong_gear if (self.driver_interacting and self.awareness > 0 and self.active_policy == MonitoringPolicy.wheeltouch) or \ @@ -379,8 +385,10 @@ class DriverMonitoring: dat = messaging.new_message('driverMonitoringState', valid=valid) dm = dat.driverMonitoringState - dm.lockout = self.too_distracted - dm.lockoutRecoveryPercent = to_percent(self.lockout_time / self.settings._LOCKOUT_TIME) + dm.lockout = self.lockout_active + dm.lockoutCount = self.lockout_count + if self.lockout_active: + dm.lockoutMinutesRemaining = max(1, round((self.lockout_duration - self.lockout_time_elapsed) * DT_DMON / 60.)) dm.alert3Count = self.alert_3_cnt dm.noResponseCount = self.no_response_cnt dm.noResponseForceDecel = self.alert_level == AlertLevel.three and self.cnt_since_alert_3 >= self.no_response_timeout diff --git a/openpilot/selfdrive/monitoring/test_monitoring.py b/openpilot/selfdrive/monitoring/test_monitoring.py index 48fffcc1c..0368dc9b6 100644 --- a/openpilot/selfdrive/monitoring/test_monitoring.py +++ b/openpilot/selfdrive/monitoring/test_monitoring.py @@ -83,21 +83,17 @@ class TestMonitoring: # engaged, distracted past red and beyond the no-response window -> unavailability response + lockout def test_distracted_lockout(self): alert_lvls, d_status = self._run_seq(always_distracted, always_false, always_true, always_false) - s = d_status.settings assert alert_lvls[int(DISTRACTED_SECONDS_TO_RED / DT_DMON)] == 3 - assert d_status.alert_3_cnt == 1 - assert d_status.no_response_cnt == s._MAX_NO_RESPONSE - assert d_status.too_distracted - assert d_status.lockout_time > 0 + assert d_status.lockout_active + assert d_status.lockout_time_elapsed > 0 + assert d_status.lockout_count >= 1 # no face -> wheeltouch red, sustained past the no-response timeout -> unavailability response + lockout def test_invisible_lockout(self): _, d_status = self._run_seq(always_no_face, always_false, always_true, always_false) - s = d_status.settings assert d_status.active_policy == log.DriverMonitoringState.MonitoringPolicy.wheeltouch - assert d_status.alert_3_cnt == 1 - assert d_status.no_response_cnt == s._MAX_NO_RESPONSE - assert d_status.too_distracted + assert d_status.lockout_active + assert d_status.lockout_count >= 1 # engaged, no face detected the whole time, no action def test_fully_invisible_driver(self): diff --git a/openpilot/selfdrive/selfdrived/events.py b/openpilot/selfdrive/selfdrived/events.py index 00d0334ad..367bc03d3 100755 --- a/openpilot/selfdrive/selfdrived/events.py +++ b/openpilot/selfdrive/selfdrived/events.py @@ -10,9 +10,8 @@ from opendbc.car.structs import car import openpilot.cereal.messaging as messaging from openpilot.common.constants import CV from openpilot.common.git import get_short_branch -from openpilot.common.realtime import DT_CTRL, DT_DMON +from openpilot.common.realtime import DT_CTRL from openpilot.selfdrive.locationd.calibrationd import MIN_SPEED_FILTER -from openpilot.selfdrive.monitoring.policy import DRIVER_MONITOR_SETTINGS from openpilot.system.micd import SAMPLE_RATE, SAMPLE_BUFFER from openpilot.selfdrive.ui.feedback.feedbackd import FEEDBACK_MAX_DURATION from openpilot.common.hardware import HARDWARE @@ -23,7 +22,6 @@ VisualAlert = car.CarControl.HUDControl.VisualAlert AudibleAlert = log.SelfdriveState.AudibleAlert EventName = log.OnroadEvent.EventName -DMON_LOCKOUT_TIME = DRIVER_MONITOR_SETTINGS()._LOCKOUT_TIME # Alert priorities class Priority(IntEnum): @@ -269,7 +267,7 @@ def calibration_incomplete_alert(CP: car.CarParams, CS: car.CarState, sm: messag def too_distracted_alert(CP: car.CarParams, CS: car.CarState, sm: messaging.SubMaster, metric: bool, soft_disable_time: int, personality) -> Alert: if sm['driverMonitoringState'].lockout: - mins_left = max(1, round((100 - sm['driverMonitoringState'].lockoutRecoveryPercent) / 100 * DMON_LOCKOUT_TIME * DT_DMON / 60.)) + mins_left = sm['driverMonitoringState'].lockoutMinutesRemaining return NoEntryAlert("Too Distracted", f"{mins_left} minute{'s' if mins_left != 1 else ''} Left", priority=Priority.HIGH) return NoEntryAlert("Pay Attention to Engage", priority=Priority.HIGH)