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