From 48340cc8cb1886d65b1e2720c4bfc81307c7f9f0 Mon Sep 17 00:00:00 2001 From: Adeeb <8762862+adeebshihadeh@users.noreply.github.com> Date: Thu, 14 May 2020 15:21:21 -0700 Subject: [PATCH] Alerts + Events refactor (#1466) old-commit-hash: d976233f696040cd5f9a5081c7b21742b7aaef66 --- cereal | 2 +- release/files_common | 2 +- selfdrive/car/car_helpers.py | 13 +- selfdrive/car/chrysler/interface.py | 8 +- selfdrive/car/ford/interface.py | 5 +- selfdrive/car/gm/interface.py | 16 +- selfdrive/car/honda/interface.py | 27 +- selfdrive/car/hyundai/interface.py | 5 +- selfdrive/car/interfaces.py | 35 +- selfdrive/car/nissan/interface.py | 5 +- selfdrive/car/subaru/interface.py | 2 +- selfdrive/car/toyota/interface.py | 14 +- selfdrive/car/volkswagen/interface.py | 8 +- selfdrive/controls/controlsd.py | 229 +++--- selfdrive/controls/dmonitoringd.py | 15 +- selfdrive/controls/lib/alertmanager.py | 21 +- selfdrive/controls/lib/alerts.py | 825 -------------------- selfdrive/controls/lib/drive_helpers.py | 29 - selfdrive/controls/lib/driver_monitor.py | 21 +- selfdrive/controls/lib/events.py | 694 ++++++++++++++++ selfdrive/controls/tests/test_alerts.py | 48 -- selfdrive/controls/tests/test_events.py | 63 ++ selfdrive/controls/tests/test_monitoring.py | 71 +- selfdrive/test/process_replay/ref_commit | 2 +- 24 files changed, 988 insertions(+), 1172 deletions(-) delete mode 100644 selfdrive/controls/lib/alerts.py create mode 100644 selfdrive/controls/lib/events.py delete mode 100755 selfdrive/controls/tests/test_alerts.py create mode 100755 selfdrive/controls/tests/test_events.py diff --git a/cereal b/cereal index 4f68db8f6a..856c9812d5 160000 --- a/cereal +++ b/cereal @@ -1 +1 @@ -Subproject commit 4f68db8f6aa31e87d968da882460e196c6b101a3 +Subproject commit 856c9812d552fe0ac640b75074b080f76c9a3cba diff --git a/release/files_common b/release/files_common index 380bfa7ce1..5257a3a670 100644 --- a/release/files_common +++ b/release/files_common @@ -202,8 +202,8 @@ selfdrive/controls/radard.py selfdrive/controls/dmonitoringd.py selfdrive/controls/lib/__init__.py selfdrive/controls/lib/alertmanager.py -selfdrive/controls/lib/alerts.py selfdrive/controls/lib/alerts_offroad.json +selfdrive/controls/lib/events.py selfdrive/controls/lib/drive_helpers.py selfdrive/controls/lib/driver_monitor.py selfdrive/controls/lib/latcontrol_pid.py diff --git a/selfdrive/car/car_helpers.py b/selfdrive/car/car_helpers.py index 9c11feb6b2..9052ebb26b 100644 --- a/selfdrive/car/car_helpers.py +++ b/selfdrive/car/car_helpers.py @@ -9,17 +9,18 @@ import cereal.messaging as messaging from selfdrive.car import gen_empty_fingerprint from cereal import car +EventName = car.CarEvent.EventName -def get_startup_alert(car_recognized, controller_available): - alert = 'startup' +def get_startup_event(car_recognized, controller_available): + event = EventName.startup if Params().get("GitRemote", encoding="utf8") in ['git@github.com:commaai/openpilot.git', 'https://github.com/commaai/openpilot.git']: if Params().get("GitBranch", encoding="utf8") not in ['devel', 'release2-staging', 'dashcam-staging', 'release2', 'dashcam']: - alert = 'startupMaster' + event = EventName.startupMaster if not car_recognized: - alert = 'startupNoCar' + event = EventName.startupNoCar elif car_recognized and not controller_available: - alert = 'startupNoControl' - return alert + event = EventName.startupNoControl + return event def load_interfaces(brand_names): diff --git a/selfdrive/car/chrysler/interface.py b/selfdrive/car/chrysler/interface.py index 3bc1db8705..66ba7159fb 100755 --- a/selfdrive/car/chrysler/interface.py +++ b/selfdrive/car/chrysler/interface.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.controls.lib.drive_helpers import EventTypes as ET, create_event from selfdrive.car.chrysler.values import Ecu, ECU_FINGERPRINT, CAR, FINGERPRINTS from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint from selfdrive.car.interfaces import CarInterfaceBase @@ -71,12 +70,13 @@ class CarInterface(CarInterfaceBase): ret.buttonEvents = [] # events - events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.low], gas_resume_speed=2.) + events = self.create_common_events(ret, extra_gears=[car.CarState.GearShifter.low], \ + gas_resume_speed=2.) if ret.vEgo < self.CP.minSteerSpeed: - events.append(create_event('belowSteerSpeed', [ET.WARNING])) + events.add(car.CarEvent.EventName.belowSteerSpeed) - ret.events = events + ret.events = events.to_msg() # copy back carState packet to CS self.CS.out = ret.as_reader() diff --git a/selfdrive/car/ford/interface.py b/selfdrive/car/ford/interface.py index 9ebaad366f..4388ae7962 100755 --- a/selfdrive/car/ford/interface.py +++ b/selfdrive/car/ford/interface.py @@ -2,7 +2,6 @@ from cereal import car from selfdrive.swaglog import cloudlog from selfdrive.config import Conversions as CV -from selfdrive.controls.lib.drive_helpers import EventTypes as ET, create_event from selfdrive.car.ford.values import MAX_ANGLE, Ecu, ECU_FINGERPRINT, FINGERPRINTS from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint from selfdrive.car.interfaces import CarInterfaceBase @@ -62,9 +61,9 @@ class CarInterface(CarInterfaceBase): events = self.create_common_events(ret) if self.CS.lkas_state not in [2, 3] and ret.vEgo > 13.* CV.MPH_TO_MS and ret.cruiseState.enabled: - events.append(create_event('steerTempUnavailableMute', [ET.WARNING])) + events.add(car.CarEvent.EventName.steerTempUnavailableMute) - ret.events = events + ret.events = events.to_msg() self.CS.out = ret.as_reader() return self.CS.out diff --git a/selfdrive/car/gm/interface.py b/selfdrive/car/gm/interface.py index ee4ef55dea..55dffce205 100755 --- a/selfdrive/car/gm/interface.py +++ b/selfdrive/car/gm/interface.py @@ -1,13 +1,13 @@ #!/usr/bin/env python3 from cereal import car from selfdrive.config import Conversions as CV -from selfdrive.controls.lib.drive_helpers import create_event, EventTypes as ET from selfdrive.car.gm.values import CAR, Ecu, ECU_FINGERPRINT, CruiseButtons, \ AccState, FINGERPRINTS from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint from selfdrive.car.interfaces import CarInterfaceBase ButtonType = car.CarState.ButtonEvent.Type +EventName = car.CarEvent.EventName class CarInterface(CarInterfaceBase): @@ -149,24 +149,24 @@ class CarInterface(CarInterfaceBase): events = self.create_common_events(ret, pcm_enable=False) if ret.vEgo < self.CP.minEnableSpeed: - events.append(create_event('speedTooLow', [ET.NO_ENTRY])) + events.add(EventName.speedTooLow) if self.CS.park_brake: - events.append(create_event('parkBrake', [ET.NO_ENTRY, ET.USER_DISABLE])) + events.add(EventName.parkBrake) if ret.cruiseState.standstill: - events.append(create_event('resumeRequired', [ET.WARNING])) + events.add(EventName.resumeRequired) if self.CS.pcm_acc_status == AccState.FAULTED: - events.append(create_event('controlsFailed', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) + events.add(EventName.controlsFailed) # handle button presses for b in ret.buttonEvents: # do enable on both accel and decel buttons if b.type in [ButtonType.accelCruise, ButtonType.decelCruise] and not b.pressed: - events.append(create_event('buttonEnable', [ET.ENABLE])) + events.add(EventName.buttonEnable) # do disable on button down if b.type == ButtonType.cancel and b.pressed: - events.append(create_event('buttonCancel', [ET.USER_DISABLE])) + events.add(EventName.buttonCancel) - ret.events = events + ret.events = events.to_msg() # copy back carState packet to CS self.CS.out = ret.as_reader() diff --git a/selfdrive/car/honda/interface.py b/selfdrive/car/honda/interface.py index d8d5cba7cc..0edae57dd9 100755 --- a/selfdrive/car/honda/interface.py +++ b/selfdrive/car/honda/interface.py @@ -5,7 +5,7 @@ from common.numpy_fast import clip, interp from common.realtime import DT_CTRL from selfdrive.swaglog import cloudlog from selfdrive.config import Conversions as CV -from selfdrive.controls.lib.drive_helpers import create_event, EventTypes as ET, get_events +from selfdrive.controls.lib.events import ET from selfdrive.car.honda.values import CruiseButtons, CAR, HONDA_BOSCH, Ecu, ECU_FINGERPRINT, FINGERPRINTS from selfdrive.car import STD_CARGO_KG, CivicParams, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint from selfdrive.controls.lib.planner import _A_CRUISE_MAX_V_FOLLOWING @@ -14,6 +14,7 @@ from selfdrive.car.interfaces import CarInterfaceBase A_ACC_MAX = max(_A_CRUISE_MAX_V_FOLLOWING) ButtonType = car.CarState.ButtonEvent.Type +EventName = car.CarEvent.EventName def compute_gb_honda(accel, speed): creep_brake = 0.0 @@ -468,25 +469,25 @@ class CarInterface(CarInterfaceBase): # events events = self.create_common_events(ret, pcm_enable=False) if self.CS.brake_error: - events.append(create_event('brakeUnavailable', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE, ET.PERMANENT])) + events.add(EventName.brakeUnavailable) if self.CS.brake_hold and self.CS.CP.carFingerprint not in HONDA_BOSCH: - events.append(create_event('brakeHold', [ET.NO_ENTRY, ET.USER_DISABLE])) + events.add(EventName.brakeHold) if self.CS.park_brake: - events.append(create_event('parkBrake', [ET.NO_ENTRY, ET.USER_DISABLE])) + events.add(EventName.parkBrake) if self.CP.enableCruise and ret.vEgo < self.CP.minEnableSpeed: - events.append(create_event('speedTooLow', [ET.NO_ENTRY])) + events.add(EventName.speedTooLow) # it can happen that car cruise disables while comma system is enabled: need to # keep braking if needed or if the speed is very low if self.CP.enableCruise and not ret.cruiseState.enabled and (c.actuators.brake <= 0. or not self.CP.openpilotLongitudinalControl): # non loud alert if cruise disbales below 25mph as expected (+ a little margin) if ret.vEgo < self.CP.minEnableSpeed + 2.: - events.append(create_event('speedTooLow', [ET.IMMEDIATE_DISABLE])) + events.add(EventName.speedTooLow) else: - events.append(create_event("cruiseDisabled", [ET.IMMEDIATE_DISABLE])) + events.add(EventName.cruiseDisabled) if self.CS.CP.minEnableSpeed > 0 and ret.vEgo < 0.001: - events.append(create_event('manualRestart', [ET.WARNING])) + events.add(EventName.manualRestart) cur_time = self.frame * DT_CTRL enable_pressed = False @@ -500,7 +501,7 @@ class CarInterface(CarInterfaceBase): # do disable on button down if b.type == "cancel" and b.pressed: - events.append(create_event('buttonCancel', [ET.USER_DISABLE])) + events.add(EventName.buttonCancel) if self.CP.enableCruise: # KEEP THIS EVENT LAST! send enable event if button is pressed and there are @@ -510,13 +511,13 @@ class CarInterface(CarInterfaceBase): if ((cur_time - self.last_enable_pressed) < 0.2 and (cur_time - self.last_enable_sent) > 0.2 and ret.cruiseState.enabled) or \ - (enable_pressed and get_events(events, [ET.NO_ENTRY])): - events.append(create_event('buttonEnable', [ET.ENABLE])) + (enable_pressed and events.any(ET.NO_ENTRY)): + events.add(EventName.buttonEnable) self.last_enable_sent = cur_time elif enable_pressed: - events.append(create_event('buttonEnable', [ET.ENABLE])) + events.add(EventName.buttonEnable) - ret.events = events + ret.events = events.to_msg() self.CS.out = ret.as_reader() return self.CS.out diff --git a/selfdrive/car/hyundai/interface.py b/selfdrive/car/hyundai/interface.py index 4664164907..183ab7eed1 100644 --- a/selfdrive/car/hyundai/interface.py +++ b/selfdrive/car/hyundai/interface.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 from cereal import car from selfdrive.config import Conversions as CV -from selfdrive.controls.lib.drive_helpers import EventTypes as ET, create_event from selfdrive.car.hyundai.values import Ecu, ECU_FINGERPRINT, CAR, FINGERPRINTS from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint from selfdrive.car.interfaces import CarInterfaceBase @@ -180,9 +179,9 @@ class CarInterface(CarInterfaceBase): if ret.vEgo > (self.CP.minSteerSpeed + 4.): self.low_speed_alert = False if self.low_speed_alert: - events.append(create_event('belowSteerSpeed', [ET.WARNING])) + events.add(car.CarEvent.EventName.belowSteerSpeed) - ret.events = events + ret.events = events.to_msg() self.CS.out = ret.as_reader() return self.CS.out diff --git a/selfdrive/car/interfaces.py b/selfdrive/car/interfaces.py index cf7931c316..65a302000d 100644 --- a/selfdrive/car/interfaces.py +++ b/selfdrive/car/interfaces.py @@ -5,10 +5,11 @@ from common.kalman.simple_kalman import KF1D from common.realtime import DT_CTRL from selfdrive.car import gen_empty_fingerprint from selfdrive.config import Conversions as CV -from selfdrive.controls.lib.drive_helpers import EventTypes as ET, create_event +from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.vehicle_model import VehicleModel GearShifter = car.CarState.GearShifter +EventName = car.CarEvent.EventName # generic car and radar interfaces @@ -81,45 +82,47 @@ class CarInterfaceBase(): raise NotImplementedError def create_common_events(self, cs_out, extra_gears=[], gas_resume_speed=-1, pcm_enable=True): - events = [] + events = Events() if cs_out.doorOpen: - events.append(create_event('doorOpen', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + events.add(EventName.doorOpen) if cs_out.seatbeltUnlatched: - events.append(create_event('seatbeltNotLatched', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + events.add(EventName.seatbeltNotLatched) if cs_out.gearShifter != GearShifter.drive and cs_out.gearShifter not in extra_gears: - events.append(create_event('wrongGear', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + events.add(EventName.wrongGear) if cs_out.gearShifter == GearShifter.reverse: - events.append(create_event('reverseGear', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) + events.add(EventName.reverseGear) if not cs_out.cruiseState.available: - events.append(create_event('wrongCarMode', [ET.NO_ENTRY, ET.USER_DISABLE])) + events.add(EventName.wrongCarMode) if cs_out.espDisabled: - events.append(create_event('espDisabled', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + events.add(EventName.espDisabled) if cs_out.gasPressed: - events.append(create_event('pedalPressed', [ET.PRE_ENABLE])) + events.add(EventName.gasPressed) + if cs_out.stockFcw: + events.add(EventName.stockFcw) if cs_out.stockAeb: - events.append(create_event('stockAeb', [])) + events.add(EventName.stockAeb) if cs_out.vEgo > 92 * CV.MPH_TO_MS: - events.append(create_event('speedTooHigh', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + events.add(EventName.speedTooHigh) if cs_out.steerError: - events.append(create_event('steerUnavailable', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE, ET.PERMANENT])) + events.add(EventName.steerUnavailable) elif cs_out.steerWarning: - events.append(create_event('steerTempUnavailable', [ET.NO_ENTRY, ET.WARNING])) + events.add(EventName.steerTempUnavailable) # Disable on rising edge of gas or brake. Also disable on brake when speed > 0. # Optionally allow to press gas at zero speed to resume. # e.g. Chrysler does not spam the resume button yet, so resuming with gas is handy. FIXME! if (cs_out.gasPressed and (not self.CS.out.gasPressed) and cs_out.vEgo > gas_resume_speed) or \ (cs_out.brakePressed and (not self.CS.out.brakePressed or not cs_out.standstill)): - events.append(create_event('pedalPressed', [ET.NO_ENTRY, ET.USER_DISABLE])) + events.add(EventName.pedalPressed) # we engage when pcm is active (rising edge) if pcm_enable: if cs_out.cruiseState.enabled and not self.CS.out.cruiseState.enabled: - events.append(create_event('pcmEnable', [ET.ENABLE])) + events.add(EventName.pcmEnable) elif not cs_out.cruiseState.enabled: - events.append(create_event('pcmDisable', [ET.USER_DISABLE])) + events.add(EventName.pcmDisable) return events diff --git a/selfdrive/car/nissan/interface.py b/selfdrive/car/nissan/interface.py index 1539fe6e80..34aeb13e65 100644 --- a/selfdrive/car/nissan/interface.py +++ b/selfdrive/car/nissan/interface.py @@ -1,6 +1,5 @@ #!/usr/bin/env python3 from cereal import car -from selfdrive.controls.lib.drive_helpers import create_event, EventTypes as ET from selfdrive.car.nissan.values import CAR from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint from selfdrive.car.interfaces import CarInterfaceBase @@ -77,9 +76,9 @@ class CarInterface(CarInterfaceBase): events = self.create_common_events(ret) if self.CS.lkas_enabled: - events.append(create_event('invalidLkasSetting', [ET.PERMANENT])) + events.add(car.CarEvent.EventName.invalidLkasSetting) - ret.events = events + ret.events = events.to_msg() self.CS.out = ret.as_reader() return self.CS.out diff --git a/selfdrive/car/subaru/interface.py b/selfdrive/car/subaru/interface.py index e205c7cc40..328666d79c 100644 --- a/selfdrive/car/subaru/interface.py +++ b/selfdrive/car/subaru/interface.py @@ -63,7 +63,7 @@ class CarInterface(CarInterfaceBase): be.type = car.CarState.ButtonEvent.Type.accelCruise buttonEvents.append(be) - ret.events = self.create_common_events(ret) + ret.events = self.create_common_events(ret).to_msg() self.CS.out = ret.as_reader() return self.CS.out diff --git a/selfdrive/car/toyota/interface.py b/selfdrive/car/toyota/interface.py index e24c38f635..84fe1b8b3a 100755 --- a/selfdrive/car/toyota/interface.py +++ b/selfdrive/car/toyota/interface.py @@ -1,12 +1,12 @@ #!/usr/bin/env python3 from cereal import car from selfdrive.config import Conversions as CV -from selfdrive.controls.lib.drive_helpers import EventTypes as ET, create_event from selfdrive.car.toyota.values import Ecu, ECU_FINGERPRINT, CAR, TSS2_CAR, FINGERPRINTS from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint from selfdrive.swaglog import cloudlog from selfdrive.car.interfaces import CarInterfaceBase +EventName = car.CarEvent.EventName class CarInterface(CarInterfaceBase): @staticmethod @@ -297,19 +297,19 @@ class CarInterface(CarInterfaceBase): events = self.create_common_events(ret) if self.cp_cam.can_invalid_cnt >= 200 and self.CP.enableCamera: - events.append(create_event('invalidGiraffeToyota', [ET.PERMANENT])) + events.add(EventName.invalidGiraffeToyota) if self.CS.low_speed_lockout and self.CP.openpilotLongitudinalControl: - events.append(create_event('lowSpeedLockout', [ET.NO_ENTRY, ET.PERMANENT])) + events.add(EventName.lowSpeedLockout) if ret.vEgo < self.CP.minEnableSpeed and self.CP.openpilotLongitudinalControl: - events.append(create_event('speedTooLow', [ET.NO_ENTRY])) + events.add(EventName.speedTooLow) if c.actuators.gas > 0.1: # some margin on the actuator to not false trigger cancellation while stopping - events.append(create_event('speedTooLow', [ET.IMMEDIATE_DISABLE])) + events.add(EventName.speedTooLow) if ret.vEgo < 0.001: # while in standstill, send a user alert - events.append(create_event('manualRestart', [ET.WARNING])) + events.add(EventName.manualRestart) - ret.events = events + ret.events = events.to_msg() self.CS.out = ret.as_reader() return self.CS.out diff --git a/selfdrive/car/volkswagen/interface.py b/selfdrive/car/volkswagen/interface.py index cc4e978cb8..8e2797de9c 100644 --- a/selfdrive/car/volkswagen/interface.py +++ b/selfdrive/car/volkswagen/interface.py @@ -1,12 +1,12 @@ from cereal import car from selfdrive.config import Conversions as CV -from selfdrive.controls.lib.drive_helpers import create_event, EventTypes as ET from selfdrive.car.volkswagen.values import CAR, BUTTON_STATES from common.params import put_nonblocking from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint from selfdrive.car.interfaces import CarInterfaceBase GEAR = car.CarState.GearShifter +EventName = car.CarEvent.EventName class CarInterface(CarInterfaceBase): def __init__(self, CP, CarController, CarState): @@ -108,11 +108,11 @@ class CarInterface(CarInterfaceBase): # Vehicle health and operation safety checks if self.CS.parkingBrakeSet: - events.append(create_event('parkBrake', [ET.NO_ENTRY, ET.USER_DISABLE])) + events.add(EventName.parkBrake) if self.CS.steeringFault: - events.append(create_event('steerTempUnavailable', [ET.NO_ENTRY, ET.WARNING])) + events.add(EventName.steerTempUnavailable) - ret.events = events + ret.events = events.to_msg() ret.buttonEvents = buttonEvents ret.canMonoTimes = canMonoTimes diff --git a/selfdrive/controls/controlsd.py b/selfdrive/controls/controlsd.py index 3f57b8c5fe..c85db56713 100755 --- a/selfdrive/controls/controlsd.py +++ b/selfdrive/controls/controlsd.py @@ -1,7 +1,6 @@ #!/usr/bin/env python3 import os import gc -import capnp from cereal import car, log from common.numpy_fast import clip from common.realtime import sec_since_boot, set_realtime_priority, Ratekeeper, DT_CTRL @@ -10,21 +9,18 @@ from common.params import Params, put_nonblocking import cereal.messaging as messaging from selfdrive.config import Conversions as CV from selfdrive.boardd.boardd import can_list_to_can_capnp -from selfdrive.car.car_helpers import get_car, get_startup_alert +from selfdrive.car.car_helpers import get_car, get_startup_event from selfdrive.controls.lib.lane_planner import CAMERA_OFFSET -from selfdrive.controls.lib.drive_helpers import get_events, \ - create_event, \ - EventTypes as ET, \ - update_v_cruise, \ - initialize_v_cruise +from selfdrive.controls.lib.drive_helpers import update_v_cruise, initialize_v_cruise from selfdrive.controls.lib.longcontrol import LongControl, STARTING_TARGET_SPEED from selfdrive.controls.lib.latcontrol_pid import LatControlPID from selfdrive.controls.lib.latcontrol_indi import LatControlINDI from selfdrive.controls.lib.latcontrol_lqr import LatControlLQR +from selfdrive.controls.lib.events import Events, ET from selfdrive.controls.lib.alertmanager import AlertManager from selfdrive.controls.lib.vehicle_model import VehicleModel from selfdrive.controls.lib.planner import LON_MPC_STEP -from selfdrive.locationd.calibration_helpers import Calibration, Filter +from selfdrive.locationd.calibration_helpers import Calibration LDW_MIN_SPEED = 31 * CV.MPH_TO_MS LANE_DEPARTURE_THRESHOLD = 0.1 @@ -38,19 +34,7 @@ LongitudinalPlanSource = log.Plan.LongitudinalPlanSource Desire = log.PathPlan.Desire LaneChangeState = log.PathPlan.LaneChangeState LaneChangeDirection = log.PathPlan.LaneChangeDirection - - -def events_to_bytes(events): - # optimization when comparing capnp structs: str() or tree traverse are much slower - ret = [] - for e in events: - if isinstance(e, capnp.lib.capnp._DynamicStructReader): - e = e.as_builder() - if not e.is_root: - e = e.copy() - ret.append(e.to_bytes()) - return ret - +EventName = car.CarEvent.EventName class Controls: def __init__(self, sm=None, pm=None, can_sock=None): @@ -91,6 +75,10 @@ class Controls: passive = params.get("Passive", encoding='utf8') == "1" or \ internet_needed or not openpilot_enabled_toggle + # detect sound card presence and ensure successful init + sounds_available = not os.path.isfile('/EON') or (os.path.isdir('/proc/asound/card0') \ + and open('/proc/asound/card0/state').read().strip() == 'ONLINE') + car_recognized = self.CP.carName != 'mock' # If stock camera is disconnected, we loaded car controls and it's not dashcam mode controller_available = self.CP.enableCamera and self.CI.CC is not None and not passive @@ -108,6 +96,7 @@ class Controls: self.CC = car.CarControl.new_message() self.AM = AlertManager() + self.events = Events() self.LoC = LongControl(self.CP, self.CI.compute_gb) self.VM = VehicleModel(self.CP) @@ -130,7 +119,8 @@ class Controls: self.can_error_counter = 0 self.last_blinker_frame = 0 self.saturated_count = 0 - self.events_prev = "" + self.events_prev = [] + self.current_alert_types = [] self.sm['liveCalibration'].calStatus = Calibration.INVALID self.sm['pathPlan'].sensorValid = True @@ -140,124 +130,96 @@ class Controls: self.sm['dMonitoringState'].awarenessStatus = 1. self.sm['dMonitoringState'].faceDetected = False - startup_alert = get_startup_alert(car_recognized, controller_available) - self.AM.add(self.sm.frame, startup_alert, False) + self.startup_event = get_startup_event(car_recognized, controller_available) + + if not sounds_available: + self.events.add(EventName.soundsUnavailable, static=True) + if internet_needed: + self.events.add(EventName.internetConnectivityNeeded, static=True) + if community_feature_disallowed: + self.events.add(EventName.communityFeatureDisallowed, static=True) + if self.read_only and not passive: + self.events.add(EventName.carUnrecognized, static=True) # controlsd is driven by can recv, expected at 100Hz self.rk = Ratekeeper(100, print_delay_threshold=None) - self.prof = Profiler(False) # off by default - # detect sound card presence and ensure successful init - sounds_available = not os.path.isfile('/EON') or (os.path.isdir('/proc/asound/card0') \ - and open('/proc/asound/card0/state').read().strip() == 'ONLINE') - self.static_events = [] - if not sounds_available: - self.static_events.append(create_event('soundsUnavailable', [ET.NO_ENTRY, ET.PERMANENT])) - if internet_needed: - self.static_events.append(create_event('internetConnectivityNeeded', [ET.NO_ENTRY, ET.PERMANENT])) - if community_feature_disallowed: - self.static_events.append(create_event('communityFeatureDisallowed', [ET.PERMANENT])) - if self.read_only and not passive: - self.static_events.append(create_event('carUnrecognized', [ET.PERMANENT])) - - - def create_events(self, CS): + def update_events(self, CS): """Compute carEvents from carState""" - events = self.static_events.copy() - events.extend(CS.events) - events.extend(self.sm['dMonitoringState'].events) + self.events.clear() + self.events.add_from_msg(CS.events) + self.events.add_from_msg(self.sm['dMonitoringState'].events) + + # Handle startup event + if self.startup_event is not None: + self.events.add(self.startup_event) + self.startup_event = None # Create events for battery, temperature, disk space, and memory if self.sm['thermal'].batteryPercent < 1 and self.sm['thermal'].chargingError: # at zero percent battery, while discharging, OP should not allowed - events.append(create_event('lowBattery', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + self.events.add(EventName.lowBattery) if self.sm['thermal'].thermalStatus >= ThermalStatus.red: - events.append(create_event('overheat', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + self.events.add(EventName.overheat) if self.sm['thermal'].freeSpace < 0.07: # under 7% of space free no enable allowed - events.append(create_event('outOfSpace', [ET.NO_ENTRY])) + self.events.add(EventName.outOfSpace) if self.sm['thermal'].memUsedPercent > 90: - events.append(create_event('lowMemory', [ET.NO_ENTRY, ET.SOFT_DISABLE, ET.PERMANENT])) + self.events.add(EventName.lowMemory) # Handle calibration status cal_status = self.sm['liveCalibration'].calStatus if cal_status != Calibration.CALIBRATED: if cal_status == Calibration.UNCALIBRATED: - events.append(create_event('calibrationIncomplete', [ET.NO_ENTRY, ET.SOFT_DISABLE, ET.PERMANENT])) + self.events.add(EventName.calibrationIncomplete) else: - events.append(create_event('calibrationInvalid', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + self.events.add(EventName.calibrationInvalid) # Handle lane change if self.sm['pathPlan'].laneChangeState == LaneChangeState.preLaneChange: if self.sm['pathPlan'].laneChangeDirection == LaneChangeDirection.left: - events.append(create_event('preLaneChangeLeft', [ET.WARNING])) + self.events.add(EventName.preLaneChangeLeft) else: - events.append(create_event('preLaneChangeRight', [ET.WARNING])) + self.events.add(EventName.preLaneChangeRight) elif self.sm['pathPlan'].laneChangeState in [LaneChangeState.laneChangeStarting, \ LaneChangeState.laneChangeFinishing]: - events.append(create_event('laneChange', [ET.WARNING])) + self.events.add(EventName.laneChange) if self.can_rcv_error: - events.append(create_event('canError', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) + self.events.add(EventName.canError) if self.mismatch_counter >= 200: - events.append(create_event('controlsMismatch', [ET.IMMEDIATE_DISABLE])) + self.events.add(EventName.controlsMismatch) if not self.sm.alive['plan'] and self.sm.alive['pathPlan']: # only plan not being received: radar not communicating - events.append(create_event('radarCommIssue', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + self.events.add(EventName.radarCommIssue) elif not self.sm.all_alive_and_valid(): - events.append(create_event('commIssue', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + self.events.add(EventName.commIssue) if not self.sm['pathPlan'].mpcSolutionValid: - events.append(create_event('plannerError', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) + self.events.add(EventName.plannerError) if not self.sm['pathPlan'].sensorValid and os.getenv("NOSENSOR") is None: - events.append(create_event('sensorDataInvalid', [ET.NO_ENTRY, ET.PERMANENT])) + self.events.add(EventName.sensorDataInvalid) if not self.sm['pathPlan'].paramsValid: - events.append(create_event('vehicleModelInvalid', [ET.WARNING])) + self.events.add(EventName.vehicleModelInvalid) if not self.sm['pathPlan'].posenetValid: - events.append(create_event('posenetInvalid', [ET.NO_ENTRY, ET.WARNING])) + self.events.add(EventName.posenetInvalid) if not self.sm['plan'].radarValid: - events.append(create_event('radarFault', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + self.events.add(EventName.radarFault) if self.sm['plan'].radarCanError: - events.append(create_event('radarCanError', [ET.NO_ENTRY, ET.SOFT_DISABLE])) + self.events.add(EventName.radarCanError) if not CS.canValid: - events.append(create_event('canError', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) + self.events.add(EventName.canError) if log.HealthData.FaultType.relayMalfunction in self.sm['health'].faults: - events.append(create_event('relayMalfunction', [ET.NO_ENTRY, ET.PERMANENT, ET.IMMEDIATE_DISABLE])) + self.events.add(EventName.relayMalfunction) + if self.sm['plan'].fcw: + self.events.add(EventName.fcw) # Only allow engagement with brake pressed when stopped behind another stopped car if CS.brakePressed and self.sm['plan'].vTargetFuture >= STARTING_TARGET_SPEED \ and not self.CP.radarOffCan and CS.vEgo < 0.3: - events.append(create_event('noTarget', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) - - - # TODO: clean up this alert creation in alerts refactor - - if self.active: - for e in get_events(events, [ET.WARNING]): - # TODO: handle non static text in a cleaner way, like a callback - extra_text = "" - if e == "belowSteerSpeed": - if self.is_metric: - extra_text = str(int(round(self.CP.minSteerSpeed * CV.MS_TO_KPH))) + " kph" - else: - extra_text = str(int(round(self.CP.minSteerSpeed * CV.MS_TO_MPH))) + " mph" - self.AM.add(self.sm.frame, e, self.enabled, extra_text_2=extra_text) - - for e in get_events(events, [ET.PERMANENT]): - # TODO: handle non static text in a cleaner way, like a callback - extra_text_1, extra_text_2 = "", "" - if e == "calibrationIncomplete": - extra_text_1 = str(self.sm['liveCalibration'].calPerc) + "%" - if self.is_metric: - extra_text_2 = str(int(round(Filter.MIN_SPEED * CV.MS_TO_KPH))) + " kph" - else: - extra_text_2 = str(int(round(Filter.MIN_SPEED * CV.MS_TO_MPH))) + " mph" - self.AM.add(self.sm.frame, str(e) + "Permanent", self.enabled, \ - extra_text_1=extra_text_1, extra_text_2=extra_text_2) - - return events + self.events.add(EventName.noTarget) def data_sample(self): @@ -289,7 +251,7 @@ class Controls: return CS - def state_transition(self, CS, events): + def state_transition(self, CS): """Compute conditional state transitions and execute actions on state transitions""" self.v_cruise_kph_last = self.v_cruise_kph @@ -304,70 +266,68 @@ class Controls: # entrance in SOFT_DISABLING state self.soft_disable_timer = max(0, self.soft_disable_timer - 1) - alert_types = [] + self.current_alert_types = [ET.PERMANENT] # ENABLED, PRE ENABLING, SOFT DISABLING if self.state != State.disabled: # user and immediate disable always have priority in a non-disabled state - if get_events(events, [ET.USER_DISABLE]): + if self.events.any(ET.USER_DISABLE): self.state = State.disabled - self.AM.add(self.sm.frame, "disable", self.enabled) + self.current_alert_types.append(ET.USER_DISABLE) - elif get_events(events, [ET.IMMEDIATE_DISABLE]): + elif self.events.any(ET.IMMEDIATE_DISABLE): self.state = State.disabled - alert_types = [ET.IMMEDIATE_DISABLE] + self.current_alert_types.append(ET.IMMEDIATE_DISABLE) else: # ENABLED if self.state == State.enabled: - if get_events(events, [ET.SOFT_DISABLE]): + if self.events.any(ET.SOFT_DISABLE): self.state = State.softDisabling self.soft_disable_timer = 300 # 3s - alert_types = [ET.SOFT_DISABLE] + self.current_alert_types.append(ET.SOFT_DISABLE) # SOFT DISABLING elif self.state == State.softDisabling: - if not get_events(events, [ET.SOFT_DISABLE]): + if not self.events.any(ET.SOFT_DISABLE): # no more soft disabling condition, so go back to ENABLED self.state = State.enabled - elif get_events(events, [ET.SOFT_DISABLE]) and self.soft_disable_timer > 0: - alert_types = [ET.SOFT_DISABLE] + elif self.events.any(ET.SOFT_DISABLE) and self.soft_disable_timer > 0: + self.current_alert_types.append(ET.SOFT_DISABLE) elif self.soft_disable_timer <= 0: self.state = State.disabled # PRE ENABLING elif self.state == State.preEnabled: - if not get_events(events, [ET.PRE_ENABLE]): + if not self.events.any(ET.PRE_ENABLE): self.state = State.enabled # DISABLED elif self.state == State.disabled: - if get_events(events, [ET.ENABLE]): - if get_events(events, [ET.NO_ENTRY]): - for e in get_events(events, [ET.NO_ENTRY]): - self.AM.add(self.sm.frame, str(e) + "NoEntry", self.enabled) + if self.events.any(ET.ENABLE): + if self.events.any(ET.NO_ENTRY): + self.current_alert_types.append(ET.NO_ENTRY) else: - if get_events(events, [ET.PRE_ENABLE]): + if self.events.any(ET.PRE_ENABLE): self.state = State.preEnabled else: self.state = State.enabled - self.AM.add(self.sm.frame, "enable", self.enabled) + self.current_alert_types.append(ET.ENABLE) self.v_cruise_kph = initialize_v_cruise(CS.vEgo, CS.buttonEvents, self.v_cruise_kph_last) - for e in get_events(events, alert_types): - self.AM.add(self.sm.frame, e, self.enabled) - # Check if actuators are enabled self.active = self.state == State.enabled or self.state == State.softDisabling + if self.active: + self.current_alert_types.append(ET.WARNING) # Check if openpilot is engaged self.enabled = self.active or self.state == State.preEnabled - def state_control(self, CS, events): + def state_control(self, CS): """Given the state, this function returns an actuators packet""" plan = self.sm['plan'] @@ -378,14 +338,6 @@ class Controls: if CS.leftBlinker or CS.rightBlinker: self.last_blinker_frame = self.sm.frame - if plan.fcw: - # send FCW alert if triggered by planner - self.AM.add(self.sm.frame, "fcw", self.enabled) - - elif CS.stockFcw: - # send a silent alert when stock fcw triggers, since the car is already beeping - self.AM.add(self.sm.frame, "fcwStock", self.enabled) - # State specific actions if not self.active: @@ -419,12 +371,12 @@ class Controls: right_deviation = actuators.steer < 0 and path_plan.dPoly[3] < -0.1 if left_deviation or right_deviation: - self.AM.add(self.sm.frame, "steerSaturated", self.enabled) + self.events.add(EventName.steerSaturated) return actuators, v_acc_sol, a_acc_sol, lac_log - def publish_logs(self, CS, events, start_time, actuators, v_acc, a_acc, lac_log): + def publish_logs(self, CS, start_time, actuators, v_acc, a_acc, lac_log): """Send actuators and hud commands to the car, send controlsstate and MPC logging""" CC = car.CarControl.new_message() @@ -466,9 +418,10 @@ class Controls: CC.hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close) if CC.hudControl.rightLaneDepart or CC.hudControl.leftLaneDepart: - self.AM.add(self.sm.frame, 'ldwPermanent', False) - events.append(create_event('ldw', [ET.PERMANENT])) + self.events.add(EventName.ldw) + alerts = self.events.create_alerts(self.current_alert_types, [self.CP, self.sm, self.is_metric]) + self.AM.add_many(self.sm.frame, alerts, self.enabled) self.AM.process_alerts(self.sm.frame) CC.hudControl.visualAlert = self.AM.visual_alert @@ -505,7 +458,7 @@ class Controls: controlsState.curvature = self.VM.calc_curvature(steer_angle_rad, CS.vEgo) controlsState.steerOverride = CS.steeringPressed controlsState.state = self.state - controlsState.engageable = not bool(get_events(events, [ET.NO_ENTRY])) + controlsState.engageable = not self.events.any(ET.NO_ENTRY) controlsState.longControlState = self.LoC.long_control_state controlsState.vPid = float(self.LoC.v_pid) controlsState.vCruise = float(self.v_cruise_kph) @@ -534,19 +487,19 @@ class Controls: self.pm.send('controlsState', dat) # carState + car_events = self.events.to_msg() cs_send = messaging.new_message('carState') cs_send.valid = CS.canValid cs_send.carState = CS - cs_send.carState.events = events + cs_send.carState.events = car_events self.pm.send('carState', cs_send) # carEvents - logged every second or on change - events_bytes = events_to_bytes(events) - if (self.sm.frame % int(1. / DT_CTRL) == 0) or (events_bytes != self.events_prev): - ce_send = messaging.new_message('carEvents', len(events)) - ce_send.carEvents = events + if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev): + ce_send = messaging.new_message('carEvents', len(self.events)) + ce_send.carEvents = car_events self.pm.send('carEvents', ce_send) - self.events_prev = events_bytes + self.events_prev = self.events.names.copy() # carParams - logged every 50 seconds (> 1 per segment) if (self.sm.frame % int(50. / DT_CTRL) == 0): @@ -571,20 +524,20 @@ class Controls: CS = self.data_sample() self.prof.checkpoint("Sample") - events = self.create_events(CS) + self.update_events(CS) if not self.read_only: # Update control state - self.state_transition(CS, events) + self.state_transition(CS) self.prof.checkpoint("State transition") # Compute actuators (runs PID loops and lateral MPC) - actuators, v_acc, a_acc, lac_log = self.state_control(CS, events) + actuators, v_acc, a_acc, lac_log = self.state_control(CS) self.prof.checkpoint("State Control") # Publish data - self.publish_logs(CS, events, start_time, actuators, v_acc, a_acc, lac_log) + self.publish_logs(CS, start_time, actuators, v_acc, a_acc, lac_log) self.prof.checkpoint("Sent") def controlsd_thread(self): diff --git a/selfdrive/controls/dmonitoringd.py b/selfdrive/controls/dmonitoringd.py index c9c3704422..06cf556005 100755 --- a/selfdrive/controls/dmonitoringd.py +++ b/selfdrive/controls/dmonitoringd.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 import gc +from cereal import car from common.realtime import set_realtime_priority from common.params import Params import cereal.messaging as messaging -from selfdrive.controls.lib.drive_helpers import create_event, EventTypes as ET +from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, MAX_TERMINAL_DURATION from selfdrive.locationd.calibration_helpers import Calibration @@ -57,7 +58,7 @@ def dmonitoringd_thread(sm=None, pm=None): v_cruise != v_cruise_last or \ sm['carState'].steeringPressed if driver_engaged: - _ = driver_status.update([], True, sm['carState'].cruiseState.enabled, sm['carState'].standstill) + driver_status.update(Events(), True, sm['carState'].cruiseState.enabled, sm['carState'].standstill) v_cruise_last = v_cruise # Get model meta @@ -66,18 +67,18 @@ def dmonitoringd_thread(sm=None, pm=None): # Get data from dmonitoringmodeld if sm.updated['driverState']: - events = [] + events = Events() driver_status.get_pose(sm['driverState'], cal_rpy, sm['carState'].vEgo, sm['carState'].cruiseState.enabled) # Block any engage after certain distrations if driver_status.terminal_alert_cnt >= MAX_TERMINAL_ALERTS or driver_status.terminal_time >= MAX_TERMINAL_DURATION: - events.append(create_event("tooDistracted", [ET.NO_ENTRY])) + events.add(car.CarEvent.EventName.tooDistracted) # Update events from driver state - events = driver_status.update(events, driver_engaged, sm['carState'].cruiseState.enabled, sm['carState'].standstill) + driver_status.update(events, driver_engaged, sm['carState'].cruiseState.enabled, sm['carState'].standstill) # dMonitoringState packet dat = messaging.new_message('dMonitoringState') dat.dMonitoringState = { - "events": events, + "events": events.to_msg(), "faceDetected": driver_status.face_detected, "isDistracted": driver_status.driver_distracted, "awarenessStatus": driver_status.awareness, @@ -100,4 +101,4 @@ def main(sm=None, pm=None): dmonitoringd_thread(sm, pm) if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/selfdrive/controls/lib/alertmanager.py b/selfdrive/controls/lib/alertmanager.py index b6aa97b6fd..42daff420f 100644 --- a/selfdrive/controls/lib/alertmanager.py +++ b/selfdrive/controls/lib/alertmanager.py @@ -1,7 +1,6 @@ from cereal import car, log from common.realtime import DT_CTRL from selfdrive.swaglog import cloudlog -from selfdrive.controls.lib.alerts import ALERTS import copy @@ -14,21 +13,21 @@ class AlertManager(): def __init__(self): self.activealerts = [] - self.alerts = {alert.alert_type: alert for alert in ALERTS} - def alertPresent(self): + def alert_present(self): return len(self.activealerts) > 0 - def add(self, frame, alert_type, enabled=True, extra_text_1='', extra_text_2=''): - alert_type = str(alert_type) - added_alert = copy.copy(self.alerts[alert_type]) - added_alert.alert_text_1 += extra_text_1 - added_alert.alert_text_2 += extra_text_2 + def add_many(self, frame, alerts, enabled=True): + for a in alerts: + self.add(frame, a, enabled=enabled) + + def add(self, frame, alert, enabled=True): + added_alert = copy.copy(alert) added_alert.start_time = frame * DT_CTRL # if new alert is higher priority, log it - if not self.alertPresent() or added_alert.alert_priority > self.activealerts[0].alert_priority: - cloudlog.event('alert_add', alert_type=alert_type, enabled=enabled) + if not self.alert_present() or added_alert.alert_priority > self.activealerts[0].alert_priority: + cloudlog.event('alert_add', alert_type=added_alert.alert_type, enabled=enabled) self.activealerts.append(added_alert) @@ -42,7 +41,7 @@ class AlertManager(): self.activealerts = [a for a in self.activealerts if a.start_time + max(a.duration_sound, a.duration_hud_alert, a.duration_text) > cur_time] - current_alert = self.activealerts[0] if self.alertPresent() else None + current_alert = self.activealerts[0] if self.alert_present() else None # start with assuming no alerts self.alert_type = "" diff --git a/selfdrive/controls/lib/alerts.py b/selfdrive/controls/lib/alerts.py deleted file mode 100644 index 875fbd83f2..0000000000 --- a/selfdrive/controls/lib/alerts.py +++ /dev/null @@ -1,825 +0,0 @@ -from cereal import car, log - -# Priority -class Priority: - LOWEST = 0 - LOWER = 1 - LOW = 2 - MID = 3 - HIGH = 4 - HIGHEST = 5 - -AlertSize = log.ControlsState.AlertSize -AlertStatus = log.ControlsState.AlertStatus -AudibleAlert = car.CarControl.HUDControl.AudibleAlert -VisualAlert = car.CarControl.HUDControl.VisualAlert - -class Alert(): - def __init__(self, - alert_type, - alert_text_1, - alert_text_2, - alert_status, - alert_size, - alert_priority, - visual_alert, - audible_alert, - duration_sound, - duration_hud_alert, - duration_text, - alert_rate=0.): - - self.alert_type = alert_type - self.alert_text_1 = alert_text_1 - self.alert_text_2 = alert_text_2 - self.alert_status = alert_status - self.alert_size = alert_size - self.alert_priority = alert_priority - self.visual_alert = visual_alert - self.audible_alert = audible_alert - - self.duration_sound = duration_sound - self.duration_hud_alert = duration_hud_alert - self.duration_text = duration_text - - self.start_time = 0. - self.alert_rate = alert_rate - - # typecheck that enums are valid on startup - tst = car.CarControl.new_message() - tst.hudControl.visualAlert = self.visual_alert - - def __str__(self): - return self.alert_text_1 + "/" + self.alert_text_2 + " " + str(self.alert_priority) + " " + str( - self.visual_alert) + " " + str(self.audible_alert) - - def __gt__(self, alert2): - return self.alert_priority > alert2.alert_priority - - -ALERTS = [ - # Miscellaneous alerts - Alert( - "enable", - "", - "", - AlertStatus.normal, AlertSize.none, - Priority.MID, VisualAlert.none, AudibleAlert.chimeEngage, .2, 0., 0.), - - Alert( - "disable", - "", - "", - AlertStatus.normal, AlertSize.none, - Priority.MID, VisualAlert.none, AudibleAlert.chimeDisengage, .2, 0., 0.), - - Alert( - "fcw", - "BRAKE!", - "Risk of Collision", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.chimeWarningRepeat, 1., 2., 2.), - - Alert( - "fcwStock", - "BRAKE!", - "Risk of Collision", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 1., 2., 2.), # no EON chime for stock FCW - - Alert( - "steerSaturated", - "TAKE CONTROL", - "Turn Exceeds Steering Limit", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1., 2., 3.), - - Alert( - "steerTempUnavailable", - "TAKE CONTROL", - "Steering Temporarily Unavailable", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning1, .4, 2., 3.), - - Alert( - "steerTempUnavailableMute", - "TAKE CONTROL", - "Steering Temporarily Unavailable", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .2, .2, .2), - - Alert( - "preDriverDistracted", - "KEEP EYES ON ROAD: Driver Distracted", - "", - AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), - - Alert( - "promptDriverDistracted", - "KEEP EYES ON ROAD", - "Driver Appears Distracted", - AlertStatus.userPrompt, AlertSize.mid, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2Repeat, .1, .1, .1), - - Alert( - "driverDistracted", - "DISENGAGE IMMEDIATELY", - "Driver Was Distracted", - AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), - - Alert( - "preDriverUnresponsive", - "TOUCH STEERING WHEEL: No Face Detected", - "", - AlertStatus.normal, AlertSize.small, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), - - Alert( - "promptDriverUnresponsive", - "TOUCH STEERING WHEEL", - "Driver Is Unresponsive", - AlertStatus.userPrompt, AlertSize.mid, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2Repeat, .1, .1, .1), - - Alert( - "driverUnresponsive", - "DISENGAGE IMMEDIATELY", - "Driver Was Unresponsive", - AlertStatus.critical, AlertSize.full, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), - - Alert( - "driverMonitorLowAcc", - "CHECK DRIVER FACE VISIBILITY", - "Driver Monitor Model Output Uncertain", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .4, 0., 1.), - - Alert( - "geofence", - "DISENGAGEMENT REQUIRED", - "Not in Geofenced Area", - AlertStatus.userPrompt, AlertSize.mid, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), - - Alert( - "startup", - "Be ready to take over at any time", - "Always keep hands on wheel and eyes on road", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), - - Alert( - "startupMaster", - "WARNING: This branch is not tested", - "Always keep hands on wheel and eyes on road", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), - - Alert( - "startupNoControl", - "Dashcam mode", - "Always keep hands on wheel and eyes on road", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), - - Alert( - "startupNoCar", - "Dashcam mode for unsupported car", - "Always keep hands on wheel and eyes on road", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), - - Alert( - "ethicalDilemma", - "TAKE CONTROL IMMEDIATELY", - "Ethical Dilemma Detected", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 1., 3., 3.), - - Alert( - "steerTempUnavailableNoEntry", - "openpilot Unavailable", - "Steering Temporarily Unavailable", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), - - Alert( - "manualRestart", - "TAKE CONTROL", - "Resume Driving Manually", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "resumeRequired", - "STOPPED", - "Press Resume to Move", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "belowSteerSpeed", - "TAKE CONTROL", - "Steer Unavailable Below ", - AlertStatus.userPrompt, AlertSize.mid, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.none, 0., 0.4, .3), - - Alert( - "debugAlert", - "DEBUG ALERT", - "", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.none, .1, .1, .1), - Alert( - "preLaneChangeLeft", - "Steer Left to Start Lane Change", - "Monitor Other Vehicles", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), - - Alert( - "preLaneChangeRight", - "Steer Right to Start Lane Change", - "Monitor Other Vehicles", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), - - Alert( - "laneChange", - "Changing Lane", - "Monitor Other Vehicles", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1), - - Alert( - "posenetInvalid", - "TAKE CONTROL", - "Vision Model Output Uncertain", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning1, .4, 2., 3.), - - # Non-entry only alerts - Alert( - "wrongCarModeNoEntry", - "openpilot Unavailable", - "Main Switch Off", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), - - Alert( - "dataNeededNoEntry", - "openpilot Unavailable", - "Calibration Needs Data. Upload Drive, Try Again", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), - - Alert( - "outOfSpaceNoEntry", - "openpilot Unavailable", - "Out of Storage Space", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 0., 3.), - - Alert( - "pedalPressedNoEntry", - "openpilot Unavailable", - "Pedal Pressed During Attempt", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, "brakePressed", AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "speedTooLowNoEntry", - "openpilot Unavailable", - "Speed Too Low", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "brakeHoldNoEntry", - "openpilot Unavailable", - "Brake Hold Active", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "parkBrakeNoEntry", - "openpilot Unavailable", - "Park Brake Engaged", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "lowSpeedLockoutNoEntry", - "openpilot Unavailable", - "Cruise Fault: Restart the Car", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "lowBatteryNoEntry", - "openpilot Unavailable", - "Low Battery", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "sensorDataInvalidNoEntry", - "openpilot Unavailable", - "No Data from Device Sensors", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "soundsUnavailableNoEntry", - "openpilot Unavailable", - "Speaker not found", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "tooDistractedNoEntry", - "openpilot Unavailable", - "Distraction Level Too High", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - # Cancellation alerts causing soft disabling - Alert( - "overheat", - "TAKE CONTROL IMMEDIATELY", - "System Overheated", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "wrongGear", - "TAKE CONTROL IMMEDIATELY", - "Gear not D", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "calibrationInvalid", - "TAKE CONTROL IMMEDIATELY", - "Calibration Invalid: Reposition Device and Recalibrate", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "calibrationIncomplete", - "TAKE CONTROL IMMEDIATELY", - "Calibration in Progress", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "doorOpen", - "TAKE CONTROL IMMEDIATELY", - "Door Open", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "seatbeltNotLatched", - "TAKE CONTROL IMMEDIATELY", - "Seatbelt Unlatched", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "espDisabled", - "TAKE CONTROL IMMEDIATELY", - "ESP Off", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "lowBattery", - "TAKE CONTROL IMMEDIATELY", - "Low Battery", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "commIssue", - "TAKE CONTROL IMMEDIATELY", - "Communication Issue between Processes", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "radarCommIssue", - "TAKE CONTROL IMMEDIATELY", - "Radar Communication Issue", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "radarCanError", - "TAKE CONTROL IMMEDIATELY", - "Radar Error: Restart the Car", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - Alert( - "radarFault", - "TAKE CONTROL IMMEDIATELY", - "Radar Error: Restart the Car", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - - Alert( - "lowMemory", - "TAKE CONTROL IMMEDIATELY", - "Low Memory: Reboot Your Device", - AlertStatus.critical, AlertSize.full, - Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, 2., 2.), - - # Cancellation alerts causing immediate disabling - Alert( - "controlsFailed", - "TAKE CONTROL IMMEDIATELY", - "Controls Failed", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "controlsMismatch", - "TAKE CONTROL IMMEDIATELY", - "Controls Mismatch", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "canError", - "TAKE CONTROL IMMEDIATELY", - "CAN Error: Check Connections", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "steerUnavailable", - "TAKE CONTROL IMMEDIATELY", - "LKAS Fault: Restart the Car", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "brakeUnavailable", - "TAKE CONTROL IMMEDIATELY", - "Cruise Fault: Restart the Car", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "gasUnavailable", - "TAKE CONTROL IMMEDIATELY", - "Gas Fault: Restart the Car", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "reverseGear", - "TAKE CONTROL IMMEDIATELY", - "Reverse Gear", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "cruiseDisabled", - "TAKE CONTROL IMMEDIATELY", - "Cruise Is Off", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "plannerError", - "TAKE CONTROL IMMEDIATELY", - "Planner Solution Error", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "relayMalfunction", - "TAKE CONTROL IMMEDIATELY", - "Harness Malfunction", - AlertStatus.critical, AlertSize.full, - Priority.HIGHEST, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), - - Alert( - "speedTooHigh", - "Speed Too High", - "Slow down to resume operation", - AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarning2Repeat, 2.2, 3., 4.), - - - # not loud cancellations (user is in control) - Alert( - "noTarget", - "openpilot Canceled", - "No close lead car", - AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), - - Alert( - "speedTooLow", - "openpilot Canceled", - "Speed too low", - AlertStatus.normal, AlertSize.mid, - Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), - - - # Cancellation alerts causing non-entry - Alert( - "overheatNoEntry", - "openpilot Unavailable", - "System overheated", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "wrongGearNoEntry", - "openpilot Unavailable", - "Gear not D", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "calibrationInvalidNoEntry", - "openpilot Unavailable", - "Calibration Invalid: Reposition Device & Recalibrate", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "calibrationIncompleteNoEntry", - "openpilot Unavailable", - "Calibration in Progress", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "doorOpenNoEntry", - "openpilot Unavailable", - "Door open", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "seatbeltNotLatchedNoEntry", - "openpilot Unavailable", - "Seatbelt unlatched", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "espDisabledNoEntry", - "openpilot Unavailable", - "ESP Off", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "geofenceNoEntry", - "openpilot Unavailable", - "Not in Geofenced Area", - AlertStatus.normal, AlertSize.mid, - Priority.MID, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "radarCanErrorNoEntry", - "openpilot Unavailable", - "Radar Error: Restart the Car", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "radarFaultNoEntry", - "openpilot Unavailable", - "Radar Error: Restart the Car", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "posenetInvalidNoEntry", - "openpilot Unavailable", - "Vision Model Output Uncertain", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "controlsFailedNoEntry", - "openpilot Unavailable", - "Controls Failed", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "canErrorNoEntry", - "openpilot Unavailable", - "CAN Error: Check Connections", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "steerUnavailableNoEntry", - "openpilot Unavailable", - "LKAS Fault: Restart the Car", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "brakeUnavailableNoEntry", - "openpilot Unavailable", - "Cruise Fault: Restart the Car", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "gasUnavailableNoEntry", - "openpilot Unavailable", - "Gas Error: Restart the Car", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "reverseGearNoEntry", - "openpilot Unavailable", - "Reverse Gear", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "cruiseDisabledNoEntry", - "openpilot Unavailable", - "Cruise is Off", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "noTargetNoEntry", - "openpilot Unavailable", - "No Close Lead Car", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "plannerErrorNoEntry", - "openpilot Unavailable", - "Planner Solution Error", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "commIssueNoEntry", - "openpilot Unavailable", - "Communication Issue between Processes", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), - - Alert( - "radarCommIssueNoEntry", - "openpilot Unavailable", - "Radar Communication Issue", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), - - Alert( - "internetConnectivityNeededNoEntry", - "openpilot Unavailable", - "Please Connect to Internet", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), - - Alert( - "lowMemoryNoEntry", - "openpilot Unavailable", - "Low Memory: Reboot Your Device", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), - - Alert( - "speedTooHighNoEntry", - "Speed Too High", - "Slow down to engage", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - Alert( - "relayMalfunctionNoEntry", - "openpilot Unavailable", - "Harness Malfunction", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), - - # permanent alerts - Alert( - "steerUnavailablePermanent", - "LKAS Fault: Restart the car to engage", - "", - AlertStatus.normal, AlertSize.small, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "brakeUnavailablePermanent", - "Cruise Fault: Restart the car to engage", - "", - AlertStatus.normal, AlertSize.small, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "lowSpeedLockoutPermanent", - "Cruise Fault: Restart the car to engage", - "", - AlertStatus.normal, AlertSize.small, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "calibrationIncompletePermanent", - "Calibration in Progress: ", - "Drive Above ", - AlertStatus.normal, AlertSize.mid, - Priority.LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "invalidGiraffeToyotaPermanent", - "Unsupported Giraffe Configuration", - "Visit comma.ai/tg", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "invalidLkasSettingPermanent", - "Stock LKAS is turned on", - "Turn off stock LKAS to engage", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "internetConnectivityNeededPermanent", - "Please connect to Internet", - "An Update Check Is Required to Engage", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "communityFeatureDisallowedPermanent", - "Community Feature Detected", - "Enable Community Features in Developer Settings", - AlertStatus.normal, AlertSize.mid, - Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), # LOW priority to overcome Cruise Error - - Alert( - "sensorDataInvalidPermanent", - "No Data from Device Sensors", - "Reboot your Device", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "soundsUnavailablePermanent", - "Speaker not found", - "Reboot your Device", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "lowMemoryPermanent", - "RAM Critically Low", - "Reboot your Device", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "carUnrecognizedPermanent", - "Dashcam Mode", - "Car Unrecognized", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "relayMalfunctionPermanent", - "Harness Malfunction", - "Please Check Hardware", - AlertStatus.normal, AlertSize.mid, - Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), - - Alert( - "vehicleModelInvalid", - "Vehicle Parameter Identification Failed", - "", - AlertStatus.normal, AlertSize.small, - Priority.LOWEST, VisualAlert.steerRequired, AudibleAlert.none, .0, .0, .1), - - Alert( - "ldwPermanent", - "TAKE CONTROL", - "Lane Departure Detected", - AlertStatus.userPrompt, AlertSize.mid, - Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1., 2., 3.), -] diff --git a/selfdrive/controls/lib/drive_helpers.py b/selfdrive/controls/lib/drive_helpers.py index a3580a8a91..629d072e82 100644 --- a/selfdrive/controls/lib/drive_helpers.py +++ b/selfdrive/controls/lib/drive_helpers.py @@ -1,4 +1,3 @@ -from cereal import car from common.numpy_fast import clip, interp from selfdrive.config import Conversions as CV @@ -23,34 +22,6 @@ class MPC_COST_LONG: JERK = 20.0 -class EventTypes: - ENABLE = 'enable' - PRE_ENABLE = 'preEnable' - NO_ENTRY = 'noEntry' - WARNING = 'warning' - USER_DISABLE = 'userDisable' - SOFT_DISABLE = 'softDisable' - IMMEDIATE_DISABLE = 'immediateDisable' - PERMANENT = 'permanent' - - -def create_event(name, types): - event = car.CarEvent.new_message() - event.name = name - for t in types: - setattr(event, t, True) - return event - - -def get_events(events, types): - out = [] - for e in events: - for t in types: - if getattr(e, t): - out.append(e.name) - return out - - def rate_limit(new_value, last_value, dw_step, up_step): return clip(new_value, last_value + dw_step, last_value + up_step) diff --git a/selfdrive/controls/lib/driver_monitor.py b/selfdrive/controls/lib/driver_monitor.py index 73ef07777e..54785df073 100644 --- a/selfdrive/controls/lib/driver_monitor.py +++ b/selfdrive/controls/lib/driver_monitor.py @@ -1,10 +1,13 @@ from common.numpy_fast import interp from math import atan2, sqrt from common.realtime import DT_DMON -from selfdrive.controls.lib.drive_helpers import create_event, EventTypes as ET from common.filter_simple import FirstOrderFilter from common.stat_live import RunningStatFilter +from cereal import car + +EventName = car.CarEvent.EventName + # ****************************************************************************************** # NOTE: To fork maintainers. # Disabling or nerfing safety features may get you and your users banned from our servers. @@ -219,13 +222,13 @@ class DriverStatus(): self.awareness = 1. self.awareness_active = 1. self.awareness_passive = 1. - return events + return driver_attentive = self.driver_distraction_filter.x < 0.37 awareness_prev = self.awareness if self.face_detected and self.hi_stds * DT_DMON > _HI_STD_TIMEOUT: - events.append(create_event('driverMonitorLowAcc', [ET.WARNING])) + events.add(EventName.driverMonitorLowAcc) if (driver_attentive and self.face_detected and self.pose.low_std and self.awareness > 0): # only restore awareness when paying attention and alert is not red @@ -234,7 +237,7 @@ class DriverStatus(): self.awareness_passive = min(self.awareness_passive + self.step_change, 1.) # don't display alert banner when awareness is recovering and has cleared orange if self.awareness > self.threshold_prompt: - return events + return # should always be counting if distracted unless at standstill and reaching orange if (not (self.face_detected and self.hi_stds * DT_DMON <= _HI_STD_FALLBACK_TIME) or (self.driver_distraction_filter.x > 0.63 and self.driver_distracted and self.face_detected)) and \ @@ -244,18 +247,16 @@ class DriverStatus(): alert = None if self.awareness <= 0.: # terminal red alert: disengagement required - alert = 'driverDistracted' if self.active_monitoring_mode else 'driverUnresponsive' + alert = EventName.driverDistracted if self.active_monitoring_mode else EventName.driverUnresponsive self.terminal_time += 1 if awareness_prev > 0.: self.terminal_alert_cnt += 1 elif self.awareness <= self.threshold_prompt: # prompt orange alert - alert = 'promptDriverDistracted' if self.active_monitoring_mode else 'promptDriverUnresponsive' + alert = EventName.promptDriverDistracted if self.active_monitoring_mode else EventName.promptDriverUnresponsive elif self.awareness <= self.threshold_pre: # pre green alert - alert = 'preDriverDistracted' if self.active_monitoring_mode else 'preDriverUnresponsive' + alert = EventName.preDriverDistracted if self.active_monitoring_mode else EventName.preDriverUnresponsive if alert is not None: - events.append(create_event(alert, [ET.WARNING])) - - return events + events.add(alert) diff --git a/selfdrive/controls/lib/events.py b/selfdrive/controls/lib/events.py new file mode 100644 index 0000000000..081ef0db81 --- /dev/null +++ b/selfdrive/controls/lib/events.py @@ -0,0 +1,694 @@ +from cereal import log, car + +from selfdrive.config import Conversions as CV + +from selfdrive.locationd.calibration_helpers import Filter + +AlertSize = log.ControlsState.AlertSize +AlertStatus = log.ControlsState.AlertStatus +VisualAlert = car.CarControl.HUDControl.VisualAlert +AudibleAlert = car.CarControl.HUDControl.AudibleAlert +EventName = car.CarEvent.EventName + +# Alert priorities +class Priority: + LOWEST = 0 + LOWER = 1 + LOW = 2 + MID = 3 + HIGH = 4 + HIGHEST = 5 + +# Event types +class ET: + ENABLE = 'enable' + PRE_ENABLE = 'preEnable' + NO_ENTRY = 'noEntry' + WARNING = 'warning' + USER_DISABLE = 'userDisable' + SOFT_DISABLE = 'softDisable' + IMMEDIATE_DISABLE = 'immediateDisable' + PERMANENT = 'permanent' + +# get event name from enum +EVENT_NAME = {v: k for k, v in EventName.schema.enumerants.items()} + +class Events: + def __init__(self): + self.events = [] + self.static_events = [] + + @property + def names(self): + return self.events + + def __len__(self): + return len(self.events) + + def add(self, event_name, static=False): + if static: + self.static_events.append(event_name) + self.events.append(event_name) + + def clear(self): + self.events = self.static_events.copy() + + def any(self, event_type): + for e in self.events: + if event_type in EVENTS.get(e, {}).keys(): + return True + return False + + def create_alerts(self, event_types, callback_args=[]): + ret = [] + for e in self.events: + types = EVENTS[e].keys() + for et in event_types: + if et in types: + alert = EVENTS[e][et] + if not isinstance(alert, Alert): + alert = alert(*callback_args) + alert.alert_type = EVENT_NAME[e] + ret.append(alert) + return ret + + def add_from_msg(self, events): + for e in events: + self.events.append(e.name.raw) + + def to_msg(self): + ret = [] + for event_name in self.events: + event = car.CarEvent.new_message() + event.name = event_name + for event_type in EVENTS.get(event_name, {}).keys(): + setattr(event, event_type , True) + ret.append(event) + return ret + +class Alert: + def __init__(self, + alert_text_1, + alert_text_2, + alert_status, + alert_size, + alert_priority, + visual_alert, + audible_alert, + duration_sound, + duration_hud_alert, + duration_text, + alert_rate=0.): + + self.alert_type = "" + self.alert_text_1 = alert_text_1 + self.alert_text_2 = alert_text_2 + self.alert_status = alert_status + self.alert_size = alert_size + self.alert_priority = alert_priority + self.visual_alert = visual_alert + self.audible_alert = audible_alert + + self.duration_sound = duration_sound + self.duration_hud_alert = duration_hud_alert + self.duration_text = duration_text + + self.start_time = 0. + self.alert_rate = alert_rate + + # typecheck that enums are valid on startup + tst = car.CarControl.new_message() + tst.hudControl.visualAlert = self.visual_alert + + def __str__(self): + return self.alert_text_1 + "/" + self.alert_text_2 + " " + str(self.alert_priority) + " " + str( + self.visual_alert) + " " + str(self.audible_alert) + + def __gt__(self, alert2): + return self.alert_priority > alert2.alert_priority + +class NoEntryAlert(Alert): + def __init__(self, alert_text_2, audible_alert=AudibleAlert.chimeError, + visual_alert=VisualAlert.none, duration_hud_alert=2.): + super().__init__("openpilot Unavailable", alert_text_2, AlertStatus.normal, + AlertSize.mid, Priority.LOW, visual_alert, + audible_alert, .4, duration_hud_alert, 3.) + + +class SoftDisableAlert(Alert): + def __init__(self, alert_text_2): + super().__init__("TAKE CONTROL IMMEDIATELY", alert_text_2, + AlertStatus.critical, AlertSize.full, + Priority.MID, VisualAlert.steerRequired, + AudibleAlert.chimeWarningRepeat, .1, 2., 2.), + + +class ImmediateDisableAlert(Alert): + def __init__(self, alert_text_2, alert_text_1="TAKE CONTROL IMMEDIATELY"): + super().__init__(alert_text_1, alert_text_2, + AlertStatus.critical, AlertSize.full, + Priority.HIGHEST, VisualAlert.steerRequired, + AudibleAlert.chimeWarningRepeat, 2.2, 3., 4.), + +class EngagementAlert(Alert): + def __init__(self, audible_alert=True): + super().__init__("", "", + AlertStatus.normal, AlertSize.none, + Priority.MID, VisualAlert.none, + audible_alert, .2, 0., 0.), + +def below_steer_speed_alert(CP, sm, metric): + speed = CP.minSteerSpeed * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH) + unit = "kph" if metric else "mph" + return Alert( + "TAKE CONTROL", + "Steer Unavailable Below %d %s" % (speed, unit), + AlertStatus.userPrompt, AlertSize.mid, + Priority.MID, VisualAlert.steerRequired, AudibleAlert.none, 0., 0.4, .3), + +def calibration_incomplete_alert(CP, sm, metric): + speed = int(Filter.MIN_SPEED * (CV.MS_TO_KPH if metric else CV.MS_TO_MPH)) + unit = "kph" if metric else "mph" + return Alert( + "Calibration in Progress: %d" % sm['liveCalibration'].calPerc, + "Drive Above %d %s" % (speed, unit), + AlertStatus.normal, AlertSize.mid, + Priority.LOWEST, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + +EVENTS = { + # ********** events with no alerts ********** + + EventName.gasPressed: {ET.PRE_ENABLE: None}, + + # ********** events only containing alerts displayed in all states ********** + + EventName.debugAlert: { + ET.PERMANENT: Alert( + "DEBUG ALERT", + "", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.none, .1, .1, .1), + }, + + EventName.startup: { + ET.PERMANENT: Alert( + "Be ready to take over at any time", + "Always keep hands on wheel and eyes on road", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), + }, + + EventName.startupMaster: { + ET.PERMANENT: Alert( + "WARNING: This branch is not tested", + "Always keep hands on wheel and eyes on road", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), + }, + + EventName.startupNoControl: { + ET.PERMANENT: Alert( + "Dashcam mode", + "Always keep hands on wheel and eyes on road", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), + }, + + EventName.startupNoCar: { + ET.PERMANENT: Alert( + "Dashcam mode for unsupported car", + "Always keep hands on wheel and eyes on road", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., 15.), + }, + + EventName.invalidGiraffeToyota: { + ET.PERMANENT: Alert( + "Unsupported Giraffe Configuration", + "Visit comma.ai/tg", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + }, + + EventName.invalidLkasSetting: { + ET.PERMANENT: Alert( + "Stock LKAS is turned on", + "Turn off stock LKAS to engage", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + }, + + EventName.communityFeatureDisallowed: { + # LOW priority to overcome Cruise Error + ET.PERMANENT: Alert( + "", + "Community Feature Detected", + "Enable Community Features in Developer Settings", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + }, + + EventName.carUnrecognized: { + ET.PERMANENT: Alert( + "Dashcam Mode", + "Car Unrecognized", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + }, + + EventName.stockAeb: { + ET.PERMANENT: Alert( + "BRAKE!", + "Stock AEB: Risk of Collision", + AlertStatus.critical, AlertSize.full, + Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 1., 2., 2.), + }, + + EventName.stockFcw: { + ET.PERMANENT: Alert( + "BRAKE!", + "Stock FCW: Risk of Collision", + AlertStatus.critical, AlertSize.full, + Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.none, 1., 2., 2.), + }, + + EventName.fcw: { + ET.PERMANENT: Alert( + "BRAKE!", + "Risk of Collision", + AlertStatus.critical, AlertSize.full, + Priority.HIGHEST, VisualAlert.fcw, AudibleAlert.chimeWarningRepeat, 1., 2., 2.), + }, + + EventName.ldw: { + ET.PERMANENT: Alert( + "TAKE CONTROL", + "Lane Departure Detected", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1., 2., 3.), + }, + + # ********** events only containing alerts that display while engaged ********** + + EventName.vehicleModelInvalid: { + ET.WARNING: Alert( + "Vehicle Parameter Identification Failed", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOWEST, VisualAlert.steerRequired, AudibleAlert.none, .0, .0, .1), + }, + + EventName.steerTempUnavailableMute: { + ET.WARNING: Alert( + "TAKE CONTROL", + "Steering Temporarily Unavailable", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.none, .2, .2, .2), + }, + + EventName.preDriverDistracted: { + ET.WARNING: Alert( + "KEEP EYES ON ROAD: Driver Distracted", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), + }, + + EventName.promptDriverDistracted: { + ET.WARNING: Alert( + "KEEP EYES ON ROAD", + "Driver Appears Distracted", + AlertStatus.userPrompt, AlertSize.mid, + Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2Repeat, .1, .1, .1), + }, + + EventName.driverDistracted: { + ET.WARNING: Alert( + "DISEventName.AGE IMMEDIATELY", + "Driver Was Distracted", + AlertStatus.critical, AlertSize.full, + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), + }, + + EventName.preDriverUnresponsive: { + ET.WARNING: Alert( + "TOUCH STEERING WHEEL: No Face Detected", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), + }, + + EventName.promptDriverUnresponsive: { + ET.WARNING: Alert( + "TOUCH STEERING WHEEL", + "Driver Is Unresponsive", + AlertStatus.userPrompt, AlertSize.mid, + Priority.MID, VisualAlert.steerRequired, AudibleAlert.chimeWarning2Repeat, .1, .1, .1), + }, + + EventName.driverUnresponsive: { + ET.WARNING: Alert( + "DISEventName.AGE IMMEDIATELY", + "Driver Was Unresponsive", + AlertStatus.critical, AlertSize.full, + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarningRepeat, .1, .1, .1), + }, + + EventName.driverMonitorLowAcc: { + ET.WARNING: Alert( + "CHECK DRIVER FACE VISIBILITY", + "Driver Monitor Model Output Uncertain", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .4, 0., 1.), + }, + + EventName.manualRestart: { + ET.WARNING: Alert( + "TAKE CONTROL", + "Resume Driving Manually", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + }, + + EventName.resumeRequired: { + ET.WARNING: Alert( + "STOPPED", + "Press Resume to Move", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + }, + + EventName.belowSteerSpeed: { + ET.WARNING: Alert( + "TAKE CONTROL", + "Steer Unavailable Below ", + AlertStatus.userPrompt, AlertSize.mid, + Priority.MID, VisualAlert.steerRequired, AudibleAlert.none, 0., 0.4, .3), + }, + + EventName.preLaneChangeLeft: { + ET.WARNING: Alert( + "Steer Left to Start Lane Change", + "Monitor Other Vehicles", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), + }, + + EventName.preLaneChangeRight: { + ET.WARNING: Alert( + "Steer Right to Start Lane Change", + "Monitor Other Vehicles", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1, alert_rate=0.75), + }, + + EventName.laneChange: { + ET.WARNING: Alert( + "Changing Lane", + "Monitor Other Vehicles", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.none, .0, .1, .1), + }, + + EventName.steerSaturated: { + ET.WARNING: Alert( + "TAKE CONTROL", + "Turn Exceeds Steering Limit", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimePrompt, 1., 2., 3.), + }, + + # ********** events that affect controls state transitions ********** + + EventName.pcmEnable: { + ET.ENABLE: EngagementAlert(AudibleAlert.chimeEngage), + }, + + EventName.buttonEnable: { + ET.ENABLE: EngagementAlert(AudibleAlert.chimeEngage), + }, + + EventName.pcmDisable: { + ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + }, + + EventName.buttonCancel: { + ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + }, + + EventName.brakeHold: { + ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("Brake Hold Active"), + }, + + EventName.parkBrake: { + ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("Park Brake Engaged"), + }, + + EventName.pedalPressed: { + ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("Pedal Pressed During Attempt", + visual_alert=VisualAlert.brakePressed), + }, + + EventName.wrongCarMode: { + ET.USER_DISABLE: EngagementAlert(AudibleAlert.chimeDisengage), + ET.NO_ENTRY: NoEntryAlert("Main Switch Off", + duration_hud_alert=0.), + }, + + EventName.steerTempUnavailable: { + ET.WARNING: Alert( + "TAKE CONTROL", + "Steering Temporarily Unavailable", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning1, .4, 2., 3.), + ET.NO_ENTRY: NoEntryAlert("Steering Temporarily Unavailable", + duration_hud_alert=0.), + }, + + EventName.posenetInvalid: { + ET.WARNING: Alert( + "TAKE CONTROL", + "Vision Model Output Uncertain", + AlertStatus.userPrompt, AlertSize.mid, + Priority.LOW, VisualAlert.steerRequired, AudibleAlert.chimeWarning1, .4, 2., 3.), + ET.NO_ENTRY: NoEntryAlert("Vision Model Output Uncertain"), + }, + + EventName.outOfSpace: { + ET.NO_ENTRY: NoEntryAlert("Out of Storage Space", + duration_hud_alert=0.), + }, + + EventName.sensorDataInvalid: { + ET.PERMANENT: Alert( + "No Data from Device Sensors", + "Reboot your Device", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY: NoEntryAlert("No Data from Device Sensors"), + }, + + EventName.soundsUnavailable: { + ET.PERMANENT: Alert( + "Speaker not found", + "Reboot your Device", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY: NoEntryAlert("Speaker not found"), + }, + + EventName.tooDistracted: { + ET.NO_ENTRY: NoEntryAlert("Distraction Level Too High"), + }, + + EventName.overheat: { + ET.SOFT_DISABLE: SoftDisableAlert("System Overheated"), + ET.NO_ENTRY: NoEntryAlert("System overheated"), + }, + + EventName.wrongGear: { + ET.SOFT_DISABLE: SoftDisableAlert("Gear not D"), + ET.NO_ENTRY: NoEntryAlert("Gear not D"), + }, + + EventName.calibrationInvalid: { + ET.SOFT_DISABLE: SoftDisableAlert("Calibration Invalid: Reposition Device and Recalibrate"), + ET.NO_ENTRY: NoEntryAlert("Calibration Invalid: Reposition Device & Recalibrate"), + }, + + EventName.calibrationIncomplete: { + ET.SOFT_DISABLE: SoftDisableAlert("Calibration in Progress"), + ET.PERMANENT: calibration_incomplete_alert, + ET.NO_ENTRY: NoEntryAlert("Calibration in Progress"), + }, + + EventName.doorOpen: { + ET.SOFT_DISABLE: SoftDisableAlert("Door Open"), + ET.NO_ENTRY: NoEntryAlert("Door open"), + }, + + EventName.seatbeltNotLatched: { + ET.SOFT_DISABLE: SoftDisableAlert("Seatbelt Unlatched"), + ET.NO_ENTRY: NoEntryAlert("Seatbelt unlatched"), + }, + + EventName.espDisabled: { + ET.SOFT_DISABLE: SoftDisableAlert("ESP Off"), + ET.NO_ENTRY: NoEntryAlert("ESP Off"), + }, + + EventName.lowBattery: { + ET.SOFT_DISABLE: SoftDisableAlert("Low Battery"), + ET.NO_ENTRY: NoEntryAlert("Low Battery"), + }, + + EventName.commIssue: { + ET.SOFT_DISABLE: SoftDisableAlert("Communication Issue between Processes"), + ET.NO_ENTRY: NoEntryAlert("Communication Issue between Processes", + audible_alert=AudibleAlert.chimeDisengage), + }, + + EventName.radarCommIssue: { + ET.SOFT_DISABLE: SoftDisableAlert("Radar Communication Issue"), + ET.NO_ENTRY: NoEntryAlert("Radar Communication Issue", + audible_alert=AudibleAlert.chimeDisengage), + }, + + EventName.radarCanError: { + ET.SOFT_DISABLE: SoftDisableAlert("Radar Error: Restart the Car"), + ET.NO_ENTRY: NoEntryAlert("Radar Error: Restart the Car"), + }, + + EventName.radarFault: { + ET.SOFT_DISABLE: SoftDisableAlert("Radar Error: Restart the Car"), + ET.NO_ENTRY : NoEntryAlert("Radar Error: Restart the Car"), + }, + + EventName.lowMemory: { + ET.SOFT_DISABLE: SoftDisableAlert("Low Memory: Reboot Your Device"), + ET.PERMANENT: Alert( + "RAM Critically Low", + "Reboot your Device", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY : NoEntryAlert("Low Memory: Reboot Your Device", + audible_alert=AudibleAlert.chimeDisengage), + }, + + EventName.controlsFailed: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Controls Failed"), + ET.NO_ENTRY: NoEntryAlert("Controls Failed"), + }, + + EventName.controlsMismatch: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Controls Mismatch"), + }, + + EventName.canError: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("CAN Error: Check Connections"), + ET.NO_ENTRY: NoEntryAlert("CAN Error: Check Connections"), + }, + + EventName.steerUnavailable: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("LKAS Fault: Restart the Car"), + ET.PERMANENT: Alert( + "LKAS Fault: Restart the car to engage", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY: NoEntryAlert("LKAS Fault: Restart the Car"), + }, + + EventName.brakeUnavailable: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Fault: Restart the Car"), + ET.PERMANENT: Alert( + "Cruise Fault: Restart the car to engage", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"), + }, + + EventName.gasUnavailable: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Gas Fault: Restart the Car"), + ET.NO_ENTRY: NoEntryAlert("Gas Error: Restart the Car"), + }, + + EventName.reverseGear: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Reverse Gear"), + ET.NO_ENTRY: NoEntryAlert("Reverse Gear"), + }, + + EventName.cruiseDisabled: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Cruise Is Off"), + }, + + EventName.plannerError: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Planner Solution Error"), + ET.NO_ENTRY: NoEntryAlert("Planner Solution Error"), + }, + + EventName.relayMalfunction: { + ET.IMMEDIATE_DISABLE: ImmediateDisableAlert("Harness Malfunction"), + ET.PERMANENT: Alert( + "Harness Malfunction", + "Please Check Hardware", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY: NoEntryAlert("Harness Malfunction"), + }, + + EventName.noTarget: { + ET.IMMEDIATE_DISABLE: Alert( + "openpilot Canceled", + "No close lead car", + AlertStatus.normal, AlertSize.mid, + Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), + ET.NO_ENTRY : NoEntryAlert("No Close Lead Car"), + }, + + EventName.speedTooLow: { + ET.IMMEDIATE_DISABLE: Alert( + "openpilot Canceled", + "Speed too low", + AlertStatus.normal, AlertSize.mid, + Priority.HIGH, VisualAlert.none, AudibleAlert.chimeDisengage, .4, 2., 3.), + ET.NO_ENTRY: NoEntryAlert("Speed Too Low"), + }, + + EventName.speedTooHigh: { + ET.IMMEDIATE_DISABLE: Alert( + "Speed Too High", + "Slow down to resume operation", + AlertStatus.normal, AlertSize.mid, + Priority.HIGH, VisualAlert.steerRequired, AudibleAlert.chimeWarning2Repeat, 2.2, 3., 4.), + ET.NO_ENTRY: Alert( + "Speed Too High", + "Slow down to engage", + AlertStatus.normal, AlertSize.mid, + Priority.LOW, VisualAlert.none, AudibleAlert.chimeError, .4, 2., 3.), + }, + + EventName.internetConnectivityNeeded: { + ET.PERMANENT: Alert( + "Please connect to Internet", + "An Update Check Is Required to Engage", + AlertStatus.normal, AlertSize.mid, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY: NoEntryAlert("Please Connect to Internet", + audible_alert=AudibleAlert.chimeDisengage), + }, + + EventName.lowSpeedLockout: { + ET.PERMANENT: Alert( + "Cruise Fault: Restart the car to engage", + "", + AlertStatus.normal, AlertSize.small, + Priority.LOWER, VisualAlert.none, AudibleAlert.none, 0., 0., .2), + ET.NO_ENTRY: NoEntryAlert("Cruise Fault: Restart the Car"), + }, + +} diff --git a/selfdrive/controls/tests/test_alerts.py b/selfdrive/controls/tests/test_alerts.py deleted file mode 100755 index faea3c06bf..0000000000 --- a/selfdrive/controls/tests/test_alerts.py +++ /dev/null @@ -1,48 +0,0 @@ -#!/usr/bin/env python3 -import os -import unittest -from PIL import Image, ImageDraw, ImageFont - -from cereal import log -from common.basedir import BASEDIR -from selfdrive.controls.lib.alerts import ALERTS - -AlertSize = log.ControlsState.AlertSize - -FONT_PATH = os.path.join(BASEDIR, "selfdrive/assets/fonts") -REGULAR_FONT_PATH = os.path.join(FONT_PATH, "opensans_semibold.ttf") -BOLD_FONT_PATH = os.path.join(FONT_PATH, "opensans_semibold.ttf") -SEMIBOLD_FONT_PATH = os.path.join(FONT_PATH, "opensans_semibold.ttf") - -MAX_TEXT_WIDTH = 1920 - 300 # full screen width is useable, minus sidebar -# TODO: get exact scale factor. found this empirically, works well enough -FONT_SIZE_SCALE = 1.85 # factor to scale from nanovg units to PIL - -class TestAlerts(unittest.TestCase): - - # ensure alert text doesn't exceed allowed width - def test_alert_text_length(self): - draw = ImageDraw.Draw(Image.new('RGB', (0, 0))) - - fonts = { - AlertSize.small: [ImageFont.truetype(SEMIBOLD_FONT_PATH, int(40*FONT_SIZE_SCALE))], - AlertSize.mid: [ImageFont.truetype(BOLD_FONT_PATH, int(48*FONT_SIZE_SCALE)), - ImageFont.truetype(REGULAR_FONT_PATH, int(36*FONT_SIZE_SCALE))], - } - - for alert in ALERTS: - # for full size alerts, both text fields wrap the text, - # so it's unlikely that they would go past the max width - if alert.alert_size in [AlertSize.none, AlertSize.full]: - continue - - for i, txt in enumerate([alert.alert_text_1, alert.alert_text_2]): - if i >= len(fonts[alert.alert_size]): break - - font = fonts[alert.alert_size][i] - w, h = draw.textsize(txt, font) - msg = "type: %s msg: %s" % (alert.alert_type, txt) - self.assertLessEqual(w, MAX_TEXT_WIDTH, msg=msg) - -if __name__ == "__main__": - unittest.main() diff --git a/selfdrive/controls/tests/test_events.py b/selfdrive/controls/tests/test_events.py new file mode 100755 index 0000000000..79662b719e --- /dev/null +++ b/selfdrive/controls/tests/test_events.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +import os +import unittest +from PIL import Image, ImageDraw, ImageFont + +from cereal import log, car +from common.basedir import BASEDIR +from selfdrive.controls.lib.events import Alert, EVENTS + +AlertSize = log.ControlsState.AlertSize + +class TestAlerts(unittest.TestCase): + + def test_events_defined(self): + # Ensure all events in capnp schema are defined in events.py + events = car.CarEvent.EventName.schema.enumerants + + for name, e in events.items(): + if not name.endswith("DEPRECATED"): + fail_msg = "%s @%d not in EVENTS" % (name, e) + self.assertTrue(e in EVENTS.keys(), msg=fail_msg) + + # ensure alert text doesn't exceed allowed width + def test_alert_text_length(self): + font_path = os.path.join(BASEDIR, "selfdrive/assets/fonts") + regular_font_path = os.path.join(font_path, "opensans_semibold.ttf") + bold_font_path = os.path.join(font_path, "opensans_semibold.ttf") + semibold_font_path = os.path.join(font_path, "opensans_semibold.ttf") + + max_text_width = 1920 - 300 # full screen width is useable, minus sidebar + # TODO: get exact scale factor. found this empirically, works well enough + font_scale_factor = 1.85 # factor to scale from nanovg units to PIL + + draw = ImageDraw.Draw(Image.new('RGB', (0, 0))) + + fonts = { + AlertSize.small: [ImageFont.truetype(semibold_font_path, int(40*font_scale_factor))], + AlertSize.mid: [ImageFont.truetype(bold_font_path, int(48*font_scale_factor)), + ImageFont.truetype(regular_font_path, int(36*font_scale_factor))], + } + + alerts = [] + for event_types in EVENTS.values(): + for alert in event_types.values(): + if isinstance(alert, Alert): + alerts.append(alert) + + for alert in alerts: + # for full size alerts, both text fields wrap the text, + # so it's unlikely that they would go past the max width + if alert.alert_size in [AlertSize.none, AlertSize.full]: + continue + + for i, txt in enumerate([alert.alert_text_1, alert.alert_text_2]): + if i >= len(fonts[alert.alert_size]): break + + font = fonts[alert.alert_size][i] + w, h = draw.textsize(txt, font) + msg = "type: %s msg: %s" % (alert.alert_type, txt) + self.assertLessEqual(w, max_text_width, msg=msg) + +if __name__ == "__main__": + unittest.main() diff --git a/selfdrive/controls/tests/test_monitoring.py b/selfdrive/controls/tests/test_monitoring.py index 163766f40c..67e97040cd 100644 --- a/selfdrive/controls/tests/test_monitoring.py +++ b/selfdrive/controls/tests/test_monitoring.py @@ -1,12 +1,16 @@ import unittest import numpy as np +from cereal import car from common.realtime import DT_DMON +from selfdrive.controls.lib.events import Events from selfdrive.controls.lib.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, \ _AWARENESS_TIME, _AWARENESS_PRE_TIME_TILL_TERMINAL, \ _AWARENESS_PROMPT_TIME_TILL_TERMINAL, _DISTRACTED_TIME, \ _DISTRACTED_PRE_TIME_TILL_TERMINAL, _DISTRACTED_PROMPT_TIME_TILL_TERMINAL, \ _POSESTD_THRESHOLD, _HI_STD_TIMEOUT +EventName = car.CarEvent.EventName + _TEST_TIMESPAN = 120 # seconds _DISTRACTED_SECONDS_TO_ORANGE = _DISTRACTED_TIME - _DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1 _DISTRACTED_SECONDS_TO_RED = _DISTRACTED_TIME + 1 @@ -59,13 +63,14 @@ def run_DState_seq(driver_state_msgs, driver_car_interaction, openpilot_status, DS = DriverStatus() events_from_DM = [] for idx in range(len(driver_state_msgs)): + e = Events() DS.get_pose(driver_state_msgs[idx], [0,0,0], 0, openpilot_status[idx]) # cal_rpy and car_speed don't matter here - event_per_state = DS.update([], driver_car_interaction[idx], openpilot_status[idx], car_standstill_status[idx]) - events_from_DM.append(event_per_state) # evaluate events at 10Hz for tests - - assert len(events_from_DM)==len(driver_state_msgs), 'somethings wrong' + # evaluate events at 10Hz for tests + DS.update(e, driver_car_interaction[idx], openpilot_status[idx], car_standstill_status[idx]) + events_from_DM.append(e) + assert len(events_from_DM) == len(driver_state_msgs), 'somethings wrong' return events_from_DM, DS class TestMonitoring(unittest.TestCase): @@ -79,11 +84,11 @@ class TestMonitoring(unittest.TestCase): events_output, d_status = run_DState_seq(always_distracted, always_false, always_true, always_false) self.assertTrue(len(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)])==0) self.assertEqual(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL+\ - ((_DISTRACTED_PRE_TIME_TILL_TERMINAL-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'preDriverDistracted') + ((_DISTRACTED_PRE_TIME_TILL_TERMINAL-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], EventName.preDriverDistracted) self.assertEqual(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL+\ - ((_DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'promptDriverDistracted') + ((_DISTRACTED_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], EventName.promptDriverDistracted) self.assertEqual(events_output[int((_DISTRACTED_TIME+\ - ((_TEST_TIMESPAN-10-_DISTRACTED_TIME)/2))/DT_DMON)][0].name, 'driverDistracted') + ((_TEST_TIMESPAN-10-_DISTRACTED_TIME)/2))/DT_DMON)].names[0], EventName.driverDistracted) self.assertIs(type(d_status.awareness), float) # 2. op engaged, no face detected the whole time, no action @@ -91,11 +96,11 @@ class TestMonitoring(unittest.TestCase): events_output = run_DState_seq(always_no_face, always_false, always_true, always_false)[0] self.assertTrue(len(events_output[int((_AWARENESS_TIME-_AWARENESS_PRE_TIME_TILL_TERMINAL)/2/DT_DMON)])==0) self.assertEqual(events_output[int((_AWARENESS_TIME-_AWARENESS_PRE_TIME_TILL_TERMINAL+\ - ((_AWARENESS_PRE_TIME_TILL_TERMINAL-_AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'preDriverUnresponsive') + ((_AWARENESS_PRE_TIME_TILL_TERMINAL-_AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], EventName.preDriverUnresponsive) self.assertEqual(events_output[int((_AWARENESS_TIME-_AWARENESS_PROMPT_TIME_TILL_TERMINAL+\ - ((_AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)][0].name, 'promptDriverUnresponsive') + ((_AWARENESS_PROMPT_TIME_TILL_TERMINAL)/2))/DT_DMON)].names[0], EventName.promptDriverUnresponsive) self.assertEqual(events_output[int((_AWARENESS_TIME+\ - ((_TEST_TIMESPAN-10-_AWARENESS_TIME)/2))/DT_DMON)][0].name, 'driverUnresponsive') + ((_TEST_TIMESPAN-10-_AWARENESS_TIME)/2))/DT_DMON)].names[0], EventName.driverUnresponsive) # 3. op engaged, down to orange, driver pays attention, back to normal; then down to orange, driver touches wheel # - should have short orange recovery time and no green afterwards; should recover rightaway on wheel touch @@ -107,9 +112,9 @@ class TestMonitoring(unittest.TestCase): [car_interaction_DETECTED] * (int(_TEST_TIMESPAN/DT_DMON)-int(_DISTRACTED_SECONDS_TO_ORANGE*3/DT_DMON)) events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false)[0] self.assertTrue(len(events_output[int(_DISTRACTED_SECONDS_TO_ORANGE*0.5/DT_DMON)])==0) - self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)][0].name, 'promptDriverDistracted') + self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) self.assertTrue(len(events_output[int(_DISTRACTED_SECONDS_TO_ORANGE*1.5/DT_DMON)])==0) - self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)][0].name, 'promptDriverDistracted') + self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE*3-0.1)/DT_DMON)].names[0], EventName.promptDriverDistracted) self.assertTrue(len(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE*3+0.1)/DT_DMON)])==0) # 4. op engaged, down to orange, driver dodges camera, then comes back still distracted, down to red, \ @@ -125,9 +130,9 @@ class TestMonitoring(unittest.TestCase): interaction_vector[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+0.5)/DT_DMON):int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)] = [True] * int(1/DT_DMON) op_vector[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+2.5)/DT_DMON):int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3)/DT_DMON)] = [False] * int(0.5/DT_DMON) events_output = run_DState_seq(ds_vector, interaction_vector, op_vector, always_false)[0] - self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)][0].name, 'promptDriverDistracted') - self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)][0].name, 'driverDistracted') - self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)][0].name, 'driverDistracted') + self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_ORANGE+0.5*_invisible_time)/DT_DMON)].names[0], EventName.promptDriverDistracted) + self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_RED+1.5*_invisible_time)/DT_DMON)].names[0], EventName.driverDistracted) + self.assertEqual(events_output[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+1.5)/DT_DMON)].names[0], EventName.driverDistracted) self.assertTrue(len(events_output[int((_DISTRACTED_SECONDS_TO_RED+2*_invisible_time+3.5)/DT_DMON)])==0) # 5. op engaged, invisible driver, down to orange, driver touches wheel; then down to orange again, driver appears @@ -141,13 +146,13 @@ class TestMonitoring(unittest.TestCase): interaction_vector[int((_INVISIBLE_SECONDS_TO_ORANGE)/DT_DMON):int((_INVISIBLE_SECONDS_TO_ORANGE+1)/DT_DMON)] = [True] * int(1/DT_DMON) events_output = run_DState_seq(ds_vector, interaction_vector, 2*always_true, 2*always_false)[0] self.assertTrue(len(events_output[int(_INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)])==0) - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)])==0) if _visible_time == 1: - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)][0].name, 'preDriverUnresponsive') + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)].names[0], EventName.preDriverUnresponsive) elif _visible_time == 10: - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)])==0) else: pass @@ -164,10 +169,10 @@ class TestMonitoring(unittest.TestCase): op_vector[int((_INVISIBLE_SECONDS_TO_RED+_visible_time+1)/DT_DMON):int((_INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)] = [False] * int(0.5/DT_DMON) events_output = run_DState_seq(ds_vector, interaction_vector, op_vector, always_false)[0] self.assertTrue(len(events_output[int(_INVISIBLE_SECONDS_TO_ORANGE*0.5/DT_DMON)])==0) - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)][0].name, 'promptDriverUnresponsive') - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)][0].name, 'driverUnresponsive') - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)][0].name, 'driverUnresponsive') - self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)][0].name, 'driverUnresponsive') + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE-0.1)/DT_DMON)].names[0], EventName.promptDriverUnresponsive) + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED-0.1)/DT_DMON)].names[0], EventName.driverUnresponsive) + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED+0.5*_visible_time)/DT_DMON)].names[0], EventName.driverUnresponsive) + self.assertEqual(events_output[int((_INVISIBLE_SECONDS_TO_RED+_visible_time+0.5)/DT_DMON)].names[0], EventName.driverUnresponsive) self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_RED+_visible_time+1+0.1)/DT_DMON)])==0) # 7. op not engaged, always distracted driver @@ -183,9 +188,9 @@ class TestMonitoring(unittest.TestCase): standstill_vector = always_true[:] standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((_TEST_TIMESPAN-_redlight_time)/DT_DMON) events_output = run_DState_seq(always_distracted, always_false, always_true, standstill_vector)[0] - self.assertEqual(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL+1)/DT_DMON)][0].name, 'preDriverDistracted') - self.assertEqual(events_output[int((_redlight_time-0.1)/DT_DMON)][0].name, 'preDriverDistracted') - self.assertEqual(events_output[int((_redlight_time+0.5)/DT_DMON)][0].name, 'promptDriverDistracted') + self.assertEqual(events_output[int((_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL+1)/DT_DMON)].names[0], EventName.preDriverDistracted) + self.assertEqual(events_output[int((_redlight_time-0.1)/DT_DMON)].names[0], EventName.preDriverDistracted) + self.assertEqual(events_output[int((_redlight_time+0.5)/DT_DMON)].names[0], EventName.promptDriverDistracted) # 9. op engaged, model is extremely uncertain. driver first attentive, then distracted # - should only pop the green alert about model uncertainty @@ -197,9 +202,9 @@ class TestMonitoring(unittest.TestCase): interaction_vector = always_false[:] events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false)[0] self.assertTrue(len(events_output[int(_UNCERTAIN_SECONDS_TO_GREEN*0.5/DT_DMON)])==0) - self.assertEqual(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN-0.1)/DT_DMON)][0].name, 'driverMonitorLowAcc') + self.assertEqual(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN-0.1)/DT_DMON)].names[0], EventName.driverMonitorLowAcc) self.assertTrue(len(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN+_DISTRACTED_SECONDS_TO_ORANGE-0.5)/DT_DMON)])==0) - self.assertEqual(events_output[int((_TEST_TIMESPAN-5.)/DT_DMON)][0].name, 'driverMonitorLowAcc') + self.assertEqual(events_output[int((_TEST_TIMESPAN-5.)/DT_DMON)].names[0], EventName.driverMonitorLowAcc) # 10. op engaged, model is somehow uncertain and driver is distracted # - should slow down the alert countdown but it still gets there @@ -208,11 +213,11 @@ class TestMonitoring(unittest.TestCase): interaction_vector = always_false[:] events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false)[0] self.assertTrue(len(events_output[int(_UNCERTAIN_SECONDS_TO_GREEN*0.5/DT_DMON)])==0) - self.assertEqual(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN)/DT_DMON)][0].name, 'driverMonitorLowAcc') - self.assertEqual(events_output[int((2.5*(_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL))/DT_DMON)][1].name, 'preDriverDistracted') - self.assertEqual(events_output[int((2.5*(_DISTRACTED_TIME-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL))/DT_DMON)][1].name, 'promptDriverDistracted') - self.assertEqual(events_output[int((_DISTRACTED_TIME+1)/DT_DMON)][1].name, 'promptDriverDistracted') - self.assertEqual(events_output[int((_DISTRACTED_TIME*2.5)/DT_DMON)][1].name, 'promptDriverDistracted') # set_timer blocked + self.assertEqual(events_output[int((_UNCERTAIN_SECONDS_TO_GREEN)/DT_DMON)].names[0], EventName.driverMonitorLowAcc) + self.assertEqual(events_output[int((2.5*(_DISTRACTED_TIME-_DISTRACTED_PRE_TIME_TILL_TERMINAL))/DT_DMON)].names[1], EventName.preDriverDistracted) + self.assertEqual(events_output[int((2.5*(_DISTRACTED_TIME-_DISTRACTED_PROMPT_TIME_TILL_TERMINAL))/DT_DMON)].names[1], EventName.promptDriverDistracted) + self.assertEqual(events_output[int((_DISTRACTED_TIME+1)/DT_DMON)].names[1], EventName.promptDriverDistracted) + self.assertEqual(events_output[int((_DISTRACTED_TIME*2.5)/DT_DMON)].names[1], EventName.promptDriverDistracted) # set_timer blocked if __name__ == "__main__": print('MAX_TERMINAL_ALERTS', MAX_TERMINAL_ALERTS) diff --git a/selfdrive/test/process_replay/ref_commit b/selfdrive/test/process_replay/ref_commit index 9023bfba8e..880f969541 100644 --- a/selfdrive/test/process_replay/ref_commit +++ b/selfdrive/test/process_replay/ref_commit @@ -1 +1 @@ -1050a84363baf1e7910d3f8f9a01e201e6041e70 \ No newline at end of file +76e577b86d113139167275b4a7379f3591abfa02 \ No newline at end of file