version: sunnypilot v2026.002.000 (staging) date: 2026-07-21T07:44:02 master commit: 2c334ede443d7391d27575af8c854a095ba702a8
141 lines
5.9 KiB
Python
141 lines
5.9 KiB
Python
"""
|
|
Copyright (c) 2021-, Haibin Wen, sunnypilot, and a number of other contributors.
|
|
|
|
This file is part of sunnypilot and is licensed under the MIT License.
|
|
See the LICENSE.md file in the root directory for more details.
|
|
"""
|
|
import pyray as rl
|
|
|
|
from openpilot.cereal import log
|
|
from openpilot.selfdrive.ui import UI_BORDER_SIZE
|
|
from openpilot.selfdrive.ui.ui_state import ui_state
|
|
from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiState
|
|
from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE
|
|
from openpilot.system.ui.lib.text_measure import measure_text_cached
|
|
|
|
|
|
class CircularAlertsRenderer:
|
|
def __init__(self):
|
|
self._green_light_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/green_light.png", 250, 250)
|
|
self._lead_depart_alert_img = gui_app.texture("../../sunnypilot/selfdrive/assets/images/lead_depart.png", 250, 250)
|
|
|
|
self._e2e_alert_display_timer = 0
|
|
self._e2e_alert_frame = 0
|
|
self._green_light_alert = False
|
|
self._lead_depart_alert = False
|
|
self._standstill_elapsed_time = 0.0
|
|
self._is_standstill = False
|
|
self._alert_text = ""
|
|
self._alert_img = None
|
|
self._allow_e2e_alerts = False
|
|
|
|
def update(self) -> None:
|
|
sm = ui_state.sm
|
|
lp_sp = sm['longitudinalPlanSP']
|
|
car_state = sm['carState']
|
|
self._green_light_alert = lp_sp.e2eAlerts.greenLightAlert
|
|
self._lead_depart_alert = lp_sp.e2eAlerts.leadDepartAlert
|
|
self._is_standstill = car_state.standstill
|
|
|
|
if not ui_state.started:
|
|
self._standstill_elapsed_time = 0.0
|
|
|
|
self._allow_e2e_alerts = sm['selfdriveState'].alertSize == log.SelfdriveState.AlertSize.none and \
|
|
sm.recv_frame['driverStateV2'] > ui_state.started_frame
|
|
|
|
if self._green_light_alert or self._lead_depart_alert:
|
|
self._e2e_alert_display_timer = 3 * gui_app.target_fps
|
|
# reset onroad sleep timer for e2e alerts
|
|
ui_state.reset_onroad_sleep_timer()
|
|
|
|
if self._e2e_alert_display_timer > 0:
|
|
self._e2e_alert_frame += 1
|
|
self._e2e_alert_display_timer -= 1
|
|
|
|
if self._green_light_alert:
|
|
self._alert_text = "GREEN\nLIGHT"
|
|
self._alert_img = self._green_light_alert_img
|
|
elif self._lead_depart_alert:
|
|
self._alert_text = "LEAD VEHICLE\nDEPARTING"
|
|
self._alert_img = self._lead_depart_alert_img
|
|
|
|
elif ui_state.standstill_timer and self._is_standstill:
|
|
self._alert_img = None
|
|
self._standstill_elapsed_time += 1.0 / gui_app.target_fps
|
|
minute = int(self._standstill_elapsed_time / 60)
|
|
second = int(self._standstill_elapsed_time - (minute * 60))
|
|
self._alert_text = f"{minute:01d}:{second:02d}"
|
|
self._e2e_alert_frame += 1
|
|
|
|
else:
|
|
self._e2e_alert_frame = 0
|
|
if not self._is_standstill:
|
|
self._standstill_elapsed_time = 0.0
|
|
|
|
def render(self, rect: rl.Rectangle) -> None:
|
|
if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (ui_state.standstill_timer and self._is_standstill)):
|
|
return
|
|
|
|
e2e_alert_size = 250
|
|
dev_ui_width_adjustment = 180 if ui_state.developer_ui in (DeveloperUiState.RIGHT, DeveloperUiState.BOTH) else 100
|
|
|
|
x = rect.x + rect.width - e2e_alert_size - dev_ui_width_adjustment - (UI_BORDER_SIZE * 3)
|
|
y = rect.y + rect.height / 2 + 20
|
|
|
|
alert_rect = rl.Rectangle(x - e2e_alert_size, y - e2e_alert_size, e2e_alert_size * 2, e2e_alert_size * 2)
|
|
center = rl.Vector2(alert_rect.x + alert_rect.width / 2, alert_rect.y + alert_rect.height / 2)
|
|
|
|
# Pulse logic
|
|
is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5)
|
|
|
|
# Standstill Timer (STOPPED) should be static white
|
|
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
|
|
frame_color = rl.Color(255, 255, 255, 75)
|
|
else:
|
|
frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75)
|
|
|
|
# Draw Circle
|
|
rl.draw_circle_v(center, e2e_alert_size, rl.Color(0, 0, 0, 190))
|
|
# Draw Ring (Border)
|
|
rl.draw_ring(center, e2e_alert_size - 7.5, e2e_alert_size + 7.5, 0, 360, 0, frame_color)
|
|
|
|
# Draw Image
|
|
if self._alert_img and self._e2e_alert_display_timer > 0:
|
|
img_x = center.x - self._alert_img.width / 2
|
|
img_y = center.y - self._alert_img.height / 2
|
|
rl.draw_texture_ex(self._alert_img, rl.Vector2(img_x, img_y), 0.0, 1.0, rl.WHITE)
|
|
|
|
# Draw Text
|
|
txt_color = rl.Color(255, 255, 255, 255) if is_pulsing else rl.Color(0, 255, 0, 190)
|
|
font = gui_app.font(FontWeight.BOLD)
|
|
text_size = 48
|
|
spacing = 0
|
|
|
|
lines = self._alert_text.split('\n')
|
|
|
|
# Position text at bottom of alert circle
|
|
bottom_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 7)
|
|
|
|
# Draw lines upwards from bottom
|
|
current_y = bottom_y - (len(lines) * text_size * FONT_SCALE)
|
|
|
|
if self._e2e_alert_display_timer == 0 and ui_state.standstill_timer and self._is_standstill:
|
|
# Standstill Timer Text
|
|
alert_alt_text = "STOPPED"
|
|
top_text_size = 80
|
|
measure_top = measure_text_cached(font, alert_alt_text, top_text_size, spacing)
|
|
top_y = alert_rect.y + alert_rect.height / 3.5
|
|
rl.draw_text_ex(font, alert_alt_text, rl.Vector2(center.x - measure_top.x / 2, top_y), top_text_size, spacing, rl.Color(255, 175, 3, 240))
|
|
|
|
# Timer
|
|
timer_text_size = 100
|
|
measure_timer = measure_text_cached(font, self._alert_text, timer_text_size, spacing)
|
|
timer_y = (alert_rect.y + alert_rect.height) - (alert_rect.height / 5) - measure_timer.y
|
|
rl.draw_text_ex(font, self._alert_text, rl.Vector2(center.x - measure_timer.x / 2, timer_y), timer_text_size, spacing, rl.WHITE)
|
|
else:
|
|
for line in lines:
|
|
measure = measure_text_cached(font, line, text_size, spacing)
|
|
line_x = center.x - measure.x / 2
|
|
rl.draw_text_ex(font, line, rl.Vector2(line_x, current_y), text_size, spacing, txt_color)
|
|
current_y += text_size * FONT_SCALE
|