|  |  | @ -1,14 +1,15 @@ | 
			
		
	
		
		
			
				
					
					|  |  |  | import numpy as np |  |  |  | import time | 
			
				
				
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | import pyray as rl |  |  |  | import pyray as rl | 
			
		
	
		
		
			
				
					
					|  |  |  | from dataclasses import dataclass |  |  |  | from dataclasses import dataclass | 
			
		
	
		
		
			
				
					
					|  |  |  | from cereal import messaging, log |  |  |  | from cereal import messaging, log | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | from openpilot.system.hardware import TICI | 
			
		
	
		
		
			
				
					
					|  |  |  | from openpilot.system.ui.lib.application import gui_app, FontWeight |  |  |  | from openpilot.system.ui.lib.application import gui_app, FontWeight | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | # Constants |  |  |  | # Constants | 
			
		
	
		
		
			
				
					
					|  |  |  | ALERT_COLORS = { |  |  |  | ALERT_COLORS = { | 
			
		
	
		
		
			
				
					
					|  |  |  |   log.SelfdriveState.AlertStatus.normal: rl.Color(0, 0, 0, 150),  # Black |  |  |  |   log.SelfdriveState.AlertStatus.normal: rl.Color(0, 0, 0, 220),  # Black | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   log.SelfdriveState.AlertStatus.userPrompt: rl.Color(0xFE, 0x8C, 0x34, 100),  # Orange |  |  |  |   log.SelfdriveState.AlertStatus.userPrompt: rl.Color(0xFE, 0x8C, 0x34, 220),  # Orange | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |   log.SelfdriveState.AlertStatus.critical: rl.Color(0xC9, 0x22, 0x31, 150),  # Red |  |  |  |   log.SelfdriveState.AlertStatus.critical: rl.Color(0xC9, 0x22, 0x31, 220),  # Red | 
			
				
				
			
		
	
		
		
	
		
		
	
		
		
	
		
		
			
				
					
					|  |  |  | } |  |  |  | } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | ALERT_HEIGHTS = { |  |  |  | ALERT_HEIGHTS = { | 
			
		
	
	
		
		
			
				
					|  |  | @ -16,6 +17,7 @@ ALERT_HEIGHTS = { | 
			
		
	
		
		
			
				
					
					|  |  |  |   log.SelfdriveState.AlertSize.mid: 420, |  |  |  |   log.SelfdriveState.AlertSize.mid: 420, | 
			
		
	
		
		
			
				
					
					|  |  |  | } |  |  |  | } | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  | ALERT_BORDER_RADIUS = 30 | 
			
		
	
		
		
			
				
					
					|  |  |  | SELFDRIVE_STATE_TIMEOUT = 5  # Seconds |  |  |  | SELFDRIVE_STATE_TIMEOUT = 5  # Seconds | 
			
		
	
		
		
			
				
					
					|  |  |  | SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10  # Seconds |  |  |  | SELFDRIVE_UNRESPONSIVE_TIMEOUT = 10  # Seconds | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -28,14 +30,30 @@ class Alert: | 
			
		
	
		
		
			
				
					
					|  |  |  |   size: log.SelfdriveState.AlertSize = log.SelfdriveState.AlertSize.none |  |  |  |   size: log.SelfdriveState.AlertSize = log.SelfdriveState.AlertSize.none | 
			
		
	
		
		
			
				
					
					|  |  |  |   status: log.SelfdriveState.AlertStatus = log.SelfdriveState.AlertStatus.normal |  |  |  |   status: log.SelfdriveState.AlertStatus = log.SelfdriveState.AlertStatus.normal | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   def is_equal(self, other: 'Alert') -> bool: |  |  |  | 
 | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     """Check if two alerts are equal.""" |  |  |  | # Pre-defined alert instances | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     return ( |  |  |  | ALERT_STARTUP_PENDING = Alert( | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       self.text1 == other.text1 |  |  |  |   text1="openpilot Unavailable", | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       and self.text2 == other.text2 |  |  |  |   text2="Waiting to start", | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       and self.alert_type == other.alert_type |  |  |  |   alert_type="selfdriveWaiting", | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       and self.size == other.size |  |  |  |   size=log.SelfdriveState.AlertSize.mid, | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       and self.status == other.status |  |  |  |   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, | 
			
		
	
		
		
			
				
					
					|  |  |  | ) |  |  |  | ) | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
	
		
		
			
				
					|  |  | @ -43,80 +61,49 @@ class AlertRenderer: | 
			
		
	
		
		
			
				
					
					|  |  |  |   def __init__(self): |  |  |  |   def __init__(self): | 
			
		
	
		
		
			
				
					
					|  |  |  |     """Initialize the alert renderer.""" |  |  |  |     """Initialize the alert renderer.""" | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.alert: Alert = Alert() |  |  |  |     self.alert: Alert = Alert() | 
			
		
	
		
		
			
				
					
					|  |  |  |  |  |  |  |     # TODO: use ui_state to determine when to start | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.started_frame: int = 0 |  |  |  |     self.started_frame: int = 0 | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL) |  |  |  |     self.font_regular: rl.Font = gui_app.font(FontWeight.NORMAL) | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) |  |  |  |     self.font_bold: rl.Font = gui_app.font(FontWeight.BOLD) | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.font_metrics_cache: dict[tuple[str, int, str], rl.Vector2] = {} |  |  |  |     self.font_metrics_cache: dict[tuple[str, int, str], rl.Vector2] = {} | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   def clear(self) -> None: |  |  |  |   def update_state(self, sm: messaging.SubMaster) -> None: | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     """Reset the alert to its default state.""" |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.alert = Alert() |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |   def update_state(self, sm: messaging.SubMaster, started_frame: int) -> None: |  |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  |     """Update alert state based on SubMaster data.""" |  |  |  |     """Update alert state based on SubMaster data.""" | 
			
		
	
		
		
			
				
					
					|  |  |  |     self.started_frame = started_frame |  |  |  |     self.alert = self.get_alert(sm) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     new_alert = self.get_alert(sm) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     if not self.alert.is_equal(new_alert): |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |       self.alert = new_alert |  |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   def get_alert(self, sm: messaging.SubMaster) -> Alert: |  |  |  |   def get_alert(self, sm: messaging.SubMaster) -> Alert: | 
			
		
	
		
		
			
				
					
					|  |  |  |     """Generate the current alert based on selfdrive state.""" |  |  |  |     """Generate the current alert based on selfdrive state.""" | 
			
		
	
		
		
			
				
					
					|  |  |  |     if not sm.valid['selfdriveState']: |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |       return Alert() |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     ss = sm['selfdriveState'] |  |  |  |     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 |  |  |  |     # Check if waiting to start | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |     if selfdrive_frame >= self.started_frame: |  |  |  |     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( |  |  |  |     return Alert( | 
			
		
	
		
		
			
				
					
					|  |  |  |         text1=ss.alertText1, |  |  |  |         text1=ss.alertText1, | 
			
		
	
		
		
			
				
					
					|  |  |  |         text2=ss.alertText2, |  |  |  |         text2=ss.alertText2, | 
			
		
	
		
		
			
				
					
					|  |  |  |         alert_type=ss.alertType, |  |  |  |         alert_type=ss.alertType, | 
			
		
	
		
		
			
				
					
					|  |  |  |         size=self._get_enum_value(ss.alertSize, log.SelfdriveState.AlertSize), |  |  |  |         size=self._get_enum_value(ss.alertSize, log.SelfdriveState.AlertSize), | 
			
		
	
		
		
			
				
					
					|  |  |  |         status=alert_status, |  |  |  |         status=self._get_enum_value(ss.alertStatus, log.SelfdriveState.AlertStatus)) | 
			
				
				
			
		
	
		
		
			
				
					
					|  |  |  |       ) |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  |  | 
			
		
	
		
		
			
				
					
					|  |  |  |     # 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() |  |  |  |  | 
			
		
	
		
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |   def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster) -> None: |  |  |  |   def draw(self, rect: rl.Rectangle, sm: messaging.SubMaster) -> None: | 
			
		
	
		
		
			
				
					
					|  |  |  |     """Render the alert within the specified rectangle.""" |  |  |  |     """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) |  |  |  |     alert_size = self._get_enum_value(self.alert.size, log.SelfdriveState.AlertSize) | 
			
		
	
		
		
			
				
					
					|  |  |  |     if alert_size == log.SelfdriveState.AlertSize.none: |  |  |  |     if alert_size == log.SelfdriveState.AlertSize.none: | 
			
		
	
		
		
			
				
					
					|  |  |  |       return |  |  |  |       return | 
			
		
	
		
		
			
				
					
					|  |  |  | 
 |  |  |  | 
 | 
			
		
	
		
		
			
				
					
					|  |  |  |     # Calculate alert rectangle |  |  |  |     # Calculate alert rectangle | 
			
		
	
		
		
			
				
					
					|  |  |  |     margin = 0 if alert_size == log.SelfdriveState.AlertSize.full else 40 |  |  |  |     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) |  |  |  |     height = ALERT_HEIGHTS.get(alert_size, rect.height) | 
			
		
	
		
		
			
				
					
					|  |  |  |     alert_rect = rl.Rectangle( |  |  |  |     alert_rect = rl.Rectangle( | 
			
		
	
		
		
			
				
					
					|  |  |  |       rect.x + margin, |  |  |  |       rect.x + margin, | 
			
		
	
	
		
		
			
				
					|  |  | @ -129,7 +116,7 @@ class AlertRenderer: | 
			
		
	
		
		
			
				
					
					|  |  |  |     alert_status = self._get_enum_value(self.alert.status, log.SelfdriveState.AlertStatus) |  |  |  |     alert_status = self._get_enum_value(self.alert.status, log.SelfdriveState.AlertStatus) | 
			
		
	
		
		
			
				
					
					|  |  |  |     color = ALERT_COLORS.get(alert_status, ALERT_COLORS[log.SelfdriveState.AlertStatus.normal]) |  |  |  |     color = ALERT_COLORS.get(alert_status, ALERT_COLORS[log.SelfdriveState.AlertStatus.normal]) | 
			
		
	
		
		
			
				
					
					|  |  |  |     if alert_size != log.SelfdriveState.AlertSize.full: |  |  |  |     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) |  |  |  |       rl.draw_rectangle_rounded(alert_rect, roundness, 10, color) | 
			
		
	
		
		
			
				
					
					|  |  |  |     else: |  |  |  |     else: | 
			
		
	
		
		
			
				
					
					|  |  |  |       rl.draw_rectangle_rec(alert_rect, color) |  |  |  |       rl.draw_rectangle_rec(alert_rect, color) | 
			
		
	
	
		
		
			
				
					|  |  | 
 |