sunnypilot v2026.002.000
version: sunnypilot v2026.002.000 (staging) date: 2026-07-21T07:44:02 master commit: 2c334ede443d7391d27575af8c854a095ba702a8
This commit is contained in:
commit
de77c89cf6
3995 files changed
+1260688
No files matched your search
Executable
+154
@@ -0,0 +1,154 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
from enum import IntEnum
|
||||
|
||||
import pyray as rl
|
||||
|
||||
from openpilot.common.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
|
||||
|
||||
|
||||
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
|
||||
TAP_RESET = 2 # user initiated a factory reset by tapping the screen during boot
|
||||
|
||||
|
||||
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 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_active_widget = None
|
||||
self._reset_failed = False
|
||||
self._timeout_st = time.monotonic()
|
||||
|
||||
self._resetting_page = ResettingPage()
|
||||
self._reset_failed_page = ResetFailedPage()
|
||||
|
||||
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)
|
||||
|
||||
# show reboot button if in recover mode
|
||||
self._cancel_button.set_visible(mode != ResetMode.RECOVER)
|
||||
self._reboot_button.set_visible(mode == ResetMode.RECOVER)
|
||||
|
||||
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)
|
||||
|
||||
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")
|
||||
|
||||
self._scroller.add_widgets([
|
||||
GreyBigButton("", "For a deeper reset, go to\nhttps://flash.comma.ai"),
|
||||
self._cancel_button,
|
||||
self._reboot_button,
|
||||
self._reset_button,
|
||||
])
|
||||
|
||||
gui_app.add_nav_stack_tick(self._nav_stack_tick)
|
||||
|
||||
def _do_erase(self):
|
||||
if PC:
|
||||
return
|
||||
|
||||
# Removing data and formatting
|
||||
rm = subprocess.run("sudo rm -rf /data/*", shell=True).returncode
|
||||
subprocess.run(f"sudo umount {USERDATA}", shell=True)
|
||||
fmt = subprocess.run(f"yes | sudo mkfs.ext4 {USERDATA}", shell=True).returncode
|
||||
|
||||
if rm == 0 or fmt == 0:
|
||||
subprocess.run("sudo reboot", shell=True)
|
||||
else:
|
||||
self._reset_failed = True
|
||||
|
||||
def _start_reset(self):
|
||||
def do_erase_thread():
|
||||
threading.Thread(target=self._do_erase, daemon=True).start()
|
||||
|
||||
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._mode != ResetMode.RECOVER and active_widget != self._resetting_page and (time.monotonic() - self._timeout_st) > TIMEOUT:
|
||||
exit(0)
|
||||
|
||||
|
||||
def main():
|
||||
mode = ResetMode.USER_RESET
|
||||
if len(sys.argv) > 1:
|
||||
if sys.argv[1] == '--recover':
|
||||
mode = ResetMode.RECOVER
|
||||
elif sys.argv[1] == '--tap-reset':
|
||||
mode = ResetMode.TAP_RESET
|
||||
|
||||
gui_app.init_window("System Reset")
|
||||
reset = Reset(mode)
|
||||
gui_app.push_widget(reset)
|
||||
|
||||
for _ in gui_app.render():
|
||||
pass
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in new issue
Block a user