[TIZI/TICI] ui: MICI style turn signals (#1504)

* mici turn signal for c3x

* sp dir

* decouple

* more

* ty

* refactor and slim down

* bigger

---------

Co-authored-by: Jason Wen <haibin.wen3@gmail.com>
This commit is contained in:
Kumar
2026-01-23 22:46:00 -07:00
committed by GitHub
parent 8c36739ebd
commit 76d50df466
2 changed files with 134 additions and 0 deletions

View File

@@ -10,6 +10,7 @@ from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.onroad.hud_renderer import HudRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.developer_ui import DeveloperUiRenderer
from openpilot.selfdrive.ui.sunnypilot.onroad.rocket_fuel import RocketFuel
from openpilot.selfdrive.ui.sunnypilot.onroad.turn_signal import TurnSignalController
class HudRendererSP(HudRenderer):
@@ -17,9 +18,17 @@ class HudRendererSP(HudRenderer):
super().__init__()
self.developer_ui = DeveloperUiRenderer()
self.rocket_fuel = RocketFuel()
self.turn_signal_controller = TurnSignalController()
def _update_state(self) -> None:
super()._update_state()
self.turn_signal_controller.update()
def _render(self, rect: rl.Rectangle) -> None:
super()._render(rect)
self.developer_ui.render(rect)
self.turn_signal_controller.render()
if ui_state.rocket_fuel:
self.rocket_fuel.render(rect, ui_state.sm)

View File

@@ -0,0 +1,125 @@
"""
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
import time
from dataclasses import dataclass
from openpilot.selfdrive.ui.ui_state import ui_state
from openpilot.selfdrive.ui.mici.onroad.alert_renderer import IconSide, TURN_SIGNAL_BLINK_PERIOD
from openpilot.system.ui.lib.application import gui_app
from openpilot.system.ui.widgets import Widget
from openpilot.common.filter_simple import FirstOrderFilter
@dataclass(frozen=True)
class TurnSignalConfig:
left_x: int = 870
left_y: int = 220
right_x: int = 1140
right_y: int = 220
size: int = 150
class TurnSignalWidget(Widget):
def __init__(self, direction: IconSide):
super().__init__()
self._direction = direction
self._active = False
self._turn_signal_timer = 0.0
self._turn_signal_alpha_filter = FirstOrderFilter(0.0, 0.3, 1 / gui_app.target_fps)
texture_path = f'icons_mici/onroad/turn_signal_{direction}.png'
self._texture = gui_app.texture(texture_path, 120, 109)
def _render(self, _):
if not self._active:
return
if time.monotonic() - self._turn_signal_timer > TURN_SIGNAL_BLINK_PERIOD:
self._turn_signal_timer = time.monotonic()
self._turn_signal_alpha_filter.x = 255 * 2
else:
self._turn_signal_alpha_filter.update(255 * 0.2)
icon_alpha = int(min(self._turn_signal_alpha_filter.x, 255))
if self._texture:
pos_x = int(self._rect.x + (self._rect.width - self._texture.width) / 2)
pos_y = int(self._rect.y + (self._rect.height - self._texture.height) / 2)
color = rl.Color(255, 255, 255, icon_alpha)
rl.draw_texture(self._texture, pos_x, pos_y, color)
def activate(self):
if not self._active:
self._turn_signal_timer = 0.0
self._active = True
def deactivate(self):
self._active = False
self._turn_signal_timer = 0.0
class TurnSignalController:
def __init__(self, config: TurnSignalConfig | None = None):
self._config = config or TurnSignalConfig()
self._left_signal = TurnSignalWidget(direction=IconSide.left)
self._right_signal = TurnSignalWidget(direction=IconSide.right)
self._last_icon_side = None
def update(self):
sm = ui_state.sm
ss = sm['selfdriveState']
event_name = ss.alertType.split('/')[0] if ss.alertType else ''
if event_name == 'preLaneChangeLeft':
self._last_icon_side = IconSide.left
self._left_signal.activate()
self._right_signal.deactivate()
elif event_name == 'preLaneChangeRight':
self._last_icon_side = IconSide.right
self._right_signal.activate()
self._left_signal.deactivate()
elif event_name == 'laneChange':
if self._last_icon_side == IconSide.left:
self._left_signal.activate()
self._right_signal.deactivate()
elif self._last_icon_side == IconSide.right:
self._right_signal.activate()
self._left_signal.deactivate()
else:
self._last_icon_side = None
self._left_signal.deactivate()
self._right_signal.deactivate()
def render(self):
if self._last_icon_side == IconSide.left:
self._left_signal.render(rl.Rectangle(
self._config.left_x,
self._config.left_y,
self._config.size,
self._config.size
))
elif self._last_icon_side == IconSide.right:
self._right_signal.render(rl.Rectangle(
self._config.right_x,
self._config.right_y,
self._config.size,
self._config.size
))
@property
def config(self) -> TurnSignalConfig:
return self._config
@config.setter
def config(self, new_config: TurnSignalConfig):
self._config = new_config