Files
StarPilot/openpilot/selfdrive/ui/mici/onroad/long_indicator.py
T
firestar5683 a82935f326 User interfaces and Android Auto
Integrate onroad views, configurable widgets, local settings, steering-profile contracts, Bluetooth and Android Auto with their device and runtime services.

Co-authored-by: Adeeb Shihadeh <adeebshihadeh@gmail.com>
Co-authored-by: Daniel Koepping <elkoled@gmail.com>
Co-authored-by: Danny <dannyirwin31@yahoo.com>
Co-authored-by: DimitriChrysafis <Dimitri.chrysafis@gmail.com>
Co-authored-by: Dom <168790843+firestar5683@users.noreply.github.com>
Co-authored-by: Harald Schäfer <harald.the.engineer@gmail.com>
Co-authored-by: Prabhaav Pillai <prabhaav@msu.edu>
Co-authored-by: Shane Smiskol <shane@smiskol.com>
Co-authored-by: dirwin31 <83434411+dirwin31@users.noreply.github.com>
Co-authored-by: firestarsdog <229254897+firestarsdog@users.noreply.github.com>
Co-authored-by: stef <19478336+stefpi@users.noreply.github.com>
Co-authored-by: whoisdomi <dcantu83@gmail.com>

Co-authored-by: Intelli <contact@intelli.software>
Co-authored-by: pharmacomaniac <blittle65@gmail.com>

Co-authored-by: prabhaavp <prabhaav@msu.edu>
2026-10-14 20:02:39 -05:00

117 lines
6.1 KiB
Python

import pyray as rl
from openpilot.cereal import log
from openpilot.common.filter_simple import FirstOrderFilter
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget
HIGHLIGHT_TIME = 2.5 # seconds
class LongIndicator(Widget):
def __init__(self):
super().__init__()
self._txt_lead_car = (self._texture('car', 35, 27), self._texture('car_green', 50, 42))
self._txt_distance = [(self._texture('distance_1', 32, 7), self._texture('distance_1_green', 60, 35)),
(self._texture('distance_2', 40, 9), self._texture('distance_2_green', 68, 37)),
(self._texture('distance_3', 48, 11), self._texture('distance_3_green', 76, 39))]
self._alpha_filter = FirstOrderFilter(0.0, 0.05, 1 / gui_app.target_fps)
# crossfade between states, a white and a green filter per icon
self._lead_car_filters = (FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps), FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps))
self._distance_filters = [(FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps), FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps))
for _ in range(3)]
self._personality: int | None = None
self._personality_changed_time = -HIGHLIGHT_TIME
self._should_draw = False
self._sidebar = False
self._traffic_mode = False
self._sidebar_personality: int | None = None
self._sidebar_long_active: bool | None = None
@staticmethod
def _texture(name: str, width: int, height: int) -> rl.Texture:
return gui_app.texture(f'icons_mici/longitudinal/{name}.png', width, height, keep_aspect_ratio=False)
def set_should_draw(self, should_draw: bool):
self._should_draw = should_draw
def render_sidebar(self, rect: rl.Rectangle, *, personality: int | None = None,
longitudinal_active: bool = False, traffic_mode: bool = False) -> None:
self._sidebar, self._traffic_mode = True, traffic_mode
self._sidebar_personality = personality
self._sidebar_long_active = longitudinal_active if personality is not None else None
try:
self.render(rect)
finally:
self._sidebar, self._traffic_mode = False, False
self._sidebar_personality = self._sidebar_long_active = None
def _render(self, rect: rl.Rectangle) -> None:
sm = ui_state.sm
if self._sidebar_personality is None and (sm.recv_frame['selfdriveState'] < ui_state.started_frame or
not sm['selfdriveState'].enabled or not ui_state.has_longitudinal_control):
self._personality = None
self._personality_changed_time = -HIGHLIGHT_TIME
self._alpha_filter.x = 0.0
return
# hidden under alerts and set speed
visible = self._should_draw and (self._sidebar_personality is not None or
sm['selfdriveState'].alertSize == log.SelfdriveState.AlertSize.none)
alpha = self._alpha_filter.update(visible)
self._draw_lead_car(rect, alpha)
self._draw_distance_bars(rect, alpha, visible)
def _draw_lead_car(self, rect: rl.Rectangle, alpha: float) -> None:
sm = ui_state.sm
plan = sm['longitudinalPlan']
has_lead = sm.alive['longitudinalPlan'] and plan.hasLead
longitudinal_active = (self._sidebar_long_active if self._sidebar_long_active is not None else
sm.alive['carControl'] and sm['carControl'].longActive)
engaged_lead = has_lead and longitudinal_active
white_target = 0.0 if engaged_lead else (1.0 if has_lead else 0.35)
white_f, green_f = self._lead_car_filters
if not longitudinal_active:
white_f.x, green_f.x = white_target, 0.0
white_alpha = white_f.update(white_target)
green_alpha = green_f.update(float(engaged_lead))
white, green = self._txt_lead_car
self._draw_centered(white, rect, 100, white_alpha * alpha)
self._draw_centered(green, rect, 100, green_alpha * alpha)
def _draw_distance_bars(self, rect: rl.Rectangle, alpha: float, visible: bool) -> None:
sm = ui_state.sm
now = rl.get_time()
personality = self._sidebar_personality if self._sidebar_personality is not None else sm['selfdriveState'].personality.raw
if self._personality is not None and personality != self._personality:
self._personality_changed_time = now
self._personality = personality
# the personality alert covers the bars, hold the highlight until they show
if not visible and now - self._personality_changed_time < HIGHLIGHT_TIME:
self._personality_changed_time = now
highlight = now - self._personality_changed_time < HIGHLIGHT_TIME
# blink at double the turn signal rate (2.67 Hz) while overriding the gas
overriding = self._sidebar_long_active is not False and any(
e.name == log.OnroadEvent.EventName.gasPressedOverride for e in sm['onroadEvents'])
blink = 0.35 / 0.9 if overriding and now % 0.375 > 0.1875 else 1.0
count = 1 if self._traffic_mode else personality + 1
for i, ((white, green), y, (active_f, green_f)) in enumerate(zip(self._txt_distance, (122, 136, 152), self._distance_filters, strict=True)):
active = active_f.update(float(i < count))
green_alpha = green_f.update(float(highlight and i == count - 1 and not self._traffic_mode))
# only lit bars blink on override
color = (200, 32, 48) if self._traffic_mode and i < count else (255, 255, 255)
self._draw_centered(white, rect, y, (0.35 * (1 - active) + 0.9 * (active - green_alpha) * blink) * alpha, color)
self._draw_centered(green, rect, y, green_alpha * blink * alpha)
def _draw_centered(self, texture: rl.Texture, rect: rl.Rectangle, y: float, alpha: float,
color: tuple[int, int, int] = (255, 255, 255)) -> None:
scale = min((rect.width - 4) / 76, (rect.height - 4) / 94) if self._sidebar else 1.0
center_x = rect.x + rect.width / 2 if self._sidebar else rect.x + 46
center_y = rect.y + rect.height / 2 + (y - 126) * scale if self._sidebar else rect.y + y
pos = rl.Vector2(center_x - texture.width * scale / 2, center_y - texture.height * scale / 2)
rl.draw_texture_ex(texture, pos, 0.0, scale, rl.Color(*color, round(255 * alpha)))