#pragma once #include // Decides how the ESP32 starts after a reset, so OTA keeps working even when the firmware // crashes. Pure logic, tested in test/test_esp32. // // Power on normal start, crash counter cleared // Software reset normal start (e.g. after an OTA update) // Crash (panic, watchdog) crash counter + 1, then: // < SAFE_MODE_CRASHES OTA-only for OTA_PANIC_WINDOW, then normal // >= SAFE_MODE_CRASHES safe mode: the recovery app, or OTA // only for SAFE_MODE_TIME without it // Other (brownout, ...) normal start, counter kept // // The recovery app (src/recovery) uses the same file to decide when to start the clock // firmware again. enum class ResetKind { PowerOn, Software, Crash, Other, }; enum class BootMode { Normal, PanicWindow, // OTA only for a short time, then normal SafeMode, // OTA only for a long time, then restart }; struct BootDecision { BootMode mode; uint8_t crash_count; }; BootDecision decideBoot(ResetKind reset, uint8_t crash_count); // Crash counter after safe mode ends: one more crash brings safe mode back uint8_t crashCountAfterSafeMode(); // A new OTA firmware is accepted (no rollback) once it ran this long with WiFi bool shouldMarkFirmwareValid(uint32_t running_ms, bool wifi_connected); // Running this long without a crash clears the crash counter bool shouldClearCrashCount(uint32_t running_ms); // Why the recovery app was started (stored in NVS, shared by both apps) enum class RecoveryReason : uint8_t { None = 0, // no firmware in ota_0/ota_1, or the bootloader fell back to recovery Wifi = 1, // the clock firmware couldn't connect to WiFi Crash = 2, // the clock firmware crashed SAFE_MODE_CRASHES times in a row Manual = 3, // Home Assistant button PowerCycle = 4, // switched on POWER_CYCLES_FOR_RECOVERY times quickly }; const char *recoveryReasonText(RecoveryReason reason); // At most 14 characters, for the flipdot display const char *recoveryReasonShort(RecoveryReason reason); // Power-on count: each power on within POWER_CYCLE_WINDOW of the previous one counts. // True when the next start should be the recovery app. bool powerCyclesRequestRecovery(uint8_t power_on_count); // Recovery app: time to start the clock firmware again? // Wifi reason: as soon as WiFi is connected again (but not before RECOVERY_MIN_TIME) // other reasons: after RECOVERY_TIME (so a manual visit has time for an OTA upload) // never when there is no clock firmware bool recoveryShouldStartMain(RecoveryReason reason, uint32_t running_ms, bool wifi_connected, bool main_available);