From b88c8865926f20ee2ca140c6d07bc77a071680f3 Mon Sep 17 00:00:00 2001 From: firestarsdog <229254897+firestarsdog@users.noreply.github.com> Date: Sat, 23 May 2026 02:19:23 -0400 Subject: [PATCH] BigUI WIP: Aethergauge --- selfdrive/ui/onroad/starpilot/aethergauge.py | 135 ++++++++++++++++++ .../onroad/starpilot/starpilot_onroad_view.py | 3 + 2 files changed, 138 insertions(+) create mode 100644 selfdrive/ui/onroad/starpilot/aethergauge.py diff --git a/selfdrive/ui/onroad/starpilot/aethergauge.py b/selfdrive/ui/onroad/starpilot/aethergauge.py new file mode 100644 index 000000000..f86cdfcc7 --- /dev/null +++ b/selfdrive/ui/onroad/starpilot/aethergauge.py @@ -0,0 +1,135 @@ +from dataclasses import dataclass +import math +import pyray as rl +from openpilot.common.constants import CV +from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.system.ui.lib.text_measure import measure_text_cached +from openpilot.selfdrive.ui.onroad.starpilot.starpilot_border import _csc_state, _intensity, _glow_color + +@dataclass +class AetherGaugeData: + text: str + unit: str = "" + label: str = "" + animation: str = "none" # e.g. "down_arrows" + color: rl.Color = rl.WHITE + +class AetherGaugeSource: + def is_active(self) -> bool: + return False + + def get_gauge_data(self) -> AetherGaugeData: + raise NotImplementedError + +class CurveSpeedSource(AetherGaugeSource): + def is_active(self) -> bool: + state = _csc_state() + return state is not None and state['active'] + + def get_gauge_data(self) -> AetherGaugeData: + state = _csc_state() + if state is None: + return AetherGaugeData(text="") + + plan = ui_state.sm["starpilotPlan"] + car_state = ui_state.sm["carState"] + + speed_conversion = CV.MS_TO_KPH if ui_state.is_metric else CV.MS_TO_MPH + speed_unit = "km/h" if ui_state.is_metric else "mph" + + csc_speed = getattr(plan, "cscSpeed", 0.0) + v_ego = car_state.vEgo if car_state else 0.0 + + csc_speed_val = int(round(min(v_ego, csc_speed) * speed_conversion)) + + intensity = _intensity(state['curvature']) + color = _glow_color(intensity) + + return AetherGaugeData( + text=str(csc_speed_val), + unit=speed_unit, + label="CURVE", + animation="down_arrows", + color=color + ) + +class AetherGauge: + def __init__(self): + self.sources: list[AetherGaugeSource] = [CurveSpeedSource()] + + def get_active_data(self) -> AetherGaugeData | None: + for source in self.sources: + if source.is_active(): + return source.get_gauge_data() + return None + + def render(self, rect: rl.Rectangle, font_bold: rl.Font, font_medium: rl.Font): + data = self.get_active_data() + if not data: + return + + cx = rect.x + rect.width / 2 + cy = rect.y + 340 + + if data.animation == "down_arrows": + self._render_down_arrows(cx, cy, data.color) + text_y = cy + 50 + else: + text_y = cy + + self._render_text(cx, text_y, data, font_bold, font_medium) + + def _render_down_arrows(self, cx: float, cy: float, color: rl.Color): + progress = (rl.get_time() * 1.5) % 1.0 + spacing = 16.0 + chevron_w = 20.0 + chevron_h = 10.0 + thickness = 4.0 + + for i in range(3): + y_off = (i + progress) * spacing + y = cy + y_off + + dist_normalized = (i + progress) / 3.0 + alpha_factor = math.sin(dist_normalized * math.pi) + alpha = int(color.a * alpha_factor) + + chevron_color = rl.Color(color.r, color.g, color.b, alpha) + + # Drop shadow (black with half of chevron's alpha) + shadow_color = rl.Color(0, 0, 0, int(alpha * 0.5)) + rl.draw_line_ex(rl.Vector2(int(cx - chevron_w), int(y - chevron_h + 2)), rl.Vector2(int(cx), int(y + 2)), thickness, shadow_color) + rl.draw_line_ex(rl.Vector2(int(cx + chevron_w), int(y - chevron_h + 2)), rl.Vector2(int(cx), int(y + 2)), thickness, shadow_color) + + # Main chevron lines + rl.draw_line_ex(rl.Vector2(int(cx - chevron_w), int(y - chevron_h)), rl.Vector2(int(cx), int(y)), thickness, chevron_color) + rl.draw_line_ex(rl.Vector2(int(cx + chevron_w), int(y - chevron_h)), rl.Vector2(int(cx), int(y)), thickness, chevron_color) + + def _render_text(self, cx: float, cy: float, data: AetherGaugeData, font_bold: rl.Font, font_medium: rl.Font): + speed_text = data.text + unit_text = data.unit + + speed_size = measure_text_cached(font_bold, speed_text, 48) + unit_size = measure_text_cached(font_medium, unit_text, 24) + + gap = 6 + total_w = speed_size.x + gap + unit_size.x + start_x = cx - total_w / 2 + + # Draw outline for speed + speed_pos = rl.Vector2(int(start_x), int(cy)) + rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x - 1, speed_pos.y - 1), 48, 0, rl.BLACK) + rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x + 1, speed_pos.y - 1), 48, 0, rl.BLACK) + rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x - 1, speed_pos.y + 1), 48, 0, rl.BLACK) + rl.draw_text_ex(font_bold, speed_text, rl.Vector2(speed_pos.x + 1, speed_pos.y + 1), 48, 0, rl.BLACK) + rl.draw_text_ex(font_bold, speed_text, speed_pos, 48, 0, rl.WHITE) + + # Draw outline for unit + unit_y = cy + (speed_size.y - unit_size.y) - 2 + unit_pos = rl.Vector2(int(start_x + speed_size.x + gap), int(unit_y)) + + rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x - 1, unit_pos.y - 1), 24, 0, rl.BLACK) + rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x + 1, unit_pos.y - 1), 24, 0, rl.BLACK) + rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x - 1, unit_pos.y + 1), 24, 0, rl.BLACK) + rl.draw_text_ex(font_medium, unit_text, rl.Vector2(unit_pos.x + 1, unit_pos.y + 1), 24, 0, rl.BLACK) + rl.draw_text_ex(font_medium, unit_text, unit_pos, 24, 0, data.color) diff --git a/selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py b/selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py index f1300ecf2..6eab56930 100644 --- a/selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py +++ b/selfdrive/ui/onroad/starpilot/starpilot_onroad_view.py @@ -11,6 +11,7 @@ from openpilot.selfdrive.ui.onroad.starpilot.slc_speed_limit import ( SET_SPEED_WIDTH_IMP, SET_SPEED_WIDTH_MET, SET_SPEED_HEIGHT, SIGN_MARGIN, ) from openpilot.selfdrive.ui.ui_state import ui_state +from openpilot.selfdrive.ui.onroad.starpilot.aethergauge import AetherGauge from openpilot.selfdrive.ui.lib.starpilot_status import ( get_screen_edge_color, CEM_OVERRIDE_COLOR, ENGAGED_COLOR, EXPERIMENTAL_COLOR, TRAFFIC_COLOR, @@ -29,6 +30,7 @@ class StarPilotOnroadView(AugmentedRoadView): self._font_medium = gui_app.font(FontWeight.MEDIUM) self._standstill_started_at = 0.0 self._smoothed_steer = 0.0 + self._aethergauge = AetherGauge() def _render(self, rect: rl.Rectangle): self._position_personality_button() @@ -60,6 +62,7 @@ class StarPilotOnroadView(AugmentedRoadView): self._render_road_name() self._render_standstill_timer() self._render_developer_metrics() + self._aethergauge.render(self._content_rect, self._font_bold, self._font_medium) self._render_bottom_row_widgets() self._render_pedals()