diff --git a/selfdrive/ui/mici/layouts/offroad_alerts.py b/selfdrive/ui/mici/layouts/offroad_alerts.py index a46002c28..de631f686 100644 --- a/selfdrive/ui/mici/layouts/offroad_alerts.py +++ b/selfdrive/ui/mici/layouts/offroad_alerts.py @@ -1,9 +1,11 @@ import pyray as rl import re +import threading import time from dataclasses import dataclass from enum import IntEnum from openpilot.common.params import Params +from openpilot.common.realtime import drop_realtime from openpilot.selfdrive.selfdrived.alertmanager import OFFROAD_ALERTS from openpilot.system.hardware import HARDWARE from openpilot.system.ui.widgets import Widget @@ -195,7 +197,6 @@ class MiciOffroadAlerts(Scroller): self.params = Params() self.sorted_alerts: list[AlertData] = [] self.alert_items: list[AlertItem] = [] - self._last_refresh = 0.0 # Create empty state label self._empty_label = UnifiedLabel(tr("no alerts"), 65, FontWeight.DISPLAY, rl.WHITE, @@ -205,6 +206,11 @@ class MiciOffroadAlerts(Scroller): # Build initial alert list self._build_alerts() + # Start param thread + self._pending_params: dict | None = None + self._params_thread = threading.Thread(target=self._params_worker, daemon=True) + self._params_thread.start() + def active_alerts(self) -> int: return sum(alert.visible for alert in self.sorted_alerts) @@ -237,12 +243,19 @@ class MiciOffroadAlerts(Scroller): self.alert_items.append(alert_item) self._scroller.add_widget(alert_item) - def refresh(self) -> int: + def _params_worker(self): + drop_realtime() + while True: + self._pending_params = ({"UpdaterNewDescription": self.params.get("UpdaterNewDescription")} | + {alert_data.key: self.params.get(alert_data.key) for alert_data in self.sorted_alerts}) + time.sleep(REFRESH_INTERVAL) + + def _refresh(self) -> int: """Refresh alerts from params and return active count.""" active_count = 0 # Handle UpdateAvailable alert specially - update_available = self.params.get_bool("UpdateAvailable") + update_available = self._pending_params["UpdateAvailable"] update_alert_data = next((alert_data for alert_data in self.sorted_alerts if alert_data.key == "UpdateAvailable"), None) if update_alert_data: @@ -250,7 +263,7 @@ class MiciOffroadAlerts(Scroller): version_string = "" # Get new version description and parse version and date - new_desc = self.params.get("UpdaterNewDescription") or "" + new_desc = self._pending_params["UpdaterNewDescription"] or "" if new_desc: # format: "version / branch / commit / date" parts = new_desc.split(" / ") @@ -271,7 +284,7 @@ class MiciOffroadAlerts(Scroller): continue # Skip, already handled above text = "" - alert_json = self.params.get(alert_data.key) + alert_json = self._pending_params[alert_data.key] if alert_json: text = alert_json.get("text", "").replace("%1", alert_json.get("extra", "")) @@ -295,19 +308,12 @@ class MiciOffroadAlerts(Scroller): return active_count - def show_event(self): - """Reset scroll position when shown and refresh alerts.""" - super().show_event() - self._last_refresh = time.monotonic() - self.refresh() - def _update_state(self): """Periodically refresh alerts.""" - # Refresh alerts periodically, not every frame - current_time = time.monotonic() - if current_time - self._last_refresh >= REFRESH_INTERVAL: - self.refresh() - self._last_refresh = current_time + # Refresh alerts when thread updates params + if self._pending_params is not None: + self._refresh() + self._pending_params = None def _render(self, rect: rl.Rectangle): """Render the alerts scroller or empty state.""" diff --git a/selfdrive/ui/tests/diff/replay_script.py b/selfdrive/ui/tests/diff/replay_script.py index dce1c12dc..ad7e8952d 100644 --- a/selfdrive/ui/tests/diff/replay_script.py +++ b/selfdrive/ui/tests/diff/replay_script.py @@ -354,7 +354,10 @@ def build_mici_script(pm: PubMaster, main_layout, script: Script) -> None: def setup_offroad_alerts_and_refresh() -> None: """Setup function to trigger offroad alerts and force a refresh on the alerts layout.""" setup_offroad_alerts() - main_layout._alerts_layout.refresh() + params = Params() + main_layout._alerts_layout._pending_params = ({"UpdaterNewDescription": params.get("UpdaterNewDescription")} | + {alert_data.key: params.get(alert_data.key) for alert_data in main_layout._alerts_layout.sorted_alerts}) + main_layout._alerts_layout._refresh() swipe_right(width, wait_after=WAIT_SHORT) # open alerts script.setup(setup_offroad_alerts_and_refresh) # show alerts