fix(firmware): reboot on FATAL failures + emit NTP_SYNC + server-coord warning

- Config-load and camera-init FATAL branches now reboot (3s LED signal
  before restart) instead of hanging forever. Matches the enum name
  REBOOT_FATAL_* and makes camera-init failures diagnosable via the
  next boot's heartbeat recent_events. Config failures produce a
  visible reboot loop rather than a silent hang.
- Emit EVT_NTP_SYNC(seconds_since_boot) on the first NTP-synced
  reporter iteration so slow / failed NTP sync is a visible signal in
  the heartbeat's recent_events window.
- README "Deploying firmware 1.1" now opens with a "Before you flash"
  warning directing the operator to land server-side heartbeat
  schema changes first (migration 005 + stub integration) to avoid a
  strict-schema 4xx reboot loop after deployment.
This commit is contained in:
2026-04-23 14:10:32 -07:00
parent d943b3df5a
commit a795cfa0ad
2 changed files with 34 additions and 2 deletions

View File

@@ -107,6 +107,7 @@ static void task_reporter(void*) {
// First valid timestamp — schedule boot report 60s from now
if (last_report_ts == 0) {
event_log_write(EVT_NTP_SYNC, (uint16_t)(millis() / 1000), 0);
last_report_ts = now - (REPORT_INTERVAL_S - BOOT_REPORT_DELAY_S);
continue;
}
@@ -170,7 +171,14 @@ 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
// Blink fast for 3s so a physically-present operator can see it,
// then reboot so EVT_BOOT history on the next heartbeat surfaces
// the failure — though in this case the device can't heartbeat
// without config, so the real signal is the fast-blink-then-reboot
// cycle visible on the LED.
uint32_t t0 = millis();
while (millis() - t0 < 3000) { led_set(!digitalRead(LED_PIN)); delay(100); }
ESP.restart();
}
// Connect to WiFi
@@ -205,7 +213,9 @@ void setup() {
if (!camera_init()) {
Serial.println("FATAL: camera init failed");
event_log_write(EVT_REBOOT, REBOOT_FATAL_CAMERA, 0);
while (true) delay(1000);
uint32_t t0 = millis();
while (millis() - t0 < 3000) { led_set(!digitalRead(LED_PIN)); delay(100); }
ESP.restart();
}
reporter_init();