Skip to content

plan 0017: SensorEgg pairing menu + live-data test page - #162

Merged
TheAngryRaven merged 6 commits into
BETAfrom
claude/sensoregg-pairing-menu
Aug 31, 2026
Merged

TheAngryRaven merged 6 commits into
BETAfrom
claude/sensoregg-pairing-menu

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

Summary

Adds the on-device pairing story the egg subsystem was missing (design record: docs/plans/0017-sensoregg-pairing-menu.md):

  • Egg main-menu row (appended after Camera — stock menus keep 6 rows, existing golden fixtures untouched) opening a camera-style dual-mode page: unpaired = live capture screen, paired = MAC + Back / Test / Unpair.
  • Window-gated pairing: a 2-minute capture window (kPairingTimeoutMs) forces the race-gated scanner on and bypasses the MAC filter; the first egg heard advertising its own pairing-window flag (egg-side long-press, payload flags bit0 — parsed since the v2 round but never consumed) is stored to the new sensoregg_mac setting. Persist-first (a failed SD write keeps the window open to retry), applied live; SENSOREGG_MAC demoted to fallback. Unpair = persist-first "" → accept-any (today's behavior).
  • EGG TEST bench page: latches the scanner on outside races and shows rf link tri-state (OK/--/HUNG), protocol version, EGT/CJ/AUX/battery, sequence + measured packet rate, live PAIR/FAULT flags, and the active MAC filter. First real consumer of sensoreggSequence().
  • Camera test page's egg soak line works on a desk again — it had silently read NA off-track since plan 0012 race-gated the scanner; entering the page now latches the egg bench mode too (stale comment fixed).
  • MAC helpers (parseMac/formatMac/macReverse/macIsWildcard/macAccepts) live in the host-tested sensoregg_protocol unit — the LSB-first byte reversal the scan callback depends on is finally under test, including a case pinning that a same-order compare rejects.
  • The scan callback stays protocol-blind: it additionally memcpys the advertiser address into a per-slot field of the existing double buffer (ready-flag-last preserved); the capture decision runs in the main-loop drain.
  • Fixes two stale CLAUDE.md subsystem-14 blocks that still described a PW-ADV-1-only 14-byte parser and hardcoded-MAC pairing.

Everything sits behind BIRDSEYE_ENABLE_SENSOREGG; the flag-0 build and the sim are byte-identical to before.

Type of change

  • Bug fix (no user-visible behavior change beyond the fix)
  • New feature / behavior
  • Refactor (no behavior change)
  • Tests only
  • CI / tooling / docs
  • Breaking change (track files, log format, BLE protocol, or a removed mode)

How it was verified

  • Host unit tests pass (ctest --test-dir tests/build) — including the new MAC-helper matrix (parse accept/reject, round-trip, reversal involution, wildcard semantics, LSB-first pin)
  • clang-tidy clean — left to CI
  • Compiles for the XIAO nRF52840 Sense — both flag arms locally (CI only builds flag-ON for BETA PRs; the OFF arm is where #if mistakes hide): ON 46% flash / 35% RAM, OFF 45% / 35%, no new warnings
  • Sim builds and golden hashes pass unchanged — the flag gate does not leak into stock renders (6/6 sim tests)
  • Tested on real hardware — pending owner bench pass: pair from the egg's 30 s window; re-pair to a second egg while paired; unpair → accept-any; desk soak on EGG TEST (~9 Hz at the egg's 111.875 ms adv interval); race pages unchanged; settings-write-failure path (SD removed) keeps the window open

Checklist

  • CHANGELOG.md updated under [Unreleased] (if user-visible)
  • ARCHITECTURE.md / CLAUDE.md updated (if a module or interface changed) — CLAUDE.md page-id section, subsystem 14, settings + tuning tables
  • New testable logic has a matching test in tests/
  • Branch is focused — refactors / behavior / tests are not mixed together

Related issues

Companion to the DovesSensorEgg repo's PerchWerks migration (its pw_adv_encode.h still claims the logger drops v2 frames — a one-line cleanup lands there separately).

🤖 Generated with Claude Code

https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg


Generated by Claude Code

claude added 6 commits August 30, 2026 23:21
parseMac/formatMac (settings-string codec), macReverse (human <->
ble_gap_addr_t LSB-first, aliasing-safe), macIsWildcard, macAccepts —
the byte-order reversal the scan callback depends on finally moves
under host test, with a case pinning that a same-order compare rejects.
kPairingTimeoutMs (120 s, camera parity) and kMacStrLen live with the
other tunables.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
Runtime MAC filter boots from the SENSOREGG_MAC fallback and loads
sensoregg_mac at SENSOREGG_SETUP(); the scan callback stays protocol-
blind (it additionally memcpys the advertiser address into a per-slot
field of the existing double buffer, ready-flag-last preserved) and
bypasses the MAC filter while the capture window is open. Capture runs
in the main-loop drain on the parsed pairingActive bit: persist first,
then the RAM filter, then close the window — a failed SD write retries
on the next frame. 120 s window timeout; bench latch (eggTestActive)
and the pairing window now force the race-gated scanner on; packet-rate
meter for the test page. Twelve new public accessors with no-op twins.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
Main-menu row 'Egg' (index 6, appended after Camera so nothing shifts),
PAGE_PAIR_EGG (-20) cloning the camera page's shape — paired =
Back/Test/Unpair with Back at index 0, unpaired = window-gated capture
status with B2 cancel and B1 reserved for future manual entry — and
PAGE_EGG_TEST (-21): rf tri-state + proto version, EGT/CJ/AUX/BAT,
sequence + measured packet rate, live PAIR/FAULT flags, active MAC
filter, Back. All five display_ui coupling points wired under the
build flag; the camera test page now latches the egg bench scan on
enter/exit, restoring the desk-soak behavior its comment promised
(stale since plan 0012's race gate — comment fixed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
Flag-0 sim never renders the Egg pages; stubs mirror the module's full
surface so a future flag-1 sim build links. Golden hashes verified
UNCHANGED — the gate does not leak into stock renders.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
…ables

Also fixes the two stale subsystem-14 blocks that still described a
PW-ADV-1-only 14-byte parser and hardcoded-MAC-only pairing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AcdBRTn52QpCQWkyVm5Lkg
@github-actions

Copy link
Copy Markdown

Coverage — host-testable units

📂 Overall coverage

Metric Coverage
Lines 🟢 2159/2192 (98.5%)
Functions 🟢 220/221 (99.5%)
Branches 🟢 1576/1746 (90.3%)

📄 File coverage

File Lines Functions Branches
BirdsEye/ble_stream.cpp 🟢 34/34 (100.0%) 🟢 8/8 (100.0%) 🟡 17/20 (85.0%)
BirdsEye/camera_fsm.cpp 🟢 238/246 (96.7%) 🟢 20/20 (100.0%) 🟡 142/160 (88.8%)
BirdsEye/course_creator.cpp 🟢 213/221 (96.4%) 🟢 21/21 (100.0%) 🟡 119/136 (87.5%)
BirdsEye/course_prune.cpp 🟢 37/37 (100.0%) 🟢 5/5 (100.0%) 🟢 47/50 (94.0%)
BirdsEye/crc32.cpp 🟢 30/30 (100.0%) 🟢 4/4 (100.0%) 🟢 24/24 (100.0%)
BirdsEye/crossing_pattern.cpp 🟢 15/15 (100.0%) 🟢 1/1 (100.0%) 🟢 12/12 (100.0%)
BirdsEye/dovex_header.cpp 🟢 106/107 (99.1%) 🟢 7/7 (100.0%) 🔴 62/88 (70.5%)
BirdsEye/drag_timer.cpp 🟢 150/154 (97.4%) 🟢 10/10 (100.0%) 🟡 76/94 (80.9%)
BirdsEye/drag_tree.cpp 🟢 112/114 (98.2%) 🟡 7/8 (87.5%) 🟢 87/94 (92.6%)
BirdsEye/filename_validator.cpp 🟢 14/14 (100.0%) 🟢 1/1 (100.0%) 🟢 30/30 (100.0%)
BirdsEye/gps_stats.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 8/8 (100.0%)
BirdsEye/gps_status_page.cpp 🟢 29/29 (100.0%) 🟢 4/4 (100.0%) 🟢 28/28 (100.0%)
BirdsEye/gps_time.cpp 🟢 45/45 (100.0%) 🟢 6/6 (100.0%) 🟢 30/32 (93.8%)
BirdsEye/gps_validation.cpp 🟢 24/24 (100.0%) 🟢 2/2 (100.0%) 🟢 66/66 (100.0%)
BirdsEye/haversine.cpp 🟢 8/8 (100.0%) 🟢 1/1 (100.0%) ⚫ 0/0 (0.0%)
BirdsEye/idle_policy.cpp 🟢 17/17 (100.0%) 🟢 2/2 (100.0%) 🟢 14/14 (100.0%)
BirdsEye/insta360_protocol.cpp 🟢 140/140 (100.0%) 🟢 16/16 (100.0%) 🟡 86/98 (87.8%)
BirdsEye/lap_format.cpp 🟢 18/18 (100.0%) 🟢 1/1 (100.0%) 🟢 9/9 (100.0%)
BirdsEye/led_animations.cpp 🟢 84/84 (100.0%) 🟢 7/7 (100.0%) 🟢 43/46 (93.5%)
BirdsEye/led_frame.cpp 🟢 21/21 (100.0%) 🟢 7/7 (100.0%) 🟢 6/6 (100.0%)
BirdsEye/led_modes.cpp 🟢 67/68 (98.5%) 🟢 6/6 (100.0%) 🟢 50/52 (96.2%)
BirdsEye/led_status.cpp 🟢 106/108 (98.1%) 🟢 11/11 (100.0%) 🟢 71/75 (94.7%)
BirdsEye/local_time.cpp 🟢 48/48 (100.0%) 🟢 6/6 (100.0%) 🟢 46/50 (92.0%)
BirdsEye/loop_profile.cpp 🟢 65/65 (100.0%) 🟢 7/7 (100.0%) 🟢 35/36 (97.2%)
BirdsEye/sat_bars.cpp 🟢 33/33 (100.0%) 🟢 2/2 (100.0%) 🟢 51/54 (94.4%)
BirdsEye/sd_access_policy.cpp 🟢 9/9 (100.0%) 🟢 3/3 (100.0%) 🟢 18/18 (100.0%)
BirdsEye/sd_format_page.cpp 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 25/26 (96.2%)
BirdsEye/sector_purple.cpp 🟢 84/85 (98.8%) 🟢 3/3 (100.0%) 🟡 57/64 (89.1%)
BirdsEye/sensoregg_protocol.cpp 🟢 87/88 (98.9%) 🟢 13/13 (100.0%) 🟢 74/76 (97.4%)
BirdsEye/setting_parse.cpp 🟢 29/30 (96.7%) 🟢 2/2 (100.0%) 🟢 38/42 (90.5%)
BirdsEye/sprint_select.cpp 🟢 25/25 (100.0%) 🟢 4/4 (100.0%) 🟢 46/48 (95.8%)
BirdsEye/tach_filter.cpp 🟢 91/91 (100.0%) 🟢 13/13 (100.0%) 🟡 72/82 (87.8%)
BirdsEye/track_json.cpp 🟢 116/120 (96.7%) 🟢 12/12 (100.0%) 🟡 67/88 (76.1%)
BirdsEye/wake_cause.cpp 🟢 14/14 (100.0%) 🟢 2/2 (100.0%) 🟢 20/20 (100.0%)

@TheAngryRaven
TheAngryRaven merged commit cc25436 into BETA Aug 31, 2026
9 checks passed
@TheAngryRaven TheAngryRaven mentioned this pull request Sep 27, 2026
18 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants