Conversation
…odules Ported unchanged from jake-fishtech/openflight@feat/iOS-ble (b053194): the Bless GATT publisher and v1 frames, the SSE shot broker, phone orientation validation, the BLE doc and an advertising hardware test. shot_v1.json now lives in tests/fixtures. Co-authored-by: Jake Fishman <jake.fishtech@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
bless==0.3.0 is a Linux-only optional extra, and [tool.uv] environments limits resolution to Linux and macOS. setup.sh installs the extra on the Pi; start-kiosk.sh syncs it only when --ble is passed, and forwards --ble to the server like any other server flag. Co-authored-by: Jake Fishman <jake.fishtech@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds --ble, /api/shots/stream, /api/club and the IWR6843 phone orientation route. Only finalized shots are published; a failed Socket.IO emit no longer stops BLE, SSE or sim delivery. All club changes share one path and broadcast club_changed, even with no monitor. --ble without Bless says Bluetooth is unavailable. Co-authored-by: Jake Fishman <jake.fishtech@gmail.com> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2 shots add type, final, shot_number, profile, carry_range, spin_source,
enrichment and a uuid5 event_id so provisional and final shots upsert.
Adds v2 events and hello {client_schema_max}. v2 is UTF-8 so profile lists
fit one BLE message; v1 is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BlueZ notifies every subscriber and Bless 0.3.0 hides the writer, so v2 cannot be per-central. The publisher adds a v2 shot/control pair beside the unchanged v1 pair and gates delivery on per-characteristic subscriptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v2 phones get a provisional and a final shot with one event_id and an enrichment status; v1 phones are unchanged. Processing, power, profile and session events fan out to BLE and SSE v2. BLE v2 commands reuse the Socket.IO handlers. SSE opts in with ?schema=2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tests/ble_harness.py runs the real publisher against a fake Bless server with virtual centrals, so BLE is tested end to end without a Pi. scripts/ble/generate_goldens.py writes framed hex goldens that client suites share; a test fails when they drift. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
apply_club_selection writes the module-global active_club and fans out over Socket.IO, SSE and BLE; two tests leaked "pw" and "3-wood" into later tests. An autouse fixture now monkeypatches all four. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Documents schema v2 (why a second characteristic pair), its payloads and commands, get_club and hello, the SSE opt-in and testing without hardware; removes ios/ paths and adds the page to the nav and changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BLE is unauthenticated, so clear_session and delete_shot are refused over Bluetooth and stay on Socket.IO. A Socket.IO delete_shot now notifies shot_deleted to BLE v2 and SSE ?schema=2 clients. Goldens regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Removes clear_session and delete_shot from the v2 command table, adds the shot_deleted event and SSE name, updates the hello example, and replaces the security note: BLE is unauthenticated, so v2 over Bluetooth only reads state and selects club or profile; deleting data and editing profiles require Wi-Fi. Changelog entry updated to match. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Maybe rename the "WiFi" part to "over network", just because it's indipendat on what transport layer you use your pi to connect to a network - could be over Ethernet or any m other medium, aslong you have a ip-connection. We are not using AP mode deliberately. |
Review feedback on open-flight#282: the HTTP/SSE/Socket.IO path works over any IP link (Wi-Fi, Ethernet, ...), and OpenFlight deliberately does not use AP mode, so "Wi-Fi" was misleading. Reword docs, comments, log strings and test names; BLE golden fixtures are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks @HuggeK, good point. Renamed it throughout in 64649fe: the title and description, docs/ios-ble.md, the changelog, server comments and log strings, and the test names now say "network" for the HTTP/SSE/Socket.IO path. The docs say it works over any IP link (Wi-Fi, Ethernet, …), and nothing assumes AP mode. The one place "Wi-Fi" stays is where the docs name the companion app's option label. |
bluetoothd's GATT client read the iPhone's own GATT database, iOS answered Insufficient Authentication, and BlueZ sent an SMP Security Request. No agent on the headless/kiosk Pi confirmed it, so after the 30 s SMP timeout BlueZ disconnected (Authentication Failure) and the phone reconnected into a new pairing prompt, forever. Add scripts/setup/configure_bluetooth.sh, which sets Client = false under [GATT] in /etc/bluetooth/main.conf idempotently, with a timestamped backup and a bluetooth restart (--check reports only). Stock Raspberry Pi OS main.conf documents #Client under [CSIS], where bluetoothd ignores it, so the script writes it into [GATT]. setup.sh offers the step on a Pi; docs/ios-ble.md gains a troubleshooting entry. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
setup.sh does not install the camera extra, so 11 tests failed on the Pi with ModuleNotFoundError: cv2. Skip them when OpenCV is absent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks for this @btripp - any way we can break this out into separate PRs? 7k lines is a lot to review :D |
A phone that left the app or dropped the link got only the latest shot back, so shots taken meanwhile never reached it. Schema v2 now catches it up with one rule on both transports: - BLE: hello accepts an optional last_event_id. The catch-up goes out on the v2 shot characteristic after the hello response, held until the phone subscribes to it, and queued with backpressure so more missed shots than the 8-message queue are not dropped. - Network: the v2 stream honours Last-Event-ID (or ?last_event_id=) and v2 shot frames carry id: <event_id>, so EventSource resumes on its own. The named shot is resent with every current-session shot after it (the whole session when none is named or it is gone), capped at 20. Resending the anchor brings a phone that only saw a provisional up to the final. The session (monitor.get_shots) decides what exists, so cleared and deleted shots are never replayed, and PhoneShotCache replays the exact bytes last sent live. hello advertises shot_catch_up; the hello goldens change only by that entry. Catch-up failures never fail hello: the phone falls back to the latest-shot replay. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9pYpWtxN9nJ6vpqbXwB7m
Adds the catch-up rule for BLE and the network stream to the phone guide (hello last_event_id, Last-Event-ID, 20-shot cap, anchor resend, broadcast and queue caveats), updates negotiation, the stream seed and delivery behaviour, lists the new tests, and notes it in the changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9pYpWtxN9nJ6vpqbXwB7m
BLE hardware test: Pi 5 + iPhoneTested
New since the last review
Not covered on hardware: Full suite: 1826 passed, 22 skipped. |
A Pi with no radar, UPS or camera could not produce shot_processing,
provisional-then-final shots or power_status, so phone apps could not
be tested against them. Mock mode now simulates each one through the
real server paths:
- MockLaunchMonitor reports capturing then calculating to the
processing callback like the rolling-buffer radar, and
simulate_shot(fail=True) (Socket.IO {"fail": true}) reports failed
without recording a shot.
- --mock-enrichment-ms MS makes mock shots take the slow-enrichment
path: recorded without horizontal launch, club path and spin axis,
published provisional, then finalized once MockLaunchMonitor.enrich
supplies them after MS. Past the 20 s deadline they finalize as
skipped, as real hardware does. Off by default, so plain --mock is
unchanged.
- --battery mock adds a deterministic simulated battery that cycles
from full through low and critical, then charges back to full. It
never falls back to a real Linux battery, so a laptop's own battery
cannot stand in for it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01R9pYpWtxN9nJ6vpqbXwB7m
Adds a phone-guide section on running a hardware-free Pi that produces every phone event (--battery mock, --mock-enrichment-ms, failed shots), lists the mock battery provider, and notes it in the changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9pYpWtxN9nJ6vpqbXwB7m
For sure no problem. Will break it out here shortly. Currently Finishing up the On Device testing and its working good so far. Then will break it down. |
Splitting this PR upThanks for the feedback on size. I'm breaking this PR into smaller PRs that each stand on their own, and converting it to a draft. It stays open as the reference for the whole feature until the pieces land.
One design change in the split: the phone protocol is now schema 2 only. No app was released against version one, so the v1 characteristic pair and its compatibility layer are gone, which removes about 850 lines of compatibility code, tests and goldens. The schema-2 wire format is unchanged: all remaining goldens are byte-identical to this branch. The KMP companion app has been updated to discover the schema-2 characteristics. jake-fishtech's SwiftUI app on #285 was re-verified on a Pi 5 with the updated app: connection, live shots, club changes in both directions, and profile selection. |
What does this PR do?
Adds phone connectivity so a mobile companion app can follow and drive a running Pi:
--bleand needs the new optionalbleextra (bless 0.3.0, Linux only). Without the extra or BlueZ, the server logs "Bluetooth unavailable" and keeps running.helloon a second characteristic pair (v1 bytes unchanged). It adds shot numbers, profiles, and provisional and final shots sharing an event_id. It also adds processing, power, session-cleared and shot-deleted events. SSE opts in with ?schema=2.Why one PR: everything shares one club path and one publisher, and a phone app can't use any piece alone. The 13 commits split the work for review. About 4,000 of the 7,178 added lines are tests and fixtures. I can split v2 into a follow-up if you prefer.
Security scope: BLE is unauthenticated, so it can only read state, select the club or profile, and calibrate. Deleting shots, clearing sessions and editing profiles stay on the network path (kiosk, Socket.IO or HTTP).
Behaviour changes: a Socket.IO set_club with no club or "unknown" is now ignored (a missing club used to select driver). With no monitor, a club change still broadcasts, as before. A failed Socket.IO shot emit no longer skips BLE, SSE or simulators. New clients get club_changed on connect. A saved phone calibration sets the tilt unless the tilt argument is given.
Why was this required?
Relates to #203 (headless Pi with a phone as the monitor). Right now a phone can only open the kiosk UI in a browser. #122 was closed because there was no mobile app and no evidence it had been tested. This PR answers both:
openflight-server --mock) against this branch on every change. That includes a route-contract check of/api/cluband the calibration route.Automated tests
1,612 → 1,770 passed, 10 skipped. Every commit passes on its own, as do Python 3.10 and a CI-style pip env on 3.11.
Re-run on the PR head
64649fe(2026-09-28):uv run pytest tests/gave 1,770 passed and 10 skipped; pylint 9.61; ruff, UI build and UI lint were all clean.Manual (human) testing
On 2026-09-28 I ran this branch at
64649feasopenflight-server --mock --web-port 8282on my Mac. I used the kiosk web UI in a browser and the Companion app (Android, on an emulator) connected over the network. I did each step by hand and cross-checked it against the server log:?schema=2, thenGET /api/club200.POST /api/club200 "Club changed to 7-iron" and the kiosk switched. The next shot (102.4 mph / 139 yds) was filed as a 7-iron.POST /api/calibration/iwr6843/orientation) and the server answered 409, as expected in mock mode with no IWR6843 enabled. A real applied calibration still needs the radar (see below).?schema=2re-subscribed, club re-synced) without a Retry tap.Hardware testing still needed (Pi and real phones; I don't have a Pi set up yet, so none of these have been run):
AI assistance: Claude Code helped port the branch, write the v2 protocol, tests and docs, and draft this text. I reviewed every changed line and can explain it. I ran the manual tests above myself.
Checklist
uv run pytest tests/ -v)uv run pylint src/openflight/ --fail-under=9)uv run ruff check src/openflight/)cd ui && npm run build)cd ui && npm run lint)