This commit is contained in:
firestar5683
2026-04-11 21:56:46 -05:00
parent d2e5f06395
commit d43b7d0d3f
187 changed files with 5499 additions and 6222 deletions
Regular → Executable
+92 -111
View File
@@ -1,78 +1,109 @@
#!/usr/bin/env python3
import os
import sys
import threading
import time
import threading
from enum import IntEnum
import pyray as rl
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.mici_setup import GreyBigButton, FailedPage
from openpilot.selfdrive.ui.mici.widgets.dialog import BigDialog, BigConfirmationCircleButton
USERDATA = "/dev/disk/by-partlabel/userdata"
TIMEOUT = 3*60
PC = not (os.path.isfile("/TICI") or os.path.isfile("/EON"))
class ResetMode(IntEnum):
USER_RESET = 0 # user initiated a factory reset from openpilot
RECOVER = 1 # userdata is corrupt for some reason, give a chance to recover
FORMAT = 2 # finish up a factory reset from a tool that doesn't flash an empty partition to userdata
TAP_RESET = 2 # user initiated a factory reset by tapping the screen during boot
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(BigDialog):
DOT_STEP = 0.6
def __init__(self):
super().__init__("resetting device", "this may take up to\na minute...",
gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64))
self._show_time = 0.0
def show_event(self):
super().show_event()
self._nav_bar._alpha = 0.0 # not dismissable
self._show_time = rl.get_time()
def _back_enabled(self) -> bool:
return False
def _render(self, _):
t = (rl.get_time() - self._show_time) % (self.DOT_STEP * 2)
dots = "." * min(int(t / (self.DOT_STEP / 4)), 3)
self._card.set_value(f"this may take up to\na minute{dots}")
super()._render(_)
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(self._cancel_callback)
self._resetting_page = ResettingPage()
self._reset_failed_page = ResetFailedPage()
self._reboot_button = FullRoundedButton("reboot")
self._reboot_button.set_click_callback(self._do_reboot)
self._reset_button = BigConfirmationCircleButton("reset &\nerase", gui_app.texture("icons_mici/settings/device/uninstall.png", 70, 70),
self._start_reset, exit_on_confirm=False, red=True)
self._cancel_button = BigConfirmationCircleButton("cancel", gui_app.texture("icons_mici/setup/cancel.png", 64, 64),
gui_app.request_close, exit_on_confirm=False)
self._reboot_button = BigConfirmationCircleButton("reboot\ndevice", gui_app.texture("icons_mici/settings/device/reboot.png", 64, 70),
HARDWARE.reboot, exit_on_confirm=False)
self._confirm_slider = SmallSlider("reset", self._confirm)
# show reboot button if in recover mode
self._cancel_button.set_visible(mode != ResetMode.RECOVER)
self._reboot_button.set_visible(mode == ResetMode.RECOVER)
self._render_status = True
main_card = GreyBigButton("factory reset", "resetting erases\nall user content & data",
gui_app.texture("icons_mici/setup/factory_reset.png", 64, 64))
self._scroller.add_widget(main_card)
def _cancel_callback(self):
self._render_status = False
if mode != ResetMode.USER_RESET:
self._scroller.add_widget(GreyBigButton("", "Resetting erases all user content & data."))
if mode == ResetMode.RECOVER:
main_card.set_value("user data partition\ncould not be mounted")
elif mode == ResetMode.TAP_RESET:
main_card.set_value("reset triggered by\ntapping the screen")
def _do_reboot(self):
if PC:
return
self._scroller.add_widgets([
GreyBigButton("", "For a deeper reset, go to\nhttps://flash.comma.ai"),
self._cancel_button,
self._reboot_button,
self._reset_button,
])
os.system("sudo reboot")
def _backup_ssh_params(self):
if PC:
return
backup_dir = "/cache/reset_backup"
os.system(f"sudo rm -rf {backup_dir}")
os.system(f"sudo mkdir -p {backup_dir}")
for key in ("GithubSshKeys", "SshEnabled"):
os.system(f"sudo cp /data/params/d/{key} {backup_dir}/{key} 2>/dev/null || true")
os.system(f"sudo chmod 600 {backup_dir}/* 2>/dev/null || true")
gui_app.add_nav_stack_tick(self._nav_stack_tick)
def _do_erase(self):
if PC:
return
self._backup_ssh_params()
# Removing data and formatting
rm = os.system("sudo rm -rf /data/*")
os.system(f"sudo umount {USERDATA}")
@@ -81,92 +112,42 @@ 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()
def _start_reset(self):
def do_erase_thread():
threading.Thread(target=self._do_erase, daemon=True).start()
def _update_state(self):
if self._reset_state != self._previous_reset_state:
self._previous_reset_state = self._reset_state
self._resetting_page.set_shown_callback(do_erase_thread)
gui_app.push_widget(self._resetting_page)
def _nav_stack_tick(self):
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._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.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))
return self._render_status
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
if len(sys.argv) > 1:
if sys.argv[1] == '--recover':
mode = ResetMode.RECOVER
elif sys.argv[1] == "--format":
mode = ResetMode.FORMAT
elif sys.argv[1] == '--tap-reset':
mode = ResetMode.TAP_RESET
gui_app.init_window("System Reset")
reset = Reset(mode)
gui_app.push_widget(reset)
if mode == ResetMode.FORMAT:
reset.start_reset()
for should_render in gui_app.render():
if should_render:
if not reset.render(rl.Rectangle(0, 0, gui_app.width, gui_app.height)):
break
for _ in gui_app.render():
pass
if __name__ == "__main__":