From f28f1d1509576a3673296c4ffb773c6e06cdca58 Mon Sep 17 00:00:00 2001 From: FrogAi <91348155+FrogAi@users.noreply.github.com> Date: Sun, 9 Jun 2024 07:56:18 -0700 Subject: [PATCH] FrogPilot features - Backup and restore FrogPilot/toggles --- .../controls/lib/frogpilot_functions.py | 48 ++++++ selfdrive/frogpilot/frogpilot_process.py | 3 + selfdrive/ui/qt/offroad/settings.cc | 159 ++++++++++++++++++ system/manager/manager.py | 24 +++ 4 files changed, 234 insertions(+) diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_functions.py b/selfdrive/frogpilot/controls/lib/frogpilot_functions.py index e7b866839..caaa519e6 100644 --- a/selfdrive/frogpilot/controls/lib/frogpilot_functions.py +++ b/selfdrive/frogpilot/controls/lib/frogpilot_functions.py @@ -1,4 +1,6 @@ +import datetime import filecmp +import glob import os import shutil import subprocess @@ -18,6 +20,52 @@ def run_cmd(cmd, success_msg, fail_msg): print(f"Unexpected error occurred: {e}") class FrogPilotFunctions: + @classmethod + def backup_frogpilot(cls): + frogpilot_backup_directory = "/data/backups" + os.makedirs(frogpilot_backup_directory, exist_ok=True) + + auto_backups = sorted(glob.glob(os.path.join(frogpilot_backup_directory, "*_auto")), key=os.path.getmtime, reverse=True) + for old_backup in auto_backups[4:]: + shutil.rmtree(old_backup) + print(f"Deleted oldest FrogPilot backup to maintain limit: {os.path.basename(old_backup)}") + + build_metadata = get_build_metadata() + branch = build_metadata.channel + commit = build_metadata.openpilot.git_commit_date[12:-16] + backup_folder_name = f"{branch}_{commit}_auto" + backup_path = os.path.join(frogpilot_backup_directory, backup_folder_name) + + if not os.path.exists(backup_path): + cmd = ['sudo', 'cp', '-a', f"{BASEDIR}", f"{backup_path}/"] + run_cmd(cmd, f"Successfully backed up FrogPilot to {backup_folder_name}.", f"Failed to backup FrogPilot to {backup_folder_name}.") + + @classmethod + def backup_toggles(cls): + params = Params() + params_storage = Params("/persist/params") + + for key in params.all_keys(): + value = params.get(key) + if value is not None: + params_storage.put(key, value) + + toggle_backup_directory = "/data/toggle_backups" + os.makedirs(toggle_backup_directory, exist_ok=True) + + auto_backups = sorted(glob.glob(os.path.join(toggle_backup_directory, "*_auto")), key=os.path.getmtime, reverse=True) + for old_backup in auto_backups[9:]: + shutil.rmtree(old_backup) + print(f"Deleted oldest toggle backup to maintain limit: {os.path.basename(old_backup)}") + + current_datetime = datetime.datetime.now().strftime("%Y-%m-%d_%I-%M%p").lower() + backup_folder_name = f"{current_datetime}_auto" + backup_path = os.path.join(toggle_backup_directory, backup_folder_name) + + if not os.path.exists(backup_path): + cmd = ['sudo', 'cp', '-a', '/data/params/.', f"{backup_path}/"] + run_cmd(cmd, f"Successfully backed up toggles to {backup_folder_name}.", f"Failed to backup toggles to {backup_folder_name}.") + @classmethod def convert_params(cls, params, params_storage, params_tracking): def convert_param(key, action_func): diff --git a/selfdrive/frogpilot/frogpilot_process.py b/selfdrive/frogpilot/frogpilot_process.py index 24a463369..450585273 100644 --- a/selfdrive/frogpilot/frogpilot_process.py +++ b/selfdrive/frogpilot/frogpilot_process.py @@ -78,6 +78,9 @@ def frogpilot_thread(frogpilot_toggles): elif update_toggles: FrogPilotVariables.update_frogpilot_params(started) + if time_validated and not started: + frogpilot_functions.backup_toggles() + update_toggles = False if now.second == 0 or not time_validated: diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index 53ac3c572..bce8fcfd4 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include #include @@ -276,6 +278,163 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { } }); + // Backup FrogPilot + std::vector frogpilotBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")}; + FrogPilotButtonsControl *frogpilotBackup = new FrogPilotButtonsControl(tr("FrogPilot Backups"), tr("Backup, delete, or restore your FrogPilot backups."), "", frogpilotBackupOptions); + + connect(frogpilotBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir backupDir("/data/backups"); + + if (id == 0) { + QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1); + if (!nameSelection.isEmpty()) { + std::thread([=]() { + frogpilotBackup->setValue(tr("Backing up...")); + + std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString(); + + std::string command = "mkdir -p " + fullBackupPath + " && rsync -av /data/openpilot/ " + fullBackupPath + "/"; + + int result = std::system(command.c_str()); + if (result == 0) { + frogpilotBackup->setValue(tr("Success!")); + } else { + frogpilotBackup->setValue(tr("Failed...")); + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + }).detach(); + } + } else if (id == 1) { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return; + std::thread([=]() { + frogpilotBackup->setValue(tr("Deleting...")); + QDir dirToDelete(backupDir.absoluteFilePath(selection)); + if (dirToDelete.removeRecursively()) { + frogpilotBackup->setValue(tr("Deleted!")); + } else { + frogpilotBackup->setValue(tr("Failed...")); + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + }).detach(); + } + } else { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this version of FrogPilot?"), tr("Restore"), this)) return; + std::thread([=]() { + frogpilotBackup->setValue(tr("Restoring...")); + + std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString(); + std::string targetPath = "/data/safe_staging/finalized"; + std::string consistentFilePath = targetPath + "/.overlay_consistent"; + + std::string command = "rsync -av --delete --exclude='.overlay_consistent' " + sourcePath + "/ " + targetPath + "/"; + int result = std::system(command.c_str()); + + if (result == 0) { + std::ofstream consistentFile(consistentFilePath); + if (consistentFile) { + consistentFile.close(); + } else { + frogpilotBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + return; + } + Hardware::reboot(); + } else { + frogpilotBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + } + }).detach(); + } + } + }); + addItem(frogpilotBackup); + + // Backup toggles + std::vector toggleBackupOptions{tr("Backup"), tr("Delete"), tr("Restore")}; + FrogPilotButtonsControl *toggleBackup = new FrogPilotButtonsControl(tr("Toggle Backups"), tr("Backup, delete, or restore your toggle backups."), "", toggleBackupOptions); + + connect(toggleBackup, &FrogPilotButtonsControl::buttonClicked, [=](int id) { + QDir backupDir("/data/toggle_backups"); + + if (id == 0) { + QString nameSelection = InputDialog::getText(tr("Name your backup"), this, "", false, 1); + if (!nameSelection.isEmpty()) { + std::thread([=]() { + toggleBackup->setValue(tr("Backing up...")); + + std::string fullBackupPath = backupDir.absolutePath().toStdString() + "/" + nameSelection.toStdString() + "/"; + + std::string command = "mkdir -p " + fullBackupPath + " && rsync -av /data/params/d/ " + fullBackupPath; + + int result = std::system(command.c_str()); + if (result == 0) { + toggleBackup->setValue(tr("Success!")); + } else { + toggleBackup->setValue(tr("Failed...")); + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + }).detach(); + } + } else if (id == 1) { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a backup to delete"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to delete this backup?"), tr("Delete"), this)) return; + std::thread([=]() { + toggleBackup->setValue(tr("Deleting...")); + QDir dirToDelete(backupDir.absoluteFilePath(selection)); + if (dirToDelete.removeRecursively()) { + toggleBackup->setValue(tr("Deleted!")); + } else { + toggleBackup->setValue(tr("Failed...")); + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + }).detach(); + } + } else { + QStringList backupNames = backupDir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); + + QString selection = MultiOptionDialog::getSelection(tr("Select a restore point"), backupNames, "", this); + if (!selection.isEmpty()) { + if (!ConfirmationDialog::confirm(tr("Are you sure you want to restore this toggle backup?"), tr("Restore"), this)) return; + std::thread([=]() { + toggleBackup->setValue(tr("Restoring...")); + + std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString() + "/"; + std::string targetPath = "/data/params/d/"; + + std::string command = "rsync -av --delete " + sourcePath + " " + targetPath; + int result = std::system(command.c_str()); + + if (result == 0) { + toggleBackup->setValue(tr("Success!")); + updateFrogPilotToggles(); + } else { + toggleBackup->setValue(tr("Failed...")); + } + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + }).detach(); + } + } + }); + addItem(toggleBackup); + // power buttons QHBoxLayout *power_layout = new QHBoxLayout(); power_layout->setSpacing(30); diff --git a/system/manager/manager.py b/system/manager/manager.py index 1fa83f3ad..91556fdde 100755 --- a/system/manager/manager.py +++ b/system/manager/manager.py @@ -2,7 +2,10 @@ import datetime import os import signal +import subprocess import sys +import threading +import time import traceback from cereal import log @@ -16,12 +19,33 @@ from openpilot.system.manager.process import ensure_running from openpilot.system.manager.process_config import managed_processes from openpilot.system.athena.registration import register, UNREGISTERED_DONGLE_ID from openpilot.common.swaglog import cloudlog, add_file_handler +from openpilot.common.time import system_time_valid from openpilot.system.version import get_build_metadata, terms_version, training_version from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import FrogPilotFunctions +def frogpilot_boot_functions(frogpilot_functions): + while not system_time_valid(): + print("Waiting for system time to become valid...") + time.sleep(1) + + try: + frogpilot_functions.backup_frogpilot() + except subprocess.CalledProcessError as e: + print(f"Failed to backup FrogPilot. Error: {e}") + return + + try: + frogpilot_functions.backup_toggles() + except subprocess.CalledProcessError as e: + print(f"Failed to backup toggles. Error: {e}") + return + def manager_init(frogpilot_functions) -> None: + frogpilot_boot = threading.Thread(target=frogpilot_boot_functions, args=(frogpilot_functions,)) + frogpilot_boot.start() + save_bootlog() build_metadata = get_build_metadata()