Telemetry recording and replay for IMU + plate-solve sessions#411
Open
mrosseel wants to merge 22 commits into
Open
Telemetry recording and replay for IMU + plate-solve sessions#411mrosseel wants to merge 22 commits into
mrosseel wants to merge 22 commits into
Conversation
Replays synthetic telemetry through real ImuDeadReckoning and integrator wrapper functions, measuring dead-reckoning error vs ground truth. Three tests: stationary drift, slew tracking, and solve correction reset. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Telemetry recorder captures solve and IMU events with timestamps - Menu entries for telemetry start/stop/replay - Integrator wired to record solves and IMU readings - UI list screen for telemetry sessions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Split integrator.py into three focused modules: - pointing.py: coordinate math (IMU dead-reckoning, plate-solve integration, roll/constellation/altaz finalization) - telemetry.py: TelemetryManager facade (recording, replay, command dispatch, image saving, replay event handling) - integrator.py: main loop and queue plumbing only Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- 55 unit tests covering TelemetryRecorder, TelemetryPlayer, TelemetryManager, and pointing.py functions - Fix test_integrator_drift.py import from PiFinder.integrator to PiFinder.pointing after refactor Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…and analysis tools - Record raw gyro and accelerometer readings in IMU events - Record target changes (name, RA/Dec, Alt/Az) for time-to-target analysis - Fix replay: solve_time, solve_source, imu_pos, failed solves, mount_type header - Reset integrator state after replay ends - Move location to separate .location sidecar file for privacy - Reduce file size with stationary IMU decimation (10x) and float rounding - Add telemetry analysis scripts: session visualizer, drift analysis, truss flex analysis, IMU free-run drift visualization - Include sample recording (session_20260309.jsonl, location scrubbed) - 67 unit tests covering all new functionality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Single conflict in python/PiFinder/integrator.py: telemetry already refactored the helper functions (update_plate_solve_and_imu, update_imu, set_cam2scope_alignment, get_roll_by_mount_type) into pointing.py. Kept telemetry's refactor; dropped the duplicate helpers that upstream modified in place. Upstream-only additions (get_alt_az, get_constellation, pointing_updated flag) and the imu_time tweak in update_imu are NOT incorporated here — apply them to pointing.py as a follow-up if wanted.
Upstream's astro_coords refactor (brickbots#420) moved RaDecRoll from pointing_model/astro_coords.py to types/coordinates.py and reworked the API: constructor now takes (ra, dec, roll, deg=...) and replaces set_from_deg/get_deg with set(...) and get(deg=True). Resolution: - integrator.py: kept HEAD; dropped duplicate helpers (now in pointing.py) - pointing.py: updated import path and ported all RaDecRoll calls to the new API (set_from_deg/get_deg → constructor / get(deg=True)) - integrator_classic.py: accepted upstream deletion (brickbots#421 dropped classic integrator); telemetry's only change was a trivial **kwargs signature
The clear-on-failure block in solver() reset solved["RA"]/Dec/Matches but left camera_center and camera_solve pinned to the last successful solve across every failure path. Integrator-side downstream is unaffected (integrator.py:96 only merges new position data when RA is not None), but stale camera_center leaks into anything reading 'solved' from the solver's perspective. Completes the existing 'otherwise old values persist' clearing block.
solver.py imports tetra3 at module scope, which transitively pulled the submodule into any caller of get_initialized_solved_dict — including integrator.py and the integration-drift tests. CI's checkout step does not init submodules, so tests/test_integrator_drift.py failed to collect: PiFinder/solver.py:28: ModuleNotFoundError: No module named 'tetra3' Move the pure-data dict factory to a tiny new module PiFinder/solved.py. Update solver.py, integrator.py, and test_integrator_drift.py to import from there. No runtime behavior change.
TakKanekoGit
left a comment
Contributor
There was a problem hiding this comment.
Hi! I just reviewed the part I'm familiar with (imu_pi.py). I use telemetry a lot so it'll be great to have it in main.
Port telemetry record/replay onto the refactored Solver/Integrator flow (upstream brickbots#429): SolveResult DTOs on solver_queue, integrator-owned PointingEstimate, ImuSample on shared_state. - integrator.py: take upstream's dataclass integrator, re-add telemetry hooks. Replay now converts recorded events back into SolveResult / ImuSample messages and feeds them through the same _apply_successful_solve / _apply_failed_solve / _advance_with_imu paths as live data (the old branch duplicated integrator logic inside TelemetryManager). - telemetry.py: recorder consumes SolveResult/ImuSample; player produces them. JSONL field names unchanged, so existing recordings stay replayable. - types/positioning.py: ImuSample gains gyro/accel fields for raw-sensor telemetry; imu_pi.py populates them. - Drop pointing.py and solved.py: superseded by types/positioning.py and the upstream integrator (incl. the legacy roll-by-mount-type logic that upstream replaced with chart-side handling). - solver.py: take upstream as-is (get_initialized_solved_dict is gone). - Tests ported to the new types; drift tests now exercise the real integrator apply/advance functions.
…corrupt sessions Port of the fix lost in the worktree resync (was 10e8c0b0), adapted to the dataclass-based telemetry, plus replay-load hardening: - Replay's set_datetime call was rejected when the recording predates the current clock (set_datetime only moves forward). Pass force=True so past sessions take effect; this also blocks GPS time updates from clobbering the replayed clock mid-replay. - Add a "Stop replay" entry to the telemetry list - replay_stop was wired in the manager but unreachable from the UI, so a long session locked the device into replay until it finished. - A corrupt or missing session no longer kills the integrator process: TelemetryPlayer._load skips unparseable/timestamp-less lines (the expected artifact of a power cut mid-recording), the replay command catches OSError and restarts the camera the UI just stopped, and malformed events are skipped during replay instead of raising. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…giene Replay no longer hijacks device state permanently: - Save the pre-replay location and restore it when replay ends (both the stop command and natural finish, consolidated in _end_replay); reset_datetime() clears the forced replay clock so GPS time resumes. - Protect source="replay" from GPS fix overwrites in main.py, matching WEB/CONFIG/MANUAL - a mid-replay GPS lock clobbered the replayed location. - Shift a header's dt back by (hdr.t - first event t) on apply, so late-written headers set the clock to the start of the event stream. IMU capture now covers the whole session: - The integrator records IMU samples before the dead-reckoning anchor gate, so sessions where the solver never solves (the most diagnosis-worthy case) still capture IMU data. - Records are stamped with the sample epoch (imu.timestamp), not the integrator's poll time, and deduped on it - the loop polls faster than the IMU updates, so the same sample was recorded repeatedly. Recorder hygiene: - Late-bind datetime/location into the session once GPS lock arrives after recording started (second hdr record + .location sidecar). - _poll_target checks target-changed before computing Alt/Az; it was running a skyfield transform every integrator loop while recording. - start() resets per-session dedup state (a second session never recorded its initial target). - Buffer overflow drops are counted and logged instead of silent. - _do_flush is locked: the background flush thread and the loop's time-gated flush could interleave writes and scramble event order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…uaternion Address review feedback on brickbots#411: the raw-sensor reads for telemetry lived in the imu_monitor loop, which spins much faster than update()'s sample-rate gate - two extra I2C transactions per spin, paired with a quaternion sampled at a different instant, reaching through imu.sensor in a way that only worked on the fake IMU because of a bare except-pass. Now update() reads gyro/accel in the same gated pass as the quaternion and stamps last_read_time; the monitor just copies imu.gyro/imu.accel onto the published sample and takes the read epoch as the sample timestamp. The per-read timestamp also keeps the telemetry recorder's dedup-by-sample-epoch working while stationary (previously the timestamp only changed on movement, so stationary heartbeat samples - gyro/accel noise floor - were never recorded). Drive-by: remove the stray `imu = Imu()` after the ImuFake fallback, which re-raised on missing hardware and made the fallback unreachable; give ImuFake the attributes the monitor reads so the degraded-ops path actually runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…_imu Address review feedback on brickbots#411: raw-sensor reads add two I2C transactions per sample on a bus the BNO055 is sensitive about, and analysis of recorded gyro data showed it wasn't very usable. Default to quaternion-only sampling; set telemetry_raw_imu=true in config to record gyro/accel (the recorder already serializes absent values as null). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Summary
~/PiFinder_data/telemetry/; optional per-solve frame capture via a new camerasave_image:command. Location is kept in a separate sidecar file so sessions can be shared without leaking it.SolveResult/ImuSamplemessages and fed through the integrator's normal apply/advance paths with original timing; the session's clock and location are force-applied for faithful Alt/Az and restored when replay ends. Start/stop from the new Telemetry menu (Record, Images, Load).test_telemetry.py(record/replay round-trip equivalence) andtest_integrator_drift.py.Test plan
nox -s unit_testspasses (includes newtest_telemetry.pyandtest_integrator_drift.py)nox -s lintpassesnox -s type_hintspasses🤖 Generated with Claude Code