diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/car.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/car.png new file mode 100644 index 0000000000..b957982284 Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/car.png differ diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/car_green.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/car_green.png new file mode 100644 index 0000000000..528f2a33eb Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/car_green.png differ diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_1.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_1.png new file mode 100644 index 0000000000..dfe8915cf1 Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_1.png differ diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_1_green.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_1_green.png new file mode 100644 index 0000000000..812dc0b92f Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_1_green.png differ diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_2.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_2.png new file mode 100644 index 0000000000..8cb90ab6f0 Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_2.png differ diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_2_green.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_2_green.png new file mode 100644 index 0000000000..59ed0b6794 Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_2_green.png differ diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_3.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_3.png new file mode 100644 index 0000000000..106c2c265b Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_3.png differ diff --git a/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_3_green.png b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_3_green.png new file mode 100644 index 0000000000..7136447657 Binary files /dev/null and b/openpilot/selfdrive/assets/icons_mici/longitudinal/distance_3_green.png differ diff --git a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py index 4b19f0db5f..eecc196946 100644 --- a/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/openpilot/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -8,6 +8,7 @@ from openpilot.selfdrive.ui.mici.onroad import SIDE_PANEL_WIDTH from openpilot.selfdrive.ui.mici.onroad.alert_renderer import AlertRenderer 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.long_indicator import LongIndicator 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.cameraview import CameraView @@ -170,6 +171,7 @@ class AugmentedRoadView(CameraView): self._model_renderer = ModelRenderer() self._hud_renderer = HudRenderer() + self._long_indicator = LongIndicator() self._alert_renderer = AlertRenderer() self._driver_state_renderer = DriverStateRenderer() self._confidence_ball = ConfidenceBall() @@ -246,6 +248,8 @@ class AugmentedRoadView(CameraView): self._driver_state_renderer.set_should_draw(should_draw_dmoji) self._driver_state_renderer.set_position(self._rect.x + 16, self._rect.y + 10) self._driver_state_renderer.render() + self._long_indicator.set_should_draw(not self._hud_renderer.drawing_top_icons()) + self._long_indicator.render(self._content_rect) self._hud_renderer.set_can_draw_top_icons(alert_to_render is None) self._hud_renderer.set_wheel_critical_icon(alert_to_render is not None and not not_animating_out and diff --git a/openpilot/selfdrive/ui/mici/onroad/long_indicator.py b/openpilot/selfdrive/ui/mici/onroad/long_indicator.py new file mode 100644 index 0000000000..1ff83082a2 --- /dev/null +++ b/openpilot/selfdrive/ui/mici/onroad/long_indicator.py @@ -0,0 +1,89 @@ +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 + + @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(self, rect: rl.Rectangle) -> None: + sm = ui_state.sm + if 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 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 + + e2e = has_lead and plan.longitudinalPlanSource == log.LongitudinalPlan.LongitudinalPlanSource.e2e + white_f, green_f = self._lead_car_filters + white_alpha = white_f.update(0.0 if e2e else 0.9 if has_lead else 0.35) + green_alpha = green_f.update(float(e2e)) + + 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 = 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 = 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 = 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)) + # only lit bars blink on override + self._draw_centered(white, rect, y, (0.35 * (1 - active) + 0.9 * (active - green_alpha) * blink) * alpha) + self._draw_centered(green, rect, y, green_alpha * blink * alpha) + + @staticmethod + def _draw_centered(texture: rl.Texture, rect: rl.Rectangle, y: float, alpha: float) -> None: + pos = rl.Vector2(rect.x + 46 - texture.width / 2, rect.y + y - texture.height / 2) + rl.draw_texture_ex(texture, pos, 0.0, 1.0, rl.Color(255, 255, 255, round(255 * alpha))) diff --git a/openpilot/selfdrive/ui/mici/onroad/model_renderer.py b/openpilot/selfdrive/ui/mici/onroad/model_renderer.py index e927fa506e..e5dcfc44bf 100644 --- a/openpilot/selfdrive/ui/mici/onroad/model_renderer.py +++ b/openpilot/selfdrive/ui/mici/onroad/model_renderer.py @@ -180,7 +180,7 @@ class ModelRenderer(Widget): ss, cs = sm['selfdriveState'], sm['carState'] # braking disengages without making openpilot unavailable available = ss.enabled or ss.engageable or cs.brakePressed - lane = (self._lane_lines[1].raw_points + self._lane_lines[2].raw_points) / 2 + lane = self._path.raw_points opacity = 0.4 if ui_state.status == UIStatus.DISENGAGED else 0.8 for lead, (present, d_rel, y_rel) in zip(self._lead_vehicles, leads, strict=True): visible = available and present and d_rel < MAX_DRAW_DISTANCE and len(lane) > 0