Add emergency shell, frst, reboot function

This commit is contained in:
unlogisch04
2026-03-20 00:25:19 +01:00
parent 20750f3a21
commit 7e4a9ed237
3 changed files with 285 additions and 99 deletions

View File

@@ -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");

View File

@@ -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

View File

@@ -22,8 +22,16 @@
*/
#include <Arduino.h>
#ifdef ESP8266
#include <user_interface.h>
#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 <user_interface.h>
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();