@@ -720,125 +720,140 @@ void *realloc_malloc(void *ptr, size_t size) {
720720// checks if the ESP reboots multiple times due to a crash or watchdog timeout
721721// if a bootloop is detected: restore settings from backup, then reset settings, then switch boot image (and repeat)
722722
723- #define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection
724- #define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /bak.cfg.json
725- #define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /rst.cfg.json)
726- #define BOOTLOOP_ACTION_OTA 2 // swap the boot partition
727- #define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset)
723+ #define BOOTLOOP_INTERVAL_MILLIS 120000 // time limit between crashes: 120 seconds (2 minutes)
724+ #define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection
725+ #define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /bkp.cfg.json
726+ #define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /rst.cfg.json)
727+ #define BOOTLOOP_ACTION_OTA 2 // swap the boot partition
728+ #define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset)
729+
730+ // Platform-agnostic abstraction
731+ enum class ResetReason {
732+ Power,
733+ Software,
734+ Crash,
735+ Brownout
736+ };
737+
728738#ifdef ESP8266
729- #define BOOTLOOP_INTERVAL_TICKS (5 * 160000 ) // time limit between crashes: ~5 seconds in RTC ticks
730- #define BOOT_TIME_IDX 0 // index in RTC memory for boot time
731- #define CRASH_COUNTER_IDX 1 // index in RTC memory for crash counter
732- #define ACTIONT_TRACKER_IDX 2 // index in RTC memory for boot action
739+ // Place variables in RTC memory via references, since RTC memory is not exposed via the linker in the Non-OS SDK
740+ // Use an offset of 32 as there's some hints that the first 128 bytes of "user" memory are used by the OTA system
741+ // Ref: https://github.com/esp8266/Arduino/blob/78d0d0aceacc1553f45ad8154592b0af22d1eede/cores/esp8266/Esp.cpp#L168
742+ static volatile uint32_t & bl_last_boottime = *(RTC_USER_MEM + 32 );
743+ static volatile uint32_t & bl_crashcounter = *(RTC_USER_MEM + 33 );
744+ static volatile uint32_t & bl_actiontracker = *(RTC_USER_MEM + 34 );
745+
746+ static inline ResetReason rebootReason () {
747+ uint32_t resetReason = system_get_rst_info ()->reason ;
748+ if (resetReason == REASON_EXCEPTION_RST
749+ || resetReason == REASON_WDT_RST
750+ || resetReason == REASON_SOFT_WDT_RST)
751+ return ResetReason::Crash;
752+ if (resetReason == REASON_SOFT_RESTART)
753+ return ResetReason::Software;
754+ return ResetReason::Power;
755+ }
756+
757+ static inline uint32_t getRtcMillis () { return system_get_rtc_time () / 160 ; }; // rtc ticks ~160000Hz
758+
733759#else
734- #define BOOTLOOP_INTERVAL_TICKS 5000 // time limit between crashes: ~5 seconds in milliseconds
735760// variables in RTC_NOINIT memory persist between reboots (but not on hardware reset)
736761RTC_NOINIT_ATTR static uint32_t bl_last_boottime;
737762RTC_NOINIT_ATTR static uint32_t bl_crashcounter;
738763RTC_NOINIT_ATTR static uint32_t bl_actiontracker;
764+
765+ static inline ResetReason rebootReason () {
766+ esp_reset_reason_t reason = esp_reset_reason ();
767+ if (reason == ESP_RST_BROWNOUT) return ResetReason::Brownout;
768+ if (reason == ESP_RST_SW) return ResetReason::Software;
769+ if (reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT) return ResetReason::Crash;
770+ return ResetReason::Power;
771+ }
772+
773+ #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
774+ static inline uint32_t getRtcMillis () { return esp_rtc_get_time_us () / 1000 ; }
775+ #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
776+ static inline uint32_t getRtcMillis () { return rtc_time_slowclk_to_us (rtc_time_get (), rtc_clk_slow_freq_get_hz ()) / 1000 ; }
777+ #endif
778+
739779void bootloopCheckOTA () { bl_actiontracker = BOOTLOOP_ACTION_OTA; } // swap boot image if bootloop is detected instead of restoring config
780+
740781#endif
741782
742783// detect bootloop by checking the reset reason and the time since last boot
743784static bool detectBootLoop () {
744- #if !defined(ESP8266)
745- #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0)
746- uint32_t rtctime = esp_rtc_get_time_us () / 1000 ; // convert to milliseconds
747- #elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(3, 3, 0)
748- uint64_t rtc_ticks = rtc_time_get ();
749- uint32_t rtctime = rtc_time_slowclk_to_us (rtc_ticks, rtc_clk_slow_freq_get_hz ()) / 1000 ; // convert to milliseconds
750- #endif
751-
752- esp_reset_reason_t reason = esp_reset_reason ();
785+ uint32_t rtctime = getRtcMillis ();
786+ bool result = false ;
753787
754- if (!(reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT)) {
755- // no crash detected, init variables
756- bl_crashcounter = 0 ;
757- bl_last_boottime = rtctime;
758- if (reason != ESP_RST_SW)
759- bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
760- } else if (reason == ESP_RST_BROWNOUT) {
761- // crash due to brownout can't be detected unless using flash memory to store bootloop variables
762- // this is a simpler way to preemtively revert the config in case current brownout is caused by a bad choice of settings
763- DEBUG_PRINTLN (F (" brownout detected" ));
764- // restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
765- } else {
766- uint32_t rebootinterval = rtctime - bl_last_boottime;
767- bl_last_boottime = rtctime; // store current runtime for next reboot
768- if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) {
769- bl_crashcounter++;
770- if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
771- DEBUG_PRINTLN (F (" !BOOTLOOP DETECTED!" ));
772- bl_crashcounter = 0 ;
773- return true ;
774- }
775- }
776- }
777- #else // ESP8266
778- rst_info* resetreason = system_get_rst_info ();
779- uint32_t bl_last_boottime;
780- uint32_t bl_crashcounter;
781- uint32_t bl_actiontracker;
782- uint32_t rtctime = system_get_rtc_time ();
783-
784- if (!(resetreason->reason == REASON_EXCEPTION_RST || resetreason->reason == REASON_WDT_RST)) {
785- // no crash detected, init variables
786- bl_crashcounter = 0 ;
787- ESP.rtcUserMemoryWrite (BOOT_TIME_IDX, &rtctime, sizeof (uint32_t ));
788- ESP.rtcUserMemoryWrite (CRASH_COUNTER_IDX, &bl_crashcounter, sizeof (uint32_t ));
789- if (resetreason->reason != REASON_SOFT_RESTART) {
788+ switch (rebootReason ()) {
789+ case ResetReason::Power:
790790 bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler)
791- ESP.rtcUserMemoryWrite (ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof (uint32_t ));
792- }
793- } else {
794- // system has crashed
795- ESP.rtcUserMemoryRead (BOOT_TIME_IDX, &bl_last_boottime, sizeof (uint32_t ));
796- ESP.rtcUserMemoryRead (CRASH_COUNTER_IDX, &bl_crashcounter, sizeof (uint32_t ));
797- uint32_t rebootinterval = rtctime - bl_last_boottime;
798- ESP.rtcUserMemoryWrite (BOOT_TIME_IDX, &rtctime, sizeof (uint32_t )); // store current ticks for next reboot
799- if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) {
800- bl_crashcounter++;
801- ESP.rtcUserMemoryWrite (CRASH_COUNTER_IDX, &bl_crashcounter, sizeof (uint32_t ));
802- if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
803- DEBUG_PRINTLN (F (" BOOTLOOP DETECTED" ));
791+ // fall through
792+ case ResetReason::Software:
793+ // no crash detected, reset counter
794+ bl_crashcounter = 0 ;
795+ break ;
796+
797+ case ResetReason::Crash:
798+ {
799+ DEBUG_PRINTLN (F (" crash detected!" ));
800+ uint32_t rebootinterval = rtctime - bl_last_boottime;
801+ if (rebootinterval < BOOTLOOP_INTERVAL_MILLIS) {
802+ bl_crashcounter++;
803+ if (bl_crashcounter >= BOOTLOOP_THRESHOLD) {
804+ DEBUG_PRINTLN (F (" !BOOTLOOP DETECTED!" ));
805+ bl_crashcounter = 0 ;
806+ result = true ;
807+ }
808+ } else {
809+ // Reset counter on long intervals to track only consecutive short-interval crashes
804810 bl_crashcounter = 0 ;
805- ESP.rtcUserMemoryWrite (CRASH_COUNTER_IDX, &bl_crashcounter, sizeof (uint32_t ));
806- return true ;
811+ // TODO: crash reporting goes here
807812 }
813+ break ;
808814 }
815+
816+ case ResetReason::Brownout:
817+ // crash due to brownout can't be detected unless using flash memory to store bootloop variables
818+ DEBUG_PRINTLN (F (" brownout detected" ));
819+ // restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all)
820+ break ;
809821 }
810- #endif
811- return false ; // no bootloop detected
822+
823+ bl_last_boottime = rtctime; // store current runtime for next reboot
824+
825+ return result;
812826}
813827
814828void handleBootLoop () {
815- DEBUG_PRINTLN ( F (" checking for bootloop" ) );
829+ DEBUG_PRINTF_P ( PSTR (" checking for bootloop: time %d, counter %d, action %d \n " ), bl_last_boottime, bl_crashcounter, bl_actiontracker );
816830 if (!detectBootLoop ()) return ; // no bootloop detected
817- # ifdef ESP8266
818- uint32_t bl_actiontracker;
819- ESP. rtcUserMemoryRead (ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof ( uint32_t ));
820- # endif
821- if (bl_actiontracker == BOOTLOOP_ACTION_RESTORE) {
822- restoreConfig (); // note: if this fails, could reset immediately. instead just let things play out and save a few lines of code
823- bl_actiontracker = BOOTLOOP_ACTION_RESET; // reset config if it keeps bootlooping
824- } else if (bl_actiontracker == BOOTLOOP_ACTION_RESET) {
825- resetConfig () ;
826- bl_actiontracker = BOOTLOOP_ACTION_OTA; // swap boot partition if it keeps bootlooping. On ESP8266 this is the same as BOOTLOOP_ACTION_NONE
827- }
831+
832+ switch ( bl_actiontracker) {
833+ case BOOTLOOP_ACTION_RESTORE:
834+ restoreConfig ();
835+ ++bl_actiontracker;
836+ break ;
837+ case BOOTLOOP_ACTION_RESET:
838+ resetConfig ();
839+ ++bl_actiontracker ;
840+ break ;
841+ case BOOTLOOP_ACTION_OTA:
828842#ifndef ESP8266
829- else if (bl_actiontracker == BOOTLOOP_ACTION_OTA) {
830- if (Update.canRollBack ()) {
831- DEBUG_PRINTLN (F (" Swapping boot partition..." ));
832- Update.rollBack (); // swap boot partition
833- }
834- bl_actiontracker = BOOTLOOP_ACTION_DUMP; // out of options
835- }
836- #endif
837- else
838- dumpFilesToSerial ();
839- #ifdef ESP8266
840- ESP.rtcUserMemoryWrite (ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof (uint32_t ));
843+ if (Update.canRollBack ()) {
844+ DEBUG_PRINTLN (F (" Swapping boot partition..." ));
845+ Update.rollBack (); // swap boot partition
846+ }
847+ ++bl_actiontracker;
848+ break ;
849+ #else
850+ // fall through
841851#endif
852+ case BOOTLOOP_ACTION_DUMP:
853+ dumpFilesToSerial ();
854+ break ;
855+ }
856+
842857 ESP.restart (); // restart cleanly and don't wait for another crash
843858}
844859
0 commit comments