FrogPilot features - Backup and restore FrogPilot/toggles

This commit is contained in:
FrogAi
2024-07-31 19:52:34 -07:00
parent 3e6a44cd1e
commit 498ab524eb
4 changed files with 227 additions and 4 deletions
@@ -1,4 +1,6 @@
import datetime
import filecmp
import glob
import http.client
import os
import shutil
@@ -11,7 +13,8 @@ import urllib.request
from openpilot.common.basedir import BASEDIR
from openpilot.common.numpy_fast import clip, interp, mean
from openpilot.common.params_pyx import Params, UnknownKeyName
from openpilot.common.params_pyx import Params, ParamKeyType, UnknownKeyName
from openpilot.common.time import system_time_valid
from openpilot.system.hardware import HARDWARE
def delete_file(file):
@@ -47,6 +50,45 @@ def run_cmd(cmd, success_msg, fail_msg):
except Exception as e:
print(f"Unexpected error occurred: {e}")
def backup_directory(backup, destination, success_msg, fail_msg):
os.makedirs(destination, exist_ok=True)
try:
run_cmd(['sudo', 'cp', '-a', os.path.join(backup, '.'), destination], success_msg, fail_msg)
except OSError as e:
if e.errno == 28:
print("Not enough space to perform the backup.")
else:
print(f"Failed to backup due to unexpected error: {e}")
def cleanup_backups(directory, limit):
backups = sorted(glob.glob(os.path.join(directory, "*_auto")), key=os.path.getmtime, reverse=True)
for old_backup in backups[limit:]:
subprocess.run(['sudo', 'rm', '-rf', old_backup], check=True)
print(f"Deleted oldest backup: {os.path.basename(old_backup)}")
def backup_frogpilot(build_metadata):
backup_path = "/data/backups"
cleanup_backups(backup_path, 4)
branch = build_metadata.channel
commit = build_metadata.openpilot.git_commit_date[12:-16]
backup_dir = f"{backup_path}/{branch}_{commit}_auto"
backup_directory(BASEDIR, backup_dir, f"Successfully backed up FrogPilot to {backup_dir}.", f"Failed to backup FrogPilot to {backup_dir}.")
def backup_toggles(params, params_storage):
for key in params.all_keys():
if params.get_key_type(key) & ParamKeyType.FROGPILOT_STORAGE:
value = params.get(key)
if value is not None:
params_storage.put(key, value)
backup_path = "/data/toggle_backups"
cleanup_backups(backup_path, 9)
backup_dir = f"{backup_path}/{datetime.datetime.now().strftime('%Y-%m-%d_%I-%M%p').lower()}_auto"
backup_directory("/data/params/d", backup_dir, f"Successfully backed up toggles to {backup_dir}.", f"Failed to backup toggles to {backup_dir}.")
def convert_params(params, params_storage):
def convert_param(key, action_func):
try:
@@ -71,9 +113,19 @@ def convert_params(params, params_storage):
print("Params successfully converted!")
params_storage.put_int_nonblocking("ParamConversionVersion", version)
def frogpilot_boot_functions(params, params_storage):
def frogpilot_boot_functions(build_metadata, params, params_storage):
convert_params(params, params_storage)
while not system_time_valid():
print("Waiting for system time to become valid...")
time.sleep(1)
try:
backup_frogpilot(build_metadata)
backup_toggles(params, params_storage)
except subprocess.CalledProcessError as e:
print(f"Backup failed: {e}")
def setup_frogpilot(build_metadata):
remount_root = ['sudo', 'mount', '-o', 'remount,rw', '/']
run_cmd(remount_root, "File system remounted as read-write.", "Failed to remount file system.")
+6 -1
View File
@@ -9,12 +9,13 @@ from openpilot.common.time import system_time_valid
from openpilot.system.hardware import HARDWARE
from openpilot.selfdrive.frogpilot.controls.frogpilot_planner import FrogPilotPlanner
from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import is_url_pingable
from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_functions import backup_toggles, is_url_pingable
from openpilot.selfdrive.frogpilot.controls.lib.frogpilot_variables import FrogPilotVariables
OFFLINE = log.DeviceState.NetworkType.none
locks = {
"backup_toggles": threading.Lock(),
"time_checks": threading.Lock(),
"update_frogpilot_params": threading.Lock(),
}
@@ -58,6 +59,7 @@ def frogpilot_thread():
params = Params()
params_memory = Params("/dev/shm/params")
params_storage = Params("/persist/params")
frogpilot_planner = FrogPilotPlanner()
@@ -91,6 +93,9 @@ def frogpilot_thread():
elif update_toggles:
run_thread_with_lock("update_frogpilot_params", locks["update_frogpilot_params"], FrogPilotVariables.update_frogpilot_params, (started,))
if time_validated and not started:
run_thread_with_lock("backup_toggles", locks["backup_toggles"], backup_toggles, (params, params_storage))
update_toggles = False
started_previously = started
+166
View File
@@ -1,5 +1,7 @@
#include <cassert>
#include <cmath>
#include <iomanip>
#include <iostream>
#include <string>
#include <tuple>
#include <vector>
@@ -274,8 +276,172 @@ DevicePanel::DevicePanel(SettingsWindow *parent) : ListWidget(parent) {
btn->setEnabled(offroad);
}
}
for (FrogPilotButtonsControl *btn : findChildren<FrogPilotButtonsControl *>()) {
btn->setEnabled(offroad);
}
});
// Backup FrogPilot
std::vector<QString> frogpilotBackupOptions{tr("BACKUP"), tr("DELETE"), tr("RESTORE")};
FrogPilotButtonsControl *frogpilotBackupBtn = new FrogPilotButtonsControl(tr("FrogPilot Backups"), tr("Backup, delete, or restore your FrogPilot backups."), "", frogpilotBackupOptions);
connect(frogpilotBackupBtn, &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([=]() {
frogpilotBackupBtn->setEnabled(false);
frogpilotBackupBtn->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());
frogpilotBackupBtn->setValue(result == 0 ? tr("Success!") : tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(2));
frogpilotBackupBtn->setValue("");
frogpilotBackupBtn->setEnabled(true);
}).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([=]() {
frogpilotBackupBtn->setEnabled(false);
frogpilotBackupBtn->setValue(tr("Deleting..."));
QDir dirToDelete(backupDir.absoluteFilePath(selection));
frogpilotBackupBtn->setValue(dirToDelete.removeRecursively() ? tr("Deleted!") : tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(2));
frogpilotBackupBtn->setValue("");
frogpilotBackupBtn->setEnabled(true);
}).detach();
}
} else if (id == 2) {
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([=]() {
frogpilotBackupBtn->setEnabled(false);
frogpilotBackupBtn->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 -l --exclude='.overlay_consistent' " + sourcePath + "/ " + targetPath + "/";
int result = std::system(command.c_str());
if (result == 0) {
std::ofstream consistentFile(consistentFilePath);
if (consistentFile) {
frogpilotBackupBtn->setValue(tr("Restored!"));
std::this_thread::sleep_for(std::chrono::seconds(2));
frogpilotBackupBtn->setValue(tr("Rebooting..."));
std::this_thread::sleep_for(std::chrono::seconds(2));
consistentFile.close();
Hardware::reboot();
} else {
frogpilotBackupBtn->setValue(tr("Failed..."));
}
} else {
frogpilotBackupBtn->setValue(tr("Failed..."));
}
std::this_thread::sleep_for(std::chrono::seconds(2));
frogpilotBackupBtn->setValue("");
frogpilotBackupBtn->setEnabled(true);
}).detach();
}
}
});
addItem(frogpilotBackupBtn);
// Backup toggles
std::vector<QString> toggleBackupOptions{tr("BACKUP"), tr("DELETE"), tr("RESTORE")};
FrogPilotButtonsControl *toggleBackupBtn = new FrogPilotButtonsControl(tr("Toggle Backups"), tr("Backup, delete, or restore your toggle backups."), "", toggleBackupOptions);
connect(toggleBackupBtn, &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([=]() {
toggleBackupBtn->setEnabled(false);
toggleBackupBtn->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());
toggleBackupBtn->setValue(result == 0 ? tr("Success!") : tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(2));
toggleBackupBtn->setValue("");
toggleBackupBtn->setEnabled(true);
}).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([=]() {
toggleBackupBtn->setEnabled(false);
toggleBackupBtn->setValue(tr("Deleting..."));
QDir dirToDelete(backupDir.absoluteFilePath(selection));
toggleBackupBtn->setValue(dirToDelete.removeRecursively() ? tr("Deleted!") : tr("Failed..."));
std::this_thread::sleep_for(std::chrono::seconds(2));
toggleBackupBtn->setValue("");
toggleBackupBtn->setEnabled(true);
}).detach();
}
} else if (id == 2) {
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([=]() {
toggleBackupBtn->setEnabled(false);
toggleBackupBtn->setValue(tr("Restoring..."));
std::string sourcePath = backupDir.absolutePath().toStdString() + "/" + selection.toStdString() + "/";
std::string targetPath = "/data/params/d/";
std::string command = "rsync -av --delete -l " + sourcePath + " " + targetPath;
int result = std::system(command.c_str());
if (result == 0) {
toggleBackupBtn->setValue(tr("Success!"));
updateFrogPilotToggles();
} else {
toggleBackupBtn->setValue(tr("Failed..."));
}
std::this_thread::sleep_for(std::chrono::seconds(2));
toggleBackupBtn->setValue("");
toggleBackupBtn->setEnabled(true);
}).detach();
}
}
});
addItem(toggleBackupBtn);
// power buttons
QHBoxLayout *power_layout = new QHBoxLayout();
power_layout->setSpacing(30);
+1 -1
View File
@@ -37,7 +37,7 @@ def manager_init() -> None:
if build_metadata.release_channel:
params.clear_all(ParamKeyType.DEVELOPMENT_ONLY)
threading.Thread(target=frogpilot_boot_functions, args=(params, params_storage,)).start()
threading.Thread(target=frogpilot_boot_functions, args=(build_metadata, params, params_storage,)).start()
default_params: list[tuple[str, str | bytes]] = [
("AlwaysOnDM", "0"),