mirror of
https://github.com/SlimeVR/SlimeVR-Tracker-ESP.git
synced 2026-04-05 17:51:57 +02:00
Add emergency shell, frst, reboot function
This commit is contained in:
@@ -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");
|
||||
|
||||
57
src/consts.h
57
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
|
||||
|
||||
307
src/init.h
307
src/init.h
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user