diff --git a/openpilot/selfdrive/ui/mici/layouts/onboarding.py b/openpilot/selfdrive/ui/mici/layouts/onboarding.py index 2b448ac73d8aed..731b96952fa10e 100644 --- a/openpilot/selfdrive/ui/mici/layouts/onboarding.py +++ b/openpilot/selfdrive/ui/mici/layouts/onboarding.py @@ -107,17 +107,20 @@ def __init__(self, continue_callback: Callable[[], None]): self._dialog = CabinCameraSetupDialog() self._bad_face_page = DMBadFaceDetected() - # Disable driver monitoring model when device times out for inactivity - def inactivity_callback(): - ui_state.params.put_bool("IsDriverViewEnabled", False) - - device.add_interactive_timeout_callback(inactivity_callback) - def show_event(self): super().show_event() self._dialog.show_event() + device.add_interactive_timeout_callback(self._on_inactivity) self._progress.x = 0.0 + def hide_event(self): + device.remove_interactive_timeout_callback(self._on_inactivity) + self._dialog.hide_event() + super().hide_event() + + def _on_inactivity(self): + ui_state.params.put_bool("IsDriverViewEnabled", False) + def _update_state(self): super()._update_state() if device.awake and not ui_state.params.get_bool("IsDriverViewEnabled"): diff --git a/openpilot/selfdrive/ui/mici/onroad/cabin_camera_dialog.py b/openpilot/selfdrive/ui/mici/onroad/cabin_camera_dialog.py index e86c0aa739e9f5..944267caf208c2 100644 --- a/openpilot/selfdrive/ui/mici/onroad/cabin_camera_dialog.py +++ b/openpilot/selfdrive/ui/mici/onroad/cabin_camera_dialog.py @@ -230,11 +230,14 @@ def _draw_eyes(self, rect: rl.Rectangle, driver_data): class CabinCameraDialog(NavWidget, BaseCabinCameraDialog): - def __init__(self): - super().__init__() - # TODO: this can grow unbounded, should be given some thought + def show_event(self): + super().show_event() device.add_interactive_timeout_callback(gui_app.pop_widget) + def hide_event(self): + device.remove_interactive_timeout_callback(gui_app.pop_widget) + super().hide_event() + if __name__ == "__main__": gui_app.init_window("Cabin Camera View (mici)") diff --git a/openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py b/openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py index 7bb1917c35fff4..8fc527d2175169 100644 --- a/openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py +++ b/openpilot/selfdrive/ui/onroad/cabin_camera_dialog.py @@ -13,11 +13,14 @@ class CabinCameraDialog(CameraView): def __init__(self): super().__init__("camerad", VisionStreamType.VISION_STREAM_CABIN) self.driver_state_renderer = DriverStateRenderer() - # TODO: this can grow unbounded, should be given some thought - device.add_interactive_timeout_callback(gui_app.pop_widget) ui_state.params.put_bool("IsDriverViewEnabled", True, block=True) + def show_event(self): + super().show_event() + device.add_interactive_timeout_callback(gui_app.pop_widget) + def hide_event(self): + device.remove_interactive_timeout_callback(gui_app.pop_widget) super().hide_event() ui_state.params.put_bool("IsDriverViewEnabled", False, block=True) self.close() diff --git a/openpilot/selfdrive/ui/tests/test_ui_state.py b/openpilot/selfdrive/ui/tests/test_ui_state.py new file mode 100644 index 00000000000000..51b51ededda81e --- /dev/null +++ b/openpilot/selfdrive/ui/tests/test_ui_state.py @@ -0,0 +1,46 @@ +import unittest +import sys +import types +from unittest import mock + +from openpilot.cereal import messaging + +application_module = types.ModuleType("openpilot.system.ui.lib.application") +application_module.gui_app = mock.Mock(target_fps=20) +with mock.patch.dict(sys.modules, {"openpilot.system.ui.lib.application": application_module}), \ + mock.patch.object(messaging, "SubMaster"): + from openpilot.selfdrive.ui.ui_state import Device + + +class TestInteractiveTimeoutCallbacks(unittest.TestCase): + def test_add_remove_callback(self): + device = Device() + + def callback(): + pass + + device.add_interactive_timeout_callback(callback) + device.add_interactive_timeout_callback(callback) + assert device._interactive_timeout_callbacks == [callback] + + device.remove_interactive_timeout_callback(callback) + device.remove_interactive_timeout_callback(callback) + assert device._interactive_timeout_callbacks == [] + + def test_callback_can_remove_itself(self): + device = Device() + calls = [] + + def first_callback(): + calls.append("first") + device.remove_interactive_timeout_callback(first_callback) + + def second_callback(): + calls.append("second") + + device.add_interactive_timeout_callback(first_callback) + device.add_interactive_timeout_callback(second_callback) + device._run_interactive_timeout_callbacks() + + assert calls == ["first", "second"] + assert device._interactive_timeout_callbacks == [second_callback] diff --git a/openpilot/selfdrive/ui/ui_state.py b/openpilot/selfdrive/ui/ui_state.py index 42e80863514eda..f830fb649b2364 100644 --- a/openpilot/selfdrive/ui/ui_state.py +++ b/openpilot/selfdrive/ui/ui_state.py @@ -253,7 +253,17 @@ def _reset_interactive_timeout(self) -> None: self._interaction_time = time.monotonic() + self.interactive_timeout def add_interactive_timeout_callback(self, callback: Callable): - self._interactive_timeout_callbacks.append(callback) + if callback not in self._interactive_timeout_callbacks: + self._interactive_timeout_callbacks.append(callback) + + def remove_interactive_timeout_callback(self, callback: Callable): + if callback in self._interactive_timeout_callbacks: + self._interactive_timeout_callbacks.remove(callback) + + def _run_interactive_timeout_callbacks(self): + # Callbacks can unregister themselves by hiding their owning widget. + for callback in self._interactive_timeout_callbacks.copy(): + callback() def update(self): self._start_brightness_thread() # start thread after manager forks ui @@ -315,8 +325,7 @@ def _update_wakefulness(self): interaction_timeout = time.monotonic() > self._interaction_time if interaction_timeout and not self._prev_timed_out: - for callback in self._interactive_timeout_callbacks: - callback() + self._run_interactive_timeout_callbacks() self._prev_timed_out = interaction_timeout self._set_awake(ui_state.ignition or not interaction_timeout or PC)