Server: lifetime stats counters in a collapsed web UI section
Build and push server image / build-and-push (push) Successful in 34s
Build and push server image / build-and-push (push) Successful in 34s
New FrameStats (first_seen, device_wakes, photos_displayed, photos_removed, battery_reports, recharge_cycles, ota_updates_applied, config_saves), persisted alongside everything else in config.json. Incremented at the existing route/photo_queue.py call sites that already own each event -- no new instrumentation plumbing, no behavior depends on these, purely informational. GET /api/stats serves them; the web UI renders them into a native <details> "Stats" card (collapsed by default, no JS needed for the expand/collapse), fetched once on page load like the battery-history chart. Verified: TestClient run through /frame/config (wakes + first_seen + OTA-applied detection), /frame/battery (reports + recharge detection), /api/config (saves); direct photo_queue.py unit checks for advance/back/remove covering the "did the current photo actually change" distinction (removing a queued-but-not-current photo bumps photos_removed but not photos_displayed).
This commit is contained in:
@@ -198,7 +198,12 @@ def frame_config(request: Request):
|
||||
with config.locked():
|
||||
cfg = config.load()
|
||||
cfg.last_seen = time.time()
|
||||
if cfg.stats.first_seen == 0:
|
||||
cfg.stats.first_seen = cfg.last_seen
|
||||
cfg.stats.device_wakes += 1
|
||||
if reported_version:
|
||||
if cfg.device_firmware_version and reported_version != cfg.device_firmware_version:
|
||||
cfg.stats.ota_updates_applied += 1
|
||||
cfg.device_firmware_version = reported_version
|
||||
config.save(cfg)
|
||||
return {
|
||||
@@ -283,10 +288,16 @@ def api_config_save(
|
||||
cfg.quiet_hours_end = quiet_hours_end
|
||||
if timezone in ALL_TIMEZONES:
|
||||
cfg.timezone = timezone
|
||||
cfg.stats.config_saves += 1
|
||||
config.save(cfg)
|
||||
return {"status": "saved"}
|
||||
|
||||
|
||||
@app.get("/api/stats", dependencies=[Depends(require_access_token)])
|
||||
def api_stats():
|
||||
return config.load().stats.model_dump()
|
||||
|
||||
|
||||
def _require_configured(cfg: config.FrameConfig) -> None:
|
||||
if not cfg.immich_url or not cfg.immich_api_key:
|
||||
raise HTTPException(400, "Immich URL/API key not configured yet")
|
||||
@@ -404,11 +415,13 @@ def frame_battery(body: BatteryReport):
|
||||
now = time.time()
|
||||
with config.locked():
|
||||
cfg = config.load()
|
||||
cfg.stats.battery_reports += 1
|
||||
if cfg.battery_history and body.percent >= cfg.battery_history[-1][1] + RECHARGE_JUMP_PCT:
|
||||
# Percent jumped up meaningfully -- the battery was recharged
|
||||
# (or swapped). Start a fresh discharge cycle so runtime and
|
||||
# discharge-rate estimates never span a charge.
|
||||
cfg.battery_history = []
|
||||
cfg.stats.recharge_cycles += 1
|
||||
cfg.battery_history.append([now, body.percent])
|
||||
cfg.battery_history = cfg.battery_history[-BATTERY_HISTORY_MAX:]
|
||||
cfg.battery_log.append([now, body.percent])
|
||||
|
||||
Reference in New Issue
Block a user