Commit Graph

12 Commits

Author SHA1 Message Date
9232766e60 feat(firmware): add NVS-backed event log ring buffer
Persistent 32-slot ring buffer of tagged diagnostic events (boot, wifi
up/down, http ok/fail, heartbeat miss, reboot). Used to diagnose field
failures post-hoc via the heartbeat payload, without needing serial
access. Native-native stub lets policy be unit-tested.
2026-04-23 13:06:38 -07:00
a37207b6ff feat: event-based walker detector tuned to real 7' overhead mount
Replace per-track line-crossing counter with a single event state machine
gated by foreground pixel count (ENTER=250, EXIT=150) and finalized by
quiet-exit or timeout. Direction inferred from centroid excursion
(up_score vs down_score) on quiet-exit fires, and from net displacement
(last_c vs first_c) on timeout fires.

Tuning reflects bench data at the intended 7' overhead mount: walkers
produce smaller centroid excursions than originally modelled, so
EXTENT gates, MIN_TRAJ, MAX_FRAMES and REFRACTORY were all relaxed from
their initial guesses. Constants and rationale live in firmware/lib/cv/cv.h.

Bench results (8 isolated walks, 4 entries + 4 exits):
  * Event detection: 8/8 (100%)
  * Aggregate entries+exits split: 4+4 (matches)
  * Per-walk direction labelling: 4/8 (~50%)

Document explicitly that per-walk direction is unreliable at this mount
and that downstream analytics should trust only gross traffic
(entries + exits). Recovering direction would require a physical mount
change or a richer signal; both are out of scope for v1.

Tooling:
  * tools/replay_logs.py — replay event state machine against captured
    [F] diagnostic lines, for offline tuning without flash-test loops.
  * firmware/src/main_capture.cpp + tools/capture_frames.py +
    tools/replay_frames.py — raw-frame capture firmware and Python port
    of the detector, kept in tree for future iteration even though the
    TimerCamera-F serial driver stripped specific byte ranges in testing
    and log-based replay became the working path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 16:03:36 -07:00
3b471992f2 feat(cv): directional once-per-track counting + detection LED blinks
A single person walking under the overhead camera was generating both an
entry and an exit within a few seconds — the line-crossing logic treated
a blob's traversal into one side of the frame and out the other as two
separate events whenever the track spawned near the line, oscillated
against shadows, or churned at creation.

Replaced line-crossing semantics with directional traversal:
- Each track records spawn_y at creation and a counted flag.
- An event fires only if the track is not yet counted, spawned firm on
  one side of the line (|spawn_y - line_y| > CV_TRAVERSAL_MARGIN_PX),
  and is now firm on the opposite side. Direction of travel determines
  entry vs exit. The track is then flagged counted — one trip, one count.
- Cooldown remains as a secondary safety net.

main.cpp: single/double LED pulse on entry/exit detections. Saves and
restores the current LED state so upload (yellow-on) and no-WiFi
indicators aren't clobbered.

Tests updated to walk blobs beyond the margin and register two new
cases: wobble-at-line doesn't count, and a reversed full traversal
doesn't double-count on the same track.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 09:46:59 -07:00
62931e26ff fix(cv): add per-direction crossing cooldown to suppress track-churn double-counts
When a blob briefly drops below CV_MIN_BLOB_PX, its track is killed and respawns,
causing the same person to generate multiple counts per visit (~50/min observed
in field). Add a per-direction cooldown (default 5 frames ≈ 0.8s @ 5 fps) that
drops subsequent entries (or exits) within the window of the last counted one.
Entry and exit cooldowns are tracked independently.

Fixed at compile time for now; exposing as a server-push tunable is deferred
until the server-push-config branch lands. See docs/server-prompt-crossing-
cooldown.md for the server-side coordination notes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-17 06:33:11 -07:00
135eb3b46c fix: HMAC format — match server POST\npath\ntimestamp\nsha256(body) scheme
- hmac_sign now takes method+path instead of device_id; builds message as
  method\npath\ntimestamp\nhex(sha256(body)) per server verify_device_hmac
- reporter: header renamed X-HMAC-Signature → X-Signature; passes "POST"+path
- test vector regenerated against new message format; timestamp-diff test updated
- .size() → .length() throughout (Arduino String has no size())

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 10:47:13 -07:00
0a6470a096 feat: CV line-crossing entry/exit detection with tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 15:10:01 -07:00
b664753596 feat: CV blob detection and centroid tracking
Add BFS flood-fill blob extraction, centroid finding, and nearest-neighbour track matching/spawning inside cv_process. Add test verifying a new blob spawns a track.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:39:23 -07:00
136b22bc1b fix: cv_init — replace memset with value-init to avoid UB on std::vector
Also fix stale path comment in test/test_cv/test_cv.cpp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:33:12 -07:00
e6843584cf feat: CV module — frame diff + threshold (blob tracking TODO)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:26:34 -07:00
7662fc4c25 fix: HMAC module — mbedTLS error handling, hex guard, test docs
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:24:48 -07:00
47f3f6afef feat: HMAC-SHA256 signing module with native tests
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-13 14:20:24 -07:00
6c46ea26ab chore: init PlatformIO project for TimerCamera-F 2026-04-13 13:31:38 -07:00