Public Access
Add ESP32 recovery app with WiFi setup portal
- New flash layout (partitions.csv): factory = recovery app, ota_0/ota_1 = clock firmware; esp32_usb uploads to ota_0 so it never overwrites it - src/recovery: WiFi + OTA + web page only. Opens the password protected 'FlipDot-Setup' AP with a captive portal when WiFi fails, lets you pick another WiFi (stored in NVS, used by both apps), start the clock or stay - Recovery is started when the clock firmware can't connect to WiFi, crashes 3 times, after 3 quick power cycles, from a new Home Assistant button, or by the bootloader when no clock firmware works - Shared NVS store and boot switching in src/esp32/system - Clock firmware keeps working on the old flash layout (falls back to OTA-only safe mode) - README: recovery app, one-time USB installation; tests: 65 total Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ddbdf7e65f
commit
4749f9e596
25 files changed
+979
-29
No files matched your search
@@ -6,6 +6,8 @@
|
||||
#include <esp_task_wdt.h>
|
||||
|
||||
#include "config.h"
|
||||
#include "system/boot_target.h"
|
||||
#include "system/system_store.h"
|
||||
|
||||
// Survives crashes and software resets, not a power loss
|
||||
static const uint32_t RTC_MAGIC = 0xF11D0C0A;
|
||||
@@ -16,7 +18,8 @@ static uint32_t main_start = 0; // millis() when the main code started
|
||||
static bool watchdog_running = false;
|
||||
static bool firmware_pending = false; // new OTA firmware, not yet marked as good
|
||||
static bool crash_count_cleared = false;
|
||||
static char status[64];
|
||||
static bool power_on_count_cleared = false;
|
||||
static char status[80];
|
||||
|
||||
// The Arduino core marks a new OTA firmware as good right at boot. Returning true here
|
||||
// leaves that to us, so the bootloader can roll back a firmware that fails early.
|
||||
@@ -70,16 +73,40 @@ static const char *firmwareState()
|
||||
|
||||
static void updateStatus()
|
||||
{
|
||||
snprintf(status, sizeof(status), "%s, %s, %u crashes", FIRMWARE_VERSION, firmwareState(),
|
||||
rtc_magic == RTC_MAGIC ? rtc_crash_count : 0);
|
||||
snprintf(status, sizeof(status), "%s, %s, %u crashes, %s", FIRMWARE_VERSION, firmwareState(),
|
||||
rtc_magic == RTC_MAGIC ? rtc_crash_count : 0,
|
||||
recoveryAvailable() ? "recovery ok" : "no recovery app");
|
||||
}
|
||||
|
||||
// Switching the power on and off quickly a few times starts the recovery app. Checked
|
||||
// before anything else, so it works even when the rest of this firmware is broken.
|
||||
static void checkPowerCycles(ResetKind reset)
|
||||
{
|
||||
if (reset != ResetKind::PowerOn)
|
||||
return;
|
||||
uint8_t count = loadPowerOnCount() + 1;
|
||||
if (powerCyclesRequestRecovery(count) && recoveryAvailable())
|
||||
{
|
||||
savePowerOnCount(0);
|
||||
rebootToRecovery(RecoveryReason::PowerCycle);
|
||||
}
|
||||
savePowerOnCount(count);
|
||||
}
|
||||
|
||||
BootMode firmwareGuardBegin()
|
||||
{
|
||||
ResetKind reset = resetKind();
|
||||
checkPowerCycles(reset);
|
||||
|
||||
uint8_t count = rtc_magic == RTC_MAGIC ? rtc_crash_count : 0;
|
||||
BootDecision decision = decideBoot(resetKind(), count);
|
||||
BootDecision decision = decideBoot(reset, count);
|
||||
setCrashCount(decision.crash_count);
|
||||
|
||||
// Tells the recovery app which slot to start and which version is in it
|
||||
const char *label = runningPartitionLabel();
|
||||
if (strncmp(label, "ota_", 4) == 0)
|
||||
saveMainSlot(label, FIRMWARE_VERSION);
|
||||
|
||||
esp_ota_img_states_t state;
|
||||
firmware_pending = esp_ota_get_state_partition(esp_ota_get_running_partition(), &state) == ESP_OK &&
|
||||
state == ESP_OTA_IMG_PENDING_VERIFY;
|
||||
@@ -126,6 +153,13 @@ void firmwareGuardLoop(bool wifi_connected)
|
||||
Serial.println("New firmware marked as good");
|
||||
}
|
||||
|
||||
// Running long enough after a power on: it was not a quick power cycle
|
||||
if (!power_on_count_cleared && millis() >= POWER_CYCLE_WINDOW)
|
||||
{
|
||||
savePowerOnCount(0);
|
||||
power_on_count_cleared = true;
|
||||
}
|
||||
|
||||
if (!crash_count_cleared && shouldClearCrashCount(running))
|
||||
{
|
||||
setCrashCount(0);
|
||||
|
||||
Reference in new issue
Block a user