diff --git a/frogpilot/common/frogpilot_utilities.py b/frogpilot/common/frogpilot_utilities.py
index 7972c90b7..ccdd4d8ec 100644
--- a/frogpilot/common/frogpilot_utilities.py
+++ b/frogpilot/common/frogpilot_utilities.py
@@ -15,6 +15,7 @@ import openpilot.system.sentry as sentry
from cereal import log, messaging
from openpilot.common.realtime import DT_DMON, DT_HW
+from panda import Panda
from openpilot.frogpilot.common.frogpilot_variables import EARTH_RADIUS, FROGS_GO_MOO_PATH
@@ -120,6 +121,19 @@ def extract_zip(zip_file, extract_path):
print(f"Extraction completed!")
+def flash_panda(params_memory):
+ for serial in Panda.list():
+ try:
+ with Panda(serial=serial) as panda:
+ print(f"Flashing Panda {serial}")
+ panda.flash()
+ except Exception as exception:
+ print(f"Failed to flash Panda {serial}: {exception}")
+ sentry.capture_exception(exception)
+
+ params_memory.remove("FlashPanda")
+
+
@cache
def is_FrogsGoMoo():
return FROGS_GO_MOO_PATH.is_file()
diff --git a/frogpilot/frogpilot_process.py b/frogpilot/frogpilot_process.py
index 108b0dff4..e7d741e95 100644
--- a/frogpilot/frogpilot_process.py
+++ b/frogpilot/frogpilot_process.py
@@ -11,7 +11,7 @@ from openpilot.common.time_helpers import system_time_valid
from openpilot.frogpilot.assets.theme_manager import THEME_COMPONENT_PARAMS, ThemeManager
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_utilities import ThreadManager, flash_panda, is_url_pingable
from openpilot.frogpilot.common.frogpilot_variables import ERROR_LOGS_PATH, FrogPilotVariables
from openpilot.frogpilot.controls.frogpilot_planner import FrogPilotPlanner
from openpilot.frogpilot.system.frogpilot_stats import send_stats
@@ -25,6 +25,9 @@ def check_assets(theme_manager, thread_manager, params_memory, frogpilot_toggles
if asset_to_download:
thread_manager.run_with_lock(theme_manager.download_theme, (asset_type, asset_to_download, asset_param, frogpilot_toggles))
+ if params_memory.get_bool("FlashPanda"):
+ thread_manager.run_with_lock(flash_panda, (params_memory))
+
def transition_offroad(frogpilot_planner, thread_manager, time_validated, sm, params, frogpilot_toggles):
params.put("LastGPSPosition", json.dumps(frogpilot_planner.gps_position))
diff --git a/frogpilot/ui/qt/offroad/utilities.cc b/frogpilot/ui/qt/offroad/utilities.cc
index 93fa66ccf..9c86abba1 100644
--- a/frogpilot/ui/qt/offroad/utilities.cc
+++ b/frogpilot/ui/qt/offroad/utilities.cc
@@ -9,6 +9,37 @@ FrogPilotUtilitiesPanel::FrogPilotUtilitiesPanel(FrogPilotSettingsWindow *parent
}
addItem(debugModeToggle);
+ ButtonControl *flashPandaButton = new ButtonControl(tr("Flash Panda"), tr("FLASH"), tr("Flash the latest, official firmware onto your Panda device to restore core functionality, fix bugs, or ensure you have the most up-to-date software."));
+ QObject::connect(flashPandaButton, &ButtonControl::clicked, [parent, flashPandaButton, this]() {
+ if (ConfirmationDialog::confirm(tr("Are you sure you want to flash the Panda firmware?"), tr("Flash"), this)) {
+ std::thread([parent, flashPandaButton, this]() {
+ parent->keepScreenOn = true;
+
+ flashPandaButton->setEnabled(false);
+ flashPandaButton->setValue(tr("Flashing..."));
+
+ params_memory.putBool("FlashPanda", true);
+ while (params_memory.getBool("FlashPanda")) {
+ util::sleep_for(UI_FREQ);
+ }
+
+ flashPandaButton->setValue(tr("Flashed!"));
+
+ util::sleep_for(2500);
+
+ flashPandaButton->setValue(tr("Rebooting..."));
+
+ util::sleep_for(2500);
+
+ Hardware::reboot();
+ }).detach();
+ }
+ });
+ if (forceOpenDescriptions) {
+ flashPandaButton->showDescription();
+ }
+ addItem(flashPandaButton);
+
ButtonControl *resetTogglesButton = new ButtonControl(tr("Reset Toggles to Default"), tr("RESET"), tr("Reset all toggles to their default values."));
QObject::connect(resetTogglesButton, &ButtonControl::clicked, [parent, resetTogglesButton, this]() {
if (ConfirmationDialog::confirm(tr("Are you sure you want to reset all toggles to their default values?"), tr("Reset"), this)) {