From 9b553646fd2545ac16e7503a5424eb07d11180ec Mon Sep 17 00:00:00 2001 From: FrogAi <91348155+FrogAi@users.noreply.github.com> Date: Thu, 9 May 2024 22:11:42 -0700 Subject: [PATCH] FrogPilot features - Backup and restore FrogPilot --- .../controls/lib/frogpilot_functions.py | 50 +++++ selfdrive/frogpilot/frogpilot_process.py | 3 + selfdrive/manager/manager.py | 12 ++ selfdrive/ui/qt/offroad/settings.cc | 182 ++++++++++++++++++ 4 files changed, 247 insertions(+) diff --git a/selfdrive/frogpilot/controls/lib/frogpilot_functions.py b/selfdrive/frogpilot/controls/lib/frogpilot_functions.py index e71de1c..f3da3ce 100644 --- a/selfdrive/frogpilot/controls/lib/frogpilot_functions.py +++ b/selfdrive/frogpilot/controls/lib/frogpilot_functions.py @@ -66,6 +66,56 @@ class FrogPilotFunctions: except Exception as e: print(f"Unexpected error occurred: {e}") + @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)}") + + branch = get_short_branch() + commit = get_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}/"] + cls.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") + + all_keys = params.all_keys() + for key in 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}/"] + cls.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): if params.get("InstallDate") == "November 21, 2023 - 02:10PM": diff --git a/selfdrive/frogpilot/frogpilot_process.py b/selfdrive/frogpilot/frogpilot_process.py index 32ab957..edfb35a 100644 --- a/selfdrive/frogpilot/frogpilot_process.py +++ b/selfdrive/frogpilot/frogpilot_process.py @@ -82,6 +82,9 @@ def frogpilot_thread(frogpilot_toggles): if FrogPilotVariables.toggles_updated: FrogPilotVariables.update_frogpilot_params(started) + if not started and time_validated: + frogpilot_functions.backup_toggles() + if not time_validated: time_validated = system_time_valid() if not time_validated: diff --git a/selfdrive/manager/manager.py b/selfdrive/manager/manager.py index 87830ca..b7cc994 100644 --- a/selfdrive/manager/manager.py +++ b/selfdrive/manager/manager.py @@ -33,6 +33,18 @@ def frogpilot_boot_functions(frogpilot_functions): 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 + except Exception as e: print(f"An unexpected error occurred: {e}") diff --git a/selfdrive/ui/qt/offroad/settings.cc b/selfdrive/ui/qt/offroad/settings.cc index db822f9..31a8b3b 100644 --- a/selfdrive/ui/qt/offroad/settings.cc +++ b/selfdrive/ui/qt/offroad/settings.cc @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -271,6 +273,186 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) { }); addItem(translateBtn); + // 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::ostringstream commandStream; + commandStream << "mkdir -p " << std::quoted(fullBackupPath) + << " && rsync -av /data/openpilot/ " << std::quoted(fullBackupPath + "/"); + std::string command = commandStream.str(); + + int result = std::system(command.c_str()); + if (result == 0) { + std::cout << "Backup successful to " << fullBackupPath << std::endl; + frogpilotBackup->setValue(tr("Success!")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + } else { + std::cerr << "Backup failed with error code: " << result << std::endl; + 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::ostringstream commandStream; + commandStream << "rsync -av --delete --exclude='.overlay_consistent' " << std::quoted(sourcePath + "/") << " " << std::quoted(targetPath + "/"); + std::string command = commandStream.str(); + int result = std::system(command.c_str()); + + if (result == 0) { + std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl; + std::ofstream consistentFile(consistentFilePath); + if (consistentFile) { + consistentFile.close(); + std::cout << ".overlay_consistent file created successfully." << std::endl; + } else { + std::cerr << "Failed to create .overlay_consistent file." << std::endl; + frogpilotBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + frogpilotBackup->setValue(""); + } + Hardware::reboot(); + } else { + std::cerr << "Restore failed with error code: " << result << std::endl; + } + }).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::ostringstream commandStream; + commandStream << "mkdir -p " << std::quoted(fullBackupPath) + << " && rsync -av /data/params/d/ " << std::quoted(fullBackupPath); + std::string command = commandStream.str(); + + int result = std::system(command.c_str()); + if (result == 0) { + std::cout << "Backup successful to " << fullBackupPath << std::endl; + toggleBackup->setValue(tr("Success!")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + } else { + std::cerr << "Backup failed with error code: " << result << std::endl; + 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::ostringstream commandStream; + commandStream << "rsync -av --delete " << std::quoted(sourcePath) << " " << std::quoted(targetPath); + std::string command = commandStream.str(); + + int result = std::system(command.c_str()); + + if (result == 0) { + std::cout << "Restore successful from " << sourcePath << " to " << targetPath << std::endl; + toggleBackup->setValue(tr("Success!")); + updateFrogPilotToggles(); + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + } else { + std::cerr << "Restore failed with error code: " << result << std::endl; + toggleBackup->setValue(tr("Failed...")); + std::this_thread::sleep_for(std::chrono::seconds(3)); + toggleBackup->setValue(""); + } + }).detach(); + } + } + }); + addItem(toggleBackup); + QObject::connect(uiState(), &UIState::offroadTransition, [=](bool offroad) { for (auto btn : findChildren()) { btn->setEnabled(offroad);