You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							137 lines
						
					
					
						
							4.5 KiB
						
					
					
				
			
		
		
	
	
							137 lines
						
					
					
						
							4.5 KiB
						
					
					
				| #!/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, FONT_SCALE
 | |
| from openpilot.system.ui.widgets import Widget
 | |
| from openpilot.system.ui.widgets.button import Button, ButtonStyle
 | |
| from openpilot.system.ui.widgets.label import gui_label, gui_text_box
 | |
| 
 | |
| 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
 | |
|   FORMAT = 2      # finish up a factory reset from a tool that doesn't flash an empty partition to userdata
 | |
| 
 | |
| 
 | |
| class ResetState(IntEnum):
 | |
|   NONE = 0
 | |
|   CONFIRM = 1
 | |
|   RESETTING = 2
 | |
|   FAILED = 3
 | |
| 
 | |
| 
 | |
| class Reset(Widget):
 | |
|   def __init__(self, mode):
 | |
|     super().__init__()
 | |
|     self._mode = mode
 | |
|     self._previous_reset_state = None
 | |
|     self._reset_state = ResetState.NONE
 | |
|     self._cancel_button = Button("Cancel", self._cancel_callback)
 | |
|     self._confirm_button = Button("Confirm", self._confirm, button_style=ButtonStyle.PRIMARY)
 | |
|     self._reboot_button = Button("Reboot", lambda: os.system("sudo reboot"))
 | |
|     self._render_status = True
 | |
| 
 | |
|   def _cancel_callback(self):
 | |
|     self._render_status = False
 | |
| 
 | |
|   def _do_erase(self):
 | |
|     if PC:
 | |
|       return
 | |
| 
 | |
|     # Removing data and formatting
 | |
|     rm = os.system("sudo rm -rf /data/*")
 | |
|     os.system(f"sudo umount {USERDATA}")
 | |
|     fmt = os.system(f"yes | sudo mkfs.ext4 {USERDATA}")
 | |
| 
 | |
|     if rm == 0 or fmt == 0:
 | |
|       os.system("sudo reboot")
 | |
|     else:
 | |
|       self._reset_state = ResetState.FAILED
 | |
| 
 | |
|   def start_reset(self):
 | |
|     self._reset_state = ResetState.RESETTING
 | |
|     threading.Timer(0.1, self._do_erase).start()
 | |
| 
 | |
|   def _update_state(self):
 | |
|     if self._reset_state != self._previous_reset_state:
 | |
|       self._previous_reset_state = self._reset_state
 | |
|       self._timeout_st = time.monotonic()
 | |
|     elif self._reset_state != ResetState.RESETTING and (time.monotonic() - self._timeout_st) > TIMEOUT:
 | |
|       exit(0)
 | |
| 
 | |
|   def _render(self, rect: rl.Rectangle):
 | |
|     label_rect = rl.Rectangle(rect.x + 140, rect.y, rect.width - 280, 100 * FONT_SCALE)
 | |
|     gui_label(label_rect, "System Reset", 100, font_weight=FontWeight.BOLD)
 | |
| 
 | |
|     text_rect = rl.Rectangle(rect.x + 140, rect.y + 140, rect.width - 280, rect.height - 90 - 100 * FONT_SCALE)
 | |
|     gui_text_box(text_rect, self._get_body_text(), 90)
 | |
| 
 | |
|     button_height = 160
 | |
|     button_spacing = 50
 | |
|     button_top = rect.y + rect.height - button_height
 | |
|     button_width = (rect.width - button_spacing) / 2.0
 | |
| 
 | |
|     if self._reset_state != ResetState.RESETTING:
 | |
|       if self._mode == ResetMode.RECOVER:
 | |
|         self._reboot_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height))
 | |
|       elif self._mode == ResetMode.USER_RESET:
 | |
|         self._cancel_button.render(rl.Rectangle(rect.x, button_top, button_width, button_height))
 | |
| 
 | |
|       if self._reset_state != ResetState.FAILED:
 | |
|         self._confirm_button.render(rl.Rectangle(rect.x + button_width + 50, button_top, button_width, button_height))
 | |
|       else:
 | |
|         self._reboot_button.render(rl.Rectangle(rect.x, button_top, rect.width, button_height))
 | |
| 
 | |
|     return self._render_status
 | |
| 
 | |
|   def _confirm(self):
 | |
|     if self._reset_state == ResetState.CONFIRM:
 | |
|       self.start_reset()
 | |
|     else:
 | |
|       self._reset_state = ResetState.CONFIRM
 | |
| 
 | |
|   def _get_body_text(self):
 | |
|     if self._reset_state == ResetState.CONFIRM:
 | |
|       return "Are you sure you want to reset your device?"
 | |
|     if self._reset_state == ResetState.RESETTING:
 | |
|       return "Resetting device...\nThis 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. Partition may be corrupted. Press confirm to erase and reset your device."
 | |
|     return "System reset triggered. Press confirm to erase all content and settings. Press cancel to resume boot."
 | |
| 
 | |
| 
 | |
| 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
 | |
| 
 | |
|   gui_app.init_window("System Reset", 20)
 | |
|   reset = Reset(mode)
 | |
| 
 | |
|   if mode == ResetMode.FORMAT:
 | |
|     reset.start_reset()
 | |
| 
 | |
|   for showing_dialog in gui_app.render():
 | |
|     if showing_dialog:
 | |
|       continue
 | |
|     if not reset.render(rl.Rectangle(45, 200, gui_app.width - 90, gui_app.height - 245)):
 | |
|       break
 | |
| 
 | |
| 
 | |
| if __name__ == "__main__":
 | |
|   main()
 | |
| 
 |