From 7e4a9ed237d1f095e333e08c27d7cfd738561f2e Mon Sep 17 00:00:00 2001 From: unlogisch04 <98281608+unlogisch04@users.noreply.github.com> Date: Fri, 20 Mar 2026 00:25:19 +0100 Subject: [PATCH] Add emergency shell, frst, reboot function --- src/configuration/Configuration.cpp | 20 ++ src/consts.h | 57 ++++++ src/init.h | 307 +++++++++++++++++++--------- 3 files changed, 285 insertions(+), 99 deletions(-) diff --git a/src/configuration/Configuration.cpp b/src/configuration/Configuration.cpp index cadca18..32112d3 100644 --- a/src/configuration/Configuration.cpp +++ b/src/configuration/Configuration.cpp @@ -38,12 +38,32 @@ #define DIR_TOGGLES_OLD "/toggles" #define DIR_TOGGLES "/sensortoggles" +// Global variable defined at file scope (not in namespace) so init.h can access it +extern bool g_safeModeDeferredFactoryResetRequested; + namespace SlimeVR::Configuration { void Configuration::setup() { if (m_Loaded) { return; } + // Safe mode defers LittleFS formatting to normal runtime for reliability. + // If requested, perform the filesystem format before config load. + if (g_safeModeDeferredFactoryResetRequested) { + m_Logger.warn("Deferred safe-mode factory reset detected, formatting LittleFS"); + g_safeModeDeferredFactoryResetRequested = false; + + LittleFS.begin(); + if (!LittleFS.format()) { + m_Logger.error("Deferred LittleFS format failed"); + } else { + m_Logger.info("Deferred LittleFS format OK"); + m_Logger.warn("Rebooting after deferred safe-mode factory reset"); + ESP.restart(); + return; + } + } + bool status = LittleFS.begin(); if (!status) { this->m_Logger.warn("Could not mount LittleFS, formatting"); diff --git a/src/consts.h b/src/consts.h index 76119c5..82b136c 100644 --- a/src/consts.h +++ b/src/consts.h @@ -98,6 +98,63 @@ enum class SensorTypeID : uint8_t { #define BOARD_ESP32S3_SUPERMINI 23 #define BOARD_DEV_RESERVED 250 // Reserved, should not be used in any release firmware +// Returns the BOARD define name (e.g. "BOARD_WEMOSD1MINI"). +// Kept Arduino-independent because this header is used outside Arduino units. +inline const char* boardName() { + switch (BOARD) { + case BOARD_SLIMEVR_LEGACY: + return "BOARD_SLIMEVR_LEGACY"; + case BOARD_SLIMEVR_DEV: + return "BOARD_SLIMEVR_DEV"; + case BOARD_NODEMCU: + return "BOARD_NODEMCU"; + case BOARD_CUSTOM: + return "BOARD_CUSTOM"; + case BOARD_WROOM32: + return "BOARD_WROOM32"; + case BOARD_WEMOSD1MINI: + return "BOARD_WEMOSD1MINI"; + case BOARD_TTGO_TBASE: + return "BOARD_TTGO_TBASE"; + case BOARD_ESP01: + return "BOARD_ESP01"; + case BOARD_SLIMEVR: + return "BOARD_SLIMEVR"; + case BOARD_LOLIN_C3_MINI: + return "BOARD_LOLIN_C3_MINI"; + case BOARD_BEETLE32C3: + return "BOARD_BEETLE32C3"; + case BOARD_ESP32C3DEVKITM1: + return "BOARD_ESP32C3DEVKITM1"; + case BOARD_OWOTRACK: + return "BOARD_OWOTRACK"; + case BOARD_WRANGLER: + return "BOARD_WRANGLER"; + case BOARD_MOCOPI: + return "BOARD_MOCOPI"; + case BOARD_WEMOSWROOM02: + return "BOARD_WEMOSWROOM02"; + case BOARD_XIAO_ESP32C3: + return "BOARD_XIAO_ESP32C3"; + case BOARD_HARITORA: + return "BOARD_HARITORA"; + case BOARD_ESP32C6DEVKITC1: + return "BOARD_ESP32C6DEVKITC1"; + case BOARD_GLOVE_IMU_SLIMEVR_DEV: + return "BOARD_GLOVE_IMU_SLIMEVR_DEV"; + case BOARD_GESTURES: + return "BOARD_GESTURES"; + case BOARD_SLIMEVR_V1_2: + return "BOARD_SLIMEVR_V1_2"; + case BOARD_ESP32S3_SUPERMINI: + return "BOARD_ESP32S3_SUPERMINI"; + case BOARD_DEV_RESERVED: + return "BOARD_DEV_RESERVED"; + default: + return "BOARD_UNKNOWN"; + } +} + #define BAT_EXTERNAL 1 #define BAT_INTERNAL 2 #define BAT_MCP3021 3 diff --git a/src/init.h b/src/init.h index dd44230..7c5cd73 100644 --- a/src/init.h +++ b/src/init.h @@ -22,8 +22,16 @@ */ #include -#ifdef ESP8266 -#include + +#ifdef ESP32 +#include "esp_chip_info.h" +#include "esp_intr_alloc.h" +#include "esp_system.h" +#include "nvs_flash.h" +#if defined(CONFIG_IDF_TARGET_ESP32C3) +#include "soc/rtc_cntl_reg.h" +#endif +#endif #define STRINGIFY(x) #x #define TOSTRING(x) STRINGIFY(x) @@ -33,38 +41,195 @@ typedef struct rtc_mem { uint32_t rebootCount; // Number of reboots } rtc_mem_t; +bool g_safeModeDeferredFactoryResetRequested = false; + +void failSafeProductInfo(Stream* Serial) { + Serial->println(F("==== SLVR Product Info ====")); + Serial->println(String(F("PRODUCT_NAME: ")) + String(PRODUCT_NAME)); + Serial->println(String(F("VENDOR_NAME: ")) + String(VENDOR_NAME)); + Serial->println(String(F("VENDOR_URL: ")) + String(VENDOR_URL)); + Serial->println(String(F("Firmware update URL: ")) + String(UPDATE_ADDRESS)); + Serial->println(String(F("BOARD: ")) + String(BOARD)); + Serial->println(String(F("BOARD NAME: ")) + String(boardName())); + Serial->println(String(F("HARDWARE_MCU: ")) + String(HARDWARE_MCU)); + Serial->println(String(F("PROTOCOL_VERSION: ")) + String(PROTOCOL_VERSION)); + Serial->println(String(F("FIRMWARE_VERSION: ")) + String(FIRMWARE_VERSION)); + Serial->println(F("SENSOR_DESC_LIST: ")); + Serial->println(String(TOSTRING(SENSOR_DESC_LIST))); +} + +void failSafeBootInfo(Stream* Serial, uint32_t resetreason, rtc_mem_t* rtcMem) { + Serial->println(F("\r\n==== SLVR Boot ====")); + Serial->println(String(F("Reboot reason code: ")) + String(resetreason)); + Serial->println(String(F("Core Version: ")) + String(ESP.getCoreVersion())); + Serial->println(String(F("SDK version: ")) + String(ESP.getSdkVersion())); + Serial->println(String(F("Sketch MD5: ")) + String(ESP.getSketchMD5())); + Serial->println(String(F("RTC Memory Version: ")) + String(rtcMem->version)); + Serial->println( + String(F("RTC Memory Reboot Count: ")) + String(rtcMem->rebootCount) + ); + Serial->println(); + failSafeProductInfo(Serial); +} + +bool cmdSafeModeFlashMode(Stream* Serial) { +#if defined(ESP8266) + Serial->println(F("Entering flash mode...")); + // Serial needs to be stay active for + // rebootIntoUartDownloadMode to work. + // Found out over testing. + delay(1000); + ESP.rebootIntoUartDownloadMode(); + return true; + +#elif defined(CONFIG_IDF_TARGET_ESP32C3) + Serial->println(F("Entering flash mode...")); + // from https://esp32.com/viewtopic.php?t=33180 + delay(1000); + REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); + esp_restart(); + return true; + +#else + Serial->println( + F("The Firmware does not support reboot into UART download mode for " + "this board.") + ); + Serial->println( + F("You need to manually enter flash mode by holding BOOT button while " + "resetting.") + ); +#endif + return false; // Not supported, caller should handle with a message +} + +void cmdSafeModeReboot() { +#if defined(ESP8266) + // During preinit, system_restart() (used by ESP.restart()) is asynchronous — + // it posts to the SDK task queue which hasn't started yet, causing a hang. + // ESP.reset() calls __real_system_restart_local() which is a direct hardware reset. + ESP.reset(); +#else + ESP.restart(); +#endif +} + +bool cmdSafeModeFactoryReset(Stream* Serial) { + // WiFi and LittleFS operations are not reliable during preinit() on either + // platform. Defer filesystem formatting to normal runtime. + g_safeModeDeferredFactoryResetRequested = true; + +#if defined(ESP8266) + ESP.eraseConfig(); // Erase SDK config sectors (WiFi credentials etc.) +#elif defined(ESP32) + nvs_flash_erase(); // Erase NVS (equivalent to ESP8266's config) +#endif + + if (Serial) { + Serial->println(F("LittleFS format deferred to runtime.")); + Serial->println(F("Continuing normal boot for format + final reboot...")); + Serial->flush(); + } + return true; +} + +void cmdSafeModeHelp(Stream* Serial) { + Serial->println(F("Available commands:")); + Serial->println( + F(" FRST - Factory reset (clears WiFi credentials " + "and config, then reboots)") + ); + Serial->println(F(" REBOOT - Restart the device")); + Serial->println(F(" SET FLASHMODE - Enter UART download / flash mode")); + Serial->println(F(" HELP - Show this help message")); +} + +// Maximum input line length — longest valid command is "SET FLASHMODE" (13 chars). +// Anything beyond this is serial noise or garbage; discard to protect the heap. +// This is especially important because mainSave() runs during preinit/initVariant +// before full system initialization. +#define MAINSAVE_CMD_MAXLEN 32 + +// mainSave is for all the Shell needed loop in case of safe mode +bool mainSave(Stream* Serial) { + String inputBuffer = ""; + + Serial->println(); + Serial->println(F("=== SLVR Emergency Shell ===")); + Serial->println(F("Safe mode command prompt. Type 'HELP' for available commands.")); + cmdSafeModeHelp(Serial); + + while (true) { + while (Serial->available()) { + char c = (char)Serial->read(); + if (c == '\r') { + continue; // ignore carriage return + } + if (c == '\n') { + inputBuffer.trim(); + String cmd = inputBuffer; + cmd.toUpperCase(); + inputBuffer = ""; + + if (cmd == "FRST") { + Serial->println(F("Performing factory reset...")); + if (cmdSafeModeFactoryReset(Serial)) { + return true; + } + } else if (cmd == "REBOOT") { + Serial->println(F("Rebooting...")); + cmdSafeModeReboot(); + } else if (cmd == "SET FLASHMODE") { + cmdSafeModeFlashMode(Serial); + } else if (cmd == "HELP") { + cmdSafeModeHelp(Serial); + } else if (cmd.length() > 0) { + Serial->print(F("Unknown command: '")); + Serial->print(cmd); + Serial->println(F("'. Type 'HELP' for available commands.")); + } + } else { + if (inputBuffer.length() >= MAINSAVE_CMD_MAXLEN) { + // Buffer overflow — likely serial noise or garbage data. Discard + // and warn. + Serial->println(F("")); + Serial->println(F("Input too long, discarding line.")); + inputBuffer = ""; + } else { + inputBuffer += c; + } + } + } + +#if defined(ESP8266) + ESP.wdtFeed(); // Feed the watchdog to prevent reset while in this loop +#endif + delay(1); + } +} + +#ifdef ESP8266 +#include + extern "C" void preinit(void) { - HardwareSerial Serialtemp(0); + HardwareSerial Serial(0); struct rst_info* resetreason; rtc_mem_t rtcMem; resetreason = ESP.getResetInfoPtr(); - // Offset 33 to avoid eboot command area - ESP.rtcUserMemoryRead(33, (uint32_t*)&rtcMem, sizeof(struct rtc_mem)); - Serialtemp.begin(115200); - Serialtemp.println(F("\r\n==== SLVR Boot ====")); - Serialtemp.println(F("Reboot reason code: ") + String(resetreason->reason)); - Serialtemp.println(F("Core Version: ") + ESP.getCoreVersion()); - Serialtemp.println(F("SDK version: ") + String(ESP.getSdkVersion())); - Serialtemp.println(F("Sketch MD5: ") + String(ESP.getSketchMD5())); - Serialtemp.println(F("RTC Memory Version: ") + String(rtcMem.version)); - Serialtemp.println(F("RTC Memory Reboot Count: ") + String(rtcMem.rebootCount)); - Serialtemp.println(); - Serialtemp.println(F("PRODUCT_NAME: ") + String(PRODUCT_NAME)); - Serialtemp.println(F("VENDOR_NAME: ") + String(VENDOR_NAME)); - Serialtemp.println(F("VENDOR_URL: ") + String(VENDOR_URL)); - Serialtemp.println(F("Firmware update URL: ") + String(UPDATE_ADDRESS)); - Serialtemp.println(F("BOARD: ") + String(BOARD)); - Serialtemp.println(F("HARDWARE_MCU: ") + String(HARDWARE_MCU)); - Serialtemp.println(F("PROTOCOL_VERSION: ") + String(PROTOCOL_VERSION)); - Serialtemp.println(F("FIRMWARE_VERSION: ") + String(FIRMWARE_VERSION)); - Serialtemp.println(F("SENSOR_DESC_LIST: ")); - Serialtemp.println(String(TOSTRING(SENSOR_DESC_LIST))); - if (rtcMem.version != 0x01) { + Serial.begin(115200); + + // Offset 33 to avoid eboot command area + bool rtcOk = ESP.rtcUserMemoryRead(33, (uint32_t*)&rtcMem, sizeof(struct rtc_mem)); + + if (!rtcOk || rtcMem.version != 0x01) { // First boot, initialize RTC memory rtcMem.version = 0x01; rtcMem.rebootCount = 0; } + + failSafeBootInfo(&Serial, resetreason->reason, &rtcMem); + if (resetreason->reason != REASON_SOFT_WDT_RST && resetreason->reason != REASON_EXCEPTION_RST && resetreason->reason != REASON_WDT_RST) { @@ -77,42 +242,27 @@ extern "C" void preinit(void) { // If more than 3 consecutive crashes, enter safe mode if (rtcMem.rebootCount >= 3) { // Boot into UART download mode - Serialtemp.println(); - Serialtemp.println(); - Serialtemp.println(F("Entering safe mode due to repeated crashes.")); - Serialtemp.println(F("Entering flash mode...")); - // Serial needs to be stay active for - // rebootIntoUartDownloadMode to work. - // Found out over testing. - delay(1000); - ESP.rebootIntoUartDownloadMode(); + Serial.println(); + Serial.println(); + Serial.println(F("Entering safe mode due to repeated crashes.")); + Serial.println(F("Starting Emergency shell...")); + if (mainSave(&Serial)) { + Serial.println(F("Leaving emergency shell and continuing boot...")); + } else { + return; + } } } ESP.rtcUserMemoryWrite(33, (uint32_t*)&rtcMem, sizeof(struct rtc_mem)); - Serialtemp.println(F("=== SLVR Boot end ===")); - Serialtemp.flush(); + Serial.println(F("=== SLVR Boot end ===")); + Serial.flush(); // Deinit UART for main code to reinitialize - Serialtemp.end(); + Serial.end(); } #endif #ifdef ESP32 -#include "esp_system.h" -#if defined(CONFIG_IDF_TARGET_ESP32C3) -#include "soc/rtc_cntl_reg.h" -#endif -#include "esp_chip_info.h" -#include "esp_intr_alloc.h" - -#define STRINGIFY(x) #x -#define TOSTRING(x) STRINGIFY(x) - -typedef struct rtc_mem { - uint32_t version; // RTC memory version - uint32_t rebootCount; // Number of reboots -} rtc_mem_t; - // infos from https://circuitlabs.net/rtc-memory-usage-in-esp-idf/ RTC_DATA_ATTR rtc_mem_t rtcMem; @@ -121,33 +271,16 @@ extern "C" void initVariant(void) { resetreason = esp_reset_reason(); - // don't need to read RTC memory as it is cleard on a WDT reset on ESP32 - - Serial.begin(115200); - Serial.println(F("\r\n==== SLVR Boot ====")); - Serial.println(String(F("Reboot reason code: ")) + String(resetreason)); - Serial.println(String(F("Core Version: ")) + String(ESP.getCoreVersion())); - Serial.println(String(F("SDK version: ")) + String(ESP.getSdkVersion())); - Serial.println(String(F("Sketch MD5: ")) + String(ESP.getSketchMD5())); - Serial.println(String(F("RTC Memory Version: ")) + String(rtcMem.version)); - Serial.println(String(F("RTC Memory Reboot Count: ")) + String(rtcMem.rebootCount)); - Serial.println(); - Serial.println(String(F("PRODUCT_NAME: ")) + String(PRODUCT_NAME)); - Serial.println(String(F("VENDOR_NAME: ")) + String(VENDOR_NAME)); - Serial.println(String(F("VENDOR_URL: ")) + String(VENDOR_URL)); - Serial.println(String(F("Firmware update URL: ")) + String(UPDATE_ADDRESS)); - Serial.println(String(F("BOARD: ")) + String(BOARD)); - Serial.println(String(F("HARDWARE_MCU: ")) + String(HARDWARE_MCU)); - Serial.println(String(F("PROTOCOL_VERSION: ")) + String(PROTOCOL_VERSION)); - Serial.println(String(F("FIRMWARE_VERSION: ")) + String(FIRMWARE_VERSION)); - Serial.println(F("SENSOR_DESC_LIST: ")); - Serial.println(String(TOSTRING(SENSOR_DESC_LIST))); - if (rtcMem.version != 0x01) { // First boot, initialize RTC memory rtcMem.version = 0x01; rtcMem.rebootCount = 0; } + + // don't need to read RTC memory as it is cleard on a WDT reset on ESP32 + Serial.begin(115200); + failSafeBootInfo(&Serial, resetreason, &rtcMem); + if (resetreason != ESP_RST_PANIC && resetreason != ESP_RST_INT_WDT && resetreason != ESP_RST_TASK_WDT && resetreason != ESP_RST_WDT && resetreason != ESP_RST_CPU_LOCKUP) { @@ -161,33 +294,9 @@ extern "C" void initVariant(void) { // If more than 3 consecutive crashes, enter safe mode if (rtcMem.rebootCount >= 3) { // Boot into UART download mode - Serial.println(); - Serial.println(); - -#if defined(CONFIG_IDF_TARGET_ESP32C3) - Serial.println(F("Entering safe mode due to repeated crashes.")); - Serial.println(F("Entering flash mode...")); - // from https://esp32.com/viewtopic.php?t=33180 - delay(1000); - REG_WRITE(RTC_CNTL_OPTION1_REG, RTC_CNTL_FORCE_DOWNLOAD_BOOT); - esp_restart(); -#else - Serial.println( - F("The Firmware does not support reboot into UART download mode for " - "this board.") - ); - Serial.println( - F("You need to manually enter flash mode by holding BOOT button while " - "resetting.") - ); - while (true) { - delay(100); // Halt - } -#endif + mainSave(&Serial); } } - // no need to write, rtcMem is in RTC memory it does it automatically - // ESP.rtcUserMemoryWrite(33, (uint32_t*)&rtcMem, sizeof(struct rtc_mem)); Serial.println(F("=== SLVR Boot end ===")); Serial.flush();