From d943b3df5ac163c32f1ccc6d734eea3a34665c4c Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Thu, 23 Apr 2026 14:03:57 -0700 Subject: [PATCH] feat(firmware): log reason before FATAL hang loops Two FATAL while(true) hangs in main.cpp (config load fail, camera init fail) previously relied on the hardware watchdog to reboot the device, leaving the cause invisible beyond a generic TWDT reset reason. Now each path logs EVT_REBOOT with REBOOT_FATAL_CONFIG or REBOOT_FATAL_CAMERA before hanging, so the next heartbeat's recent_events surfaces which branch hung. Server-side decoder updated for the two new enum values. Co-Authored-By: Claude Opus 4.7 (1M context) --- firmware/lib/event_log/event_log.h | 2 ++ firmware/src/main.cpp | 2 ++ server/heartbeat_diagnostics_stub.py | 2 ++ 3 files changed, 6 insertions(+) diff --git a/firmware/lib/event_log/event_log.h b/firmware/lib/event_log/event_log.h index 470f45a..6f68978 100644 --- a/firmware/lib/event_log/event_log.h +++ b/firmware/lib/event_log/event_log.h @@ -19,6 +19,8 @@ enum RebootReason : uint8_t { REBOOT_FACTORY_RESET = 2, REBOOT_OTA = 3, REBOOT_WIFI_REPROV = 4, + REBOOT_FATAL_CONFIG = 5, + REBOOT_FATAL_CAMERA = 6, }; struct EventLogEntry { diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 9767a87..6f5576c 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -169,6 +169,7 @@ void setup() { if (!config_load(g_cfg)) { Serial.println("FATAL: device_id/location_id/hmac_secret not provisioned"); + event_log_write(EVT_REBOOT, REBOOT_FATAL_CONFIG, 0); while (true) { delay(500); led_set(!digitalRead(LED_PIN)); } // fast blink } @@ -203,6 +204,7 @@ void setup() { if (!camera_init()) { Serial.println("FATAL: camera init failed"); + event_log_write(EVT_REBOOT, REBOOT_FATAL_CAMERA, 0); while (true) delay(1000); } diff --git a/server/heartbeat_diagnostics_stub.py b/server/heartbeat_diagnostics_stub.py index ea2a714..30e7943 100644 --- a/server/heartbeat_diagnostics_stub.py +++ b/server/heartbeat_diagnostics_stub.py @@ -122,4 +122,6 @@ REBOOT_REASON_DECODER = { 2: "FACTORY_RESET", 3: "OTA", 4: "WIFI_REPROV", + 5: "FATAL_CONFIG", + 6: "FATAL_CAMERA", }