From 35c87a151972c88f8251f8d877be615ced253334 Mon Sep 17 00:00:00 2001 From: royjr Date: Sun, 8 Feb 2026 17:00:37 -0500 Subject: [PATCH] [TIZI/TICI] ui: steering arc (#1628) * init * lint * add toggle * Update params_keys.h * Update params_metadata.json * Update params_keys.h * bool * decouple * no * make it perfect * fade it * only with torque bar * dynamic * in another PR --------- Co-authored-by: Jason Wen --- common/params_keys.h | 1 + selfdrive/ui/mici/onroad/torque_bar.py | 16 ++++++++------- selfdrive/ui/onroad/augmented_road_view.py | 8 +++++--- .../sunnypilot/onroad/augmented_road_view.py | 20 ++++++++++++++++++- .../ui/sunnypilot/onroad/hud_renderer.py | 9 +++++++++ selfdrive/ui/sunnypilot/ui_state.py | 1 + sunnypilot/sunnylink/params_metadata.json | 4 ++++ 7 files changed, 48 insertions(+), 11 deletions(-) diff --git a/common/params_keys.h b/common/params_keys.h index ecc656cc7..44584c911 100644 --- a/common/params_keys.h +++ b/common/params_keys.h @@ -115,6 +115,7 @@ inline static std::unordered_map keys = { {"SnoozeUpdate", {CLEAR_ON_MANAGER_START | CLEAR_ON_OFFROAD_TRANSITION, BOOL}}, {"SshEnabled", {PERSISTENT | BACKUP, BOOL}}, {"TermsVersion", {PERSISTENT, STRING}}, + {"TorqueBar", {PERSISTENT | BACKUP, BOOL, "0"}}, {"TrainingVersion", {PERSISTENT, STRING}}, {"UbloxAvailable", {PERSISTENT, BOOL}}, {"UpdateAvailable", {CLEAR_ON_MANAGER_START | CLEAR_ON_ONROAD_TRANSITION, BOOL}}, diff --git a/selfdrive/ui/mici/onroad/torque_bar.py b/selfdrive/ui/mici/onroad/torque_bar.py index c8485a310..c1de69463 100644 --- a/selfdrive/ui/mici/onroad/torque_bar.py +++ b/selfdrive/ui/mici/onroad/torque_bar.py @@ -146,9 +146,11 @@ def arc_bar_pts(cx: float, cy: float, class TorqueBar(Widget): - def __init__(self, demo: bool = False): + def __init__(self, demo: bool = False, scale: float = 1.0, always: bool = False): super().__init__() self._demo = demo + self._scale = scale + self._always = always self._torque_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) self._torque_line_alpha_filter = FirstOrderFilter(0.0, 0.1, 1 / gui_app.target_fps) @@ -180,8 +182,8 @@ class TorqueBar(Widget): def _render(self, rect: rl.Rectangle) -> None: # adjust y pos with torque - torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22, 26]) - torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14, 56]) + torque_line_offset = np.interp(abs(self._torque_filter.x), [0.5, 1], [22 * self._scale, 26 * self._scale]) + torque_line_height = np.interp(abs(self._torque_filter.x), [0.5, 1], [14 * self._scale, 56 * self._scale]) # animate alpha and angle span if not self._demo: @@ -195,7 +197,7 @@ class TorqueBar(Widget): torque_line_bg_color = rl.Color(255, 255, 255, int(255 * 0.15 * self._torque_line_alpha_filter.x)) # draw curved line polygon torque bar - torque_line_radius = 1200 + torque_line_radius = 1200 * self._scale top_angle = -90 torque_bg_angle_span = self._torque_line_alpha_filter.x * TORQUE_ANGLE_SPAN torque_start_angle = top_angle - torque_bg_angle_span / 2 @@ -207,13 +209,13 @@ class TorqueBar(Widget): cy = rect.y + rect.height + torque_line_radius - torque_line_offset # draw bg torque indicator line - bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle) + bg_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, torque_start_angle, torque_end_angle, cap_radius=7 * self._scale) draw_polygon(rect, bg_pts, color=torque_line_bg_color) # draw torque indicator line a0s = top_angle a1s = a0s + torque_bg_angle_span / 2 * self._torque_filter.x - sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s) + sl_pts = arc_bar_pts(cx, cy, mid_r, torque_line_height, a0s, a1s, cap_radius=7 * self._scale) # draw beautiful gradient from center to 65% of the bg torque bar width start_grad_pt = cx / rect.width @@ -252,5 +254,5 @@ class TorqueBar(Widget): # draw center torque bar dot if abs(self._torque_filter.x) < 0.5: dot_y = self._rect.y + self._rect.height - torque_line_offset - torque_line_height / 2 - rl.draw_circle(int(cx), int(dot_y), 10 // 2, + rl.draw_circle(int(cx), int(dot_y), (10 // 2 * self._scale), rl.Color(182, 182, 182, int(255 * 0.9 * self._torque_line_alpha_filter.x))) diff --git a/selfdrive/ui/onroad/augmented_road_view.py b/selfdrive/ui/onroad/augmented_road_view.py index bcbcb2dcf..76e7b078d 100644 --- a/selfdrive/ui/onroad/augmented_road_view.py +++ b/selfdrive/ui/onroad/augmented_road_view.py @@ -15,7 +15,7 @@ from openpilot.common.transformations.camera import DEVICE_CAMERAS, DeviceCamera from openpilot.common.transformations.orientation import rot_from_euler if gui_app.sunnypilot_ui(): - from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP + from openpilot.selfdrive.ui.sunnypilot.onroad.augmented_road_view import BORDER_COLORS_SP, AugmentedRoadViewSP from openpilot.selfdrive.ui.sunnypilot.onroad.driver_state import DriverStateRendererSP as DriverStateRenderer from openpilot.selfdrive.ui.sunnypilot.onroad.hud_renderer import HudRendererSP as HudRenderer from openpilot.selfdrive.ui.sunnypilot.ui_state import OnroadTimerStatus @@ -38,9 +38,10 @@ ROAD_CAM_MIN_SPEED = 15.0 # m/s (34 mph) INF_POINT = np.array([1000.0, 0.0, 0.0]) -class AugmentedRoadView(CameraView): +class AugmentedRoadView(CameraView, AugmentedRoadViewSP): def __init__(self, stream_type: VisionStreamType = VisionStreamType.VISION_STREAM_ROAD): - super().__init__("camerad", stream_type) + CameraView.__init__(self, "camerad", stream_type) + AugmentedRoadViewSP.__init__(self) self._set_placeholder_color(BORDER_COLORS[UIStatus.DISENGAGED]) self.device_camera: DeviceCameraConfig | None = None @@ -92,6 +93,7 @@ class AugmentedRoadView(CameraView): # Draw all UI overlays self.model_renderer.render(self._content_rect) + AugmentedRoadViewSP.update_fade_out_bottom_overlay(self, self._content_rect) self._hud_renderer.render(self._content_rect) self.alert_renderer.render(self._content_rect) self.driver_state_renderer.render(self._content_rect) diff --git a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py index 0a5739cc0..c7dedee54 100644 --- a/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py +++ b/selfdrive/ui/sunnypilot/onroad/augmented_road_view.py @@ -5,9 +5,27 @@ 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.selfdrive.ui.ui_state import UIStatus +from openpilot.common.filter_simple import FirstOrderFilter +from openpilot.selfdrive.ui.ui_state import UIStatus, ui_state +from openpilot.system.ui.lib.application import gui_app BORDER_COLORS_SP = { UIStatus.LAT_ONLY: rl.Color(0x00, 0xC8, 0xC8, 0xFF), # Cyan for lateral-only state UIStatus.LONG_ONLY: rl.Color(0x96, 0x1C, 0xA8, 0xFF), # Purple for longitudinal-only state } + + +class AugmentedRoadViewSP: + def __init__(self): + self._fade_texture = gui_app.texture("icons_mici/onroad/onroad_fade.png") + self._fade_alpha_filter = FirstOrderFilter(0, 0.1, 1 / gui_app.target_fps) + + def update_fade_out_bottom_overlay(self, _content_rect): + # Fade out bottom of overlays for looks (only when engaged) + fade_alpha = self._fade_alpha_filter.update(ui_state.status != UIStatus.DISENGAGED) + if ui_state.torque_bar and fade_alpha > 1e-2: + # Scale the fade texture to the content rect + rl.draw_texture_pro(self._fade_texture, + rl.Rectangle(0, 0, self._fade_texture.width, self._fade_texture.height), + _content_rect, rl.Vector2(0, 0), 0.0, + rl.Color(255, 255, 255, int(255 * fade_alpha))) diff --git a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py index 8ca726980..74e15fe0b 100644 --- a/selfdrive/ui/sunnypilot/onroad/hud_renderer.py +++ b/selfdrive/ui/sunnypilot/onroad/hud_renderer.py @@ -6,6 +6,7 @@ See the LICENSE.md file in the root directory for more details. """ import pyray as rl +from openpilot.selfdrive.ui.mici.onroad.torque_bar import TorqueBar 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 @@ -23,6 +24,7 @@ class HudRendererSP(HudRenderer): self.rocket_fuel = RocketFuel() self.speed_limit_renderer = SpeedLimitRenderer() self.turn_signal_controller = TurnSignalController() + self._torque_bar = TorqueBar(scale=3.0, always=True) def _update_state(self) -> None: super()._update_state() @@ -32,6 +34,13 @@ class HudRendererSP(HudRenderer): def _render(self, rect: rl.Rectangle) -> None: super()._render(rect) + + if ui_state.torque_bar and ui_state.sm['controlsState'].lateralControlState.which() != 'angleState': + torque_rect = rect + if ui_state.developer_ui in (DeveloperUiRenderer.DEV_UI_BOTTOM, DeveloperUiRenderer.DEV_UI_BOTH): + torque_rect = rl.Rectangle(rect.x, rect.y, rect.width, rect.height - DeveloperUiRenderer.BOTTOM_BAR_HEIGHT) + self._torque_bar.render(torque_rect) + self.developer_ui.render(rect) self.road_name_renderer.render(rect) self.speed_limit_renderer.render(rect) diff --git a/selfdrive/ui/sunnypilot/ui_state.py b/selfdrive/ui/sunnypilot/ui_state.py index f38280d49..98ddbe526 100644 --- a/selfdrive/ui/sunnypilot/ui_state.py +++ b/selfdrive/ui/sunnypilot/ui_state.py @@ -125,6 +125,7 @@ class UIStateSP: self.rocket_fuel = self.params.get_bool("RocketFuel") self.rainbow_path = self.params.get_bool("RainbowMode") self.chevron_metrics = self.params.get("ChevronInfo") + self.torque_bar = self.params.get_bool("TorqueBar") 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) diff --git a/sunnypilot/sunnylink/params_metadata.json b/sunnypilot/sunnylink/params_metadata.json index 7f597a962..090beb49f 100644 --- a/sunnypilot/sunnylink/params_metadata.json +++ b/sunnypilot/sunnylink/params_metadata.json @@ -1239,6 +1239,10 @@ "title": "Tesla Coop Steering", "description": "" }, + "TorqueBar": { + "title": "Steering Arc", + "description": "[TIZI/TICI only] Display steering arc on the driving screen when lateral control is enabled." + }, "TorqueParamsOverrideEnabled": { "title": "Manual Real-Time Tuning", "description": ""