diff --git a/openpilot/selfdrive/assets/icons_mici/personality_1.png b/openpilot/selfdrive/assets/icons_mici/personality_1.png new file mode 100644 index 00000000000000..05d36531d87d22 --- /dev/null +++ b/openpilot/selfdrive/assets/icons_mici/personality_1.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ccd0ea3d1134fa43f59a6789275a07580394985c9e7ccccfc83cf98b61364b52 +size 12786 diff --git a/openpilot/selfdrive/assets/icons_mici/personality_2.png b/openpilot/selfdrive/assets/icons_mici/personality_2.png new file mode 100644 index 00000000000000..6ef34b8bb98ee1 --- /dev/null +++ b/openpilot/selfdrive/assets/icons_mici/personality_2.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7da1a434e8ddcf57e57ec81ac1a3cb71eb9d20ebd116b6423afe0c803d26ad16 +size 13334 diff --git a/openpilot/selfdrive/assets/icons_mici/personality_3.png b/openpilot/selfdrive/assets/icons_mici/personality_3.png new file mode 100644 index 00000000000000..9a54ad80d95006 --- /dev/null +++ b/openpilot/selfdrive/assets/icons_mici/personality_3.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9dd8ae37a93dba44e0db8a6ac5141a147b3fb9b5df52ca872ca870e4f27602c4 +size 12990 diff --git a/openpilot/selfdrive/assets/icons_mici/speed_limiter_cruise.png b/openpilot/selfdrive/assets/icons_mici/speed_limiter_cruise.png new file mode 100644 index 00000000000000..7e05f4236fcb1f --- /dev/null +++ b/openpilot/selfdrive/assets/icons_mici/speed_limiter_cruise.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:9488b6343873ac0874b0eca7f268278f13cd6627071a30f995b6456c0cf8b73b +size 17736 diff --git a/openpilot/selfdrive/assets/icons_mici/speed_limiter_lead.png b/openpilot/selfdrive/assets/icons_mici/speed_limiter_lead.png new file mode 100644 index 00000000000000..42c4c1b6c8e5fb --- /dev/null +++ b/openpilot/selfdrive/assets/icons_mici/speed_limiter_lead.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:2c5d7fc864392e99e1089b43ab1879ce3ec9a729b5f9cdb2a7d6a90c3334522a +size 20459 diff --git a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py index 25061f12aeed45..bbf10e8ba50927 100644 --- a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -9,7 +9,7 @@ from openpilot.selfdrive.ui.mici.onroad.driver_state import DriverStateRenderer from openpilot.selfdrive.ui.mici.onroad.hud_renderer import HudRenderer from openpilot.selfdrive.ui.mici.onroad.model_renderer import ModelRenderer -from openpilot.selfdrive.ui.mici.onroad.confidence_ball import ConfidenceBall +from openpilot.selfdrive.ui.mici.onroad.status_icons import StatusIconColumn from openpilot.selfdrive.ui.mici.onroad.cameraview import CameraView from openpilot.system.ui.lib.application import FontWeight, gui_app, MousePos, MouseEvent from openpilot.system.ui.widgets.label import UnifiedLabel @@ -151,7 +151,7 @@ def __init__(self, bookmark_callback=None, stream_type: VisionStreamType = Visio self._hud_renderer = HudRenderer() self._alert_renderer = AlertRenderer() self._driver_state_renderer = DriverStateRenderer() - self._confidence_ball = ConfidenceBall() + self._status_icons = StatusIconColumn() self._offroad_label = UnifiedLabel("start the car to\nuse openpilot", 54, FontWeight.DISPLAY, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), alignment=rl.GuiTextAlignment.TEXT_ALIGN_CENTER, @@ -240,7 +240,7 @@ def _render(self, _): # Custom UI extension point - add custom overlays here # Use self._content_rect for positioning within camera bounds - self._confidence_ball.render(self.rect) + self._status_icons.render(self.rect) self._bookmark_icon.render(self.rect) diff --git a/openpilot/selfdrive/ui/mici/onroad/confidence_ball.py b/openpilot/selfdrive/ui/mici/onroad/confidence_ball.py deleted file mode 100644 index a5c95470f54c1e..00000000000000 --- a/openpilot/selfdrive/ui/mici/onroad/confidence_ball.py +++ /dev/null @@ -1,78 +0,0 @@ -import math -import pyray as rl -from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH -from openpilot.selfdrive.ui.ui_state import ui_state, UIStatus -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.lib.application import gui_app -from openpilot.common.filter_simple import FirstOrderFilter - - -def draw_circle_gradient(center_x: float, center_y: float, radius: int, - top: rl.Color, bottom: rl.Color) -> None: - # Draw a square with the gradient - rl.draw_rectangle_gradient_v(int(center_x - radius), int(center_y - radius), - radius * 2, radius * 2, - top, bottom) - - # Paint over square with a ring - outer_radius = math.ceil(radius * math.sqrt(2)) + 1 - rl.draw_ring(rl.Vector2(int(center_x), int(center_y)), radius, outer_radius, - 0.0, 360.0, - 20, rl.BLACK) - - -class ConfidenceBall(Widget): - def __init__(self, demo: bool = False): - super().__init__() - self._demo = demo - self._confidence_filter = FirstOrderFilter(-0.5, 0.5, 1 / gui_app.target_fps) - - def update_filter(self, value: float): - self._confidence_filter.update(value) - - def _update_state(self): - if self._demo: - return - - # animate status dot in from bottom - if ui_state.status == UIStatus.DISENGAGED: - self._confidence_filter.update(-0.5) - else: - self._confidence_filter.update((1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.brakeDisengageProbs or [1])) * - (1 - max(ui_state.sm['modelV2'].meta.disengagePredictions.steerOverrideProbs or [1]))) - - def _render(self, _): - content_rect = rl.Rectangle( - self.rect.x + self.rect.width - SIDE_PANEL_WIDTH, - self.rect.y, - SIDE_PANEL_WIDTH, - self.rect.height, - ) - - status_dot_radius = 24 - dot_height = (1 - self._confidence_filter.x) * (content_rect.height - 2 * status_dot_radius) + status_dot_radius - dot_height = self._rect.y + dot_height - - # confidence zones - if ui_state.status == UIStatus.ENGAGED or self._demo: - if self._confidence_filter.x > 0.5: - top_dot_color = rl.Color(0, 255, 204, 255) - bottom_dot_color = rl.Color(0, 255, 38, 255) - elif self._confidence_filter.x > 0.2: - top_dot_color = rl.Color(255, 200, 0, 255) - bottom_dot_color = rl.Color(255, 115, 0, 255) - else: - top_dot_color = rl.Color(255, 0, 21, 255) - bottom_dot_color = rl.Color(255, 0, 89, 255) - - elif ui_state.status == UIStatus.OVERRIDE: - top_dot_color = rl.Color(255, 255, 255, 255) - bottom_dot_color = rl.Color(82, 82, 82, 255) - - else: - top_dot_color = rl.Color(50, 50, 50, 255) - bottom_dot_color = rl.Color(13, 13, 13, 255) - - draw_circle_gradient(content_rect.x + content_rect.width - status_dot_radius, - dot_height, status_dot_radius, - top_dot_color, bottom_dot_color) diff --git a/openpilot/selfdrive/ui/mici/onroad/status_icons.py b/openpilot/selfdrive/ui/mici/onroad/status_icons.py new file mode 100644 index 00000000000000..a18646cb512c84 --- /dev/null +++ b/openpilot/selfdrive/ui/mici/onroad/status_icons.py @@ -0,0 +1,84 @@ +import pyray as rl + +from openpilot.cereal import log +from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH +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 + + +PlanSource = log.LongitudinalPlan.LongitudinalPlanSource + + +class StatusIconColumn(Widget): + """Four-slot status rail for experimental mode, speed limiter, eGPU use, and personality.""" + + def __init__(self): + super().__init__() + + self._experimental_texture = gui_app.texture("icons_mici/experimental_mode.png", 48, 48) + self._cruise_texture = gui_app.texture("icons_mici/speed_limiter_cruise.png", 48, 48) + self._lead_texture = gui_app.texture("icons_mici/speed_limiter_lead.png", 48, 48) + self._egpu_texture = gui_app.texture("icons_mici/egpu.png", 50, 37) + self._personality_textures = tuple( + gui_app.texture(f"icons_mici/personality_{bar_count}.png", 56, 20) + for bar_count in range(1, 4) + ) + + self._experimental_mode = False + self._speed_limiter_texture = self._cruise_texture + self._speed_limiter_active = False + self._using_egpu = False + self._personality_bar_count = 0 + + def _update_state(self): + sm = ui_state.sm + selfdrive_state_valid = sm.alive["selfdriveState"] and sm.valid["selfdriveState"] + self._experimental_mode = selfdrive_state_valid and sm["selfdriveState"].experimentalMode + personality_available = selfdrive_state_valid and ui_state.has_longitudinal_control + personality = sm["selfdriveState"].personality + if not personality_available: + self._personality_bar_count = 0 + elif personality == log.LongitudinalPersonality.relaxed: + self._personality_bar_count = 1 + elif personality == log.LongitudinalPersonality.standard: + self._personality_bar_count = 2 + elif personality == log.LongitudinalPersonality.aggressive: + self._personality_bar_count = 3 + else: + self._personality_bar_count = 0 + source = sm["longitudinalPlan"].longitudinalPlanSource + if source in (PlanSource.lead0, PlanSource.lead1, PlanSource.lead2): + self._speed_limiter_texture = self._lead_texture + elif source == PlanSource.e2e: + self._speed_limiter_texture = self._experimental_texture + else: + self._speed_limiter_texture = self._cruise_texture + self._speed_limiter_active = (sm.alive["carControl"] and sm.valid["carControl"] and + sm["carControl"].longActive and + sm.alive["longitudinalPlan"] and sm.valid["longitudinalPlan"]) + self._using_egpu = ui_state.usbgpu and ui_state.usbgpu_compiled + + @staticmethod + def _draw_texture_centered(texture: rl.Texture, center: rl.Vector2, tint: rl.Color = rl.WHITE): + pos = rl.Vector2(center.x - texture.width / 2, center.y - texture.height / 2) + rl.draw_texture_ex(texture, pos, 0.0, 1.0, tint) + + def _render(self, _): + panel = rl.Rectangle( + self.rect.x + self.rect.width - SIDE_PANEL_WIDTH, + self.rect.y, + SIDE_PANEL_WIDTH, + self.rect.height, + ) + center_x = panel.x + panel.width / 2 + centers = [rl.Vector2(center_x, panel.y + panel.height * (slot + 0.5) / 4) for slot in range(4)] + + if self._experimental_mode: + self._draw_texture_centered(self._experimental_texture, centers[0]) + if self._speed_limiter_active: + self._draw_texture_centered(self._speed_limiter_texture, centers[1]) + if self._using_egpu: + self._draw_texture_centered(self._egpu_texture, centers[2]) + if self._personality_bar_count: + self._draw_texture_centered(self._personality_textures[self._personality_bar_count - 1], centers[3])