From e59f675715cff7df8406cff741fbe95921e393bb Mon Sep 17 00:00:00 2001 From: Shane Smiskol Date: Wed, 4 Mar 2026 22:36:25 -0800 Subject: [PATCH] new reset (#37563) * start new reset w navwidgets * full port * clean up * clean up * clean up * fixes * rm --- .../assets/icons_mici/setup/factory_reset.png | 3 + .../assets/icons_mici/setup/reset_failed.png | 3 + system/ui/mici_reset.py | 176 +++++++++--------- system/ui/mici_setup.py | 11 +- 4 files changed, 103 insertions(+), 90 deletions(-) create mode 100644 selfdrive/assets/icons_mici/setup/factory_reset.png create mode 100644 selfdrive/assets/icons_mici/setup/reset_failed.png diff --git a/selfdrive/assets/icons_mici/setup/factory_reset.png b/selfdrive/assets/icons_mici/setup/factory_reset.png new file mode 100644 index 000000000..bcb3ea92c --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/factory_reset.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:122a614d1aa26187507951f932160eebfddfebcb4293e78f8d23e350fc97bc0f +size 11489 diff --git a/selfdrive/assets/icons_mici/setup/reset_failed.png b/selfdrive/assets/icons_mici/setup/reset_failed.png new file mode 100644 index 000000000..680df97cb --- /dev/null +++ b/selfdrive/assets/icons_mici/setup/reset_failed.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d5b8f76e5f47e77e5af3016ebdbe548ad3bc9af83a1111b3214bf4017c95a28 +size 11792 diff --git a/system/ui/mici_reset.py b/system/ui/mici_reset.py index de22dea5d..5cbba5e8e 100755 --- a/system/ui/mici_reset.py +++ b/system/ui/mici_reset.py @@ -1,18 +1,18 @@ #!/usr/bin/env python3 import os import sys -import threading import time from enum import IntEnum import pyray as rl -from openpilot.system.hardware import PC -from openpilot.system.ui.lib.application import gui_app, FontWeight -from openpilot.system.ui.widgets import Widget -from openpilot.system.ui.widgets.slider import SmallSlider -from openpilot.system.ui.widgets.button import SmallButton, FullRoundedButton -from openpilot.system.ui.widgets.label import gui_label, gui_text_box +from openpilot.system.hardware import HARDWARE, PC +from openpilot.system.ui.lib.application import gui_app +from openpilot.system.ui.widgets.scroller import Scroller +from openpilot.system.ui.widgets.nav_widget import NavWidget +from openpilot.system.ui.mici_setup import GreyBigButton, FailedPage +from openpilot.selfdrive.ui.mici.widgets.dialog import BigConfirmationDialogV2 +from openpilot.selfdrive.ui.mici.widgets.button import BigCircleButton USERDATA = "/dev/disk/by-partlabel/userdata" TIMEOUT = 3*60 @@ -24,32 +24,87 @@ class ResetMode(IntEnum): FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata -class ResetState(IntEnum): - NONE = 0 - RESETTING = 1 - FAILED = 2 +class ResetFailedPage(FailedPage): + def __init__(self): + super().__init__(None, "reset failed", "reboot to try again", icon="icons_mici/setup/reset_failed.png") + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + + def _back_enabled(self) -> bool: + return False -class Reset(Widget): +class ResettingPage(NavWidget): + def __init__(self): + super().__init__() + + self._resetting_card = GreyBigButton("resetting device", "this may take up to\na minute...", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + + def show_event(self): + super().show_event() + self._nav_bar._alpha = 0.0 # not dismissable + + def _back_enabled(self) -> bool: + return False + + def _render(self, _): + self._resetting_card.render(rl.Rectangle( + self._rect.x + self._rect.width / 2 - self._resetting_card.rect.width / 2, + self._rect.y + self._rect.height / 2 - self._resetting_card.rect.height / 2, + self._resetting_card.rect.width, + self._resetting_card.rect.height, + )) + + +class Reset(Scroller): def __init__(self, mode): super().__init__() self._mode = mode - self._previous_reset_state = None - self._reset_state = ResetState.NONE + self._previous_active_widget = None + self._reset_failed = False + self._timeout_st = time.monotonic() - self._cancel_button = SmallButton("cancel") - self._cancel_button.set_click_callback(gui_app.request_close) + self._resetting_page = ResettingPage() + self._reset_failed_page = ResetFailedPage() - self._reboot_button = FullRoundedButton("reboot") - self._reboot_button.set_click_callback(self._do_reboot) + def show_confirm_dialog(): + dialog = BigConfirmationDialogV2("erase\ndevice", "icons_mici/settings/device/uninstall.png", red=True, + confirm_callback=self.start_reset) + gui_app.push_widget(dialog) - self._confirm_slider = SmallSlider("reset", self._confirm) + def show_cancel_dialog(): + dialog = BigConfirmationDialogV2("normal\nstartup", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=gui_app.request_close) + gui_app.push_widget(dialog) - def _do_reboot(self): - if PC: - return + def show_reboot_dialog(): + dialog = BigConfirmationDialogV2("reboot\ndevice", "icons_mici/settings/device/reboot.png", + exit_on_confirm=False, confirm_callback=HARDWARE.reboot) + gui_app.push_widget(dialog) - os.system("sudo reboot") + self._reset_button = BigCircleButton("icons_mici/settings/device/uninstall.png", red=True) + self._reset_button.set_click_callback(show_confirm_dialog) + + self._cancel_button = BigCircleButton("icons_mici/settings/device/reboot.png") + self._cancel_button.set_click_callback(show_cancel_dialog) + + main_card = GreyBigButton("factory reset", "all content and\nsettings will be erased", + gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64)) + + # cancel button becomes reboot button + if mode == ResetMode.RECOVER: + main_card.set_text("unable to mount\ndata partition") + main_card.set_value("it may be corrupted") + self._cancel_button.set_click_callback(show_reboot_dialog) + + self._scroller.add_widgets([ + main_card, + self._reset_button, + self._cancel_button, + ]) def _do_erase(self): if PC: @@ -63,72 +118,26 @@ class Reset(Widget): if rm == 0 or fmt == 0: os.system("sudo reboot") else: - self._reset_state = ResetState.FAILED + self._reset_failed = True def start_reset(self): - self._reset_state = ResetState.RESETTING - threading.Timer(0.1, self._do_erase).start() + self._resetting_page.set_shown_callback(self._do_erase) + gui_app.push_widget(self._resetting_page) def _update_state(self): - if self._reset_state != self._previous_reset_state: - self._previous_reset_state = self._reset_state + super()._update_state() + + if self._reset_failed: + self._reset_failed = False + gui_app.pop_widgets_to(self, lambda: gui_app.push_widget(self._reset_failed_page)) + + active_widget = gui_app.get_active_widget() + if active_widget != self._previous_active_widget: + self._previous_active_widget = active_widget self._timeout_st = time.monotonic() - elif self._mode != ResetMode.RECOVER and self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT: + elif self._mode != ResetMode.RECOVER and active_widget != self._resetting_page and (time.monotonic() - self._timeout_st) > TIMEOUT: exit(0) - def _render(self, rect: rl.Rectangle): - label_rect = rl.Rectangle(rect.x + 8, rect.y + 8, rect.width, 50) - gui_label(label_rect, "factory reset", 48, font_weight=FontWeight.BOLD, - color=rl.Color(255, 255, 255, int(255 * 0.9))) - - text_rect = rl.Rectangle(rect.x + 8, rect.y + 56, rect.width - 8 * 2, rect.height - 80) - gui_text_box(text_rect, self._get_body_text(), 36, font_weight=FontWeight.ROMAN, line_scale=0.9) - - if self._reset_state != ResetState.RESETTING: - # fade out cancel button as slider is moved, set visible to prevent pressing invisible cancel - self._cancel_button.set_opacity(1.0 - self._confirm_slider.slider_percentage) - self._cancel_button.set_visible(self._confirm_slider.slider_percentage < 0.8) - - if self._mode == ResetMode.RECOVER: - self._cancel_button.set_text("reboot") - self._cancel_button.set_click_callback(self._do_reboot) - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - elif self._mode == ResetMode.USER_RESET and self._reset_state != ResetState.FAILED: - self._cancel_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._cancel_button.rect.height, - self._cancel_button.rect.width, - self._cancel_button.rect.height)) - - if self._reset_state != ResetState.FAILED: - self._confirm_slider.render(rl.Rectangle( - rect.x + rect.width - self._confirm_slider.rect.width, - rect.y + rect.height - self._confirm_slider.rect.height, - self._confirm_slider.rect.width, - self._confirm_slider.rect.height)) - else: - self._reboot_button.render(rl.Rectangle( - rect.x + 8, - rect.y + rect.height - self._reboot_button.rect.height, - self._reboot_button.rect.width, - self._reboot_button.rect.height)) - - def _confirm(self): - self.start_reset() - - def _get_body_text(self): - if self._reset_state == ResetState.RESETTING: - return "Resetting device... This may take up to a minute." - if self._reset_state == ResetState.FAILED: - return "Reset failed. Reboot to try again." - if self._mode == ResetMode.RECOVER: - return "Unable to mount data partition. It may be corrupted." - return "All content and settings will be erased." - def main(): mode = ResetMode.USER_RESET @@ -140,12 +149,11 @@ def main(): gui_app.init_window("System Reset") reset = Reset(mode) + gui_app.push_widget(reset) if mode == ResetMode.FORMAT: reset.start_reset() - gui_app.push_widget(reset) - for _ in gui_app.render(): pass diff --git a/system/ui/mici_setup.py b/system/ui/mici_setup.py index 3265a325e..92ac1df44 100755 --- a/system/ui/mici_setup.py +++ b/system/ui/mici_setup.py @@ -221,7 +221,8 @@ class DownloadingPage(NavWidget): class FailedPage(NavScroller): - def __init__(self, retry_callback: Callable, title: str = "download failed", red_icon: bool = False): + def __init__(self, retry_callback: Callable | None, title: str = "download failed", + description: str | None = None, icon: str = "icons_mici/setup/warning.png"): super().__init__() self.set_back_callback(retry_callback) @@ -236,11 +237,9 @@ class FailedPage(NavScroller): 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)), + GreyBigButton(title, description or "swipe down to go\nback and try again", + gui_app.texture(icon, 64, 58)), self._reason_card, reboot_button, ]) @@ -459,7 +458,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(self._pop_to_software_selection, red_icon=True) + self._download_failed_page = FailedPage(self._pop_to_software_selection, icon="icons_mici/setup/red_warning.png") self._custom_software_warning_page = CustomSoftwareWarningPage(lambda: self._push_network_setup(True), self._pop_to_software_selection)