feat: add deep sleep batch caching, wake log, and log viewer tools

- Batch-fetch asset IDs in deep sleep mode with NVS caching to reduce
  network calls and improve battery life (configurable batch size 1-50)
- Add wake_log module: persistent CSV log of each photo cycle surviving
  reboots and deep sleep, with download/clear via web UI
- Add browser-based log viewer and log comparison tools (tools/)
- Update .gitignore to exclude .cursor/, chat-summaries/, wake_*.csv,
  and .DS_Store

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-31 12:59:03 -04:00
parent 150ca56542
commit 7fa74c03c1
15 changed files with 1465 additions and 33 deletions

View File

@@ -273,6 +273,69 @@ String ImmichClient::fetchOneRandomAssetId(const RandomFetchFilter& filter) {
return "";
}
std::vector<String> ImmichClient::fetchFilteredBatch(const RandomFetchFilter& filter, int count) {
std::vector<String> results;
String url = buildUrl("/api/search/random");
JsonDocument reqDoc;
reqDoc["size"] = count;
reqDoc["type"] = "IMAGE";
if (!filter.albumIds.empty()) {
JsonArray arr = reqDoc["albumIds"].to<JsonArray>();
for (auto& id : filter.albumIds) arr.add(id);
}
if (filter.favoriteOnly) {
reqDoc["isFavorite"] = true;
}
if (filter.takenAfter.length() > 0) {
reqDoc["takenAfter"] = filter.takenAfter;
}
if (filter.takenBefore.length() > 0) {
reqDoc["takenBefore"] = filter.takenBefore;
}
String body;
serializeJson(reqDoc, body);
Serial.printf("[immich] fetchBatch(%d): %s\n", count, body.c_str());
String response = httpPost(url, body);
if (response.isEmpty()) {
Serial.println("[immich] fetchBatch: empty response");
return results;
}
JsonDocument doc;
DeserializationError err = deserializeJson(doc, response);
if (err) {
Serial.printf("[immich] fetchBatch JSON error: %s\n", err.c_str());
return results;
}
JsonArray arr = doc.as<JsonArray>();
for (JsonObject asset : arr) {
String id = asset["id"].as<String>();
if (id.length() == 0) continue;
// Skip RAW files
String filename = asset["originalFileName"] | "";
filename.toLowerCase();
if (filename.endsWith(".dng") || filename.endsWith(".raw") ||
filename.endsWith(".cr2") || filename.endsWith(".cr3") ||
filename.endsWith(".nef") || filename.endsWith(".arw") ||
filename.endsWith(".orf") || filename.endsWith(".rw2") ||
filename.endsWith(".raf") || filename.endsWith(".srw")) {
continue;
}
results.push_back(id);
}
Serial.printf("[immich] fetchBatch: got %d usable assets\n", (int)results.size());
return results;
}
AssetInfo ImmichClient::fetchAssetInfo(const String& assetId) {
AssetInfo info;
info.id = assetId;