From bcb02b6d73d5c07b55438f0d248a3f30b6c269f0 Mon Sep 17 00:00:00 2001 From: Peter Woolery Date: Thu, 16 Apr 2026 17:43:00 -0700 Subject: [PATCH] docs(readme): document server-push config + roadmap entry for gated local portal --- README.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/README.md b/README.md index 1755822..8ef327d 100644 --- a/README.md +++ b/README.md @@ -84,6 +84,61 @@ Endpoint: `http://logs.research.bike` All requests are HMAC-SHA256 signed. See [design spec](docs/superpowers/specs/2026-04-13-door-counter-design.md) for full API shapes and auth scheme. +## Runtime Configuration + +The backend can push CV tuning parameters to individual devices in the response to `POST /api/v1/heartbeat`. No HTTP server runs on the device — updates ride the existing outbound, HMAC-authenticated channel. + +### Configurable fields + +| Field | Type / Range | Meaning | +|-------|--------------|---------| +| `cfg_version` | uint32, non-zero | Monotonic version; device ignores updates with version ≤ stored. | +| `diff_thresh` | 5–120 | Per-pixel motion threshold; higher = less sensitive. | +| `min_blob_px` | 16–4096 | Minimum connected foreground pixels to count as a blob; higher = fewer false positives from small motion. | +| `max_move` | 2.0–50.0 | Max inter-frame track displacement, in pixels on the 96×96 frame. | +| `max_missed` | 1–60 | Frames a track can be missed before dropped. | +| `line_offset` | 0–100 | Virtual counting line, as percent of frame height. | + +### Push flow + +The heartbeat response MAY include a `config` object. All fields except `cfg_version` are optional; missing fields retain the device's current value. + +```json +{ + "config": { + "cfg_version": 7, + "diff_thresh": 25, + "min_blob_px": 200, + "max_move": 12.0, + "max_missed": 8, + "line_offset": 55 + } +} +``` + +### Validation and apply rules + +- Missing response body, non-200, malformed JSON, or missing `config` object → silent no-op. +- Missing `cfg_version` → rejected, logged `[CFG] missing cfg_version`. +- `cfg_version` ≤ stored → rejected as stale, logged. +- Any present field with wrong JSON type → whole update rejected, logged `[CFG] rejected malformed config`. +- Any field out of range → whole update rejected, logged `[CFG] rejected invalid config`. +- Valid update → applied atomically under mutex, persisted to NVS, logged `[CFG] applied v=N`. + +### Persistence + +Tuning is stored in the `doorcounter` NVS namespace and survives reboot. On boot, the device loads the persisted values; if none present, compiled defaults apply. + +### Trust model + +The device enforces validation defensively, but the backend is the source of truth and SHOULD validate bounds before pushing. Integrity of pushed config rides on the HMAC trust boundary that already authenticates the backend. Updates are keyed per `device_id`, so operators can tune one customer without affecting others. + +No inbound HTTP surface is exposed on customer LANs by design — the device only makes outbound requests. + +## Roadmap + +**Gated local config portal.** Holding the BOOT button for ~3 seconds would raise a WiFiManager-style captive portal on the local network for ~5 minutes, exposing a tuning page for field techs operating without backend connectivity. Deferred because (a) the server-push mechanism above covers routine tuning, (b) an always-on HTTP server on customer LANs is an undesirable attack surface, and (c) the gated-by-physical-access model needs additional auth design to be safe. + ## Project Structure ```