This commit is contained in:
firestar5683
2026-04-06 14:18:10 -05:00
parent da3a047bb0
commit 99771693fd
5 changed files with 102 additions and 59 deletions
+3
View File
@@ -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"
)
+28 -5
View File
@@ -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"
+28 -5
View File
@@ -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"
-44
View File
@@ -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)
@@ -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