Server: permanent battery history log + graph in the web UI
Build and push server image / build-and-push (push) Successful in 35s

battery_history stays cycle-scoped (reset on recharge, feeds the "on
battery for"/estimate numbers), but nothing kept a permanent record --
added battery_log, appended on every report and never reset, capped at
~2 years of hourly reports as a sanity bound rather than a real limit.
New GET /api/battery-log serves it; the web UI draws it as a plain
canvas line chart (no chart library) under a new "Battery history"
section, loaded once on page load.

Also caught up server/README.md, which never documented the OTA
firmware endpoints or the /api/queue response's current "device" shape
from the earlier status-panel work.
This commit is contained in:
2026-07-20 23:21:20 -04:00
parent 594b0bd513
commit a7b6c6d77a
4 changed files with 128 additions and 8 deletions
+32 -8
View File
@@ -82,8 +82,14 @@ algorithm itself -- it just streams the response straight to the panel.
back -- it displaces the current photo onto the front of the upcoming
queue rather than discarding it. Same response shape as
`/frame/image`. Used by the device's back-photo button.
- `GET /frame/config` -- `{"refresh_interval_s": ...}`, polled by the frame
each wake alongside its reachability check
- `GET /frame/config` -- `{"refresh_interval_s": ..., "firmware_version": "1.2.3" | null}`,
polled by the frame each wake alongside its reachability check.
`firmware_version` is whatever's currently uploaded via
`POST /api/firmware` below (`null` if nothing's been uploaded) -- the
device compares it against its own running version
(`esp_app_get_description()->version`, sent as an `X-Frame-Version`
request header, stored as `device_firmware_version`) to decide whether
to OTA
- `GET /frame/photo-info` -- `{"asset_id": ..., "location_line1": ... |
null, "location_line2": ... | null, "taken_at": ... | null}` for the
current photo (same idempotent current-photo semantics as
@@ -109,13 +115,31 @@ algorithm itself -- it just streams the response straight to the panel.
`app/face_labels.py`); `count: 0` if none are named. Used by the
device manage button's escalated second menu level
- `POST /frame/battery` -- `{"percent": 0-100}`; the device's last
battery reading, stored with a timestamp. Only sent when the device
is actually running on battery (see `firmware/README.md`'s Battery
section) -- a frame on mains power never reports
battery reading, stored with a timestamp plus two histories: a
per-discharge-cycle one (reset whenever a report jumps up enough to
look like a recharge) feeding the "on battery for"/estimate numbers,
and a permanent, never-reset log (capped at `BATTERY_LOG_MAX`, ~2
years at hourly reports) feeding the web UI's battery graph. Only sent
when the device is actually running on battery (see
`firmware/README.md`'s Battery section) -- a frame on mains power
never reports
- `GET /api/battery-log` -- `{"log": [[timestamp, percent], ...]}`, the
full permanent battery history above; used by the web UI's "Battery
history" chart
- `POST /api/firmware` -- multipart upload (`file`) of a built
`espresso_frame.bin`. Parses the embedded `esp_app_desc_t` (rejects
anything that isn't a valid image for this project) and stores it as
the available firmware; devices pick it up via `GET /frame/config`
above on their next wake
- `GET /frame/firmware` -- streams back whatever was last uploaded via
`POST /api/firmware`, for the device's OTA fetch. 404 if nothing's
been uploaded yet
- `GET /api/queue` -- `{"current": {...} | null, "upcoming": [...],
"battery": {"percent": N, "as_of": ts} | null}`, each queue entry an
asset id + thumbnail URL; used by the config UI (which shows the
battery line under "Now displaying" when present)
"device": {"last_seen": ts | null, "overdue": bool,
"firmware_version": "1.2.3" | null, "firmware_available": "1.2.4" | null,
"battery": {"percent": N, "as_of": ts} | null, "on_battery_since": ts | null,
"battery_estimate_s": N | null}}`, each queue entry an asset id +
thumbnail URL; used by the config UI's "Device" panel
- `POST /api/queue/reorder` -- reorders the upcoming queue; body is
`{"queue": [asset_id, ...]}`. Tolerant of drift from the queue having
changed server-side since the client's last fetch (e.g. a top-up/trim)