Offroad/onroad guarding

This commit is contained in:
firestarsdog
2026-07-31 21:46:08 -04:00
parent e5af9d38b8
commit 349f8c45f8
5 changed files with 144 additions and 2 deletions
@@ -835,6 +835,9 @@ class AugmentedRoadView(CameraView):
self._reverse_driver_camera_active = False
return
if getattr(self, "_onroad_reentry_pending", False):
self._refresh_available_streams()
if self._update_reverse_driver_camera_state():
self.switch_stream(DRIVER_CAM)
return
@@ -857,7 +860,8 @@ class AugmentedRoadView(CameraView):
else:
target = ROAD_CAM
if self.stream_type != target or (self._switching and self._target_stream_type != target):
if (getattr(self, "_onroad_reentry_pending", False) or
self.stream_type != target or (self._switching and self._target_stream_type != target)):
self.switch_stream(target)
def _update_calibration(self):
@@ -164,3 +164,34 @@ def test_reverse_activation_cancels_mismatched_pending_switch():
assert view._target_client is None
assert view._target_stream_type is None
assert not view._switching
def test_onroad_transition_marks_camera_reentry(monkeypatch):
module = big_cameraview
class FakeClient:
pass
view = module.CameraView.__new__(module.CameraView)
view._name = "camerad"
view._stream_type = object()
view.client = FakeClient()
view.frame = None
view.available_streams = []
view._target_client = None
view._target_stream_type = None
view._switching = False
view._texture_needs_update = False
view._regressive_frame_count = 1
view._closed = True
view._onroad_reentry_pending = False
view._reentry_stream_selected = False
view._clear_textures = lambda: None
monkeypatch.setattr(module, "VisionIpcClient", lambda *_args, **_kwargs: FakeClient())
monkeypatch.setattr(module.ui_state, "is_onroad", lambda: True)
view._offroad_transition()
assert view._onroad_reentry_pending
assert not view._reentry_stream_selected
+5 -1
View File
@@ -205,6 +205,9 @@ class AugmentedRoadView(CameraView):
self._reverse_driver_camera_active = False
return
if getattr(self, "_onroad_reentry_pending", False):
self._refresh_available_streams()
if self._update_reverse_driver_camera_state():
target = DRIVER_CAM
elif camera_view == CAMERA_VIEW_DRIVER:
@@ -225,7 +228,8 @@ class AugmentedRoadView(CameraView):
else:
target = ROAD_CAM
if self.stream_type != target or (self._switching and self._target_stream_type != target):
if (getattr(self, "_onroad_reentry_pending", False) or
self.stream_type != target or (self._switching and self._target_stream_type != target)):
self.switch_stream(target)
def _update_calibration(self):
+41
View File
@@ -130,6 +130,8 @@ class CameraView(Widget):
self._placeholder_color: rl.Color | None = None
self._closed = False
self._onroad_reentry_pending = False
self._reentry_stream_selected = False
if self._use_egl and not self._create_egl_texture():
cloudlog.error("CameraView EGL texture creation failed, falling back to texture rendering")
@@ -164,12 +166,23 @@ class CameraView(Widget):
self._switching = False
self._texture_needs_update = True
self.last_connection_attempt = 0.0
self._onroad_reentry_pending = ui_state.is_onroad()
self._reentry_stream_selected = False
def _set_placeholder_color(self, color: rl.Color):
"""Set a placeholder color to be drawn when no frame is available."""
self._placeholder_color = color
def _refresh_available_streams(self) -> None:
streams = VisionIpcClient.available_streams(self._name, block=False)
if streams:
self.available_streams = list(streams)
def switch_stream(self, stream_type: VisionStreamType) -> None:
if getattr(self, "_onroad_reentry_pending", False):
self._select_reentry_stream(stream_type)
return
if self._switching:
if self._target_stream_type == stream_type:
return
@@ -194,6 +207,23 @@ class CameraView(Widget):
self._target_stream_type = None
self._switching = False
def _select_reentry_stream(self, stream_type: VisionStreamType) -> None:
"""Select the desired stream before displaying any post-transition frame."""
self._cancel_pending_switch()
if self._stream_type != stream_type:
old_client = self.client
self.client = None
del old_client
self.client = VisionIpcClient(self._name, stream_type, conflate=True)
self._stream_type = stream_type
self.frame = None
self._last_frame_id = -1
self._regressive_frame_count = 0
self._texture_needs_update = True
self._reentry_stream_selected = True
@property
def stream_type(self) -> VisionStreamType:
return self._stream_type
@@ -219,6 +249,8 @@ class CameraView(Widget):
self.available_streams.clear()
self.client = None
self._target_client = None
self._onroad_reentry_pending = False
self._reentry_stream_selected = False
def __del__(self):
self.close()
@@ -245,6 +277,10 @@ class CameraView(Widget):
if self._switching:
self._handle_switch()
if self._onroad_reentry_pending and not self._reentry_stream_selected:
# Standalone CameraView users have no higher-level stream selector.
self._select_reentry_stream(self._stream_type)
if not self._ensure_connection():
self._draw_placeholder(rect)
return
@@ -333,6 +369,8 @@ class CameraView(Widget):
self._last_frame_id = content_frame_id
self._regressive_frame_count = 0
self._texture_needs_update = True
self._onroad_reentry_pending = False
self._reentry_stream_selected = False
return True
def _render_egl(self, src_rect: rl.Rectangle, dst_rect: rl.Rectangle) -> bool:
@@ -475,6 +513,9 @@ class CameraView(Widget):
# Initialize textures for new stream
self._initialize_textures()
available_streams = getattr(self.client, "available_streams", None)
if available_streams is not None:
self.available_streams = available_streams(self._name, block=False)
def _initialize_textures(self):
self._clear_textures()
@@ -15,6 +15,7 @@ class FakeFrame:
def _camera_view():
view = big_cameraview.CameraView.__new__(big_cameraview.CameraView)
view._name = "camerad"
view._stream_type = big_cameraview.VisionStreamType.VISION_STREAM_ROAD
view.frame = None
view._last_frame_id = -1
view._regressive_frame_count = 0
@@ -41,6 +42,67 @@ def test_pending_switch_is_cancelled_when_requested_stream_is_current():
assert not view._switching
def test_onroad_reentry_selects_requested_stream_before_rendering(monkeypatch):
view = _camera_view()
view._name = "camerad"
view._stream_type = big_cameraview.VisionStreamType.VISION_STREAM_WIDE_ROAD
view.client = object()
view._target_client = object()
view._target_stream_type = big_cameraview.VisionStreamType.VISION_STREAM_ROAD
view._switching = True
view._onroad_reentry_pending = True
view._reentry_stream_selected = False
clients = []
class FakeClient:
def __init__(self, name, stream_type, conflate):
self.name = name
self.stream_type = stream_type
self.conflate = conflate
clients.append(self)
monkeypatch.setattr(big_cameraview, "VisionIpcClient", FakeClient)
view.switch_stream(big_cameraview.VisionStreamType.VISION_STREAM_ROAD)
assert len(clients) == 1
assert view.client is clients[0]
assert view.client.stream_type == big_cameraview.VisionStreamType.VISION_STREAM_ROAD
assert view.stream_type == big_cameraview.VisionStreamType.VISION_STREAM_ROAD
assert view._target_client is None
assert view._target_stream_type is None
assert not view._switching
assert view._reentry_stream_selected
def test_onroad_reentry_guard_clears_on_first_fresh_frame():
view = _camera_view()
view._onroad_reentry_pending = True
view._reentry_stream_selected = True
assert view._accept_frame(FakeFrame(frame_id=1, idx=0), packet_frame_id=1)
assert not view._onroad_reentry_pending
assert not view._reentry_stream_selected
def test_standalone_camera_reentry_selects_configured_stream():
view = _camera_view()
view._switching = False
view._onroad_reentry_pending = True
view._reentry_stream_selected = False
selected = []
placeholders = []
view._select_reentry_stream = lambda stream_type: (
selected.append(stream_type), setattr(view, "_reentry_stream_selected", True)
)
view._draw_placeholder = lambda rect: placeholders.append(rect)
view._ensure_connection = lambda: False
view._render(object())
assert selected == [view._stream_type]
assert len(placeholders) == 1
def test_reused_egl_slot_cannot_move_camera_backwards(monkeypatch):
monkeypatch.setattr(big_cameraview.cloudlog, "warning", lambda *_args, **_kwargs: None)
view = _camera_view()