setup: new scroller failed screen (#37561)

* better update flow

* clean up

* clean up

* cmt

* clean up

* todo

* failed scroller

* fix for setup

* show wrong url

* setup failed is red not orange

* clean up and fix all flashing in setup
This commit is contained in:
Shane Smiskol
2026-03-04 20:44:29 -08:00
committed by GitHub
parent 0274b73760
commit 5beae930e4
2 changed files with 47 additions and 63 deletions
+46 -62
View File
@@ -21,14 +21,13 @@ from openpilot.system.ui.lib.application import gui_app, FontWeight
from openpilot.system.ui.lib.wifi_manager import WifiManager
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets.nav_widget import NavWidget
from openpilot.system.ui.widgets.button import SmallButton
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.scroller import Scroller, NavScroller, ITEM_SPACING
from openpilot.system.ui.widgets.slider import LargerSlider, SmallSlider
from openpilot.system.ui.widgets.slider import LargerSlider
from openpilot.selfdrive.ui.mici.layouts.settings.network import WifiNetworkButton
from openpilot.selfdrive.ui.mici.layouts.settings.network.wifi_ui import WifiUIMici
from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog
from openpilot.selfdrive.ui.mici.widgets.button import BigButton
from openpilot.selfdrive.ui.mici.widgets.dialog import BigInputDialog, BigConfirmationDialogV2
from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton, BigButton
NetworkType = log.DeviceState.NetworkType
@@ -181,7 +180,8 @@ class CustomSoftwareWarningPage(NavScroller):
])
class DownloadingPage(Widget):
# TODO: unifi with updater's progress page
class DownloadingPage(NavWidget):
def __init__(self):
super().__init__()
@@ -191,8 +191,12 @@ class DownloadingPage(Widget):
font_weight=FontWeight.ROMAN, alignment_vertical=rl.GuiTextAlignmentVertical.TEXT_ALIGN_BOTTOM)
self._progress = 0
def _back_enabled(self) -> bool:
return False
def show_event(self):
super().show_event()
self._nav_bar._alpha = 0.0 # not dismissable
self.set_progress(0)
def set_progress(self, progress: int):
@@ -216,59 +220,37 @@ class DownloadingPage(Widget):
))
class FailedPage(NavWidget):
def __init__(self, reboot_callback: Callable, retry_callback: Callable, title: str = "download failed"):
class FailedPage(NavScroller):
def __init__(self, retry_callback: Callable, title: str = "download failed", red_icon: bool = False):
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)),
font_weight=FontWeight.ROMAN)
def show_reboot_dialog():
dialog = BigConfirmationDialogV2("slide to reboot", "icons_mici/settings/device/reboot.png",
exit_on_confirm=False, confirm_callback=HARDWARE.reboot)
gui_app.push_widget(dialog)
self._reboot_slider = SmallSlider("reboot", reboot_callback)
self._reboot_slider.set_enabled(lambda: self.enabled) # for nav stack
reboot_button = BigCircleButton("icons_mici/settings/device/reboot.png", red=False, icon_size=(64, 70))
reboot_button.set_click_callback(show_reboot_dialog)
self._retry_button = SmallButton("retry")
self._retry_button.set_click_callback(retry_callback)
self._retry_button.set_enabled(lambda: self.enabled) # for nav stack
self._reason_card = GreyBigButton("", "")
self._reason_card.set_visible(False)
warning_icon = "icons_mici/setup/red_warning.png" if red_icon else "icons_mici/setup/warning.png"
self._scroller.add_widgets([
GreyBigButton(title, "swipe down to go\nback and try again",
gui_app.texture(warning_icon, 64, 58)),
self._reason_card,
reboot_button,
])
def set_reason(self, reason: str):
self._reason_label.set_text(reason)
def show_event(self):
super().show_event()
self._reboot_slider.reset()
def _render(self, rect: rl.Rectangle):
self._title_label.render(rl.Rectangle(
rect.x + 8,
rect.y + 10,
rect.width,
64,
))
self._reason_label.render(rl.Rectangle(
rect.x + 8,
rect.y + 10 + 64,
rect.width,
36,
))
self._retry_button.set_opacity(1 - self._reboot_slider.slider_percentage)
self._retry_button.render(rl.Rectangle(
self._rect.x + 8,
self._rect.y + self._rect.height - self._retry_button.rect.height,
self._retry_button.rect.width,
self._retry_button.rect.height,
))
self._reboot_slider.render(rl.Rectangle(
self._rect.x + self._rect.width - self._reboot_slider.rect.width,
self._rect.y + self._rect.height - self._reboot_slider.rect.height,
self._reboot_slider.rect.width,
self._reboot_slider.rect.height,
))
if reason:
self._reason_card.set_value(reason)
self._reason_card.set_visible(True)
else:
self._reason_card.set_visible(False)
class GreyBigButton(BigButton):
@@ -300,6 +282,9 @@ class GreyBigButton(BigButton):
def _width_hint(self) -> int:
return int(self._rect.width - self.LABEL_HORIZONTAL_PADDING * 2)
def _get_label_font_size(self):
return 36
def _render(self, _):
rl.draw_rectangle_rounded(self._rect, 0.4, 10, rl.Color(255, 255, 255, int(255 * 0.15)))
self._draw_content(self._rect.y)
@@ -474,7 +459,7 @@ class Setup(Widget):
self._software_selection_page = SoftwareSelectionPage(self._use_openpilot, lambda: gui_app.push_widget(self._custom_software_warning_page))
self._download_failed_page = FailedPage(HARDWARE.reboot, self._pop_to_software_selection)
self._download_failed_page = FailedPage(self._pop_to_software_selection, red_icon=True)
self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection)
@@ -489,8 +474,7 @@ class Setup(Widget):
reason = self._download_failed_reason
self._download_failed_reason = None
self._download_failed_page.set_reason(reason)
gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders
gui_app.push_widget(self._download_failed_page)
gui_app.pop_widgets_to(self._software_selection_page, lambda: gui_app.push_widget(self._download_failed_page))
def _render(self, rect: rl.Rectangle):
self._start_page.render(rect)
@@ -524,13 +508,11 @@ class Setup(Widget):
def _network_setup_continue_callback(self, custom_software: bool):
if not custom_software:
gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders
self._download(OPENPILOT_URL)
else:
def handle_keyboard_result(text):
url = text.strip()
if url:
gui_app.pop_widgets_to(self._software_selection_page, instant=True) # don't reset sliders
self._download(url)
keyboard = BigInputDialog("custom software URL...", confirm_callback=handle_keyboard_result, auto_return_to_letters="./")
@@ -545,10 +527,12 @@ class Setup(Widget):
self.download_url = (urlparse(f"https://{url}") if not parsed.netloc else parsed).geturl()
self.download_progress = 0
gui_app.push_widget(self._downloading_page)
def start_download():
self.download_thread = threading.Thread(target=self._download_thread, daemon=True)
self.download_thread.start()
self.download_thread = threading.Thread(target=self._download_thread, daemon=True)
self.download_thread.start()
self._downloading_page.set_shown_callback(start_download)
gui_app.push_widget(self._downloading_page)
def _download_thread(self):
try:
@@ -583,7 +567,7 @@ class Setup(Widget):
is_elf = header == b'\x7fELF'
if not is_elf:
self._download_failed_reason = "No custom software found at this URL."
self._download_failed_reason = "No custom software found at this URL: " + self.download_url.replace("https://", "", 1)
return
# AGNOS might try to execute the installer before this process exits.
@@ -600,9 +584,9 @@ class Setup(Widget):
except urllib.error.HTTPError as e:
if e.code == 409:
self._download_failed_reason = "Incompatible openpilot version"
self._download_failed_reason = "Incompatible openpilot version."
except Exception:
self._download_failed_reason = "Invalid URL"
self._download_failed_reason = "Invalid URL: " + self.download_url.replace("https://", "", 1)
def main():
+1 -1
View File
@@ -80,7 +80,7 @@ class Updater(Scroller):
self._progress_page = ProgressPage()
self._failed_page = FailedPage(HARDWARE.reboot, self._retry, title="update failed")
self._failed_page = FailedPage(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))