feat(firmware): include diagnostics in heartbeat payload

Heartbeat v1.1.0 now carries heap stats (free + min_free since boot),
esp_reset_reason(), last WiFi disconnect code, and the last 8
persisted event-log entries. Makes field failures diagnosable
server-side without retrieving the device: the post-reboot heartbeat
will include EVT_BOOT with reset reason and whatever EVT_WIFI_DOWN
or EVT_HTTP_FAIL entries preceded it.
This commit is contained in:
2026-04-23 13:54:55 -07:00
parent f08f70a8fb
commit 5c9f5df0ce

View File

@@ -2,6 +2,7 @@
#include "reporter.h" #include "reporter.h"
#include "hmac.h" #include "hmac.h"
#include "event_log.h" #include "event_log.h"
#include "net_guard.h"
#include <HTTPClient.h> #include <HTTPClient.h>
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <WiFi.h> #include <WiFi.h>
@@ -9,6 +10,8 @@
#include <time.h> #include <time.h>
#include <freertos/semphr.h> #include <freertos/semphr.h>
#include <esp_task_wdt.h> #include <esp_task_wdt.h>
#include <esp_system.h>
#include <esp_heap_caps.h>
static std::vector<CameraHourlyRecord> s_cam_buf; static std::vector<CameraHourlyRecord> s_cam_buf;
static std::vector<BLEHourlyRecord> s_ble_buf; static std::vector<BLEHourlyRecord> s_ble_buf;
@@ -175,11 +178,31 @@ void reporter_submit_ble(const DeviceConfig& cfg, const BLEHourlyRecord& rec) {
bool reporter_heartbeat(const DeviceConfig& cfg, uint32_t uptime_s, int wifi_rssi) { bool reporter_heartbeat(const DeviceConfig& cfg, uint32_t uptime_s, int wifi_rssi) {
JsonDocument doc; JsonDocument doc;
doc["device_id"] = cfg.device_id; doc["device_id"] = cfg.device_id;
doc["firmware_version"] = "1.0.0"; doc["firmware_version"] = "1.1.0";
doc["free_storage_pct"] = 100; doc["free_storage_pct"] = 100;
doc["wifi_rssi"] = wifi_rssi; doc["wifi_rssi"] = wifi_rssi;
doc["pending_records"] = (int)(s_cam_buf.size() + s_ble_buf.size()); doc["pending_records"] = (int)(s_cam_buf.size() + s_ble_buf.size());
doc["uptime_seconds"] = uptime_s; doc["uptime_seconds"] = uptime_s;
// Diagnostics (new in 1.1.0)
doc["reset_reason"] = (int)esp_reset_reason();
doc["heap_free"] = (int)esp_get_free_heap_size();
doc["heap_min_free"] = (int)esp_get_minimum_free_heap_size();
doc["last_disconnect_code"] = (int)net_guard_last_disconnect_reason();
// Last 8 event-log entries, newest first
EventLogEntry recent[8];
size_t n = event_log_read_recent(recent, 8);
JsonArray evs = doc["recent_events"].to<JsonArray>();
for (size_t i = 0; i < n; i++) {
JsonObject e = evs.add<JsonObject>();
e["t"] = recent[i].tag;
e["d0"] = recent[i].data0;
e["d1"] = recent[i].data1;
e["ts"] = recent[i].ts_unix;
e["up"] = recent[i].uptime_s;
}
String body; serializeJson(doc, body); String body; serializeJson(doc, body);
return post_json(cfg, "/api/v1/heartbeat", body); return post_json(cfg, "/api/v1/heartbeat", body);
} }