From a9229e11a067ef75c64565f4065b58f6bb38e404 Mon Sep 17 00:00:00 2001 From: Jason Wen Date: Sun, 8 Feb 2026 19:40:04 -0500 Subject: [PATCH] [TIZI/TICI] ui: standstill timer (#1677) * standstill timer * final --- .../{e2e_alerts.py => circular_alerts.py} | 59 ++++++++++++++++--- .../ui/sunnypilot/onroad/hud_renderer.py | 8 +-- selfdrive/ui/sunnypilot/ui_state.py | 1 + 3 files changed, 55 insertions(+), 13 deletions(-) rename selfdrive/ui/sunnypilot/onroad/{e2e_alerts.py => circular_alerts.py} (59%) diff --git a/selfdrive/ui/sunnypilot/onroad/e2e_alerts.py b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py similarity index 59% rename from selfdrive/ui/sunnypilot/onroad/e2e_alerts.py rename to selfdrive/ui/sunnypilot/onroad/circular_alerts.py index b15db94c16..965ce7fe77 100644 --- a/selfdrive/ui/sunnypilot/onroad/e2e_alerts.py +++ b/selfdrive/ui/sunnypilot/onroad/circular_alerts.py @@ -14,7 +14,7 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight, FONT_SCALE from openpilot.system.ui.lib.text_measure import measure_text_cached -class E2eAlertsRenderer: +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) @@ -23,6 +23,9 @@ class E2eAlertsRenderer: self._e2e_alert_frame = 0 self._green_light_alert = False self._lead_depart_alert = False + self._standstill_timer = False + self._standstill_elapsed_time = 0.0 + self._is_standstill = False self._alert_text = "" self._alert_img = None self._allow_e2e_alerts = False @@ -30,14 +33,22 @@ class E2eAlertsRenderer: 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._standstill_timer = ui_state.standstill_timer + 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 @@ -49,11 +60,22 @@ class E2eAlertsRenderer: elif self._lead_depart_alert: self._alert_text = "LEAD VEHICLE\nDEPARTING" self._alert_img = self._lead_depart_alert_img + + elif self._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: + if not self._allow_e2e_alerts or (self._e2e_alert_display_timer <= 0 and not (self._standstill_timer and self._is_standstill)): return e2e_alert_size = 250 @@ -67,7 +89,12 @@ class E2eAlertsRenderer: # Pulse logic is_pulsing = (self._e2e_alert_frame % gui_app.target_fps) < (gui_app.target_fps / 2.5) - frame_color = rl.Color(255, 255, 255, 75) if is_pulsing else rl.Color(0, 255, 0, 75) + + # Standstill Timer (STOPPED) should be static white + if self._e2e_alert_display_timer == 0 and self._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)) @@ -75,7 +102,7 @@ class E2eAlertsRenderer: 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: + if self._alert_img and self._e2e_alert_display_timer > 0: img_x = int(center.x - self._alert_img.width / 2) img_y = int(center.y - self._alert_img.height / 2) rl.draw_texture(self._alert_img, img_x, img_y, rl.WHITE) @@ -94,8 +121,22 @@ class E2eAlertsRenderer: # Draw lines upwards from bottom current_y = bottom_y - (len(lines) * text_size * FONT_SCALE) - 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 + if self._e2e_alert_display_timer == 0 and self._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 diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index d99e3cb0d1..f765936d6e 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -15,7 +15,7 @@ from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel from openpilot.selfdrive.ui.sunnypilot.onroad.speed_limit import SpeedLimitRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.smart_cruise_control import SmartCruiseControlRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController -from openpilot.selfdrive.ui.sunnypilot.onroad.e2e_alerts import E2eAlertsRenderer +from openpilot.selfdrive.ui.sunnypilot.onroad.circular_alerts import CircularAlertsRenderer class HudRendererSP(HudRenderer): @@ -27,7 +27,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer = SpeedLimitRenderer() self.smart_cruise_control_renderer = SmartCruiseControlRenderer() self.turn_signal_controller = TurnSignalController() - self.e2e_alerts_renderer = E2eAlertsRenderer() + self.circular_alerts_renderer = CircularAlertsRenderer() self._torque_bar = TorqueBar(scale=3.0, always=True) def _update_state(self) -> None: @@ -36,7 +36,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer.update() self.smart_cruise_control_renderer.update() self.turn_signal_controller.update() - self.e2e_alerts_renderer.update() + self.circular_alerts_renderer.update() def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) @@ -52,7 +52,7 @@ class HudRendererSP(HudRenderer): self.speed_limit_renderer.render(rect) self.smart_cruise_control_renderer.render(rect) self.turn_signal_controller.render(rect) - self.e2e_alerts_renderer.render(rect) + self.circular_alerts_renderer.render(rect) if ui_state.rocket_fuel: self.rocket_fuel.render(rect, ui_state.sm) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index 98ddbe5266..89a650bae9 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -129,6 +129,7 @@ class UIStateSP: self.active_bundle = self.params.get("ModelManager_ActiveBundle") self.custom_interactive_timeout = self.params.get("InteractivityTimeout", return_default=True) self.speed_limit_mode = self.params.get("SpeedLimitMode", return_default=True) + self.standstill_timer = self.params.get_bool("StandstillTimer") # Onroad Screen Brightness self.onroad_brightness = int(float(self.params.get("OnroadScreenOffBrightness", return_default=True)))