Skip to content

plan 0002 (PR B): BLE sprint-track sync — TSLIST / TSGET / TSPUT / TSDEL - #120

Merged
TheAngryRaven merged 1 commit into
BETAfrom
claude/ble-sprint-track-sync
Aug 3, 2026
Merged

plan 0002 (PR B): BLE sprint-track sync — TSLIST / TSGET / TSPUT / TSDEL#120
TheAngryRaven merged 1 commit into
BETAfrom
claude/ble-sprint-track-sync

Conversation

@TheAngryRaven

Copy link
Copy Markdown
Owner

Summary

Phase 2, PR B of plan 0002. Sprint courses have lived in /TRACKS/SPRINT since #116, but every BLE track command spliced /TRACKS — so the new folder was only reachable by plugging in a USB cable. That makes sprint mode effectively unusable from the web app. This closes that gap.

Adds TS-prefixed twins of the four track verbs:

Command Behaviour
TSLIST TSFILE:name.json per file, then TSEND
TSGET:name.json identical to TGET: (SIZE:N → chunks → DONE)
TSPUT:name.json identical to TPUT: (TREADY → chunks → TDONETOK)
TSDEL:name.json identical to TDEL: (TOK / TERR:NO_FILE)

Two deliberate choices worth reviewing:

Shared implementations, not copies. The variants thread a kind through the existing handlers (trackFolderFor(), bleSendTrackList(kind), trackUploadKind / trackDeleteKind) instead of duplicating four command bodies. On an image sitting at ~99% of the OTA cap, a copy-paste would have cost real flash for zero behavioural difference.

The folder never comes off the wire. filename_validator stays strict — /, .. and FAT-unsafe bytes are still rejected on every track command — and which of the two folders a command targets is decided by the opcode alone. A client cannot path its way between them, which is why this didn't become "allow a SPRINT/ prefix in the filename".

Only TSLIST gets distinct reply tokens (TSFILE:/TSEND); the other three are 1:1 request/response where the client already knows what it asked for, so reusing the existing replies keeps the protocol (and the flash) smaller.

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)

Protocol addition only — every existing command keeps its exact wire behaviour, and circuit paths resolve byte-identically to before.

How it was verified

  • Host unit tests pass — unaffected; the security-relevant piece (filename_validator) is unchanged and still covered
  • clang-tidy clean — deferred to CI
  • Compiles for the XIAO nRF52840 Sense — deferred to CI, and this is the real check: bluetooth.ino is excluded from the simulator TU and has no host build, so nothing local compiles it
  • Tested on real hardware — needs a web-app or nRF Connect round-trip: TSPUT: a sprint course, TSLIST it back, confirm it lands in /TRACKS/SPRINT and that TLIST still shows only circuit tracks
  • Extra check I ran locally: extracted the new opcode-parsing and path-building logic verbatim into a standalone program and asserted all six verbs resolve correctly — TGET:OKC.json/TRACKS/OKC.json (unchanged), TSGET:AX.json/TRACKS/SPRINT/AX.json.

Checklist

  • CHANGELOG.md updated under [Unreleased] (if user-visible)
  • ARCHITECTURE.md / CLAUDE.md updated (if a module or interface changed) — subsystem 6 protocol list, incl. the folder-from-opcode rule
  • New testable logic has a matching test in tests/judgment call, flagging it: the added logic is dispatch glue inside an Arduino TU (a kind ternary and an opcode offset), not extractable pure logic worth its own unit on a flash-constrained image. The validation it depends on is already host-tested.
  • Branch is focused

Related issues

Plan 0002. Follows #116 (sprint core). Remaining: PR C, the on-device course creator — which likely needs the plan 0004 OTA rework first for flash headroom.

🤖 Generated with Claude Code

https://claude.ai/code/session_01HnTP6BdA9xjLR5hSWE9frb


Generated by Claude Code

Sprint courses live in /TRACKS/SPRINT since plan 0002, but the BLE track
commands all spliced /TRACKS - the new folder was only reachable over USB
mass storage, which makes sprint mode unusable from the web app.

Adds TS-prefixed twins of the four track verbs. They share the circuit
code paths through a kind parameter (trackFolderFor()) rather than
duplicating handlers, so the flash cost is a few hundred bytes on an
image already at the OTA ceiling. TSLIST answers with its own
TSFILE:/TSEND tokens so a sprint enumeration can't be mistaken for a
circuit one; TSGET/TSPUT/TSDEL reuse the existing replies.

Security posture is unchanged and deliberate: filename_validator still
rejects '/', '..' and FAT-unsafe bytes on every track command, and the
target folder is chosen by the OPCODE, never parsed from the wire - a
client cannot path between the two folders. Circuit paths resolve
byte-identically to before.

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

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Coverage — host-testable units

📂 Overall coverage

Metric Coverage
Lines 🟢 837/847 (98.8%)
Functions 🟢 89/89 (100.0%)
Branches 🟢 677/743 (91.1%)

📄 File coverage

File Lines Functions Branches
BirdsEye/camera_fsm.cpp 🟢 222/230 (96.5%) 🟢 20/20 (100.0%) 🟡 126/144 (87.5%)
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/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 🟢 25/25 (100.0%) 🟢 3/3 (100.0%) 🟢 24/24 (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/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/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/sensoregg_protocol.cpp 🟢 44/45 (97.8%) 🟢 7/7 (100.0%) 🟢 33/34 (97.1%)
BirdsEye/sprint_select.cpp 🟢 25/25 (100.0%) 🟢 4/4 (100.0%) 🟢 46/48 (95.8%)
BirdsEye/tach_filter.cpp 🟢 15/15 (100.0%) 🟢 3/3 (100.0%) 🟡 7/8 (87.5%)
BirdsEye/wake_cause.cpp 🟢 14/14 (100.0%) 🟢 2/2 (100.0%) 🟢 20/20 (100.0%)

@TheAngryRaven
TheAngryRaven merged commit 3cc4753 into BETA Aug 3, 2026
8 checks passed
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