|
1 | 1 | #include "wled.h" |
2 | 2 | #include "fcn_declare.h" |
3 | 3 | #include "const.h" |
| 4 | +#ifdef ESP8266 |
| 5 | +#include "user_interface.h" // for bootloop detection |
| 6 | +#elif ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) |
| 7 | +#include "esp32/rtc.h" // for bootloop detection |
| 8 | +#include <Update.h> |
| 9 | +#endif |
4 | 10 |
|
5 | 11 |
|
6 | 12 | //helper to get int value at a position in string |
@@ -706,6 +712,125 @@ void *realloc_malloc(void *ptr, size_t size) { |
706 | 712 | } |
707 | 713 | #endif |
708 | 714 |
|
| 715 | +// bootloop detection and handling |
| 716 | +// checks if the ESP reboots multiple times due to a crash or watchdog timeout |
| 717 | +// if a bootloop is detected: restore settings from backup, then reset settings, then switch boot image (and repeat) |
| 718 | + |
| 719 | +#define BOOTLOOP_THRESHOLD 5 // number of consecutive crashes to trigger bootloop detection |
| 720 | +#define BOOTLOOP_ACTION_RESTORE 0 // default action: restore config from /cfg.bak |
| 721 | +#define BOOTLOOP_ACTION_RESET 1 // if restore does not work, reset config (rename /cfg.json to /cfg.fault) |
| 722 | +#define BOOTLOOP_ACTION_OTA 2 // swap the boot partition |
| 723 | +#define BOOTLOOP_ACTION_DUMP 3 // nothing seems to help, dump files to serial and reboot (until hardware reset) |
| 724 | +#ifdef ESP8266 |
| 725 | +#define BOOTLOOP_INTERVAL_TICKS (5 * 160000) // time limit between crashes: ~5 seconds in RTC ticks |
| 726 | +#define BOOT_TIME_IDX 0 // index in RTC memory for boot time |
| 727 | +#define CRASH_COUNTER_IDX 1 // index in RTC memory for crash counter |
| 728 | +#define ACTIONT_TRACKER_IDX 2 // index in RTC memory for boot action |
| 729 | +#else |
| 730 | +#define BOOTLOOP_INTERVAL_TICKS 5000 // time limit between crashes: ~5 seconds in milliseconds |
| 731 | +// variables in RTC_NOINIT memory persist between reboots (but not on hardware reset) |
| 732 | +RTC_NOINIT_ATTR static uint32_t bl_last_boottime; |
| 733 | +RTC_NOINIT_ATTR static uint32_t bl_crashcounter; |
| 734 | +RTC_NOINIT_ATTR static uint32_t bl_actiontracker; |
| 735 | +void bootloopCheckOTA() { bl_actiontracker = BOOTLOOP_ACTION_OTA; } // swap boot image if bootloop is detected instead of restoring config |
| 736 | +#endif |
| 737 | + |
| 738 | +// detect bootloop by checking the reset reason and the time since last boot |
| 739 | +static bool detectBootLoop() { |
| 740 | +#if !defined(ESP8266) |
| 741 | + #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(4, 4, 0) |
| 742 | + uint32_t rtctime = esp_rtc_get_time_us() / 1000; // convert to milliseconds |
| 743 | + esp_reset_reason_t reason = esp_reset_reason(); |
| 744 | + |
| 745 | + if (!(reason == ESP_RST_PANIC || reason == ESP_RST_WDT || reason == ESP_RST_INT_WDT || reason == ESP_RST_TASK_WDT)) { |
| 746 | + // no crash detected, init variables |
| 747 | + bl_crashcounter = 0; |
| 748 | + bl_last_boottime = rtctime; |
| 749 | + if(reason != ESP_RST_SW) |
| 750 | + bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler) |
| 751 | + } else if (reason == ESP_RST_BROWNOUT) { |
| 752 | + // crash due to brownout can't be detected unless using flash memory to store bootloop variables |
| 753 | + // this is a simpler way to preemtively revert the config in case current brownout is caused by a bad choice of settings |
| 754 | + DEBUG_PRINTLN(F("brownout detected")); |
| 755 | + //restoreConfig(); // TODO: blindly restoring config if brownout detected is a bad idea, need a better way (if at all) |
| 756 | + } else { |
| 757 | + uint32_t rebootinterval = rtctime - bl_last_boottime; |
| 758 | + bl_last_boottime = rtctime; // store current runtime for next reboot |
| 759 | + if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) { |
| 760 | + bl_crashcounter++; |
| 761 | + if (bl_crashcounter >= BOOTLOOP_THRESHOLD) { |
| 762 | + DEBUG_PRINTLN(F("!BOOTLOOP DETECTED!")); |
| 763 | + bl_crashcounter = 0; |
| 764 | + return true; |
| 765 | + } |
| 766 | + } |
| 767 | + } |
| 768 | + #endif |
| 769 | +#else // ESP8266 |
| 770 | + rst_info* resetreason = system_get_rst_info(); |
| 771 | + uint32_t bl_last_boottime; |
| 772 | + uint32_t bl_crashcounter; |
| 773 | + uint32_t bl_actiontracker; |
| 774 | + uint32_t rtctime = system_get_rtc_time(); |
| 775 | + |
| 776 | + if (!(resetreason->reason == REASON_EXCEPTION_RST || resetreason->reason == REASON_WDT_RST)) { |
| 777 | + // no crash detected, init variables |
| 778 | + bl_crashcounter = 0; |
| 779 | + ESP.rtcUserMemoryWrite(BOOT_TIME_IDX, &rtctime, sizeof(uint32_t)); |
| 780 | + ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t)); |
| 781 | + if(resetreason->reason != REASON_SOFT_RESTART) { |
| 782 | + bl_actiontracker = BOOTLOOP_ACTION_RESTORE; // init action tracker if not an intentional reboot (e.g. from OTA or bootloop handler) |
| 783 | + ESP.rtcUserMemoryWrite(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t)); |
| 784 | + } |
| 785 | + } else { |
| 786 | + // system has crashed |
| 787 | + ESP.rtcUserMemoryRead(BOOT_TIME_IDX, &bl_last_boottime, sizeof(uint32_t)); |
| 788 | + ESP.rtcUserMemoryRead(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t)); |
| 789 | + uint32_t rebootinterval = rtctime - bl_last_boottime; |
| 790 | + ESP.rtcUserMemoryWrite(BOOT_TIME_IDX, &rtctime, sizeof(uint32_t)); // store current ticks for next reboot |
| 791 | + if (rebootinterval < BOOTLOOP_INTERVAL_TICKS) { |
| 792 | + bl_crashcounter++; |
| 793 | + ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t)); |
| 794 | + if (bl_crashcounter >= BOOTLOOP_THRESHOLD) { |
| 795 | + DEBUG_PRINTLN(F("BOOTLOOP DETECTED")); |
| 796 | + bl_crashcounter = 0; |
| 797 | + ESP.rtcUserMemoryWrite(CRASH_COUNTER_IDX, &bl_crashcounter, sizeof(uint32_t)); |
| 798 | + return true; |
| 799 | + } |
| 800 | + } |
| 801 | + } |
| 802 | +#endif |
| 803 | + return false; // no bootloop detected |
| 804 | +} |
| 805 | + |
| 806 | +void handleBootLoop() { |
| 807 | + DEBUG_PRINTLN(F("checking for bootloop")); |
| 808 | + if (!detectBootLoop()) return; // no bootloop detected |
| 809 | +#ifdef ESP8266 |
| 810 | + uint32_t bl_actiontracker; |
| 811 | + ESP.rtcUserMemoryRead(ACTIONT_TRACKER_IDX, &bl_actiontracker, sizeof(uint32_t)); |
| 812 | +#endif |
| 813 | + if (bl_actiontracker == BOOTLOOP_ACTION_RESTORE) { |
| 814 | + restoreConfig(); // note: if this fails, could reset immediately. instead just let things play out and save a few lines of code |
| 815 | + bl_actiontracker = BOOTLOOP_ACTION_RESET; // reset config if it keeps bootlooping |
| 816 | + } else if (bl_actiontracker == BOOTLOOP_ACTION_RESET) { |
| 817 | + resetConfig(); |
| 818 | + bl_actiontracker = BOOTLOOP_ACTION_OTA; // swap boot partition if it keeps bootlooping. On ESP8266 this is the same as BOOTLOOP_ACTION_NONE |
| 819 | + } |
| 820 | +#ifndef ESP8266 |
| 821 | + else if (bl_actiontracker == BOOTLOOP_ACTION_OTA) { |
| 822 | + if(Update.canRollBack()) { |
| 823 | + DEBUG_PRINTLN(F("Swapping boot partition...")); |
| 824 | + Update.rollBack(); // swap boot partition |
| 825 | + } |
| 826 | + bl_actiontracker = BOOTLOOP_ACTION_DUMP; // out of options |
| 827 | + } |
| 828 | + #endif |
| 829 | + else |
| 830 | + dumpFilesToSerial(); |
| 831 | + ESP.restart(); // restart cleanly and don't wait for another crash |
| 832 | +} |
| 833 | + |
709 | 834 | /* |
710 | 835 | * Fixed point integer based Perlin noise functions by @dedehai |
711 | 836 | * Note: optimized for speed and to mimic fastled inoise functions, not for accuracy or best randomness |
|
0 commit comments