Alerts + Events refactor (#1466)

old-commit-hash: d976233f69
This commit is contained in:
Adeeb 2020-05-14 15:21:21 -07:00 committed by GitHub
parent 05a09435b0
commit 48340cc8cb
24 changed files with 988 additions and 1172 deletions

2
cereal

@ -1 +1 @@
Subproject commit 4f68db8f6aa31e87d968da882460e196c6b101a3 Subproject commit 856c9812d552fe0ac640b75074b080f76c9a3cba

View File

@ -202,8 +202,8 @@ selfdrive/controls/radard.py
selfdrive/controls/dmonitoringd.py selfdrive/controls/dmonitoringd.py
selfdrive/controls/lib/__init__.py selfdrive/controls/lib/__init__.py
selfdrive/controls/lib/alertmanager.py selfdrive/controls/lib/alertmanager.py
selfdrive/controls/lib/alerts.py
selfdrive/controls/lib/alerts_offroad.json selfdrive/controls/lib/alerts_offroad.json
selfdrive/controls/lib/events.py
selfdrive/controls/lib/drive_helpers.py selfdrive/controls/lib/drive_helpers.py
selfdrive/controls/lib/driver_monitor.py selfdrive/controls/lib/driver_monitor.py
selfdrive/controls/lib/latcontrol_pid.py selfdrive/controls/lib/latcontrol_pid.py

View File

@ -9,17 +9,18 @@ import cereal.messaging as messaging
from selfdrive.car import gen_empty_fingerprint from selfdrive.car import gen_empty_fingerprint
from cereal import car from cereal import car
EventName = car.CarEvent.EventName
def get_startup_alert(car_recognized, controller_available): def get_startup_event(car_recognized, controller_available):
alert = 'startup' 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("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']: if Params().get("GitBranch", encoding="utf8") not in ['devel', 'release2-staging', 'dashcam-staging', 'release2', 'dashcam']:
alert = 'startupMaster' event = EventName.startupMaster
if not car_recognized: if not car_recognized:
alert = 'startupNoCar' event = EventName.startupNoCar
elif car_recognized and not controller_available: elif car_recognized and not controller_available:
alert = 'startupNoControl' event = EventName.startupNoControl
return alert return event
def load_interfaces(brand_names): def load_interfaces(brand_names):

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from cereal import car 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.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 import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
@ -71,12 +70,13 @@ class CarInterface(CarInterfaceBase):
ret.buttonEvents = [] ret.buttonEvents = []
# events # 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: 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 # copy back carState packet to CS
self.CS.out = ret.as_reader() self.CS.out = ret.as_reader()

View File

@ -2,7 +2,6 @@
from cereal import car from cereal import car
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
from selfdrive.config import Conversions as CV 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.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 import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
@ -62,9 +61,9 @@ class CarInterface(CarInterfaceBase):
events = self.create_common_events(ret) 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: 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() self.CS.out = ret.as_reader()
return self.CS.out return self.CS.out

View File

@ -1,13 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from cereal import car from cereal import car
from selfdrive.config import Conversions as CV 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, \ from selfdrive.car.gm.values import CAR, Ecu, ECU_FINGERPRINT, CruiseButtons, \
AccState, FINGERPRINTS AccState, FINGERPRINTS
from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint 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 from selfdrive.car.interfaces import CarInterfaceBase
ButtonType = car.CarState.ButtonEvent.Type ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@ -149,24 +149,24 @@ class CarInterface(CarInterfaceBase):
events = self.create_common_events(ret, pcm_enable=False) events = self.create_common_events(ret, pcm_enable=False)
if ret.vEgo < self.CP.minEnableSpeed: if ret.vEgo < self.CP.minEnableSpeed:
events.append(create_event('speedTooLow', [ET.NO_ENTRY])) events.add(EventName.speedTooLow)
if self.CS.park_brake: if self.CS.park_brake:
events.append(create_event('parkBrake', [ET.NO_ENTRY, ET.USER_DISABLE])) events.add(EventName.parkBrake)
if ret.cruiseState.standstill: if ret.cruiseState.standstill:
events.append(create_event('resumeRequired', [ET.WARNING])) events.add(EventName.resumeRequired)
if self.CS.pcm_acc_status == AccState.FAULTED: 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 # handle button presses
for b in ret.buttonEvents: for b in ret.buttonEvents:
# do enable on both accel and decel buttons # do enable on both accel and decel buttons
if b.type in [ButtonType.accelCruise, ButtonType.decelCruise] and not b.pressed: 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 # do disable on button down
if b.type == ButtonType.cancel and b.pressed: 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 # copy back carState packet to CS
self.CS.out = ret.as_reader() self.CS.out = ret.as_reader()

View File

@ -5,7 +5,7 @@ from common.numpy_fast import clip, interp
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
from selfdrive.config import Conversions as CV 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.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.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 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) A_ACC_MAX = max(_A_CRUISE_MAX_V_FOLLOWING)
ButtonType = car.CarState.ButtonEvent.Type ButtonType = car.CarState.ButtonEvent.Type
EventName = car.CarEvent.EventName
def compute_gb_honda(accel, speed): def compute_gb_honda(accel, speed):
creep_brake = 0.0 creep_brake = 0.0
@ -468,25 +469,25 @@ class CarInterface(CarInterfaceBase):
# events # events
events = self.create_common_events(ret, pcm_enable=False) events = self.create_common_events(ret, pcm_enable=False)
if self.CS.brake_error: 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: 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: 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: 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 # 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 # 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): 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) # non loud alert if cruise disbales below 25mph as expected (+ a little margin)
if ret.vEgo < self.CP.minEnableSpeed + 2.: if ret.vEgo < self.CP.minEnableSpeed + 2.:
events.append(create_event('speedTooLow', [ET.IMMEDIATE_DISABLE])) events.add(EventName.speedTooLow)
else: else:
events.append(create_event("cruiseDisabled", [ET.IMMEDIATE_DISABLE])) events.add(EventName.cruiseDisabled)
if self.CS.CP.minEnableSpeed > 0 and ret.vEgo < 0.001: 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 cur_time = self.frame * DT_CTRL
enable_pressed = False enable_pressed = False
@ -500,7 +501,7 @@ class CarInterface(CarInterfaceBase):
# do disable on button down # do disable on button down
if b.type == "cancel" and b.pressed: if b.type == "cancel" and b.pressed:
events.append(create_event('buttonCancel', [ET.USER_DISABLE])) events.add(EventName.buttonCancel)
if self.CP.enableCruise: if self.CP.enableCruise:
# KEEP THIS EVENT LAST! send enable event if button is pressed and there are # 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 if ((cur_time - self.last_enable_pressed) < 0.2 and
(cur_time - self.last_enable_sent) > 0.2 and (cur_time - self.last_enable_sent) > 0.2 and
ret.cruiseState.enabled) or \ ret.cruiseState.enabled) or \
(enable_pressed and get_events(events, [ET.NO_ENTRY])): (enable_pressed and events.any(ET.NO_ENTRY)):
events.append(create_event('buttonEnable', [ET.ENABLE])) events.add(EventName.buttonEnable)
self.last_enable_sent = cur_time self.last_enable_sent = cur_time
elif enable_pressed: 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() self.CS.out = ret.as_reader()
return self.CS.out return self.CS.out

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from cereal import car from cereal import car
from selfdrive.config import Conversions as CV 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.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 import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
@ -180,9 +179,9 @@ class CarInterface(CarInterfaceBase):
if ret.vEgo > (self.CP.minSteerSpeed + 4.): if ret.vEgo > (self.CP.minSteerSpeed + 4.):
self.low_speed_alert = False self.low_speed_alert = False
if self.low_speed_alert: 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() self.CS.out = ret.as_reader()
return self.CS.out return self.CS.out

View File

@ -5,10 +5,11 @@ from common.kalman.simple_kalman import KF1D
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from selfdrive.car import gen_empty_fingerprint from selfdrive.car import gen_empty_fingerprint
from selfdrive.config import Conversions as CV 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 from selfdrive.controls.lib.vehicle_model import VehicleModel
GearShifter = car.CarState.GearShifter GearShifter = car.CarState.GearShifter
EventName = car.CarEvent.EventName
# generic car and radar interfaces # generic car and radar interfaces
@ -81,45 +82,47 @@ class CarInterfaceBase():
raise NotImplementedError raise NotImplementedError
def create_common_events(self, cs_out, extra_gears=[], gas_resume_speed=-1, pcm_enable=True): def create_common_events(self, cs_out, extra_gears=[], gas_resume_speed=-1, pcm_enable=True):
events = [] events = Events()
if cs_out.doorOpen: if cs_out.doorOpen:
events.append(create_event('doorOpen', [ET.NO_ENTRY, ET.SOFT_DISABLE])) events.add(EventName.doorOpen)
if cs_out.seatbeltUnlatched: 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: 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: 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: 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: if cs_out.espDisabled:
events.append(create_event('espDisabled', [ET.NO_ENTRY, ET.SOFT_DISABLE])) events.add(EventName.espDisabled)
if cs_out.gasPressed: 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: if cs_out.stockAeb:
events.append(create_event('stockAeb', [])) events.add(EventName.stockAeb)
if cs_out.vEgo > 92 * CV.MPH_TO_MS: 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: 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: 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. # 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. # 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! # 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 \ 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)): (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) # we engage when pcm is active (rising edge)
if pcm_enable: if pcm_enable:
if cs_out.cruiseState.enabled and not self.CS.out.cruiseState.enabled: 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: elif not cs_out.cruiseState.enabled:
events.append(create_event('pcmDisable', [ET.USER_DISABLE])) events.add(EventName.pcmDisable)
return events return events

View File

@ -1,6 +1,5 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from cereal import car 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.nissan.values import CAR
from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint from selfdrive.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
@ -77,9 +76,9 @@ class CarInterface(CarInterfaceBase):
events = self.create_common_events(ret) events = self.create_common_events(ret)
if self.CS.lkas_enabled: 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() self.CS.out = ret.as_reader()
return self.CS.out return self.CS.out

View File

@ -63,7 +63,7 @@ class CarInterface(CarInterfaceBase):
be.type = car.CarState.ButtonEvent.Type.accelCruise be.type = car.CarState.ButtonEvent.Type.accelCruise
buttonEvents.append(be) 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() self.CS.out = ret.as_reader()
return self.CS.out return self.CS.out

View File

@ -1,12 +1,12 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
from cereal import car from cereal import car
from selfdrive.config import Conversions as CV 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.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.car import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, is_ecu_disconnected, gen_empty_fingerprint
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
EventName = car.CarEvent.EventName
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
@staticmethod @staticmethod
@ -297,19 +297,19 @@ class CarInterface(CarInterfaceBase):
events = self.create_common_events(ret) events = self.create_common_events(ret)
if self.cp_cam.can_invalid_cnt >= 200 and self.CP.enableCamera: 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: 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: 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: if c.actuators.gas > 0.1:
# some margin on the actuator to not false trigger cancellation while stopping # 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: if ret.vEgo < 0.001:
# while in standstill, send a user alert # 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() self.CS.out = ret.as_reader()
return self.CS.out return self.CS.out

View File

@ -1,12 +1,12 @@
from cereal import car from cereal import car
from selfdrive.config import Conversions as CV 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 selfdrive.car.volkswagen.values import CAR, BUTTON_STATES
from common.params import put_nonblocking 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 import STD_CARGO_KG, scale_rot_inertia, scale_tire_stiffness, gen_empty_fingerprint
from selfdrive.car.interfaces import CarInterfaceBase from selfdrive.car.interfaces import CarInterfaceBase
GEAR = car.CarState.GearShifter GEAR = car.CarState.GearShifter
EventName = car.CarEvent.EventName
class CarInterface(CarInterfaceBase): class CarInterface(CarInterfaceBase):
def __init__(self, CP, CarController, CarState): def __init__(self, CP, CarController, CarState):
@ -108,11 +108,11 @@ class CarInterface(CarInterfaceBase):
# Vehicle health and operation safety checks # Vehicle health and operation safety checks
if self.CS.parkingBrakeSet: if self.CS.parkingBrakeSet:
events.append(create_event('parkBrake', [ET.NO_ENTRY, ET.USER_DISABLE])) events.add(EventName.parkBrake)
if self.CS.steeringFault: 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.buttonEvents = buttonEvents
ret.canMonoTimes = canMonoTimes ret.canMonoTimes = canMonoTimes

View File

@ -1,7 +1,6 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import os import os
import gc import gc
import capnp
from cereal import car, log from cereal import car, log
from common.numpy_fast import clip from common.numpy_fast import clip
from common.realtime import sec_since_boot, set_realtime_priority, Ratekeeper, DT_CTRL 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 import cereal.messaging as messaging
from selfdrive.config import Conversions as CV from selfdrive.config import Conversions as CV
from selfdrive.boardd.boardd import can_list_to_can_capnp 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.lane_planner import CAMERA_OFFSET
from selfdrive.controls.lib.drive_helpers import get_events, \ from selfdrive.controls.lib.drive_helpers import update_v_cruise, initialize_v_cruise
create_event, \
EventTypes as ET, \
update_v_cruise, \
initialize_v_cruise
from selfdrive.controls.lib.longcontrol import LongControl, STARTING_TARGET_SPEED from selfdrive.controls.lib.longcontrol import LongControl, STARTING_TARGET_SPEED
from selfdrive.controls.lib.latcontrol_pid import LatControlPID from selfdrive.controls.lib.latcontrol_pid import LatControlPID
from selfdrive.controls.lib.latcontrol_indi import LatControlINDI from selfdrive.controls.lib.latcontrol_indi import LatControlINDI
from selfdrive.controls.lib.latcontrol_lqr import LatControlLQR 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.alertmanager import AlertManager
from selfdrive.controls.lib.vehicle_model import VehicleModel from selfdrive.controls.lib.vehicle_model import VehicleModel
from selfdrive.controls.lib.planner import LON_MPC_STEP 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 LDW_MIN_SPEED = 31 * CV.MPH_TO_MS
LANE_DEPARTURE_THRESHOLD = 0.1 LANE_DEPARTURE_THRESHOLD = 0.1
@ -38,19 +34,7 @@ LongitudinalPlanSource = log.Plan.LongitudinalPlanSource
Desire = log.PathPlan.Desire Desire = log.PathPlan.Desire
LaneChangeState = log.PathPlan.LaneChangeState LaneChangeState = log.PathPlan.LaneChangeState
LaneChangeDirection = log.PathPlan.LaneChangeDirection LaneChangeDirection = log.PathPlan.LaneChangeDirection
EventName = car.CarEvent.EventName
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
class Controls: class Controls:
def __init__(self, sm=None, pm=None, can_sock=None): def __init__(self, sm=None, pm=None, can_sock=None):
@ -91,6 +75,10 @@ class Controls:
passive = params.get("Passive", encoding='utf8') == "1" or \ passive = params.get("Passive", encoding='utf8') == "1" or \
internet_needed or not openpilot_enabled_toggle 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' car_recognized = self.CP.carName != 'mock'
# If stock camera is disconnected, we loaded car controls and it's not dashcam mode # 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 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.CC = car.CarControl.new_message()
self.AM = AlertManager() self.AM = AlertManager()
self.events = Events()
self.LoC = LongControl(self.CP, self.CI.compute_gb) self.LoC = LongControl(self.CP, self.CI.compute_gb)
self.VM = VehicleModel(self.CP) self.VM = VehicleModel(self.CP)
@ -130,7 +119,8 @@ class Controls:
self.can_error_counter = 0 self.can_error_counter = 0
self.last_blinker_frame = 0 self.last_blinker_frame = 0
self.saturated_count = 0 self.saturated_count = 0
self.events_prev = "" self.events_prev = []
self.current_alert_types = []
self.sm['liveCalibration'].calStatus = Calibration.INVALID self.sm['liveCalibration'].calStatus = Calibration.INVALID
self.sm['pathPlan'].sensorValid = True self.sm['pathPlan'].sensorValid = True
@ -140,124 +130,96 @@ class Controls:
self.sm['dMonitoringState'].awarenessStatus = 1. self.sm['dMonitoringState'].awarenessStatus = 1.
self.sm['dMonitoringState'].faceDetected = False self.sm['dMonitoringState'].faceDetected = False
startup_alert = get_startup_alert(car_recognized, controller_available) self.startup_event = get_startup_event(car_recognized, controller_available)
self.AM.add(self.sm.frame, startup_alert, False)
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 # controlsd is driven by can recv, expected at 100Hz
self.rk = Ratekeeper(100, print_delay_threshold=None) self.rk = Ratekeeper(100, print_delay_threshold=None)
self.prof = Profiler(False) # off by default 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 = [] def update_events(self, CS):
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):
"""Compute carEvents from carState""" """Compute carEvents from carState"""
events = self.static_events.copy() self.events.clear()
events.extend(CS.events) self.events.add_from_msg(CS.events)
events.extend(self.sm['dMonitoringState'].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 # Create events for battery, temperature, disk space, and memory
if self.sm['thermal'].batteryPercent < 1 and self.sm['thermal'].chargingError: if self.sm['thermal'].batteryPercent < 1 and self.sm['thermal'].chargingError:
# at zero percent battery, while discharging, OP should not allowed # 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: 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: if self.sm['thermal'].freeSpace < 0.07:
# under 7% of space free no enable allowed # 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: 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 # Handle calibration status
cal_status = self.sm['liveCalibration'].calStatus cal_status = self.sm['liveCalibration'].calStatus
if cal_status != Calibration.CALIBRATED: if cal_status != Calibration.CALIBRATED:
if cal_status == Calibration.UNCALIBRATED: if cal_status == Calibration.UNCALIBRATED:
events.append(create_event('calibrationIncomplete', [ET.NO_ENTRY, ET.SOFT_DISABLE, ET.PERMANENT])) self.events.add(EventName.calibrationIncomplete)
else: else:
events.append(create_event('calibrationInvalid', [ET.NO_ENTRY, ET.SOFT_DISABLE])) self.events.add(EventName.calibrationInvalid)
# Handle lane change # Handle lane change
if self.sm['pathPlan'].laneChangeState == LaneChangeState.preLaneChange: if self.sm['pathPlan'].laneChangeState == LaneChangeState.preLaneChange:
if self.sm['pathPlan'].laneChangeDirection == LaneChangeDirection.left: if self.sm['pathPlan'].laneChangeDirection == LaneChangeDirection.left:
events.append(create_event('preLaneChangeLeft', [ET.WARNING])) self.events.add(EventName.preLaneChangeLeft)
else: else:
events.append(create_event('preLaneChangeRight', [ET.WARNING])) self.events.add(EventName.preLaneChangeRight)
elif self.sm['pathPlan'].laneChangeState in [LaneChangeState.laneChangeStarting, \ elif self.sm['pathPlan'].laneChangeState in [LaneChangeState.laneChangeStarting, \
LaneChangeState.laneChangeFinishing]: LaneChangeState.laneChangeFinishing]:
events.append(create_event('laneChange', [ET.WARNING])) self.events.add(EventName.laneChange)
if self.can_rcv_error: 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: 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']: if not self.sm.alive['plan'] and self.sm.alive['pathPlan']:
# only plan not being received: radar not communicating # 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(): 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: 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: 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: if not self.sm['pathPlan'].paramsValid:
events.append(create_event('vehicleModelInvalid', [ET.WARNING])) self.events.add(EventName.vehicleModelInvalid)
if not self.sm['pathPlan'].posenetValid: 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: 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: 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: 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: 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 # Only allow engagement with brake pressed when stopped behind another stopped car
if CS.brakePressed and self.sm['plan'].vTargetFuture >= STARTING_TARGET_SPEED \ if CS.brakePressed and self.sm['plan'].vTargetFuture >= STARTING_TARGET_SPEED \
and not self.CP.radarOffCan and CS.vEgo < 0.3: and not self.CP.radarOffCan and CS.vEgo < 0.3:
events.append(create_event('noTarget', [ET.NO_ENTRY, ET.IMMEDIATE_DISABLE])) self.events.add(EventName.noTarget)
# 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
def data_sample(self): def data_sample(self):
@ -289,7 +251,7 @@ class Controls:
return CS return CS
def state_transition(self, CS, events): def state_transition(self, CS):
"""Compute conditional state transitions and execute actions on state transitions""" """Compute conditional state transitions and execute actions on state transitions"""
self.v_cruise_kph_last = self.v_cruise_kph self.v_cruise_kph_last = self.v_cruise_kph
@ -304,70 +266,68 @@ class Controls:
# entrance in SOFT_DISABLING state # entrance in SOFT_DISABLING state
self.soft_disable_timer = max(0, self.soft_disable_timer - 1) self.soft_disable_timer = max(0, self.soft_disable_timer - 1)
alert_types = [] self.current_alert_types = [ET.PERMANENT]
# ENABLED, PRE ENABLING, SOFT DISABLING # ENABLED, PRE ENABLING, SOFT DISABLING
if self.state != State.disabled: if self.state != State.disabled:
# user and immediate disable always have priority in a non-disabled state # 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.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 self.state = State.disabled
alert_types = [ET.IMMEDIATE_DISABLE] self.current_alert_types.append(ET.IMMEDIATE_DISABLE)
else: else:
# ENABLED # ENABLED
if self.state == State.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.state = State.softDisabling
self.soft_disable_timer = 300 # 3s self.soft_disable_timer = 300 # 3s
alert_types = [ET.SOFT_DISABLE] self.current_alert_types.append(ET.SOFT_DISABLE)
# SOFT DISABLING # SOFT DISABLING
elif self.state == State.softDisabling: 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 # no more soft disabling condition, so go back to ENABLED
self.state = State.enabled self.state = State.enabled
elif get_events(events, [ET.SOFT_DISABLE]) and self.soft_disable_timer > 0: elif self.events.any(ET.SOFT_DISABLE) and self.soft_disable_timer > 0:
alert_types = [ET.SOFT_DISABLE] self.current_alert_types.append(ET.SOFT_DISABLE)
elif self.soft_disable_timer <= 0: elif self.soft_disable_timer <= 0:
self.state = State.disabled self.state = State.disabled
# PRE ENABLING # PRE ENABLING
elif self.state == State.preEnabled: 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 self.state = State.enabled
# DISABLED # DISABLED
elif self.state == State.disabled: elif self.state == State.disabled:
if get_events(events, [ET.ENABLE]): if self.events.any(ET.ENABLE):
if get_events(events, [ET.NO_ENTRY]): if self.events.any(ET.NO_ENTRY):
for e in get_events(events, [ET.NO_ENTRY]): self.current_alert_types.append(ET.NO_ENTRY)
self.AM.add(self.sm.frame, str(e) + "NoEntry", self.enabled)
else: else:
if get_events(events, [ET.PRE_ENABLE]): if self.events.any(ET.PRE_ENABLE):
self.state = State.preEnabled self.state = State.preEnabled
else: else:
self.state = State.enabled 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) 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 # Check if actuators are enabled
self.active = self.state == State.enabled or self.state == State.softDisabling 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 # Check if openpilot is engaged
self.enabled = self.active or self.state == State.preEnabled 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""" """Given the state, this function returns an actuators packet"""
plan = self.sm['plan'] plan = self.sm['plan']
@ -378,14 +338,6 @@ class Controls:
if CS.leftBlinker or CS.rightBlinker: if CS.leftBlinker or CS.rightBlinker:
self.last_blinker_frame = self.sm.frame 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 # State specific actions
if not self.active: if not self.active:
@ -419,12 +371,12 @@ class Controls:
right_deviation = actuators.steer < 0 and path_plan.dPoly[3] < -0.1 right_deviation = actuators.steer < 0 and path_plan.dPoly[3] < -0.1
if left_deviation or right_deviation: 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 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""" """Send actuators and hud commands to the car, send controlsstate and MPC logging"""
CC = car.CarControl.new_message() 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) CC.hudControl.rightLaneDepart = bool(r_lane_change_prob > LANE_DEPARTURE_THRESHOLD and r_lane_close)
if CC.hudControl.rightLaneDepart or CC.hudControl.leftLaneDepart: if CC.hudControl.rightLaneDepart or CC.hudControl.leftLaneDepart:
self.AM.add(self.sm.frame, 'ldwPermanent', False) self.events.add(EventName.ldw)
events.append(create_event('ldw', [ET.PERMANENT]))
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) self.AM.process_alerts(self.sm.frame)
CC.hudControl.visualAlert = self.AM.visual_alert 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.curvature = self.VM.calc_curvature(steer_angle_rad, CS.vEgo)
controlsState.steerOverride = CS.steeringPressed controlsState.steerOverride = CS.steeringPressed
controlsState.state = self.state 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.longControlState = self.LoC.long_control_state
controlsState.vPid = float(self.LoC.v_pid) controlsState.vPid = float(self.LoC.v_pid)
controlsState.vCruise = float(self.v_cruise_kph) controlsState.vCruise = float(self.v_cruise_kph)
@ -534,19 +487,19 @@ class Controls:
self.pm.send('controlsState', dat) self.pm.send('controlsState', dat)
# carState # carState
car_events = self.events.to_msg()
cs_send = messaging.new_message('carState') cs_send = messaging.new_message('carState')
cs_send.valid = CS.canValid cs_send.valid = CS.canValid
cs_send.carState = CS cs_send.carState = CS
cs_send.carState.events = events cs_send.carState.events = car_events
self.pm.send('carState', cs_send) self.pm.send('carState', cs_send)
# carEvents - logged every second or on change # carEvents - logged every second or on change
events_bytes = events_to_bytes(events) if (self.sm.frame % int(1. / DT_CTRL) == 0) or (self.events.names != self.events_prev):
if (self.sm.frame % int(1. / DT_CTRL) == 0) or (events_bytes != self.events_prev): ce_send = messaging.new_message('carEvents', len(self.events))
ce_send = messaging.new_message('carEvents', len(events)) ce_send.carEvents = car_events
ce_send.carEvents = events
self.pm.send('carEvents', ce_send) 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) # carParams - logged every 50 seconds (> 1 per segment)
if (self.sm.frame % int(50. / DT_CTRL) == 0): if (self.sm.frame % int(50. / DT_CTRL) == 0):
@ -571,20 +524,20 @@ class Controls:
CS = self.data_sample() CS = self.data_sample()
self.prof.checkpoint("Sample") self.prof.checkpoint("Sample")
events = self.create_events(CS) self.update_events(CS)
if not self.read_only: if not self.read_only:
# Update control state # Update control state
self.state_transition(CS, events) self.state_transition(CS)
self.prof.checkpoint("State transition") self.prof.checkpoint("State transition")
# Compute actuators (runs PID loops and lateral MPC) # 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") self.prof.checkpoint("State Control")
# Publish data # 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") self.prof.checkpoint("Sent")
def controlsd_thread(self): def controlsd_thread(self):

View File

@ -1,9 +1,10 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import gc import gc
from cereal import car
from common.realtime import set_realtime_priority from common.realtime import set_realtime_priority
from common.params import Params from common.params import Params
import cereal.messaging as messaging 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.controls.lib.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, MAX_TERMINAL_DURATION
from selfdrive.locationd.calibration_helpers import Calibration from selfdrive.locationd.calibration_helpers import Calibration
@ -57,7 +58,7 @@ def dmonitoringd_thread(sm=None, pm=None):
v_cruise != v_cruise_last or \ v_cruise != v_cruise_last or \
sm['carState'].steeringPressed sm['carState'].steeringPressed
if driver_engaged: 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 v_cruise_last = v_cruise
# Get model meta # Get model meta
@ -66,18 +67,18 @@ def dmonitoringd_thread(sm=None, pm=None):
# Get data from dmonitoringmodeld # Get data from dmonitoringmodeld
if sm.updated['driverState']: if sm.updated['driverState']:
events = [] events = Events()
driver_status.get_pose(sm['driverState'], cal_rpy, sm['carState'].vEgo, sm['carState'].cruiseState.enabled) driver_status.get_pose(sm['driverState'], cal_rpy, sm['carState'].vEgo, sm['carState'].cruiseState.enabled)
# Block any engage after certain distrations # Block any engage after certain distrations
if driver_status.terminal_alert_cnt >= MAX_TERMINAL_ALERTS or driver_status.terminal_time >= MAX_TERMINAL_DURATION: 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 # 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 # dMonitoringState packet
dat = messaging.new_message('dMonitoringState') dat = messaging.new_message('dMonitoringState')
dat.dMonitoringState = { dat.dMonitoringState = {
"events": events, "events": events.to_msg(),
"faceDetected": driver_status.face_detected, "faceDetected": driver_status.face_detected,
"isDistracted": driver_status.driver_distracted, "isDistracted": driver_status.driver_distracted,
"awarenessStatus": driver_status.awareness, "awarenessStatus": driver_status.awareness,
@ -100,4 +101,4 @@ def main(sm=None, pm=None):
dmonitoringd_thread(sm, pm) dmonitoringd_thread(sm, pm)
if __name__ == '__main__': if __name__ == '__main__':
main() main()

View File

@ -1,7 +1,6 @@
from cereal import car, log from cereal import car, log
from common.realtime import DT_CTRL from common.realtime import DT_CTRL
from selfdrive.swaglog import cloudlog from selfdrive.swaglog import cloudlog
from selfdrive.controls.lib.alerts import ALERTS
import copy import copy
@ -14,21 +13,21 @@ class AlertManager():
def __init__(self): def __init__(self):
self.activealerts = [] self.activealerts = []
self.alerts = {alert.alert_type: alert for alert in ALERTS}
def alertPresent(self): def alert_present(self):
return len(self.activealerts) > 0 return len(self.activealerts) > 0
def add(self, frame, alert_type, enabled=True, extra_text_1='', extra_text_2=''): def add_many(self, frame, alerts, enabled=True):
alert_type = str(alert_type) for a in alerts:
added_alert = copy.copy(self.alerts[alert_type]) self.add(frame, a, enabled=enabled)
added_alert.alert_text_1 += extra_text_1
added_alert.alert_text_2 += extra_text_2 def add(self, frame, alert, enabled=True):
added_alert = copy.copy(alert)
added_alert.start_time = frame * DT_CTRL added_alert.start_time = frame * DT_CTRL
# if new alert is higher priority, log it # if new alert is higher priority, log it
if not self.alertPresent() or added_alert.alert_priority > self.activealerts[0].alert_priority: if not self.alert_present() or added_alert.alert_priority > self.activealerts[0].alert_priority:
cloudlog.event('alert_add', alert_type=alert_type, enabled=enabled) cloudlog.event('alert_add', alert_type=added_alert.alert_type, enabled=enabled)
self.activealerts.append(added_alert) self.activealerts.append(added_alert)
@ -42,7 +41,7 @@ class AlertManager():
self.activealerts = [a for a in self.activealerts if a.start_time + 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] 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 # start with assuming no alerts
self.alert_type = "" self.alert_type = ""

View File

@ -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.),
]

View File

@ -1,4 +1,3 @@
from cereal import car
from common.numpy_fast import clip, interp from common.numpy_fast import clip, interp
from selfdrive.config import Conversions as CV from selfdrive.config import Conversions as CV
@ -23,34 +22,6 @@ class MPC_COST_LONG:
JERK = 20.0 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): def rate_limit(new_value, last_value, dw_step, up_step):
return clip(new_value, last_value + dw_step, last_value + up_step) return clip(new_value, last_value + dw_step, last_value + up_step)

View File

@ -1,10 +1,13 @@
from common.numpy_fast import interp from common.numpy_fast import interp
from math import atan2, sqrt from math import atan2, sqrt
from common.realtime import DT_DMON 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.filter_simple import FirstOrderFilter
from common.stat_live import RunningStatFilter from common.stat_live import RunningStatFilter
from cereal import car
EventName = car.CarEvent.EventName
# ****************************************************************************************** # ******************************************************************************************
# NOTE: To fork maintainers. # NOTE: To fork maintainers.
# Disabling or nerfing safety features may get you and your users banned from our servers. # 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 = 1.
self.awareness_active = 1. self.awareness_active = 1.
self.awareness_passive = 1. self.awareness_passive = 1.
return events return
driver_attentive = self.driver_distraction_filter.x < 0.37 driver_attentive = self.driver_distraction_filter.x < 0.37
awareness_prev = self.awareness awareness_prev = self.awareness
if self.face_detected and self.hi_stds * DT_DMON > _HI_STD_TIMEOUT: 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): 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 # 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.) self.awareness_passive = min(self.awareness_passive + self.step_change, 1.)
# don't display alert banner when awareness is recovering and has cleared orange # don't display alert banner when awareness is recovering and has cleared orange
if self.awareness > self.threshold_prompt: if self.awareness > self.threshold_prompt:
return events return
# should always be counting if distracted unless at standstill and reaching orange # 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 \ 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 alert = None
if self.awareness <= 0.: if self.awareness <= 0.:
# terminal red alert: disengagement required # 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 self.terminal_time += 1
if awareness_prev > 0.: if awareness_prev > 0.:
self.terminal_alert_cnt += 1 self.terminal_alert_cnt += 1
elif self.awareness <= self.threshold_prompt: elif self.awareness <= self.threshold_prompt:
# prompt orange alert # 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: elif self.awareness <= self.threshold_pre:
# pre green alert # 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: if alert is not None:
events.append(create_event(alert, [ET.WARNING])) events.add(alert)
return events

View File

@ -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"),
},
}

View File

@ -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()

View File

@ -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()

View File

@ -1,12 +1,16 @@
import unittest import unittest
import numpy as np import numpy as np
from cereal import car
from common.realtime import DT_DMON from common.realtime import DT_DMON
from selfdrive.controls.lib.events import Events
from selfdrive.controls.lib.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, \ from selfdrive.controls.lib.driver_monitor import DriverStatus, MAX_TERMINAL_ALERTS, \
_AWARENESS_TIME, _AWARENESS_PRE_TIME_TILL_TERMINAL, \ _AWARENESS_TIME, _AWARENESS_PRE_TIME_TILL_TERMINAL, \
_AWARENESS_PROMPT_TIME_TILL_TERMINAL, _DISTRACTED_TIME, \ _AWARENESS_PROMPT_TIME_TILL_TERMINAL, _DISTRACTED_TIME, \
_DISTRACTED_PRE_TIME_TILL_TERMINAL, _DISTRACTED_PROMPT_TIME_TILL_TERMINAL, \ _DISTRACTED_PRE_TIME_TILL_TERMINAL, _DISTRACTED_PROMPT_TIME_TILL_TERMINAL, \
_POSESTD_THRESHOLD, _HI_STD_TIMEOUT _POSESTD_THRESHOLD, _HI_STD_TIMEOUT
EventName = car.CarEvent.EventName
_TEST_TIMESPAN = 120 # seconds _TEST_TIMESPAN = 120 # seconds
_DISTRACTED_SECONDS_TO_ORANGE = _DISTRACTED_TIME - _DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1 _DISTRACTED_SECONDS_TO_ORANGE = _DISTRACTED_TIME - _DISTRACTED_PROMPT_TIME_TILL_TERMINAL + 1
_DISTRACTED_SECONDS_TO_RED = _DISTRACTED_TIME + 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() DS = DriverStatus()
events_from_DM = [] events_from_DM = []
for idx in range(len(driver_state_msgs)): for idx in range(len(driver_state_msgs)):
e = Events()
DS.get_pose(driver_state_msgs[idx], [0,0,0], 0, openpilot_status[idx]) DS.get_pose(driver_state_msgs[idx], [0,0,0], 0, openpilot_status[idx])
# cal_rpy and car_speed don't matter here # 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]) # evaluate events at 10Hz for tests
events_from_DM.append(event_per_state) # 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' assert len(events_from_DM) == len(driver_state_msgs), 'somethings wrong'
return events_from_DM, DS return events_from_DM, DS
class TestMonitoring(unittest.TestCase): 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) 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.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+\ 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+\ 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+\ 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) self.assertIs(type(d_status.awareness), float)
# 2. op engaged, no face detected the whole time, no action # 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] 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.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+\ 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+\ 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+\ 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 # 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 # - 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)) [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] 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.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.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) 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, \ # 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) 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) 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] 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_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)][0].name, 'driverDistracted') 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)][0].name, '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) 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 # 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) 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] 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.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) self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE+0.1)/DT_DMON)])==0)
if _visible_time == 1: 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)/DT_DMON)].names[0], EventName.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+_visible_time)/DT_DMON)].names[0], EventName.preDriverUnresponsive)
elif _visible_time == 10: 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) self.assertTrue(len(events_output[int((_INVISIBLE_SECONDS_TO_ORANGE*2+1+0.1+_visible_time)/DT_DMON)])==0)
else: else:
pass 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) 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] 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.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.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.1)/DT_DMON)].names[0], EventName.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+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)][0].name, '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) 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 # 7. op not engaged, always distracted driver
@ -183,9 +188,9 @@ class TestMonitoring(unittest.TestCase):
standstill_vector = always_true[:] standstill_vector = always_true[:]
standstill_vector[int(_redlight_time/DT_DMON):] = [False] * int((_TEST_TIMESPAN-_redlight_time)/DT_DMON) 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] 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((_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)][0].name, '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)][0].name, 'promptDriverDistracted') 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 # 9. op engaged, model is extremely uncertain. driver first attentive, then distracted
# - should only pop the green alert about model uncertainty # - should only pop the green alert about model uncertainty
@ -197,9 +202,9 @@ class TestMonitoring(unittest.TestCase):
interaction_vector = always_false[:] interaction_vector = always_false[:]
events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false)[0] 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.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.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 # 10. op engaged, model is somehow uncertain and driver is distracted
# - should slow down the alert countdown but it still gets there # - should slow down the alert countdown but it still gets there
@ -208,11 +213,11 @@ class TestMonitoring(unittest.TestCase):
interaction_vector = always_false[:] interaction_vector = always_false[:]
events_output = run_DState_seq(ds_vector, interaction_vector, always_true, always_false)[0] 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.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((_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)][1].name, 'preDriverDistracted') 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)][1].name, 'promptDriverDistracted') 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)][1].name, '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)][1].name, 'promptDriverDistracted') # set_timer blocked self.assertEqual(events_output[int((_DISTRACTED_TIME*2.5)/DT_DMON)].names[1], EventName.promptDriverDistracted) # set_timer blocked
if __name__ == "__main__": if __name__ == "__main__":
print('MAX_TERMINAL_ALERTS', MAX_TERMINAL_ALERTS) print('MAX_TERMINAL_ALERTS', MAX_TERMINAL_ALERTS)

View File

@ -1 +1 @@
1050a84363baf1e7910d3f8f9a01e201e6041e70 76e577b86d113139167275b4a7379f3591abfa02