diff --git a/frogpilot/common/frogpilot_functions.py b/frogpilot/common/frogpilot_functions.py index 6076838e..6fe83ea6 100644 --- a/frogpilot/common/frogpilot_functions.py +++ b/frogpilot/common/frogpilot_functions.py @@ -71,3 +71,42 @@ def update_boot_logo(frogpilot=False, stock=False): run_cmd(["sudo", "mount", "-o", "remount,rw", "/"], "Successfully remounted / as read-write", "Failed to remount /") run_cmd(["sudo", "cp", target_logo, boot_logo_location], "Successfully replaced boot logo", "Failed to replace boot logo") run_cmd(["sudo", "mount", "-o", f"remount,{mount_options}", "/"], "Successfully restored / mount options", "Failed to restore / mount options") + + +def update_openpilot(thread_manager, params): + def update_available(): + run_cmd(["pkill", "-SIGUSR1", "-f", "system.updated.updated"], "Checking for updates...", "Failed to check for update...", report=False) + + while params.get("UpdaterState") != "checking...": + time.sleep(1) + + while params.get("UpdaterState") == "checking...": + time.sleep(1) + + if not params.get_bool("UpdaterFetchAvailable"): + return False + + while params.get_bool("IsOnroad") or thread_manager.is_thread_alive("lock_doors"): + time.sleep(60) + + run_cmd(["pkill", "-SIGHUP", "-f", "system.updated.updated"], "Update available, downloading...", "Failed to download update...", report=False) + + while not params.get_bool("UpdateAvailable"): + time.sleep(60) + + return True + + if params.get("UpdaterState") != "idle": + return + + while params.get_bool("IsOnroad") or thread_manager.is_thread_alive("lock_doors"): + time.sleep(60) + + if not update_available(): + return + + while True: + if not update_available(): + break + + HARDWARE.reboot() diff --git a/frogpilot/frogpilot_process.py b/frogpilot/frogpilot_process.py index a96fda74..d7c43cea 100644 --- a/frogpilot/frogpilot_process.py +++ b/frogpilot/frogpilot_process.py @@ -9,6 +9,7 @@ from openpilot.common.realtime import DT_MDL, Priority, Ratekeeper, config_realt from openpilot.common.time_helpers import system_time_valid from openpilot.frogpilot.common.frogpilot_backups import backup_toggles +from openpilot.frogpilot.common.frogpilot_functions import update_openpilot from openpilot.frogpilot.common.frogpilot_utilities import ThreadManager, is_url_pingable from openpilot.frogpilot.common.frogpilot_variables import FrogPilotVariables from openpilot.frogpilot.controls.frogpilot_planner import FrogPilotPlanner @@ -31,6 +32,9 @@ def update_checks(now, thread_manager, params, params_memory, frogpilot_toggles, while not (is_url_pingable("https://github.com") or is_url_pingable("https://gitlab.com")): time.sleep(60) + if frogpilot_toggles.automatic_updates: + thread_manager.run_with_lock(update_openpilot, (thread_manager, params)) + time.sleep(1) def update_toggles(frogpilot_variables, started, thread_manager, time_validated, params): @@ -102,6 +106,7 @@ def frogpilot_thread(): if params_memory.get_bool("FrogPilotTogglesUpdated"): frogpilot_toggles = update_toggles(frogpilot_variables, started, thread_manager, time_validated, params) + run_update_checks |= params_memory.get_bool("ManualUpdateInitiated") run_update_checks |= now.second == 0 and (now.minute % 60 == 0) run_update_checks &= time_validated diff --git a/frogpilot/ui/frogpilot_ui.h b/frogpilot/ui/frogpilot_ui.h index b3b3ae78..808cb88f 100644 --- a/frogpilot/ui/frogpilot_ui.h +++ b/frogpilot/ui/frogpilot_ui.h @@ -7,6 +7,7 @@ struct FrogPilotUIScene { bool always_on_lateral_active; + bool downloading_update; bool enabled; bool frogpilot_panel_active; bool online; diff --git a/selfdrive/ui/qt/offroad/software_settings.cc b/selfdrive/ui/qt/offroad/software_settings.cc index 2bb7a551..d7cc3336 100644 --- a/selfdrive/ui/qt/offroad/software_settings.cc +++ b/selfdrive/ui/qt/offroad/software_settings.cc @@ -21,7 +21,7 @@ void SoftwarePanel::checkForUpdates() { } SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { - onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off.")); + onroadLbl = new QLabel(tr("Updates are only downloaded while the car is off or in park.")); onroadLbl->setStyleSheet("font-size: 50px; font-weight: 400; text-align: left; padding-top: 30px; padding-bottom: 30px;"); addItem(onroadLbl); @@ -29,6 +29,12 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { versionLbl = new LabelControl(tr("Current Version"), ""); addItem(versionLbl); + // automatic updates toggle + ParamControl *automaticUpdatesToggle = new ParamControl("AutomaticUpdates", tr("Automatically Update FrogPilot"), + tr("Automatically update FrogPilot when the vehicle is parked with an active internet connection."), ""); + automaticUpdatesToggle->setVisible(params.getBool("IsReleaseBranch")); + addItem(automaticUpdatesToggle); + // download update btn downloadBtn = new ButtonControl(tr("Download"), tr("CHECK")); connect(downloadBtn, &ButtonControl::clicked, [=]() { @@ -38,6 +44,7 @@ SoftwarePanel::SoftwarePanel(QWidget* parent) : ListWidget(parent) { } else { std::system("pkill -SIGHUP -f system.updated.updated"); } + frogpilotUIState()->params_memory.putBool("ManualUpdateInitiated", true); }); addItem(downloadBtn); @@ -110,6 +117,10 @@ void SoftwarePanel::showEvent(QShowEvent *event) { // FrogPilot variables FrogPilotUIState &fs = *frogpilotUIState(); FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + + if (frogpilot_scene.online && params.get("UpdaterState") == "idle") { + checkForUpdates(); + } } void SoftwarePanel::updateLabels() { @@ -117,6 +128,8 @@ void SoftwarePanel::updateLabels() { FrogPilotUIState &fs = *frogpilotUIState(); FrogPilotUIScene &frogpilot_scene = fs.frogpilot_scene; + bool parked = frogpilot_scene.parked; + // add these back in case the files got removed fs_watch->addParam("LastUpdateTime"); fs_watch->addParam("UpdateFailedCount"); @@ -125,12 +138,13 @@ void SoftwarePanel::updateLabels() { if (!isVisible()) { // FrogPilot variables + frogpilot_scene.downloading_update = false; return; } - // updater only runs offroad - onroadLbl->setVisible(is_onroad); - downloadBtn->setVisible(!is_onroad); + // updater only runs offroad or when parked + onroadLbl->setVisible(is_onroad && !parked); + downloadBtn->setVisible(!is_onroad || parked); // download update QString updater_state = QString::fromStdString(params.get("UpdaterState")); @@ -140,6 +154,7 @@ void SoftwarePanel::updateLabels() { downloadBtn->setValue(updater_state); // FrogPilot variables + frogpilot_scene.downloading_update = true; } else { if (failed) { downloadBtn->setText(tr("CHECK")); @@ -159,6 +174,7 @@ void SoftwarePanel::updateLabels() { downloadBtn->setEnabled(true); // FrogPilot variables + frogpilot_scene.downloading_update = false; } targetBranchBtn->setValue(QString::fromStdString(params.get("UpdaterTargetBranch"))); @@ -166,7 +182,7 @@ void SoftwarePanel::updateLabels() { versionLbl->setText(QString::fromStdString(params.get("UpdaterCurrentDescription"))); versionLbl->setDescription(QString::fromStdString(params.get("UpdaterCurrentReleaseNotes"))); - installBtn->setVisible(!is_onroad && params.getBool("UpdateAvailable")); + installBtn->setVisible((!is_onroad || parked) && params.getBool("UpdateAvailable")); installBtn->setValue(QString::fromStdString(params.get("UpdaterNewDescription"))); installBtn->setDescription(QString::fromStdString(params.get("UpdaterNewReleaseNotes"))); diff --git a/selfdrive/ui/ui.cc b/selfdrive/ui/ui.cc index 998403ad..ab8fae0f 100644 --- a/selfdrive/ui/ui.cc +++ b/selfdrive/ui/ui.cc @@ -154,7 +154,7 @@ void UIState::update() { FrogPilotUIScene &frogpilot_scene = fs->frogpilot_scene; QJsonObject &frogpilot_toggles = frogpilot_scene.frogpilot_toggles; - if (frogpilot_scene.frogpilot_panel_active) { + if (frogpilot_scene.downloading_update || frogpilot_scene.frogpilot_panel_active) { device()->resetInteractiveTimeout(); } diff --git a/system/manager/process_config.py b/system/manager/process_config.py index 2c42ba1d..31114b4e 100644 --- a/system/manager/process_config.py +++ b/system/manager/process_config.py @@ -106,7 +106,7 @@ procs = [ PythonProcess("radard", "selfdrive.controls.radard", only_onroad), PythonProcess("hardwared", "system.hardware.hardwared", always_run), PythonProcess("tombstoned", "system.tombstoned", always_run, enabled=not PC), - PythonProcess("updated", "system.updated.updated", only_offroad, enabled=not PC), + PythonProcess("updated", "system.updated.updated", always_run, enabled=not PC), PythonProcess("uploader", "system.loggerd.uploader", always_run), PythonProcess("statsd", "system.statsd", always_run), PythonProcess("feedbackd", "selfdrive.ui.feedback.feedbackd", only_onroad), diff --git a/system/updated/updated.py b/system/updated/updated.py index 6704d0a2..b2255389 100644 --- a/system/updated/updated.py +++ b/system/updated/updated.py @@ -422,10 +422,6 @@ class Updater: def main() -> None: params = Params() - if params.get_bool("DisableUpdates"): - cloudlog.warning("updates are disabled by the DisableUpdates param") - exit(0) - with open(LOCK_FILE, 'w') as ov_lock_fd: try: fcntl.flock(ov_lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB) @@ -467,6 +463,9 @@ def main() -> None: # FrogPilot variables frogpilot_toggles = get_frogpilot_toggles() + manual_update_requested = params_memory.get_bool("ManualUpdateInitiated") + params_memory.remove("ManualUpdateInitiated") + # Attempt an update exception = None try: @@ -483,21 +482,24 @@ def main() -> None: update_failed_count += 1 - # check for update - params.put("UpdaterState", "checking...") - updater.check_for_update() + if manual_update_requested or params.get_bool("IsOffroad"): + # check for update + params.put("UpdaterState", "checking...") + updater.check_for_update() - # 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 - 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") + # 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 + 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() + write_time_to_param(params, "UpdaterLastFetchTime") else: - updater.fetch_update() - write_time_to_param(params, "UpdaterLastFetchTime") + cloudlog.info("skipping fetch, vehicle is onroad") update_failed_count = 0 except subprocess.CalledProcessError as e: cloudlog.event( @@ -522,7 +524,7 @@ def main() -> None: # infrequent attempts if we successfully updated recently wait_helper.user_request = UserRequest.NONE - wait_helper.sleep(5*60 if update_failed_count > 0 else 1.5*60*60) + wait_helper.sleep(60*60*24*365*100) if __name__ == "__main__":