From 9d2930bd8bbf01456ea564cfa8a359c3ec8fb001 Mon Sep 17 00:00:00 2001 From: Eiswolf-BG <37225519+Eiswolf-BG@users.noreply.github.com> Date: Wed, 12 Aug 2026 05:34:22 +0200 Subject: [PATCH] Add Backup & Reset sub-page with factory reset (developer feature) Moves the previously standalone "Backup settings"/"Restore settings" buttons out of the System menu into a new "Backup & Reset" sub-page (replaced in the System menu by a single folder button, placed directly above "Info"), and adds a third entry "Reset settings" - a factory reset for developers/testers that deletes the entire Flightradar folder from the SD card (settings, logbook, location presets, WiFi, calibration - everything) and restarts the device. The next boot picks up isFirstRun automatically (via the missing SD_SETTINGS_FILE) and runs the full first-time setup again, exactly like on the very first boot - no need to pull the SD card. Since this is irreversible, it requires a full-screen red warning confirmation first, reusing the same overlay previously built only for the flight-logbook enable warning (confirmLogbookEnable() generalized to confirmWarningScreen(), taking title/body StringIds as parameters). New SdStorage::deleteDirectoryRecursive() and SettingsBackup::factoryReset() implement the actual deletion + restart. Co-Authored-By: Claude Sonnet 5 --- src/i18n.h | 15 +++++ src/i18n_de.h | 6 ++ src/i18n_en.h | 6 ++ src/i18n_es.h | 6 ++ src/i18n_fr.h | 6 ++ src/i18n_it.h | 6 ++ src/i18n_tr.h | 6 ++ src/menu_screen.cpp | 127 ++++++++++++++++++++++++++++++++-------- src/sd_storage.cpp | 31 ++++++++++ src/sd_storage.h | 8 +++ src/settings_backup.cpp | 20 +++++++ src/settings_backup.h | 8 +++ 12 files changed, 219 insertions(+), 26 deletions(-) diff --git a/src/i18n.h b/src/i18n.h index c7f2de7..8e4c706 100644 --- a/src/i18n.h +++ b/src/i18n.h @@ -282,6 +282,21 @@ enum class StringId : uint8_t { // Hoehen-Farbstufen erscheinen. LEGEND_GROUND_VEHICLE, + // Neue "Sicherung & Reset"-Unterseite im System-Menue (fasst die + // bisher einzeln im System-Menue stehenden Backup/Restore-Buttons + // zusammen und ergaenzt einen dritten Punkt "Einstellungen + // zuruecksetzen" - ein kompletter Werksreset fuer Entwickler/Tester, + // der den gesamten Flightradar-Ordner von der SD-Karte loescht und + // neu startet, siehe settings_backup.cpp::factoryReset()). Titel und + // Button-Label teilen sich MENU_BACKUP_RESET, genau wie + // MENU_CATEGORY_SYSTEM sowohl fuer den Menue-Button als auch den + // Bildschirmtitel der System-Seite verwendet wird. + MENU_BACKUP_RESET, + MENU_FACTORY_RESET, + MENU_FACTORY_RESET_WARNING_BODY, + MENU_FACTORY_RESET_DELETING, + MENU_FACTORY_RESET_FAILED, + COUNT }; diff --git a/src/i18n_de.h b/src/i18n_de.h index 6a82e41..77351d1 100644 --- a/src/i18n_de.h +++ b/src/i18n_de.h @@ -231,6 +231,12 @@ static const char* const I18N_DE[] = { "z.B. Zuhause, Arbeit, Ferienhaus", "Bodenfahrzeug", + + "Sicherung & Reset", + "Einstellungen zurücksetzen", + "Löscht ALLE Daten von der SD-Karte (Einstellungen, Flugbuch, Standort-Presets, WLAN, Kalibrierung) und startet das Gerät neu. Danach läuft die komplette Ersteinrichtung erneut - wie beim allerersten Start. Das kann NICHT rückgängig gemacht werden!", + "Lösche alle Daten...", + "Zurücksetzen fehlgeschlagen.", }; static_assert(sizeof(I18N_DE) / sizeof(I18N_DE[0]) == (size_t)StringId::COUNT, diff --git a/src/i18n_en.h b/src/i18n_en.h index 203fb50..312ab74 100644 --- a/src/i18n_en.h +++ b/src/i18n_en.h @@ -232,6 +232,12 @@ static const char* const I18N_EN[] = { "e.g. Home, Work, Holiday home", "Ground vehicle", + + "Backup & Reset", + "Reset settings", + "Deletes ALL data from the SD card (settings, logbook, location presets, WiFi, calibration) and restarts the device. The full first-time setup will then run again, just like on the very first boot. This CANNOT be undone!", + "Deleting all data...", + "Reset failed.", }; static_assert(sizeof(I18N_EN) / sizeof(I18N_EN[0]) == (size_t)StringId::COUNT, diff --git a/src/i18n_es.h b/src/i18n_es.h index cb3225c..6b9d8a9 100644 --- a/src/i18n_es.h +++ b/src/i18n_es.h @@ -231,6 +231,12 @@ static const char* const I18N_ES[] = { "ej. Casa, Trabajo, Casa de vacaciones", "Vehículo terrestre", + + "Copia de seguridad y reinicio", + "Reiniciar ajustes", + "Elimina TODOS los datos de la tarjeta SD (ajustes, registro de vuelos, ubicaciones guardadas, WiFi, calibración) y reinicia el dispositivo. Después se ejecutará de nuevo la configuración inicial completa, como en el primer arranque. ¡Esto NO se puede deshacer!", + "Eliminando todos los datos...", + "Error al reiniciar.", }; static_assert(sizeof(I18N_ES) / sizeof(I18N_ES[0]) == (size_t)StringId::COUNT, diff --git a/src/i18n_fr.h b/src/i18n_fr.h index 6bb8a32..2317717 100644 --- a/src/i18n_fr.h +++ b/src/i18n_fr.h @@ -231,6 +231,12 @@ static const char* const I18N_FR[] = { "ex. Maison, Travail, Maison de vacances", "Véhicule au sol", + + "Sauvegarde et réinitialisation", + "Réinitialiser réglages", + "Supprime TOUTES les données de la carte SD (réglages, journal, préréglages de position, WiFi, calibrage) et redémarre l'appareil. La configuration initiale complète s'exécutera alors à nouveau, comme au tout premier démarrage. Cette action est IRRÉVERSIBLE !", + "Suppression de toutes les données...", + "Échec de la réinitialisation.", }; static_assert(sizeof(I18N_FR) / sizeof(I18N_FR[0]) == (size_t)StringId::COUNT, diff --git a/src/i18n_it.h b/src/i18n_it.h index 29ab84f..cc73466 100644 --- a/src/i18n_it.h +++ b/src/i18n_it.h @@ -231,6 +231,12 @@ static const char* const I18N_IT[] = { "es. Casa, Lavoro, Casa vacanze", "Veicolo a terra", + + "Backup e reimpostazione", + "Reimposta impostazioni", + "Elimina TUTTI i dati dalla scheda SD (impostazioni, registro voli, posizioni preimpostate, WiFi, calibrazione) e riavvia il dispositivo. Verrà quindi eseguita di nuovo la configurazione iniziale completa, come al primo avvio. Questa azione NON può essere annullata!", + "Eliminazione di tutti i dati...", + "Reimpostazione fallita.", }; static_assert(sizeof(I18N_IT) / sizeof(I18N_IT[0]) == (size_t)StringId::COUNT, diff --git a/src/i18n_tr.h b/src/i18n_tr.h index dc40287..26a87f0 100644 --- a/src/i18n_tr.h +++ b/src/i18n_tr.h @@ -231,6 +231,12 @@ static const char* const I18N_TR[] = { "örn. Ev, İş, Tatil evi", "Kara aracı", + + "Yedekleme ve Sıfırlama", + "Ayarları sıfırla", + "SD karttaki TÜM verileri (ayarlar, uçuş kayıt defteri, konum ön ayarları, WiFi, kalibrasyon) siler ve cihazı yeniden başlatır. Ardından ilk çalıştırmadaki gibi kurulum işlemi tekrar başlar. Bu işlem GERİ ALINAMAZ!", + "Tüm veriler siliniyor...", + "Sıfırlama başarısız.", }; static_assert(sizeof(I18N_TR) / sizeof(I18N_TR[0]) == (size_t)StringId::COUNT, diff --git a/src/menu_screen.cpp b/src/menu_screen.cpp index 5e1fbf8..6be85d0 100644 --- a/src/menu_screen.cpp +++ b/src/menu_screen.cpp @@ -80,7 +80,12 @@ namespace { // Bildschirmflaeche errechnet, sodass die Buttons den Platz bis knapp // ueber den Bildschirmrand ausfuellen - andere Seiten bleiben bei ihren // eigenen, unveraenderten Zeilenhoehen. - constexpr uint8_t SYSTEM_ROW_COUNT = 10; + // War 10, bevor "Sicherung"/"Wiederherstellen" in die neue + // "Sicherung & Reset"-Unterseite ausgelagert wurden (siehe + // BACKUP_RESET_ROW_COUNT unten) und dort durch einen einzelnen + // Ordner-Button ersetzt wurden: -2 (Backup/Restore raus) +1 (neuer + // Ordner-Button) = 9. + constexpr uint8_t SYSTEM_ROW_COUNT = 9; constexpr int16_t SYSTEM_ROW_GAP = 4; constexpr int16_t SYSTEM_START_Y = 18; constexpr int16_t SYSTEM_END_Y = Config::SCREEN_HEIGHT - 10; @@ -91,6 +96,21 @@ namespace { (int16_t)(Config::SCREEN_WIDTH - 20), SYSTEM_ROW_H}; } + // Eigene Zeilenhoehe fuer die neue "Sicherung & Reset"-Unterseite (4 + // Eintraege: Sichern/Wiederherstellen/Zuruecksetzen/Zurueck) - gleiches + // "aus verfuegbarem Platz errechnen"-Muster wie bei SYSTEM_ROW_H. + // ROW_START_Y wird mitbenutzt (siehe oben, gemeinsam mit rowRect()/ + // flightRowRect()), nur GAP/COUNT/H sind eigene Werte. + constexpr uint8_t BACKUP_RESET_ROW_COUNT = 4; + constexpr int16_t BACKUP_RESET_ROW_GAP = 10; + constexpr int16_t BACKUP_RESET_END_Y = Config::SCREEN_HEIGHT - 10; + constexpr int16_t BACKUP_RESET_ROW_H = + (BACKUP_RESET_END_Y - ROW_START_Y - (BACKUP_RESET_ROW_COUNT - 1) * BACKUP_RESET_ROW_GAP) / BACKUP_RESET_ROW_COUNT; + Rect backupResetRowRect(uint8_t index) { + return {10, (int16_t)(ROW_START_Y + index * (BACKUP_RESET_ROW_H + BACKUP_RESET_ROW_GAP)), + (int16_t)(Config::SCREEN_WIDTH - 20), BACKUP_RESET_ROW_H}; + } + void drawButton(TFT_eSPI& tft, const Rect& r, const String& label, bool active = false, bool danger = false) { uint16_t accent = danger ? TFT_RED : TFT_GREEN; @@ -161,16 +181,21 @@ namespace { } // Warn-Ueberlage, die praktisch den kompletten Bildschirm einnimmt (nur - // ein paar Pixel Rand) und vor dem Einschalten des Flugbuchs erklaert, - // warum es sich nach 24h automatisch wieder abschaltet. "Achtung!!!" - // steht ganz oben, mit einer Leerzeile Abstand zum Fliesstext darunter; - // der Text scrollt bei Bedarf (laengere Uebersetzungen) ueber eigene - // Pfeil-Buttons, OK/Zurueck bleiben dabei immer unten fix und + // ein paar Pixel Rand) - urspruenglich nur fuers Einschalten des + // Flugbuchs gebaut (erklaert, warum es sich nach 24h automatisch + // wieder abschaltet), jetzt generisch mit uebergebenen Titel-/Text- + // StringIds, damit sie auch fuer die "Einstellungen zuruecksetzen"- + // Bestaetigung (Werksreset) wiederverwendet werden kann - beides sind + // seltene, potenziell folgenreiche Aktionen, die dieselbe deutliche + // Warnung verdienen. "Achtung!!!" (titleId) steht ganz oben, mit einer + // Leerzeile Abstand zum Fliesstext (bodyId) darunter; der Text + // scrollt bei Bedarf (laengere Uebersetzungen) ueber eigene Pfeil- + // Buttons, OK/Zurueck bleiben dabei immer unten fix und // kollisionsfrei sichtbar. Sternchen laufen im Hintergrund mit, wie auf // allen anderen Menue-Screens (nur der Radar-Screen selbst spart sich // das wegen der CPU-Last durch Abfragen/Zeichnen). Gibt true zurueck, // wenn "OK" angetippt wurde, false bei "Zurueck". - bool confirmLogbookEnable(TFT_eSPI& tft) { + bool confirmWarningScreen(TFT_eSPI& tft, StringId titleId, StringId bodyId) { constexpr int16_t BOX_X = 4; constexpr int16_t BOX_Y = 4; constexpr int16_t BOX_W = Config::SCREEN_WIDTH - 2 * BOX_X; @@ -195,7 +220,7 @@ namespace { constexpr int16_t VIEW_BOTTOM_NO_SCROLL = OK_Y - 8; constexpr int16_t VIEW_BOTTOM_SCROLL = VIEW_BOTTOM_NO_SCROLL - SCROLL_ROW_H - SCROLL_ROW_GAP; - String body = I18n::t(StringId::MENU_LOGBOOK_WARNING_BODY); + String body = I18n::t(bodyId); int16_t totalH = layoutWrapped(tft, BOX_X + 10, VIEW_TOP, TEXT_MAX_WIDTH, LINE_H, body, 0, 0, 0, false); bool scrollable = (totalH - VIEW_BOTTOM_NO_SCROLL) > 0; @@ -220,7 +245,7 @@ namespace { tft.setTextDatum(MC_DATUM); tft.setTextColor(TFT_RED, TFT_BLACK); tft.setTextSize(2); - tft.drawString(I18n::t(StringId::MENU_LOGBOOK_WARNING_TITLE), BOX_X + BOX_W / 2, TITLE_Y); + tft.drawString(I18n::t(titleId), BOX_X + BOX_W / 2, TITLE_Y); tft.setTextSize(1); tft.setTextDatum(TL_DATUM); @@ -257,7 +282,7 @@ namespace { } } - enum class Page { Main, Region, System, Flight }; + enum class Page { Main, Region, System, Flight, BackupReset }; } void run(TFT_eSPI& tft) { @@ -342,14 +367,15 @@ void run(TFT_eSPI& tft) { Rect brightnessBtn = systemRowRect(2); Rect timeoutBtn = systemRowRect(3); Rect nightDimBtn = systemRowRect(4); - Rect backupBtn = systemRowRect(5); - Rect restoreBtn = systemRowRect(6); - Rect webuiBtn = systemRowRect(7); - // "Info" (About) steht bewusst als letzter Punkt direkt ueber dem - // Zurueck-Button - so bleibt seine Position stabil, egal wie + Rect webuiBtn = systemRowRect(5); + // "Sicherung & Reset" (Backup/Restore/Werksreset, siehe + // Page::BackupReset unten) steht bewusst direkt ueber "Info", + // das seinerseits als letzter Punkt direkt ueber dem Zurueck- + // Button steht - so bleiben beide Positionen stabil, egal wie // viele weitere Punkte davor noch dazukommen. - Rect aboutBtn = systemRowRect(8); - Rect backBtn = systemRowRect(9); + Rect backupResetBtn = systemRowRect(6); + Rect aboutBtn = systemRowRect(7); + Rect backBtn = systemRowRect(8); drawButton(tft, calibBtn, I18n::t(StringId::MENU_CALIBRATE)); @@ -361,9 +387,8 @@ void run(TFT_eSPI& tft) { drawButton(tft, brightnessBtn, brightnessLabel(SettingsStore::brightnessPercent())); drawButton(tft, timeoutBtn, screenTimeoutLabel(SettingsStore::screenTimeoutMinutes())); drawButton(tft, nightDimBtn, I18n::t(StringId::MENU_NIGHT_DIMMING) + onOff(SettingsStore::nightDimmingEnabled())); - drawButton(tft, backupBtn, I18n::t(StringId::MENU_BACKUP)); - drawButton(tft, restoreBtn, I18n::t(StringId::MENU_RESTORE)); drawButton(tft, webuiBtn, I18n::t(StringId::MENU_LOGBOOK_WEBUI)); + drawButton(tft, backupResetBtn, I18n::t(StringId::MENU_BACKUP_RESET)); drawButton(tft, aboutBtn, I18n::t(StringId::MENU_ABOUT)); drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW)); @@ -388,7 +413,42 @@ void run(TFT_eSPI& tft) { SettingsStore::setScreenTimeoutMinutes(next); } else if (nightDimBtn.contains(tap.x, tap.y)) { SettingsStore::setNightDimmingEnabled(!SettingsStore::nightDimmingEnabled()); - } else if (backupBtn.contains(tap.x, tap.y)) { + } else if (backupResetBtn.contains(tap.x, tap.y)) { + page = Page::BackupReset; + } else if (aboutBtn.contains(tap.x, tap.y)) { + AboutScreen::run(tft); + } else if (webuiBtn.contains(tap.x, tap.y)) { + WebUiScreen::run(tft); + } else if (backBtn.contains(tap.x, tap.y)) { + page = Page::Main; + } + + } else if (page == Page::BackupReset) { + tft.setTextColor(TFT_GREEN, TFT_BLACK); + tft.setCursor(10, 14); + tft.println(I18n::t(StringId::MENU_BACKUP_RESET)); + + Rect backupBtn = backupResetRowRect(0); + Rect restoreBtn = backupResetRowRect(1); + Rect resetBtn = backupResetRowRect(2); + Rect backBtn = backupResetRowRect(3); + + drawButton(tft, backupBtn, I18n::t(StringId::MENU_BACKUP)); + drawButton(tft, restoreBtn, I18n::t(StringId::MENU_RESTORE)); + // Danger-Akzent (rot) - deutlich von Sichern/Wiederherstellen + // abgesetzt, da diese Aktion (nach Bestaetigung) ALLE Daten + // unwiderruflich loescht, siehe confirmWarningScreen() unten. + drawButton(tft, resetBtn, I18n::t(StringId::MENU_FACTORY_RESET), false, true); + drawButton(tft, backBtn, I18n::t(StringId::BACK_ARROW)); + + TouchInput::Point tap; + while (true) { + if (TouchInput::wasTapped(tap)) break; + MenuStars::update(tft); + delay(20); + } + + if (backupBtn.contains(tap.x, tap.y)) { bool ok = SettingsBackup::backup(); showBriefMessage(tft, I18n::t(ok ? StringId::MENU_BACKUP_SAVED : StringId::MENU_BACKUP_FAILED), ok ? TFT_GREEN : TFT_RED); @@ -398,12 +458,26 @@ void run(TFT_eSPI& tft) { showBriefMessage(tft, I18n::t(ok ? StringId::MENU_RESTORED : StringId::MENU_RESTORE_FAILED), ok ? TFT_GREEN : TFT_RED); } - } else if (aboutBtn.contains(tap.x, tap.y)) { - AboutScreen::run(tft); - } else if (webuiBtn.contains(tap.x, tap.y)) { - WebUiScreen::run(tft); + } else if (resetBtn.contains(tap.x, tap.y)) { + if (confirmWarningScreen(tft, StringId::MENU_LOGBOOK_WARNING_TITLE, + StringId::MENU_FACTORY_RESET_WARNING_BODY)) { + tft.fillScreen(TFT_BLACK); + tft.setTextDatum(MC_DATUM); + tft.setTextColor(TFT_RED, TFT_BLACK); + tft.drawString(I18n::t(StringId::MENU_FACTORY_RESET_DELETING), + Config::SCREEN_WIDTH / 2, Config::SCREEN_HEIGHT / 2); + tft.setTextDatum(TL_DATUM); + // Erfolgsfall: factoryReset() startet das Geraet neu und + // kehrt nie zurueck - dieser Code danach laeuft nur im + // (seltenen) Fehlerfall (SD nicht eingehaengt) ueberhaupt + // weiter. + bool ok = SettingsBackup::factoryReset(); + if (!ok) { + showBriefMessage(tft, I18n::t(StringId::MENU_FACTORY_RESET_FAILED), TFT_RED); + } + } } else if (backBtn.contains(tap.x, tap.y)) { - page = Page::Main; + page = Page::System; } } else { // Page::Flight @@ -486,7 +560,8 @@ void run(TFT_eSPI& tft) { SettingsStore::setFlightLogbookEnabled(false); SettingsStore::setFlightLogbookEnabledAtEpoch(0); SettingsStore::setFlightLogbookSessionFile(""); - } else if (confirmLogbookEnable(tft)) { + } else if (confirmWarningScreen(tft, StringId::MENU_LOGBOOK_WARNING_TITLE, + StringId::MENU_LOGBOOK_WARNING_BODY)) { SettingsStore::setFlightLogbookEnabled(true); SettingsStore::setFlightLogbookEnabledAtEpoch((uint32_t)time(nullptr)); // Leerer Eintrag erzwingt eine frische Sitzungsdatei beim diff --git a/src/sd_storage.cpp b/src/sd_storage.cpp index f30c34b..0c4c87a 100644 --- a/src/sd_storage.cpp +++ b/src/sd_storage.cpp @@ -143,4 +143,35 @@ void logEvent(const char* csvLine) { f.close(); } +bool deleteDirectoryRecursive(const char* path) { + File dir = SD.open(path); + if (!dir || !dir.isDirectory()) { + if (dir) dir.close(); + return false; + } + + // Gleiches vorsichtiges "entry.name() koennte relativ ODER absolut + // sein"-Muster wie in flight_logbook.cpp::resetAllData() - anders als + // dort aber mit echter Rekursion in Unterordner (statt sie zu + // ueberspringen), da der Flightradar-Ordner welche enthaelt (logs/, + // screenshots/). + File entry = dir.openNextFile(); + while (entry) { + bool isDir = entry.isDirectory(); + String name = String(entry.name()); + entry.close(); + + String fullPath = name.startsWith("/") ? name : String(path) + "/" + name; + if (isDir) { + deleteDirectoryRecursive(fullPath.c_str()); + } else { + SD.remove(fullPath); + } + entry = dir.openNextFile(); + } + dir.close(); + + return SD.rmdir(path); +} + } \ No newline at end of file diff --git a/src/sd_storage.h b/src/sd_storage.h index 5dad7fa..76cff56 100644 --- a/src/sd_storage.h +++ b/src/sd_storage.h @@ -8,4 +8,12 @@ namespace SdStorage { void seedDefaultDataFiles(); void logEvent(const char* csvLine); + // Loescht rekursiv einen kompletten Ordner (alle Dateien und + // Unterordner) inklusive des Ordners selbst. Fuer den Menuepunkt + // "Einstellungen zuruecksetzen" (settings_backup.cpp::factoryReset()) - + // der einzige aktuelle Aufrufer, der damit den kompletten + // Flightradar-Ordner von der SD-Karte entfernt. Gibt false zurueck, + // wenn der Pfad nicht existiert/kein Ordner ist. + bool deleteDirectoryRecursive(const char* path); + } \ No newline at end of file diff --git a/src/settings_backup.cpp b/src/settings_backup.cpp index 0d73851..e3b6ac0 100644 --- a/src/settings_backup.cpp +++ b/src/settings_backup.cpp @@ -57,4 +57,24 @@ bool hasBackup() { return SD.exists(SETTINGS_BACKUP_FILE) || SD.exists(WIFI_BACKUP_FILE); } +bool factoryReset() { + if (!SdStorage::isMounted()) return false; + + bool ok; + { + // Guard-Block bewusst vor dem Neustart wieder verlassen (statt + // ueber die Funktion hinweg zu halten) - reine Vorsicht, auch wenn + // ESP.restart() den Chip ohnehin sofort zuruecksetzt. + SdMutex::Guard guard; + ok = SdStorage::deleteDirectoryRecursive(Config::SD_ROOT_DIR); + } + + if (ok) { + delay(200); + ESP.restart(); + // Wird nie erreicht - ESP.restart() kehrt nicht zurueck. + } + return ok; +} + } \ No newline at end of file diff --git a/src/settings_backup.h b/src/settings_backup.h index bf23e32..75cb473 100644 --- a/src/settings_backup.h +++ b/src/settings_backup.h @@ -5,4 +5,12 @@ namespace SettingsBackup { bool backup(); bool restore(); bool hasBackup(); + + // Loescht den kompletten Flightradar-Ordner von der SD-Karte und + // startet das Geraet neu - beim naechsten Boot laeuft dadurch wieder + // die komplette Ersteinrichtung (siehe main.cpp: isFirstRun-Erkennung + // ueber Config::SD_SETTINGS_FILE). Kehrt nur im Fehlerfall zurueck + // (z.B. SD nicht eingehaengt) - im Erfolgsfall startet das Geraet + // neu, bevor die Funktion "zurueckkehrt" (ESP.restart()). + bool factoryReset(); } \ No newline at end of file