From 2c4e114b51ff1526c295bcb7ee49ecc52103191a Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 17:35:24 -0800 Subject: [PATCH] updater: new scroller style (#37556) * good start * reset on push * clean up * why tf it remove comments * no more base unnav * repack --- system/hardware/tici/updater_magic | 4 +- system/ui/mici_setup.py | 14 +-- system/ui/mici_updater.py | 194 ++++++++++++----------------- 3 files changed, 87 insertions(+), 125 deletions(-) diff --git a/system/hardware/tici/updater_magic b/system/hardware/tici/updater_magic index 9674d85f0..b4f776565 100755 --- a/system/hardware/tici/updater_magic +++ b/system/hardware/tici/updater_magic @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d815a9140e69242d85e57f74a14c6372809eebefe8958be9152efa7874928ccc -size 71215510 +oid sha256:d376e7aa4bb44bf78db9965ff55a5362ac823335b8d2a81dd3aebc5c8c2f239f +size 71214189 diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 5dc9905dd..8a7271252 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -216,9 +216,11 @@ class DownloadingPage(Widget): )) -class FailedPageBase(Widget): +class FailedPage(NavWidget): def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): super().__init__() + self.set_back_callback(retry_callback) + self._title_label = UnifiedLabel(title, 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY) self._reason_label = UnifiedLabel("", 36, text_color=rl.Color(255, 255, 255, int(255 * 0.9 * 0.65)), @@ -269,12 +271,6 @@ class FailedPageBase(Widget): )) -class FailedPage(FailedPageBase, NavWidget): - def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"): - super().__init__(reboot_callback, retry_callback, title) - self.set_back_callback(retry_callback) - - class GreyBigButton(BigButton): """Users should manage newlines with this class themselves""" @@ -426,10 +422,6 @@ class NetworkSetupPageBase(Scroller): self._continue_button.set_text("install openpilot" if not custom_software else "choose software") self._continue_button.set_green(not custom_software) - def set_is_updater(self): - self._continue_button.set_text("download\n& install") - self._continue_button.set_green(False) - def _update_state(self): super()._update_state() diff --git a/system/ui/mici_updater.py b/system/ui/mici_updater.py index cd1f99062..8e6f0a195 100755 --- a/system/ui/mici_updater.py +++ b/system/ui/mici_updater.py @@ -3,57 +3,28 @@ import sys import subprocess import threading import pyray as rl -from enum import IntEnum from openpilot.common.realtime import config_realtime_process, set_core_affinity from openpilot.system.hardware import HARDWARE, TICI from openpilot.common.swaglog import cloudlog from openpilot.system.ui.lib.application import gui_app, FontWeight from openpilot.system.ui.widgets import Widget +from openpilot.system.ui.widgets.scroller import Scroller from openpilot.system.ui.widgets.label import UnifiedLabel -from openpilot.system.ui.widgets.button import FullRoundedButton -from openpilot.system.ui.mici_setup import NetworkSetupPageBase, FailedPageBase, NetworkConnectivityMonitor +from openpilot.system.ui.mici_setup import (NetworkSetupPage, FailedPage, NetworkConnectivityMonitor, + GreyBigButton, BigPillButton) -class Screen(IntEnum): - PROMPT = 0 - WIFI = 1 - PROGRESS = 2 - FAILED = 3 +class UpdaterNetworkSetupPage(NetworkSetupPage): + def __init__(self, network_monitor, continue_callback): + super().__init__(network_monitor, continue_callback, back_callback=None) + self._continue_button.set_text("download\n& install") + self._continue_button.set_green(False) -class Updater(Widget): - def __init__(self, updater_path, manifest_path): +class ProgressPage(Widget): + def __init__(self): super().__init__() - self.updater = updater_path - self.manifest = manifest_path - self.current_screen = Screen.PROMPT - - self.progress_value = 0 - self.progress_text = "loading" - self.process = None - self.update_thread = None - self._network_monitor = NetworkConnectivityMonitor() - self._network_monitor.start() - - # TODO: network page is rendered inline, not pushed on nav stack, so auto-dismiss on internet connect doesn't work - self._network_setup_page = NetworkSetupPageBase(self._network_monitor, self._network_setup_continue_callback, - disable_connect_hint=True) - self._network_setup_page.set_is_updater() - self._network_setup_page.set_enabled(lambda: self.enabled) # for nav stack - - # Buttons - self._continue_button = FullRoundedButton("continue") - self._continue_button.set_click_callback(lambda: self.set_current_screen(Screen.WIFI)) - - self._title_label = UnifiedLabel("update required", 48, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.DISPLAY) - self._subtitle_label = UnifiedLabel("The download size is approximately 1GB.", 36, - text_color=rl.Color(255, 255, 255, int(255 * 0.9)), - font_weight=FontWeight.ROMAN) - - self._update_failed_page = FailedPageBase(HARDWARE.reboot, self._update_failed_retry_callback, - title="update failed") self._progress_title_label = UnifiedLabel("", 64, text_color=rl.Color(255, 255, 255, int(255 * 0.9)), font_weight=FontWeight.DISPLAY, line_height=0.8) @@ -61,37 +32,87 @@ class Updater(Widget): font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM) + def set_progress(self, text: str, value: int): + self._progress_title_label.set_text(text.replace("_", "_\n") + "...") + self._progress_percent_label.set_text(f"{value}%") + + def show_event(self): + super().show_event() + self.set_progress("downloading", 0) + + def _render(self, rect: rl.Rectangle): + rl.draw_rectangle_rec(rect, rl.BLACK) + self._progress_title_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 2, + rect.width, + self._progress_title_label.get_content_height(int(rect.width - 20)), + )) + + self._progress_percent_label.render(rl.Rectangle( + rect.x + 12, + rect.y + 18, + rect.width, + rect.height, + )) + + +class Updater(Scroller): + def __init__(self, updater_path, manifest_path): + super().__init__() + self.updater = updater_path + self.manifest = manifest_path + + self.progress_value = 0 + self.progress_text = "loading" + self.process = None + self.update_thread = None + self._update_failed = False + + self._network_monitor = NetworkConnectivityMonitor() + self._network_monitor.start() + + self._network_setup_page = UpdaterNetworkSetupPage(self._network_monitor, self._network_setup_continue_callback) + + self._progress_page = ProgressPage() + + self._failed_page = FailedPage(HARDWARE.reboot, self._retry, title="update failed") + + self._continue_button = BigPillButton("next") + self._continue_button.set_click_callback(lambda: gui_app.push_widget(self._network_setup_page)) + + self._scroller.add_widgets([ + GreyBigButton("update required", "The download size is\napproximately 1 GB", + gui_app.texture("icons_mici/offroad_alerts/green_wheel.png", 64, 64)), + self._continue_button, + ]) + + gui_app.add_nav_stack_tick(self._nav_stack_tick) + def _network_setup_continue_callback(self, _): self.install_update() - def _update_failed_retry_callback(self): - self.set_current_screen(Screen.PROMPT) + def _retry(self): + gui_app.pop_widgets_to(self) - def set_current_screen(self, screen: Screen): - if self.current_screen != screen: - if screen == Screen.PROGRESS: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.WIFI: - if self._network_setup_page: - self._network_setup_page.show_event() - elif screen == Screen.PROMPT: - if self._network_setup_page: - self._network_setup_page.hide_event() - elif screen == Screen.FAILED: - if self._network_setup_page: - self._network_setup_page.hide_event() + def _nav_stack_tick(self): + self._progress_page.set_progress(self.progress_text, self.progress_value) - self.current_screen = screen + if self._update_failed: + self._update_failed = False + self.show_event() + gui_app.pop_widgets_to(self, instant=True) + gui_app.push_widget(self._failed_page) def install_update(self): - self.set_current_screen(Screen.PROGRESS) self.progress_value = 0 self.progress_text = "downloading" + gui_app.pop_widgets_to(self, instant=True) + gui_app.push_widget(self._progress_page) + # Start the update process in a separate thread - self.update_thread = threading.Thread(target=self._run_update_process) - self.update_thread.daemon = True + self.update_thread = threading.Thread(target=self._run_update_process, daemon=True) self.update_thread.start() def _run_update_process(self): @@ -101,7 +122,7 @@ class Updater(Widget): self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, text=True, bufsize=1, universal_newlines=True) except Exception: - self.set_current_screen(Screen.FAILED) + self._update_failed = True return if self.process.stdout is not None: @@ -118,58 +139,7 @@ class Updater(Widget): if exit_code == 0: HARDWARE.reboot() else: - self.set_current_screen(Screen.FAILED) - - def render_prompt_screen(self, rect: rl.Rectangle): - self._title_label.render(rl.Rectangle( - rect.x + 8, - rect.y - 5, - rect.width, - 48, - )) - - subtitle_width = rect.width - 16 - subtitle_height = self._subtitle_label.get_content_height(int(subtitle_width)) - self._subtitle_label.render(rl.Rectangle( - rect.x + 8, - rect.y + 48, - subtitle_width, - subtitle_height, - )) - - self._continue_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._continue_button.rect.height, - self._continue_button.rect.width, - self._continue_button.rect.height, - )) - - def render_progress_screen(self, rect: rl.Rectangle): - self._progress_title_label.set_text(self.progress_text.replace("_", "_\n") + "...") - self._progress_title_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 2, - rect.width, - self._progress_title_label.get_content_height(int(rect.width - 20)), - )) - - self._progress_percent_label.set_text(f"{self.progress_value}%") - self._progress_percent_label.render(rl.Rectangle( - rect.x + 12, - rect.y + 18, - rect.width, - rect.height, - )) - - def _render(self, rect: rl.Rectangle): - if self.current_screen == Screen.PROMPT: - self.render_prompt_screen(rect) - elif self.current_screen == Screen.WIFI: - self._network_setup_page.render(rect) - elif self.current_screen == Screen.PROGRESS: - self.render_progress_screen(rect) - elif self.current_screen == Screen.FAILED: - self._update_failed_page.render(rect) + self._update_failed = True def close(self): self._network_monitor.stop()