From 7b546d0ed7ecd86fd16cdbc0af53fe54631be88f Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Thu, 23 Apr 2026 13:49:05 -0700 Subject: [PATCH] feat(firmware): enable task watchdog on camera/reporter/loop tasks 30s TWDT subscribes all three long-running tasks and panics on hang. The reporter task's retry loop explicitly feeds between attempts so the 3-try sequence (worst case 52s) does not itself trip the dog. Reset reason on next boot is visible via esp_reset_reason() which EVT_BOOT already logs. --- firmware/src/main.cpp | 12 ++++++++++++ firmware/src/reporter.cpp | 9 ++++++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/firmware/src/main.cpp b/firmware/src/main.cpp index 4cb595d..b3eea6c 100644 --- a/firmware/src/main.cpp +++ b/firmware/src/main.cpp @@ -11,6 +11,7 @@ #include "event_log.h" #include "net_guard.h" #include +#include // LED on GPIO2 (TimerCamera-F built-in LED) — verify against board schematic // Factory reset: hold GPIO37 (BOOT button) for 5 seconds @@ -53,6 +54,7 @@ static void check_factory_reset() { ESP.restart(); } delay(50); + esp_task_wdt_reset(); } } @@ -60,6 +62,7 @@ static void check_factory_reset() { static void task_camera(void*) { static uint8_t frame[CV_PIXELS]; // static: avoids 9KB on task stack int last_logged_track_id = 0; // diagnostic: log each new track once + esp_task_wdt_add(nullptr); while (true) { if (camera_capture_96(frame)) { if (xSemaphoreTake(s_cv_mutex, pdMS_TO_TICKS(100)) == pdTRUE) { @@ -86,15 +89,18 @@ static void task_camera(void*) { } } vTaskDelay(pdMS_TO_TICKS(CAM_INTERVAL_MS)); + esp_task_wdt_reset(); } } // Hourly reporter task — runs on core 0 static void task_reporter(void*) { uint32_t last_report_ts = 0; // 0 = not initialized yet + esp_task_wdt_add(nullptr); while (true) { vTaskDelay(pdMS_TO_TICKS(10000)); // check every 10s + esp_task_wdt_reset(); uint32_t now = (uint32_t)(time(nullptr)); if (now < 1700000000UL) continue; // NTP not synced @@ -203,11 +209,17 @@ void setup() { s_cv_mutex = xSemaphoreCreateMutex(); + // Task watchdog: 30s timeout, panic on trigger so we reboot and log + // via esp_reset_reason() in EVT_BOOT on the next boot. + esp_task_wdt_init(30, /*panic=*/true); + esp_task_wdt_add(nullptr); // subscribe the Arduino loopTask + xTaskCreatePinnedToCore(task_camera, "cam", 8192, nullptr, 2, nullptr, 1); xTaskCreatePinnedToCore(task_reporter, "rep", 8192, nullptr, 1, nullptr, 0); } void loop() { + esp_task_wdt_reset(); ArduinoOTA.handle(); check_factory_reset(); net_guard_tick(); diff --git a/firmware/src/reporter.cpp b/firmware/src/reporter.cpp index a980a13..8880f4c 100644 --- a/firmware/src/reporter.cpp +++ b/firmware/src/reporter.cpp @@ -8,6 +8,7 @@ #include #include #include +#include static std::vector s_cam_buf; static std::vector s_ble_buf; @@ -53,12 +54,14 @@ static bool post_json_once(const DeviceConfig& cfg, const char* path, const Stri } static bool post_json(const DeviceConfig& cfg, const char* path, const String& body) { - // 3 attempts with 0/2s/5s delays. Total worst case ~ 2x(5+10)s + 7s = 37s; - // acceptable for an hourly task and within the 30s TWDT once Task 6 lands - // IF TWDT is fed between attempts (see Task 6). + // 3 attempts. Worst case per call: 3 × (5s connect + 10s response) + 0 + 2 + 5 = 52s. + // TWDT is fed before the backoff delay and before each attempt so the 30s + // timeout doesn't fire mid-sequence. static const uint16_t DELAYS_MS[] = { 0, 2000, 5000 }; for (int i = 0; i < 3; i++) { + esp_task_wdt_reset(); if (DELAYS_MS[i]) vTaskDelay(pdMS_TO_TICKS(DELAYS_MS[i])); + esp_task_wdt_reset(); if (post_json_once(cfg, path, body)) return true; } return false;