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.
198 lines
6.8 KiB
198 lines
6.8 KiB
5 days ago
|
#!/usr/bin/env python3
|
||
|
import sys
|
||
|
import subprocess
|
||
|
import threading
|
||
|
import pyray as rl
|
||
|
from enum import IntEnum
|
||
|
|
||
|
from openpilot.system.hardware import HARDWARE
|
||
|
from openpilot.system.ui.lib.application import gui_app, FontWeight
|
||
|
from openpilot.system.ui.lib.button import gui_button, ButtonStyle
|
||
|
from openpilot.system.ui.lib.label import gui_text_box, gui_label
|
||
|
|
||
|
# Constants
|
||
|
MARGIN = 50
|
||
|
BUTTON_HEIGHT = 160
|
||
|
BUTTON_WIDTH = 400
|
||
|
PROGRESS_BAR_HEIGHT = 72
|
||
|
TITLE_FONT_SIZE = 80
|
||
|
BODY_FONT_SIZE = 65
|
||
|
BACKGROUND_COLOR = rl.BLACK
|
||
|
PROGRESS_BG_COLOR = rl.Color(41, 41, 41, 255)
|
||
|
PROGRESS_COLOR = rl.Color(54, 77, 239, 255)
|
||
|
|
||
|
|
||
|
class Screen(IntEnum):
|
||
|
PROMPT = 0
|
||
|
WIFI = 1
|
||
|
PROGRESS = 2
|
||
|
|
||
|
|
||
|
class Updater:
|
||
|
def __init__(self, updater_path, manifest_path):
|
||
|
self.updater = updater_path
|
||
|
self.manifest = manifest_path
|
||
|
self.current_screen = Screen.PROMPT
|
||
|
|
||
|
self.progress_value = 0
|
||
|
self.progress_text = "Loading..."
|
||
|
self.show_reboot_button = False
|
||
|
self.process = None
|
||
|
self.update_thread = None
|
||
|
|
||
|
def install_update(self):
|
||
|
self.current_screen = Screen.PROGRESS
|
||
|
self.progress_value = 0
|
||
|
self.progress_text = "Downloading..."
|
||
|
self.show_reboot_button = False
|
||
|
|
||
|
# Start the update process in a separate thread
|
||
|
self.update_thread = threading.Thread(target=self._run_update_process)
|
||
|
self.update_thread.daemon = True
|
||
|
self.update_thread.start()
|
||
|
|
||
|
def _run_update_process(self):
|
||
|
# TODO: just import it and run in a thread without a subprocess
|
||
|
cmd = [self.updater, "--swap", self.manifest]
|
||
|
self.process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
||
|
text=True, bufsize=1, universal_newlines=True)
|
||
|
|
||
|
for line in self.process.stdout:
|
||
|
parts = line.strip().split(":")
|
||
|
if len(parts) == 2:
|
||
|
self.progress_text = parts[0]
|
||
|
try:
|
||
|
self.progress_value = int(float(parts[1]))
|
||
|
except ValueError:
|
||
|
pass
|
||
|
|
||
|
exit_code = self.process.wait()
|
||
|
if exit_code == 0:
|
||
|
HARDWARE.reboot()
|
||
|
else:
|
||
|
self.progress_text = "Update failed"
|
||
|
self.show_reboot_button = True
|
||
|
|
||
|
def render_prompt_screen(self):
|
||
|
# Title
|
||
|
title_rect = rl.Rectangle(MARGIN + 50, 250, gui_app.width - MARGIN * 2 - 100, TITLE_FONT_SIZE)
|
||
|
gui_label(title_rect, "Update Required", TITLE_FONT_SIZE, font_weight=FontWeight.BOLD)
|
||
|
|
||
|
# Description
|
||
|
desc_text = "An operating system update is required. Connect your device to Wi-Fi for the fastest update experience. \
|
||
|
The download size is approximately 1GB."
|
||
|
desc_rect = rl.Rectangle(MARGIN + 50, 250 + TITLE_FONT_SIZE + 75, gui_app.width - MARGIN * 2 - 100, BODY_FONT_SIZE * 3)
|
||
|
gui_text_box(desc_rect, desc_text, BODY_FONT_SIZE)
|
||
|
|
||
|
# Buttons at the bottom
|
||
|
button_y = gui_app.height - MARGIN - BUTTON_HEIGHT
|
||
|
button_width = (gui_app.width - MARGIN * 3) // 2
|
||
|
|
||
|
# WiFi button
|
||
|
wifi_button_rect = rl.Rectangle(MARGIN, button_y, button_width, BUTTON_HEIGHT)
|
||
|
if gui_button(wifi_button_rect, "Connect to Wi-Fi"):
|
||
|
self.current_screen = Screen.WIFI
|
||
|
return # Return to avoid processing other buttons after screen change
|
||
|
|
||
|
# Install button
|
||
|
install_button_rect = rl.Rectangle(MARGIN * 2 + button_width, button_y, button_width, BUTTON_HEIGHT)
|
||
|
if gui_button(install_button_rect, "Install", button_style=ButtonStyle.PRIMARY):
|
||
|
self.install_update()
|
||
|
return # Return to avoid further processing after action
|
||
|
|
||
|
def render_wifi_screen(self):
|
||
|
# Title and back button
|
||
|
title_rect = rl.Rectangle(MARGIN + 50, MARGIN, gui_app.width - MARGIN * 2 - 100, 60)
|
||
|
gui_label(title_rect, "Wi-Fi Networks", 60, font_weight=FontWeight.BOLD)
|
||
|
|
||
|
back_button_rect = rl.Rectangle(MARGIN, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
|
||
|
if gui_button(back_button_rect, "Back"):
|
||
|
self.current_screen = Screen.PROMPT
|
||
|
return # Return to avoid processing other interactions after screen change
|
||
|
|
||
|
# Draw placeholder for WiFi implementation
|
||
|
placeholder_rect = rl.Rectangle(
|
||
|
MARGIN,
|
||
|
title_rect.y + title_rect.height + MARGIN,
|
||
|
gui_app.width - MARGIN * 2,
|
||
|
gui_app.height - title_rect.height - MARGIN * 3 - BUTTON_HEIGHT
|
||
|
)
|
||
|
|
||
|
# Draw rounded rectangle background
|
||
|
rl.draw_rectangle_rounded(
|
||
|
placeholder_rect,
|
||
|
0.1,
|
||
|
10,
|
||
|
rl.Color(41, 41, 41, 255)
|
||
|
)
|
||
|
|
||
|
# Draw placeholder text
|
||
|
placeholder_text = "WiFi Implementation Placeholder"
|
||
|
text_size = rl.measure_text_ex(gui_app.font(), placeholder_text, 80, 1)
|
||
|
text_pos = rl.Vector2(
|
||
|
placeholder_rect.x + (placeholder_rect.width - text_size.x) / 2,
|
||
|
placeholder_rect.y + (placeholder_rect.height - text_size.y) / 2
|
||
|
)
|
||
|
rl.draw_text_ex(gui_app.font(), placeholder_text, text_pos, 80, 1, rl.WHITE)
|
||
|
|
||
|
# Draw instructions
|
||
|
instructions_text = "Real WiFi functionality would be implemented here"
|
||
|
instructions_size = rl.measure_text_ex(gui_app.font(), instructions_text, 40, 1)
|
||
|
instructions_pos = rl.Vector2(
|
||
|
placeholder_rect.x + (placeholder_rect.width - instructions_size.x) / 2,
|
||
|
text_pos.y + text_size.y + 20
|
||
|
)
|
||
|
rl.draw_text_ex(gui_app.font(), instructions_text, instructions_pos, 40, 1, rl.GRAY)
|
||
|
|
||
|
def render_progress_screen(self):
|
||
|
title_rect = rl.Rectangle(MARGIN + 100, 330, gui_app.width - MARGIN * 2 - 200, 100)
|
||
|
gui_label(title_rect, self.progress_text, 90, font_weight=FontWeight.SEMI_BOLD)
|
||
|
|
||
|
# Progress bar
|
||
|
bar_rect = rl.Rectangle(MARGIN + 100, 330 + 100 + 100, gui_app.width - MARGIN * 2 - 200, PROGRESS_BAR_HEIGHT)
|
||
|
rl.draw_rectangle_rounded(bar_rect, 0.5, 10, PROGRESS_BG_COLOR)
|
||
|
|
||
|
# Calculate the width of the progress chunk
|
||
|
progress_width = (bar_rect.width * self.progress_value) / 100
|
||
|
if progress_width > 0:
|
||
|
progress_rect = rl.Rectangle(bar_rect.x, bar_rect.y, progress_width, bar_rect.height)
|
||
|
rl.draw_rectangle_rounded(progress_rect, 0.5, 10, PROGRESS_COLOR)
|
||
|
|
||
|
# Show reboot button if needed
|
||
|
if self.show_reboot_button:
|
||
|
reboot_rect = rl.Rectangle(MARGIN + 100, gui_app.height - MARGIN - BUTTON_HEIGHT, BUTTON_WIDTH, BUTTON_HEIGHT)
|
||
|
if gui_button(reboot_rect, "Reboot"):
|
||
|
# Return True to signal main loop to exit before rebooting
|
||
|
HARDWARE.reboot()
|
||
|
return
|
||
|
|
||
|
def render(self):
|
||
|
if self.current_screen == Screen.PROMPT:
|
||
|
self.render_prompt_screen()
|
||
|
elif self.current_screen == Screen.WIFI:
|
||
|
self.render_wifi_screen()
|
||
|
elif self.current_screen == Screen.PROGRESS:
|
||
|
self.render_progress_screen()
|
||
|
|
||
|
|
||
|
def main():
|
||
|
if len(sys.argv) < 3:
|
||
|
print("Usage: updater.py <updater_path> <manifest_path>")
|
||
|
sys.exit(1)
|
||
|
|
||
|
updater_path = sys.argv[1]
|
||
|
manifest_path = sys.argv[2]
|
||
|
|
||
|
try:
|
||
|
gui_app.init_window("System Update")
|
||
|
updater = Updater(updater_path, manifest_path)
|
||
|
for _ in gui_app.render():
|
||
|
updater.render()
|
||
|
finally:
|
||
|
# Make sure we clean up even if there's an error
|
||
|
gui_app.close()
|
||
|
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
main()
|