2026-10-13 01:05:06 +02:00
|
|
|
#pragma once
|
|
|
|
|
|
|
|
|
|
#include <stdint.h>
|
|
|
|
|
|
|
|
|
|
// 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
|
2026-10-13 01:21:04 +02:00
|
|
|
// >= SAFE_MODE_CRASHES safe mode: the recovery app, or OTA
|
|
|
|
|
// only for SAFE_MODE_TIME without it
|
2026-10-13 01:05:06 +02:00
|
|
|
// Other (brownout, ...) normal start, counter kept
|
2026-10-13 01:21:04 +02:00
|
|
|
//
|
|
|
|
|
// The recovery app (src/recovery) uses the same file to decide when to start the clock
|
|
|
|
|
// firmware again.
|
2026-10-13 01:05:06 +02:00
|
|
|
|
|
|
|
|
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);
|
2026-10-13 01:21:04 +02:00
|
|
|
|
|
|
|
|
// 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);
|
2026-10-13 01:34:04 +02:00
|
|
|
// At most 14 characters, for the flipdot display
|
|
|
|
|
const char *recoveryReasonShort(RecoveryReason reason);
|
2026-10-13 01:21:04 +02:00
|
|
|
|
|
|
|
|
// 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);
|