Merge pull request #7 from infiniteCable2/updater_mici

branch switching for MICI, force update buttons for TICI/MICI
This commit is contained in:
infiniteCable2
2026-05-10 14:15:44 +02:00
committed by GitHub
3 changed files with 158 additions and 8 deletions
+11
View File
@@ -58,6 +58,7 @@ class SoftwareLayout(Widget):
self._onroad_label = ListItem(lambda: tr("Updates are only downloaded while the car is off."))
self._version_item = text_item(lambda: tr("Current Version"), ui_state.params.get("UpdaterCurrentDescription") or "")
self._download_btn = button_item(lambda: tr("Download"), lambda: tr("CHECK"), callback=self._on_download_update)
self._force_download_btn = button_item(lambda: tr("Force Download"), lambda: tr("FORCE"), callback=self._on_force_download)
# Install button is initially hidden
self._install_btn = button_item(lambda: tr("Install Update"), lambda: tr("INSTALL"), callback=self._on_install_update)
@@ -76,6 +77,7 @@ class SoftwareLayout(Widget):
self._onroad_label,
self._version_item,
self._download_btn,
self._force_download_btn,
self._install_btn,
self._branch_btn,
button_item(lambda: tr("Uninstall"), lambda: tr("UNINSTALL"), callback=self._on_uninstall),
@@ -100,6 +102,7 @@ class SoftwareLayout(Widget):
# Update download button visibility and state
self._download_btn.set_visible(ui_state.is_offroad())
self._force_download_btn.set_visible(ui_state.is_offroad())
updater_state = ui_state.params.get("UpdaterState") or "idle"
failed_count = ui_state.params.get("UpdateFailedCount") or 0
@@ -110,6 +113,7 @@ class SoftwareLayout(Widget):
# Updater responded
self._waiting_for_updater = False
self._download_btn.action_item.set_enabled(False)
self._force_download_btn.action_item.set_enabled(False)
# Use the mapping, with a fallback to the original state string
display_text = STATE_TO_DISPLAY_TEXT.get(updater_state, updater_state)
self._download_btn.action_item.set_value(display_text)
@@ -135,6 +139,7 @@ class SoftwareLayout(Widget):
# Only enable if we're not waiting for updater to flip out of idle
self._download_btn.action_item.set_enabled(not self._waiting_for_updater)
self._force_download_btn.action_item.set_enabled(not self._waiting_for_updater)
# Update target branch button value
current_branch = ui_state.params.get("UpdaterTargetBranch") or ""
@@ -180,6 +185,12 @@ class SoftwareLayout(Widget):
self._install_btn.action_item.set_enabled(False)
ui_state.params.put_bool("DoReboot", True)
def _on_force_download(self):
self._force_download_btn.action_item.set_enabled(False)
self._waiting_for_updater = True
self._waiting_start_ts = time.monotonic()
os.system("pkill -SIGUSR2 -f system.updated.updated")
def _on_select_branch(self):
# Get available branches and order
current_git_branch = ui_state.params.get("GitBranch") or ""
+118 -1
View File
@@ -15,7 +15,8 @@ from openpilot.selfdrive.ui.mici.onroad.driver_camera_dialog import DriverCamera
from openpilot.selfdrive.ui.mici.layouts.onboarding import TrainingGuide, TermsPage
from openpilot.system.ui.lib.application import gui_app, FontWeight, MousePos
from openpilot.system.ui.lib.multilang import tr
from openpilot.system.ui.widgets import Widget
from openpilot.system.ui.widgets import Widget, DialogResult
from openpilot.system.ui.widgets.option_dialog import MultiOptionDialog
from openpilot.selfdrive.ui.ui_state import device, ui_state
from openpilot.system.ui.widgets.label import UnifiedLabel
from openpilot.system.ui.widgets.html_render import HtmlModal, HtmlRenderer
@@ -167,6 +168,42 @@ class PairBigButton(BigButton):
UPDATER_TIMEOUT = 10.0 # seconds to wait for updater to respond
class TargetBranchBigButton(BigButton):
def __init__(self):
super().__init__("target branch", "", gui_app.texture("icons_mici/settings/device/update.png", 64, 75), scroll=True)
self._branch_dialog: MultiOptionDialog | None = None
self.set_click_callback(self._on_select_branch)
def _on_select_branch(self):
current_git_branch = ui_state.params.get("GitBranch") or ""
branches_str = ui_state.params.get("UpdaterAvailableBranches") or ""
branches = [b for b in branches_str.split(",") if b]
for b in [current_git_branch, "devel-staging", "devel", "nightly", "nightly-dev", "master"]:
if b in branches:
branches.remove(b)
branches.insert(0, b)
current_target = ui_state.params.get("UpdaterTargetBranch") or ""
def handle_selection(result: DialogResult):
if result == DialogResult.CONFIRM and self._branch_dialog is not None and self._branch_dialog.selection:
selection = self._branch_dialog.selection
ui_state.params.put("UpdaterTargetBranch", selection)
os.system("pkill -SIGUSR1 -f system.updated.updated")
self.set_value(selection)
self._branch_dialog = None
self._branch_dialog = MultiOptionDialog(tr("Select a branch"), branches, current_target, callback=handle_selection)
gui_app.push_widget(self._branch_dialog)
def _update_state(self):
super()._update_state()
current_branch = ui_state.params.get("UpdaterTargetBranch") or ui_state.params.get("GitBranch") or ""
if self.get_value() != current_branch:
self.set_value(current_branch)
class UpdateOpenpilotBigButton(BigButton):
def __init__(self):
self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 75)
@@ -285,6 +322,84 @@ class UpdateOpenpilotBigButton(BigButton):
self._waiting_for_updater_t = None
class ForceDownloadBigButton(BigButton):
def __init__(self):
self._txt_update_icon = gui_app.texture("icons_mici/settings/device/update.png", 64, 75)
super().__init__("force download", "", self._txt_update_icon)
self._waiting_for_updater_t: float | None = None
self._hide_value_t: float | None = None
self._state: UpdaterState = UpdaterState.IDLE
ui_state.add_offroad_transition_callback(self.offroad_transition)
def offroad_transition(self):
if ui_state.is_offroad():
self.set_enabled(True)
def _handle_mouse_release(self, mouse_pos: MousePos):
super()._handle_mouse_release(mouse_pos)
if not system_time_valid():
dlg = BigDialog("", tr("Please connect to Wi-Fi to update."))
gui_app.push_widget(dlg)
return
self.set_enabled(False)
self._state = UpdaterState.WAITING_FOR_UPDATER
self.set_icon(self._txt_update_icon)
def run():
os.system("pkill -SIGUSR2 -f system.updated.updated")
threading.Thread(target=run, daemon=True).start()
def _update_state(self):
super()._update_state()
if ui_state.started:
self.set_enabled(False)
return
updater_state = ui_state.params.get("UpdaterState") or ""
if self._state == UpdaterState.WAITING_FOR_UPDATER:
self.set_rotate_icon(True)
if updater_state != "idle":
self._state = UpdaterState.UPDATER_RESPONDING
if self._waiting_for_updater_t is None:
self._waiting_for_updater_t = rl.get_time()
if self._waiting_for_updater_t is not None and rl.get_time() - self._waiting_for_updater_t > UPDATER_TIMEOUT:
self.set_rotate_icon(False)
self.set_value("updater failed\nto respond")
self._state = UpdaterState.IDLE
self._hide_value_t = rl.get_time()
elif self._state == UpdaterState.UPDATER_RESPONDING:
if updater_state == "idle":
self.set_rotate_icon(False)
self._state = UpdaterState.IDLE
self._hide_value_t = rl.get_time()
elif self.get_value() != updater_state:
self.set_value(updater_state)
elif self._state == UpdaterState.IDLE:
self.set_rotate_icon(False)
self.set_enabled(True)
if self._hide_value_t is not None:
if rl.get_time() - self._hide_value_t > 3.0:
self._hide_value_t = None
self.set_value("")
elif self.get_value() != "":
self.set_value("")
if self._state != UpdaterState.WAITING_FOR_UPDATER:
self._waiting_for_updater_t = None
class DeviceLayoutMici(NavScroller):
def __init__(self):
super().__init__()
@@ -339,7 +454,9 @@ class DeviceLayoutMici(NavScroller):
self._scroller.add_widgets([
DeviceInfoLayoutMici(),
TargetBranchBigButton(),
UpdateOpenpilotBigButton(),
ForceDownloadBigButton(),
PairBigButton(),
review_training_guide_btn,
driver_cam_btn,
+29 -7
View File
@@ -42,6 +42,7 @@ class UserRequest:
NONE = 0
CHECK = 1
FETCH = 2
FORCE_FETCH = 3
class WaitTimeHelper:
def __init__(self):
@@ -49,6 +50,7 @@ class WaitTimeHelper:
self.user_request = UserRequest.NONE
signal.signal(signal.SIGHUP, self.update_now)
signal.signal(signal.SIGUSR1, self.check_now)
signal.signal(signal.SIGUSR2, self.force_update_now)
def update_now(self, signum: int, frame) -> None:
cloudlog.info("caught SIGHUP, attempting to downloading update")
@@ -60,6 +62,11 @@ class WaitTimeHelper:
self.user_request = UserRequest.CHECK
self.ready_event.set()
def force_update_now(self, signum: int, frame) -> None:
cloudlog.info("caught SIGUSR2, attempting forced update download")
self.user_request = UserRequest.FORCE_FETCH
self.ready_event.set()
def sleep(self, t: float) -> None:
self.ready_event.wait(timeout=t)
@@ -359,7 +366,7 @@ class Updater:
else:
cloudlog.info(f"up to date on {cur_branch} ({str(cur_commit)[:7]})")
def fetch_update(self) -> None:
def fetch_update(self, force: bool = False) -> None:
cloudlog.info("attempting git fetch inside staging overlay")
self.params.put("UpdaterState", "downloading...")
@@ -373,7 +380,10 @@ class Updater:
run(["git", "config", "--replace-all", "remote.origin.fetch", "+refs/heads/*:refs/remotes/origin/*"], OVERLAY_MERGED)
branch = self.target_branch
git_fetch_output = run(["git", "fetch", "origin", branch], OVERLAY_MERGED)
fetch_cmd = ["git", "fetch", "origin", branch]
if force:
fetch_cmd = ["git", "fetch", "--prune", "origin", branch]
git_fetch_output = run(fetch_cmd, OVERLAY_MERGED)
cloudlog.info("git fetch success: %s", git_fetch_output)
cloudlog.info("git reset in progress")
@@ -382,10 +392,22 @@ class Updater:
["git", "branch", "--set-upstream-to", f"origin/{branch}"],
["git", "reset", "--hard"],
["git", "clean", "-xdff"],
["git", "submodule", "sync"],
["git", "submodule", "update", "--init", "--recursive"],
["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"],
]
if force:
cmds += [
["git", "submodule", "sync", "--recursive"],
["git", "submodule", "deinit", "--force", "--all"],
["git", "clean", "-xdff"],
["git", "submodule", "update", "--init", "--force", "--recursive"],
["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"],
["git", "submodule", "foreach", "--recursive", "git", "clean", "-xdff"],
]
else:
cmds += [
["git", "submodule", "sync"],
["git", "submodule", "update", "--init", "--recursive"],
["git", "submodule", "foreach", "--recursive", "git", "reset", "--hard"],
]
r = [run(cmd, OVERLAY_MERGED) for cmd in cmds]
cloudlog.info("git reset success: %s", '\n'.join(r))
@@ -463,13 +485,13 @@ def main() -> None:
# download update
last_fetch = params.get("UpdaterLastFetchTime")
timed_out = last_fetch is None or (datetime.datetime.now(datetime.UTC).replace(tzinfo=None) - last_fetch > datetime.timedelta(days=3))
user_requested_fetch = wait_helper.user_request == UserRequest.FETCH
user_requested_fetch = wait_helper.user_request in (UserRequest.FETCH, UserRequest.FORCE_FETCH)
if params.get_bool("NetworkMetered") and not timed_out and not user_requested_fetch:
cloudlog.info("skipping fetch, connection metered")
elif wait_helper.user_request == UserRequest.CHECK:
cloudlog.info("skipping fetch, only checking")
else:
updater.fetch_update()
updater.fetch_update(force=(wait_helper.user_request == UserRequest.FORCE_FETCH))
write_time_to_param(params, "UpdaterLastFetchTime")
update_failed_count = 0
except subprocess.CalledProcessError as e: