Files
2026-08-22 23:42:41 -05:00

164 lines
6.4 KiB
Python

import copy
import json
import os
import random
from PIL import Image, ImageDraw, ImageFont
from iqpilot.cereal import log, car, custom
from iqpilot.cereal.messaging import SubMaster
from iqpilot.common.basedir import BASEDIR
from iqpilot.common.params import Params
from iqpilot.selfdrive.selfdrived.events import Alert, EVENTS, ET
from iqpilot.selfdrive.selfdrived.iq_events import EVENTS_IQ
from iqpilot.selfdrive.selfdrived.events import invalid_lkas_setting_alert, invalid_lkas_setting_no_entry_alert
from iqpilot.selfdrive.selfdrived.alertmanager import set_offroad_alert
from iqpilot.selfdrive.test.process_replay.process_replay import CONFIGS
AlertSize = log.SelfdriveState.AlertSize
OFFROAD_ALERTS_PATH = os.path.join(BASEDIR, "iqpilot/selfdrive/selfdrived/alerts_offroad.json")
# TODO: add callback alerts
ALERTS = []
for event_types in EVENTS.values():
for alert in event_types.values():
ALERTS.append(alert)
class TestAlerts:
def test_wrong_gear_alerts_are_silent_and_invisible(self):
wrong_gear_alerts = (EVENTS[log.OnroadEvent.EventName.wrongGear][ET.SOFT_DISABLE],
EVENTS[log.OnroadEvent.EventName.wrongGear][ET.NO_ENTRY],
EVENTS_IQ[custom.IQOnroadEvent.EventName.gearNotDriveSilent][ET.NO_ENTRY])
for alert in wrong_gear_alerts:
assert alert.alert_size == AlertSize.none
assert alert.audible_alert == car.CarControl.HUDControl.AudibleAlert.none
assert alert.alert_text_1 == ""
assert alert.alert_text_2 == ""
@classmethod
def setup_class(cls):
with open(OFFROAD_ALERTS_PATH) as f:
cls.offroad_alerts = json.loads(f.read())
# Create fake objects for callback
cls.CS = car.CarState.new_message()
cls.CP = car.CarParams.new_message()
cfg = [c for c in CONFIGS if c.proc_name == 'selfdrived'][0]
cls.sm = SubMaster(cfg.pubs)
def test_events_defined(self):
# Ensure all events in capnp schema are defined in events.py
events = log.OnroadEvent.EventName.schema.enumerants
for name, e in events.items():
if not name.endswith("DEPRECATED") and not name.startswith("eventReserved"):
fail_msg = f"{name} @{e} not in EVENTS"
assert e in EVENTS.keys(), fail_msg
# ensure alert text doesn't exceed allowed width
def test_alert_text_length(self):
font_path = os.path.join(BASEDIR, "iqpilot/selfdrive/assets/fonts")
regular_font_path = os.path.join(font_path, "Inter-SemiBold.ttf")
bold_font_path = os.path.join(font_path, "Inter-Bold.ttf")
semibold_font_path = os.path.join(font_path, "Inter-SemiBold.ttf")
max_text_width = 2160 - 300 # full screen width is usable, minus sidebar
draw = ImageDraw.Draw(Image.new('RGB', (0, 0)))
fonts = {
AlertSize.small: [ImageFont.truetype(semibold_font_path, 74)],
AlertSize.mid: [ImageFont.truetype(bold_font_path, 88),
ImageFont.truetype(regular_font_path, 66)],
}
for alert in ALERTS:
if not isinstance(alert, Alert):
alert = alert(self.CP, self.CS, self.sm, metric=False, soft_disable_time=100, personality=log.LongitudinalPersonality.standard)
# 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]
left, _, right, _ = draw.textbbox((0, 0), txt, font)
width = right - left
msg = f"type: {alert.alert_type} msg: {txt}"
assert width <= max_text_width, msg
def test_alert_sanity_check(self):
for event_types in EVENTS.values():
for event_type, a in event_types.items():
# TODO: add callback alerts
if not isinstance(a, Alert):
continue
if a.alert_size == AlertSize.none:
assert len(a.alert_text_1) == 0
assert len(a.alert_text_2) == 0
elif a.alert_size == AlertSize.small:
assert len(a.alert_text_1) > 0
assert len(a.alert_text_2) == 0
elif a.alert_size == AlertSize.mid:
assert len(a.alert_text_1) > 0
assert len(a.alert_text_2) > 0
else:
assert len(a.alert_text_1) > 0
assert a.duration >= 0.
if event_type not in (ET.WARNING, ET.PERMANENT, ET.PRE_ENABLE):
assert a.creation_delay == 0.
def test_offroad_alerts(self):
params = Params()
for a in self.offroad_alerts:
# set the alert
alert = copy.copy(self.offroad_alerts[a])
set_offroad_alert(a, True)
alert['extra'] = ''
assert alert == params.get(a)
# then delete it
set_offroad_alert(a, False)
assert params.get(a) is None
def test_offroad_alerts_extra_text(self):
params = Params()
for i in range(50):
# set the alert
a = random.choice(list(self.offroad_alerts))
alert = self.offroad_alerts[a]
set_offroad_alert(a, True, extra_text="a"*i)
written_alert = params.get(a)
assert "a"*i == written_alert['extra']
assert alert["text"] == written_alert['text']
def test_invalid_lkas_setting_alert_tesla_dashcam_mode(self):
self.CP.brand = "tesla"
alert = invalid_lkas_setting_alert(self.CP, self.CS, self.sm, metric=False, soft_disable_time=100, personality=log.LongitudinalPersonality.standard)
no_entry = invalid_lkas_setting_no_entry_alert(self.CP, self.CS, self.sm, metric=False, soft_disable_time=100, personality=log.LongitudinalPersonality.standard)
assert alert.alert_text_1 == "Dashcam Mode"
assert alert.alert_text_2 == "FSD / Autosteer is active"
assert no_entry.alert_text_1 == "Dashcam Mode"
assert no_entry.alert_text_2 == "FSD / Autosteer is active"
def test_invalid_lkas_setting_alert_non_tesla_unchanged(self):
self.CP.brand = "mazda"
alert = invalid_lkas_setting_alert(self.CP, self.CS, self.sm, metric=False, soft_disable_time=100, personality=log.LongitudinalPersonality.standard)
no_entry = invalid_lkas_setting_no_entry_alert(self.CP, self.CS, self.sm, metric=False, soft_disable_time=100, personality=log.LongitudinalPersonality.standard)
assert alert.alert_text_1 == "Invalid LKAS setting"
assert alert.alert_text_2 == "Enable your car's LKAS to engage"
assert no_entry.alert_text_1 == "IQ.Pilot Unavailable"
assert no_entry.alert_text_2 == "Invalid LKAS setting"