diff --git a/system/ui/onroad/alert_renderer.py b/system/ui/onroad/alert_renderer.py index 600c721680..947b19663d 100644 --- a/system/ui/onroad/alert_renderer.py +++ b/system/ui/onroad/alert_renderer.py @@ -1,14 +1,15 @@ -import numpy as np +import time import pyray as rl from dataclasses import dataclass from cereal import messaging, log +from openpilot.system.hardware import TICI from openpilot.system.ui.lib.application import gui_app, FontWeight # Constants ALERT_COLORS = { - log.SelfdriveState.AlertStatus.normal: rl.Color(0, 0, 0, 150), # Black - log.SelfdriveState.AlertStatus.userPrompt: rl.Color(0xFE, 0x8C, 0x34, 100), # Orange - log.SelfdriveState.AlertStatus.critical: rl.Color(0xC9, 0x22, 0x31, 150), # Red + log.SelfdriveState.AlertStatus.normal: rl.Color(0, 0, 0, 220), # Black + log.SelfdriveState.AlertStatus.userPrompt: rl.Color(0xFE, 0x8C, 0x34, 220), # Orange + log.SelfdriveState.AlertStatus.critical: rl.Color(0xC9, 0x22, 0x31, 220), # Red } ALERT_HEIGHTS = { @@ -16,6 +17,7 @@ ALERT_HEIGHTS = { log.SelfdriveState.AlertSize.mid: 420, } +ALERT_BORDER_RADIUS = 30 SELFDRIVE_STATE_TIMEOUT = 5 # Seconds SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10 # Seconds @@ -28,95 +30,80 @@ class Alert: size: log.SelfdriveState.AlertSize = log.SelfdriveState.AlertSize.none status: log.SelfdriveState.AlertStatus = log.SelfdriveState.AlertStatus.normal - def is_equal(self, other: 'Alert') -> bool: - """Check if two alerts are equal.""" - return ( - self.text1 == other.text1 - and self.text2 == other.text2 - and self.alert_type == other.alert_type - and self.size == other.size - and self.status == other.status - ) + +# Pre-defined alert instances +ALERT_STARTUP_PENDING = Alert( + text1="openpilot Unavailable", + text2="Waiting to start", + alert_type="selfdriveWaiting", + size=log.SelfdriveState.AlertSize.mid, + status=log.SelfdriveState.AlertStatus.normal, +) + +ALERT_CRITICAL_TIMEOUT = Alert( + text1="TAKE CONTROL IMMEDIATELY", + text2="System Unresponsive", + alert_type="selfdriveUnresponsive", + size=log.SelfdriveState.AlertSize.full, + status=log.SelfdriveState.AlertStatus.critical, +) + +ALERT_CRITICAL_REBOOT = Alert( + text1="System Unresponsive", + text2="Reboot Device", + alert_type="selfdriveUnresponsivePermanent", + size=log.SelfdriveState.AlertSize.full, + status=log.SelfdriveState.AlertStatus.critical, +) class AlertRenderer: def __init__(self): """Initialize the alert renderer.""" self.alert: Alert = Alert() + # TODO: use ui_state to determine when to start self.started_frame: int = 0 self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL) self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) self.font_metrics_cache: dict[tuple[str, int, str], rl.Vector2] = {} - def clear(self) -> None: - """Reset the alert to its default state.""" - self.alert = Alert() - - def update_state(self, sm: messaging.SubMaster, started_frame: int) -> None: + def update_state(self, sm: messaging.SubMaster) -> None: """Update alert state based on SubMaster data.""" - self.started_frame = started_frame - new_alert = self.get_alert(sm) - if not self.alert.is_equal(new_alert): - self.alert = new_alert + self.alert = self.get_alert(sm) def get_alert(self, sm: messaging.SubMaster) -> Alert: """Generate the current alert based on selfdrive state.""" - if not sm.valid['selfdriveState']: - return Alert() - ss = sm['selfdriveState'] - selfdrive_frame = sm.recv_frame['selfdriveState'] - alert_status = self._get_enum_value(ss.alertStatus, log.SelfdriveState.AlertStatus) - # Return current alert if selfdrive state is recent - if selfdrive_frame >= self.started_frame: - return Alert( + # Check if waiting to start + if sm.recv_frame['selfdriveState'] < self.started_frame: + return ALERT_STARTUP_PENDING + + # Handle selfdrive timeout + ss_missing = time.monotonic() - sm.recv_time['selfdriveState'] + if TICI: + if ss_missing > SELFDRIVE_STATE_TIMEOUT: + if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT: + return ALERT_CRITICAL_TIMEOUT + return ALERT_CRITICAL_REBOOT + + # Return current alert from selfdrive state + return Alert( text1=ss.alertText1, text2=ss.alertText2, alert_type=ss.alertType, size=self._get_enum_value(ss.alertSize, log.SelfdriveState.AlertSize), - status=alert_status, - ) - - # Handle selfdrive timeout - ss_missing = (np.uint64(rl.get_time() * 1e9) - sm.recv_time['selfdriveState']) / 1e9 - if selfdrive_frame < self.started_frame: - return Alert( - text1="openpilot Unavailable", - text2="Waiting to start", - alert_type="selfdriveWaiting", - size=log.SelfdriveState.AlertSize.mid, - status=log.SelfdriveState.AlertStatus.normal, - ) - elif ss_missing > SELFDRIVE_STATE_TIMEOUT: - if ss.enabled and (ss_missing - SELFDRIVE_STATE_TIMEOUT) < SELFDRIVE_UNRESPONSIVE_TIMEOUT: - return Alert( - text1="TAKE CONTROL IMMEDIATELY", - text2="System Unresponsive", - alert_type="selfdriveUnresponsive", - size=log.SelfdriveState.AlertSize.full, - status=log.SelfdriveState.AlertStatus.critical, - ) - return Alert( - text1="System Unresponsive", - text2="Reboot Device", - alert_type="selfdriveUnresponsivePermanent", - size=log.SelfdriveState.AlertSize.mid, - status=log.SelfdriveState.AlertStatus.normal, - ) - - return Alert() + status=self._get_enum_value(ss.alertStatus, log.SelfdriveState.AlertStatus)) def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster) -> None: """Render the alert within the specified rectangle.""" - self.update_state(sm, sm.recv_frame['selfdriveState']) + self.update_state(sm) alert_size = self._get_enum_value(self.alert.size, log.SelfdriveState.AlertSize) if alert_size == log.SelfdriveState.AlertSize.none: return # Calculate alert rectangle margin = 0 if alert_size == log.SelfdriveState.AlertSize.full else 40 - radius = 0 if alert_size == log.SelfdriveState.AlertSize.full else 30 height = ALERT_HEIGHTS.get(alert_size, rect.height) alert_rect = rl.Rectangle( rect.x + margin, @@ -129,7 +116,7 @@ class AlertRenderer: alert_status = self._get_enum_value(self.alert.status, log.SelfdriveState.AlertStatus) color = ALERT_COLORS.get(alert_status, ALERT_COLORS[log.SelfdriveState.AlertStatus.normal]) if alert_size != log.SelfdriveState.AlertSize.full: - roundness = radius / (min(alert_rect.width, alert_rect.height) / 2) + roundness = ALERT_BORDER_RADIUS / (min(alert_rect.width, alert_rect.height) / 2) rl.draw_rectangle_rounded(alert_rect, roundness, 10, color) else: rl.draw_rectangle_rec(alert_rect, color)