Repository navigation
feat(mock): simulate the optional hardware phone apps react to - #299
Merged
Merged
Conversation
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. The flag is rejected when negative or without --mock
(launch monitor mode).
- --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.
Docs: a "Simulating hardware on a Pi without it" section in the phone
BLE guide, the mock provider in the battery guide, the new flag in the
running guide and CLI reference, and a changelog entry.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jewbetcha
approved these changes
Oct 18, 2026
HarjotDhanota
added a commit
to HarjotDhanota/openflight
that referenced
this pull request
Oct 20, 2026
…#305, open-flight#307) Conflicts: - server.py: upstream's mock-enrichment shortcut runs first, then our trace marks. - sim/resolver.py: both sides tagged estimated launch angles; ours keeps _resolve_carry, which already replaced the launch-angle carry badge (E4, C8). - tests/test_sim_resolver.py: both test sets kept; upstream's measured-VLA test now expects the estimated carry badge this fork gives an OPS-only carry. - tests/test_mock_hardware.py: the deadline publishes OPS-only, still pending, and the late enrichment finalizes the same shot (D29), not 'skipped'. - README.md: both contributor bullets kept. - AGENTS.md: the bench's master plan is linked by repository, not a local doc path.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reviewing this PR
One commit, 11 files. Production code: about 206 added and 33 removed lines in
src/(server.py+124/−32, newpower/providers/mock.py+74,power/factory.py+6,power/providers/__init__.py+2/−1). Tests: two new files, 404 lines. Docs: +48/−2.Suggested order:
src/openflight/power/providers/mock.pyandpower/factory.py: the simulated battery and its registration as--battery mock.src/openflight/server.py,MockLaunchMonitor: processing states,fail,enrichment_ms,enrich().src/openflight/server.py, wiring:handle_simulate_shot(data),_mock_enrichment_enabled(), the mock early return in_enrich_shot_from_optional_hardware,_has_slow_shot_enrichment,start_monitor(mock_enrichment_ms=...), and the--mock-enrichment-msflag with its validation.tests/test_mock_hardware.py,tests/test_mock_power.py.The PR does not depend on #297 (network shot stream) or the phone tilt calibration PR, and it does not touch either feature.
What does this PR do?
Mock mode can now produce every event a phone app reacts to, so a Pi with no radar, UPS or camera can exercise the app end to end:
MockLaunchMonitorreportsshot_processingcapturingand thencalculatingto the processing callback, the same way the rolling-buffer radar does.start_monitornow passesprocessing_callback=on_shot_processingto the mock monitor.simulate_shotSocket.IO event accepts{"fail": true}. The mock then reportscapturing,calculatingandfailed, and records no shot.MockSwingSpeedMonitorworks as before.--mock-enrichment-ms MS, mock shots go through the real slow-enrichment pipeline. Each shot is recorded without horizontal launch, club path and spin axis and is published as provisional (enrichment: pending). AfterMSmilliseconds,MockLaunchMonitor.enrich()fills in those fields and the shot is finalized with the sameevent_id. IfMSis longer than the 20 s deadline, the shot is finalized asskipped/deadline, as on real hardware. The flag is off by default, so plain--mockbehaves exactly as before. The server rejects the flag when it is negative, or when it is used without--mockin launch-monitor mode (including with--mock-swing-speed).--battery mockadds a deterministic simulated battery. Each 5 s poll moves it one step: from 100% on battery down throughlowandcritical, thenplugged_incharging back to 100%. A full cycle takes about 100 s. It never falls back to the Linux sysfs battery, so a laptop's own battery cannot replace the simulation.Docs: a "Simulating hardware on a Pi without it" section in
docs/ios-ble.md, the mock provider indocs/using/battery.md, the new flag indocs/using/running.mdanddocs/reference/cli.md, and a changelog entry under Unreleased → Added.Why was this required?
This is follow-up 4 from #285. Phone apps handle
shot_processing, provisional and final shots (upserted byevent_id), skipped enrichment andpower_status. Until now, the only way to produce those events was a Pi with an OPS243 rolling buffer, IWR6843 or camera, and a UPS. App developers without that hardware could not test those code paths. This PR is independent of #297 and of the calibration PR.Automated tests
tests/test_mock_hardware.py(new):capturingthencalculating.fail=Truereportsfailedand records nothing. Shots still work with no processing callback, and when the callback raises. Enrichment is off by default and leaves the direction fields populated. With enrichment on, the direction fields stay empty untilenrich()runs. A negative delay is rejected._has_slow_shot_enrichmentis false for mock shots by default and true with enrichment on.handle_simulate_shot({"fail": True})fails a shot andhandle_simulate_shot()records one.tests/ble_harness.py): processing states, then a provisional shot, then a final shot with oneevent_id. Past the deadline (shortened to 0.2 s in the test), the shot finalizes asskipped/deadline. A failed capture showsfailedand sends no shot.--mock-enrichment-mswithout--mock, with--mock-swing-speed, or with a negative value exits with code 2.tests/test_mock_power.py(new): the cycle starts full on battery, reaches all four power states, discharges and then charges monotonically, repeats exactly, and keeps percent and voltage plausible. The factory picks the mock even when a real sysfs battery exists.PowerMonitor(provider="mock")publishes statuses.--battery mockparses. Non-positive steps are rejected.MockLaunchMonitor()and callsimulate_shot(test_server.py,test_ble_loopback.py,test_phone_transport.py) pass unchanged.Manual (human) testing
Run on macOS (no Pi, no Bluetooth):
Started
uv run openflight-server --mock --mock-enrichment-ms 800 --battery mock --host 127.0.0.1 --web-port 8767, then drove it with apython-socketioclient:simulate_shot:shot_processingcapturing(t=1.01 s), thencalculating(1.17 s). Ashotevent at 1.33 s hadpendingset andlaunch_angle_horizontal,club_path_degandspin_axis_degallnull. Ashot_updatearrived about 0.8 s later for the same shot number, with those fields filled in (e.g. −4.4 / −2.3 / −6.3).simulate_shotwith{"fail": true}:capturing,calculating,failed, and noshotevent.power_statuswithprovider: "mock",state: "on_battery", stepping 92 → 84 → 76 → 68% every 5 s.openflight-server --mock-enrichment-ms 5exits 2 with--mock-enrichment-ms requires --mock (launch monitor mode).--mock --mock-enrichment-ms -1exits 2 withmust not be negative.--mock-swing-speed --mock-enrichment-ms 5is rejected the same way as the first case.scripts/start-kiosk.sh --mock --ble --battery mock --mock-enrichment-ms 1500 --dry-runforwards every flag unchanged toopenflight-server.Phone payloads, after merging current
main. Bluetooth can't run on macOS (blessis Linux-only), so a small launcher replacedBleShotPublisherwith a subclass whosestart()doesn't open the radio. The subclass still runs the realpublish_shot/publish_eventencoding and records the exact bytes a phone would receive. The rest is the stockserver.main().--mock --battery mock --mock-enrichment-ms 1500: a provisional shot (final:false,enrichment.status:"pending", horizontal launch / club path / spin axisnull), then a final shot with the sameevent_id1.51 s later (enrichment.status:"complete", fields filled in). Preceded byshot_processingcapturing→calculating.simulate_shot {"fail": true}:capturing→calculating→failed, with no shot.on_battery, 20 and 12%low, 5%critical, then 17 → 29 … 89 → 100%plugged_inwithexternal_power:true, then repeats (about 105 s per cycle).--mock-enrichment-ms 25000: the final shot arrives 20.0 s after the provisional one withenrichment:{"status":"skipped","reason":"deadline"}. The log showsoptional enrichment exceeded deadline; finalizing OPS-only, thenIgnoring late enrichment for finalized shot #1when the mock result arrives at 25 s.--mockwith no new flags: one completeshotper swing, with noshot_updateand no warnings in the log. The only change is thecapturing/calculatingsteps, which add about 0.3 s before the shot.--bleon macOS logsBluetooth unavailable; shot recording will continue without BLE, and shots keep working.Not yet tested: the phone app over real Bluetooth on a Pi. No Pi was set up for this PR. I'll add the results here once one is.
Checklist
tests/test_mock_hardware.py,tests/test_mock_power.pyuv run pytest -q: 1831 passed, 10 skipped, after merging currentmain)uv run pylint src/openflight/ --fail-under=9: 9.60/10, no messages on added lines)uv run ruff check src/openflight/,ruff format --checkon touched files)ui/changes)ui/changes)🤖 Generated with Claude Code