#include "wake_log.h" #include #include #include #include "time_utils.h" static const char* LOG_PATH = "/wake_log.csv"; static const char* NVS_LOG_NS = "wake_log"; static constexpr size_t MAX_LOG_SIZE = 200 * 1024; // 200KB cap static constexpr size_t KEEP_SIZE = 100 * 1024; // Keep last 100KB on truncation static bool _logReady = false; static uint32_t _bootCount = 0; static bool _rtcValidAtBoot = false; static const char* resultStr(WakeLogResult r) { switch (r) { case WakeLogResult::Ok: return "ok"; case WakeLogResult::FailFetch: return "fail_fetch"; case WakeLogResult::FailDownload: return "fail_download"; case WakeLogResult::FailProcess: return "fail_process"; default: { WakeLogResult unreachable = r; (void)unreachable; return "unknown"; } } } void wakeLogBegin() { // Record RTC validity before anything else might change the clock _rtcValidAtBoot = hasValidTime(); // Mount LittleFS (idempotent — safe to call if already mounted by web server) if (!LittleFS.begin(true)) { Serial.println("[wake_log] LittleFS mount failed — logging disabled"); _logReady = false; return; } // Increment boot counter in NVS (fire-and-forget) Preferences prefs; if (prefs.begin(NVS_LOG_NS, false)) { _bootCount = prefs.getUInt("boot_cnt", 0) + 1; prefs.putUInt("boot_cnt", _bootCount); prefs.end(); } else { Serial.println("[wake_log] NVS open failed — using boot_count=0"); _bootCount = 0; } _logReady = true; Serial.printf("[wake_log] Ready (boot #%u, rtc_valid=%d)\n", _bootCount, _rtcValidAtBoot ? 1 : 0); } // Truncate log file to keep only the last KEEP_SIZE bytes. // Uses PSRAM buffer. Skips silently on failure. static void truncateIfNeeded() { File f = LittleFS.open(LOG_PATH, "r"); if (!f) return; size_t fileSize = f.size(); if (fileSize <= MAX_LOG_SIZE) { f.close(); return; } Serial.printf("[wake_log] Log at %uKB — truncating to last %uKB\n", (unsigned)(fileSize / 1024), (unsigned)(KEEP_SIZE / 1024)); // Allocate buffer in PSRAM uint8_t* buf = (uint8_t*)ps_malloc(KEEP_SIZE); if (!buf) { Serial.println("[wake_log] PSRAM alloc failed — skipping truncation"); f.close(); return; } // Seek to (fileSize - KEEP_SIZE) and read the tail f.seek(fileSize - KEEP_SIZE); size_t bytesRead = f.read(buf, KEEP_SIZE); f.close(); if (bytesRead == 0) { free(buf); return; } // Find first newline to avoid a partial line at the start size_t start = 0; for (size_t i = 0; i < bytesRead; i++) { if (buf[i] == '\n') { start = i + 1; break; } } // Rewrite the file with just the tail File out = LittleFS.open(LOG_PATH, "w"); if (!out) { Serial.println("[wake_log] File rewrite failed — skipping truncation"); free(buf); return; } out.write(buf + start, bytesRead - start); out.close(); free(buf); Serial.printf("[wake_log] Truncated to %uKB\n", (unsigned)((bytesRead - start) / 1024)); } void wakeLogAppend(uint8_t battPct, int wifiRssi, uint32_t wifiMs, const String& assetId, const String& photoDate, const String& filter, WakeLogResult result, bool isDeepSleep) { if (!_logReady) return; // Read error tracking from NVS (defaults on failure) uint32_t errorsTotal = 0; String lastErrorTs = ""; Preferences prefs; if (prefs.begin(NVS_LOG_NS, false)) { errorsTotal = prefs.getUInt("err_total", 0); lastErrorTs = prefs.getString("err_last_ts", ""); // Update error counters if this is a failure if (result != WakeLogResult::Ok) { errorsTotal++; lastErrorTs = hasValidTime() ? isoNow() : "unknown"; prefs.putUInt("err_total", errorsTotal); prefs.putString("err_last_ts", lastErrorTs); } prefs.end(); } else { Serial.println("[wake_log] NVS read failed — using defaults"); } // Build CSV line uint32_t cycleMs = millis(); uint32_t freePsramKb = (uint32_t)(heap_caps_get_free_size(MALLOC_CAP_SPIRAM) / 1024); uint32_t freeHeapKb = (uint32_t)(heap_caps_get_free_size(MALLOC_CAP_INTERNAL) / 1024); String timestamp = hasValidTime() ? isoNow() : "no_clock"; char line[512]; snprintf(line, sizeof(line), "%s,%u,%u,%u,%d,%u,%u,%d,%u,%s,%s,%s,%s,%s,%s,%u\n", timestamp.c_str(), (unsigned)battPct, (unsigned)freePsramKb, (unsigned)freeHeapKb, wifiRssi, (unsigned)wifiMs, (unsigned)cycleMs, _rtcValidAtBoot ? 1 : 0, (unsigned)_bootCount, filter.c_str(), assetId.c_str(), photoDate.c_str(), resultStr(result), isDeepSleep ? "sleep" : "normal", lastErrorTs.c_str(), (unsigned)errorsTotal); // Append to log file File f = LittleFS.open(LOG_PATH, "a"); if (!f) { Serial.println("[wake_log] File open failed — skipping log entry"); return; } f.print(line); f.close(); Serial.printf("[wake_log] Logged: %s %s (%s)\n", resultStr(result), assetId.c_str(), isDeepSleep ? "sleep" : "normal"); // Check if truncation is needed truncateIfNeeded(); }