From 99771693fd23ab6c2c42f4def39f65d22a8e3634 Mon Sep 17 00:00:00 2001 From: firestar5683 <168790843+firestar5683@users.noreply.github.com> Date: Mon, 6 Apr 2026 14:18:10 -0500 Subject: [PATCH] patches --- scripts/host_tool_runner.sh | 3 ++ scripts/launch_ui_c4_desktop.sh | 33 +++++++++++-- scripts/launch_ui_raybig_desktop.sh | 33 +++++++++++-- selfdrive/ui/mici/layouts/main.py | 44 ----------------- .../ui/mici/onroad/augmented_road_view.py | 48 +++++++++++++++++-- 5 files changed, 102 insertions(+), 59 deletions(-) diff --git a/scripts/host_tool_runner.sh b/scripts/host_tool_runner.sh index 09be23940a..f61905d89d 100755 --- a/scripts/host_tool_runner.sh +++ b/scripts/host_tool_runner.sh @@ -311,11 +311,14 @@ sync_worktree() { "msgq_repo/msgq/visionipc/visionipc_pyx.so" "rednose_repo/rednose/helpers/ekf_sym_pyx.so" "selfdrive/modeld/models/commonmodel_pyx.so" + "selfdrive/pandad/libcan_list_to_can_capnp.a" "selfdrive/pandad/pandad_api_impl.so" "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so" + "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.dylib" "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so" + "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.dylib" "third_party/libjson11.a" "third_party/libkaitai.a" ) diff --git a/scripts/launch_ui_c4_desktop.sh b/scripts/launch_ui_c4_desktop.sh index 75564d7609..6479fd48a7 100755 --- a/scripts/launch_ui_c4_desktop.sh +++ b/scripts/launch_ui_c4_desktop.sh @@ -64,6 +64,14 @@ runtime_artifacts=( "msgq/ipc_pyx.so" "msgq/visionipc/visionipc_pyx.so" "common/transformations/transformations.so" + "selfdrive/pandad/pandad_api_impl.so" + "selfdrive/pandad/pandad_api_impl.o" + "selfdrive/pandad/can_list_to_can_capnp.o" + "selfdrive/pandad/libcan_list_to_can_capnp.a" + "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" + "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.dylib" + "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" + "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.dylib" ) collect_tracked_dirty() { @@ -181,16 +189,27 @@ prepare_msgq_host_artifacts() { fi } +prepare_pandad_host_artifacts() { + remove_if_elf "selfdrive/pandad/pandad_api_impl.so" + remove_if_elf "selfdrive/pandad/pandad_api_impl.o" + remove_if_elf "selfdrive/pandad/can_list_to_can_capnp.o" + + # Host builds always regenerate this small archive locally. Device-built + # variants are not reusable across platforms and can poison the link step. + rm -f "${ROOT_DIR}/selfdrive/pandad/libcan_list_to_can_capnp.a" +} + python_ui_runtime_ok() { "${PY_BIN}" - <<'PY' import pyray # noqa: F401 import openpilot.common.params_pyx # noqa: F401 import openpilot.common.transformations.transformations # noqa: F401 +import openpilot.selfdrive.pandad.pandad_api_impl # noqa: F401 import msgq.ipc_pyx # noqa: F401 import msgq.visionipc.visionipc_pyx # noqa: F401 +import openpilot.selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx # noqa: F401 +import openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx # noqa: F401 PY - [[ -f "${ROOT_DIR}/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" ]] && - [[ -f "${ROOT_DIR}/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" ]] } sync_deps() { @@ -253,17 +272,21 @@ if ! python_ui_runtime_ok >/dev/null 2>&1; then sync_raylib prepare_common_host_artifacts prepare_msgq_host_artifacts + prepare_pandad_host_artifacts remove_if_elf "common/params_pyx.so" remove_if_elf "common/transformations/transformations.so" remove_if_elf "msgq/ipc_pyx.so" remove_if_elf "msgq/visionipc/visionipc_pyx.so" + remove_if_elf "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" + remove_if_elf "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" run_scons "${jobs}" common/params_pyx.so common/transformations/transformations.so + # Building the Cython module pulls in the platform-specific acados solver + # library via SCons dependencies, so the host path does not need to name the + # solver shared library extension explicitly. run_scons "${jobs}" \ selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so \ - selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so \ - selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so \ - selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so + selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so ( cd "${ROOT_DIR}/msgq_repo" local_scons_bin="${ROOT_DIR}/.venv/bin/scons" diff --git a/scripts/launch_ui_raybig_desktop.sh b/scripts/launch_ui_raybig_desktop.sh index 1165e3defd..f2d1aec4c2 100755 --- a/scripts/launch_ui_raybig_desktop.sh +++ b/scripts/launch_ui_raybig_desktop.sh @@ -64,6 +64,14 @@ runtime_artifacts=( "msgq/ipc_pyx.so" "msgq/visionipc/visionipc_pyx.so" "common/transformations/transformations.so" + "selfdrive/pandad/pandad_api_impl.so" + "selfdrive/pandad/pandad_api_impl.o" + "selfdrive/pandad/can_list_to_can_capnp.o" + "selfdrive/pandad/libcan_list_to_can_capnp.a" + "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" + "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.dylib" + "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" + "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.dylib" ) collect_tracked_dirty() { @@ -181,16 +189,27 @@ prepare_msgq_host_artifacts() { fi } +prepare_pandad_host_artifacts() { + remove_if_elf "selfdrive/pandad/pandad_api_impl.so" + remove_if_elf "selfdrive/pandad/pandad_api_impl.o" + remove_if_elf "selfdrive/pandad/can_list_to_can_capnp.o" + + # Host builds always regenerate this small archive locally. Device-built + # variants are not reusable across platforms and can poison the link step. + rm -f "${ROOT_DIR}/selfdrive/pandad/libcan_list_to_can_capnp.a" +} + python_ui_runtime_ok() { "${PY_BIN}" - <<'PY' import pyray # noqa: F401 import openpilot.common.params_pyx # noqa: F401 import openpilot.common.transformations.transformations # noqa: F401 +import openpilot.selfdrive.pandad.pandad_api_impl # noqa: F401 import msgq.ipc_pyx # noqa: F401 import msgq.visionipc.visionipc_pyx # noqa: F401 +import openpilot.selfdrive.controls.lib.lateral_mpc_lib.c_generated_code.acados_ocp_solver_pyx # noqa: F401 +import openpilot.selfdrive.controls.lib.longitudinal_mpc_lib.c_generated_code.acados_ocp_solver_pyx # noqa: F401 PY - [[ -f "${ROOT_DIR}/selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" ]] && - [[ -f "${ROOT_DIR}/selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" ]] } sync_deps() { @@ -253,17 +272,21 @@ if ! python_ui_runtime_ok >/dev/null 2>&1; then sync_raylib prepare_common_host_artifacts prepare_msgq_host_artifacts + prepare_pandad_host_artifacts remove_if_elf "common/params_pyx.so" remove_if_elf "common/transformations/transformations.so" remove_if_elf "msgq/ipc_pyx.so" remove_if_elf "msgq/visionipc/visionipc_pyx.so" + remove_if_elf "selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" + remove_if_elf "selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so" run_scons "${jobs}" common/params_pyx.so common/transformations/transformations.so + # Building the Cython module pulls in the platform-specific acados solver + # library via SCons dependencies, so the host path does not need to name the + # solver shared library extension explicitly. run_scons "${jobs}" \ selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so \ - selfdrive/controls/lib/lateral_mpc_lib/c_generated_code/libacados_ocp_solver_lat.so \ - selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so \ - selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/libacados_ocp_solver_long.so + selfdrive/controls/lib/longitudinal_mpc_lib/c_generated_code/acados_ocp_solver_pyx.so ( cd "${ROOT_DIR}/msgq_repo" local_scons_bin="${ROOT_DIR}/.venv/bin/scons" diff --git a/selfdrive/ui/mici/layouts/main.py b/selfdrive/ui/mici/layouts/main.py index e7ed89e425..5d0d27b0c0 100644 --- a/selfdrive/ui/mici/layouts/main.py +++ b/selfdrive/ui/mici/layouts/main.py @@ -6,7 +6,6 @@ from openpilot.selfdrive.ui.mici.layouts.home import MiciHomeLayout from openpilot.selfdrive.ui.mici.layouts.settings.settings import SettingsLayout from openpilot.selfdrive.ui.mici.layouts.offroad_alerts import MiciOffroadAlerts from openpilot.selfdrive.ui.mici.onroad.augmented_road_view import AugmentedRoadView -from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCameraDialog from openpilot.selfdrive.ui.ui_state import device, ui_state from openpilot.selfdrive.ui.mici.layouts.onboarding import OnboardingWindow from openpilot.system.ui.widgets import Widget @@ -15,7 +14,6 @@ from openpilot.system.ui.lib.application import gui_app ONROAD_DELAY = 2.5 # seconds -REVERSE_DRIVER_CAMERA_DELAY = 0.5 # seconds class MainState(IntEnum): @@ -33,8 +31,6 @@ class MiciMainLayout(Widget): self._prev_onroad = False self._prev_standstill = False self._onroad_time_delay: float | None = None - self._reverse_started_time: float | None = None - self._reverse_driver_camera: DriverCameraDialog | None = None self._setup = False # Initialize widgets @@ -65,7 +61,6 @@ class MiciMainLayout(Widget): # Set callbacks self._setup_callbacks() - gui_app.set_modal_overlay_tick(self._modal_overlay_tick) # Skip onboarding on desktop; keep normal flow on device. self._onboarding_window = None @@ -89,8 +84,6 @@ class MiciMainLayout(Widget): if self._current_mode is None: self._set_mode(MainState.MAIN) - self._update_reverse_driver_camera() - if not self._setup: if self._alerts_layout.active_alerts() > 0: self._scroller.scroll_to(self._alerts_layout.rect.x) @@ -115,8 +108,6 @@ class MiciMainLayout(Widget): self._current_mode = mode def _handle_transitions(self): - self._update_reverse_driver_camera() - if ui_state.started != self._prev_onroad: self._prev_onroad = ui_state.started @@ -159,38 +150,3 @@ class MiciMainLayout(Widget): user_bookmark = messaging.new_message('bookmarkButton') user_bookmark.valid = True self._pm.send('bookmarkButton', user_bookmark) - - def _modal_overlay_tick(self): - self._update_reverse_driver_camera() - - def _update_reverse_driver_camera(self): - current_overlay = gui_app._modal_overlay.overlay - reverse_overlay = self._reverse_driver_camera - should_show = ui_state.started and ui_state.params.get_bool("DriverCamera") and self._onroad_layout.is_in_reverse() - - if not should_show: - self._reverse_started_time = None - if current_overlay is reverse_overlay: - gui_app.set_modal_overlay(None) - self._reverse_driver_camera = None - return - - now = rl.get_time() - if self._reverse_started_time is None: - self._reverse_started_time = now - return - - if now - self._reverse_started_time < REVERSE_DRIVER_CAMERA_DELAY: - return - - if current_overlay is reverse_overlay: - return - - if current_overlay is not None: - return - - if reverse_overlay is None: - self._reverse_driver_camera = DriverCameraDialog(no_escape=True) - reverse_overlay = self._reverse_driver_camera - - gui_app.set_modal_overlay(reverse_overlay) diff --git a/selfdrive/ui/mici/onroad/augmented_road_view.py b/selfdrive/ui/mici/onroad/augmented_road_view.py index 403c4f09bc..61e04e3de0 100644 --- a/selfdrive/ui/mici/onroad/augmented_road_view.py +++ b/selfdrive/ui/mici/onroad/augmented_road_view.py @@ -2,6 +2,7 @@ import time import numpy as np import pyray as rl from cereal import messaging, car, log +from opendbc.car import structs from msgq.visionipc import VisionStreamType from openpilot.common.constants import CV from openpilot.selfdrive.ui.ui_state import ui_state @@ -32,6 +33,7 @@ CALIBRATED = log.LiveCalibrationData.Status.calibrated ROAD_CAM = VisionStreamType.VISION_STREAM_ROAD WIDE_CAM = VisionStreamType.VISION_STREAM_WIDE_ROAD DRIVER_CAM = VisionStreamType.VISION_STREAM_DRIVER +GEAR_SHIFTER_REVERSE = structs.CarState.GearShifter.reverse DEFAULT_DEVICE_CAMERA = DEVICE_CAMERAS["tici", "ar0231"] CAMERA_VIEW_AUTO = 0 @@ -49,6 +51,7 @@ WIDE_CAM_MAX_SPEED = 10.0 # m/s ROAD_CAM_MIN_SPEED = 15.0 # m/s CAM_Y_OFFSET = 20 +REVERSE_DRIVER_CAMERA_DELAY_FRAMES = max(1, int(round(gui_app.target_fps * 0.5))) class BookmarkIcon(Widget): @@ -367,6 +370,13 @@ class StandstillTimerOverlay: rl.draw_text_ex(font, text, shadow_pos, font_size, 0, rl.Color(0, 0, 0, 170)) rl.draw_text_ex(font, text, text_pos, font_size, 0, color) + @staticmethod + def _fit_font_size(font: rl.Font, text: str, initial_size: int, max_width: float, minimum_size: int) -> int: + font_size = max(initial_size, minimum_size) + while font_size > minimum_size and rl.measure_text_ex(font, text, font_size, 0).x > max_width: + font_size -= 2 + return font_size + def render(self, rect: rl.Rectangle, in_reverse: bool) -> bool: self._update_state(in_reverse) if self._standstill_duration == 0: @@ -374,8 +384,11 @@ class StandstillTimerOverlay: minute_text, second_text = self._format_duration_text(self._standstill_duration) duration_color = self._get_duration_color() - self._draw_centered_text(rect, minute_text, 210, self._font_bold, 176, duration_color) - self._draw_centered_text(rect, second_text, 290, self._font_medium, 66, rl.Color(255, 255, 255, 242)) + max_text_width = max(rect.width - 36, 120) + minute_font_size = self._fit_font_size(self._font_bold, minute_text, int(rect.height * 0.34), max_text_width, 28) + second_font_size = self._fit_font_size(self._font_medium, second_text, int(rect.height * 0.15), max_text_width, 16) + self._draw_centered_text(rect, minute_text, rect.height * 0.42, self._font_bold, minute_font_size, duration_color) + self._draw_centered_text(rect, second_text, rect.height * 0.62, self._font_medium, second_font_size, rl.Color(255, 255, 255, 242)) return True @@ -395,6 +408,8 @@ class AugmentedRoadView(CameraView): self._cached_matrix: np.ndarray | None = None self._content_rect = rl.Rectangle() self._last_click_time = 0.0 + self._reverse_driver_camera_frames = 0 + self._reverse_driver_camera_active = False # Bookmark icon with swipe gesture self._bookmark_icon = BookmarkIcon(bookmark_callback) @@ -421,6 +436,17 @@ class AugmentedRoadView(CameraView): def _controls_ready() -> bool: return ui_state.sm.recv_frame["selfdriveState"] >= ui_state.started_frame + def _update_reverse_driver_camera_state(self) -> bool: + should_force_driver = ui_state.started and ui_state.params.get_bool("DriverCamera") and self._is_in_reverse() + if not should_force_driver: + self._reverse_driver_camera_frames = 0 + self._reverse_driver_camera_active = False + return False + + self._reverse_driver_camera_frames = min(self._reverse_driver_camera_frames + 1, REVERSE_DRIVER_CAMERA_DELAY_FRAMES) + self._reverse_driver_camera_active = self._reverse_driver_camera_frames >= REVERSE_DRIVER_CAMERA_DELAY_FRAMES + return self._reverse_driver_camera_active + def is_swiping_left(self) -> bool: """Check if currently swiping left (for scroller to disable).""" return self._bookmark_icon.is_swiping_left() @@ -503,7 +529,7 @@ 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) - if not in_reverse: + if is_driver_stream or not in_reverse: self._driver_state_renderer.render() self._hud_renderer.set_can_draw_top_icons((not in_reverse) and (not is_driver_stream) and (alert_to_render is None)) @@ -529,7 +555,7 @@ class AugmentedRoadView(CameraView): # Use self._content_rect for positioning within camera bounds if not in_reverse and not is_driver_stream: self._confidence_ball.render(self.rect) - if not in_reverse: + if is_driver_stream or not in_reverse: self._draw_border() self._bookmark_icon.render(self.rect) @@ -557,21 +583,33 @@ class AugmentedRoadView(CameraView): @staticmethod def _is_in_reverse() -> bool: + if ui_state.sm.recv_frame["carState"] < ui_state.started_frame: + return False + try: gear = ui_state.sm["carState"].gearShifter except Exception: return False + if gear == GEAR_SHIFTER_REVERSE: + return True + reverse_enum = getattr(car.CarState.GearShifter, "reverse", None) if reverse_enum is not None and gear == reverse_enum: return True - return str(gear).lower().endswith("reverse") + return str(gear).split(".")[-1].lower() == "reverse" def is_in_reverse(self) -> bool: return self._is_in_reverse() def _switch_stream_if_needed(self, sm): + if self._update_reverse_driver_camera_state(): + target = DRIVER_CAM + if self.stream_type != target: + self.switch_stream(target) + return + camera_view = ui_state.params.get_int("CameraView", return_default=True, default=CAMERA_VIEW_WIDE) if camera_view not in (CAMERA_VIEW_AUTO, CAMERA_VIEW_DRIVER, CAMERA_VIEW_STANDARD, CAMERA_VIEW_WIDE): camera_view = CAMERA_VIEW_WIDE