Skip to content

feat(bench): app UI on the real plates over a 200 Hz bench stream - #13

Merged
jewbetcha merged 2 commits into
mainfrom
feat/bench-live-plates
Oct 28, 2026
Merged

jewbetcha merged 2 commits into
mainfrom
feat/bench-live-plates

Conversation

@jewbetcha

@jewbetcha jewbetcha commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

What does this PR do?

Runs the app's own web UI on the real plates, using real data from all 8 cells: live lead/trail weight shift, CoP on each plate, swing capture and the calibration wizard. It does this before the production firmware and board-side main.py exist.

  • bench/ads1220_stream (new sketch): reads all 8 ADS1220s on each L1 DRDY tick (~1 kHz, or a 1 ms timer without L1). It averages the reads into 5 ms frames, the 200 Hz protocol.py expects, and prints 10 frames per Monitor line: B,<seq>,<t_first_us>,<period_us>,<flags>,<hex>, 6 hex digits per 24-bit value.
    • A frame with no fresh read, because the loop was blocked printing, repeats the previous one.
    • Flags follow protocol.py: 0x01 means no reads for 50 ms, 0x02 means a board is at full scale.
    • r re-checks every board and restarts the stream.
  • tools/bench/live_plates.py (new): parses those lines into protocol.Batches and runs them through open_fp.app.Core behind the simulator's SimWebUI stand-in, serving app/assets on :7070. It's sim_server.py with the Q in place of synth.
    • It sends r once SSH is attached, prints which boards answer, and zeroes the empty plates at 6 s (--no-zero turns that off).
    • A fresh bench/live/ data dir (git-ignored) gets signs of −1, because these cells read lower under load (every press in the bench press test was negative).
  • app/assets/js/stance.js: the stance view used to show only the newest sample of each live message, so the CoP dot moved in 20 Hz steps. Batches arrive every 50 ms, on the board as well as here. Now the samples queue and play out one per animation frame, 70 ms behind the newest; the trail follows the sample shown. A stream restart (time going backwards) clears the queue.
  • Firmware CI compiles the new sketch.
  • docs/harness.md §5: a short "Both plates in the app's UI" section.

Why was this required?

Every unit on both carriers now passes the bench and press tests, and the owner wants to see weight move between the plates in something more polished than the bench live view. That means the app's UI, with both plates. The UI, pipeline and swing detector are written and tested but have only ever run on synthetic data, because the board side (M2/M3 firmware and main.py) isn't written yet. This gets real data into them now with no new transport risk: it uses the same SSH monitor every bench tool uses. The production Bridge path stays a separate piece of work.

Automated tests

tests/test_bench_live_plates.py:

  • A batch line round-trips through the parser, including negative and full-scale 24-bit values.
  • Malformed B, lines (partial values, non-hex, missing fields) are dropped.
  • Readback lines still parse like the bench sketches'.
  • seed_config writes signs of −1 to a fresh data dir and never overwrites an existing config.
  • End to end: stream lines drive Core. Plates are zeroed empty, then a 70/30 stance with real-looking per-channel offsets. The live message reports lead 70 %, L 560 N and R 240 N.

pytest: 207 passed. ruff: clean. pylint: 10.00 on the new file. The sketch compiles locally (arduino-cli compile --warnings default).

Manual (human) testing

On the bench with both carriers and all 8 cells:

  • Flashed bench/ads1220_stream and captured the monitor for 10 s:
    • all 8 boards read back 0E C4 40 00;
    • 118 batch lines, sequence 95–212 with no gaps;
    • 197 frames/s by MCU time, flags 0, about 5 % of frames held (repeated while printing);
    • unloaded noise at 200 Hz of 660–2,700 counts per channel;
    • the one time step is the r restart (127 ms), as designed.
  • Fed that capture through the parser.
  • The UI on the plates:
    • Ran live_plates.py with the owner on the plates; lead/trail and the force chart follow weight shifts.
    • Two problems in the first run, both fixed here: readouts flickered from empty live pushes (the server now ticks only after new frames), and the dot stepped at 20 Hz (the playback above). After the fix, checked in the browser: the new stance.js loads and runs with no console errors, at 207–221 Hz.
    • Not fixed here: the corners within each plate don't match the physical layout yet. The press test only proved CS ↔ label, not label ↔ corner. A corner map (or moving the CS wires) comes next.

Checklist

  • Single feature/fix — this PR is scoped to one thing with a clear story above
  • Automated tests included — new or updated tests cover this change
  • Manual testing described — I documented what I verified by hand above
  • Python tests pass (pytest)
  • Ruff passes (ruff check app/python tools tests docs/img)
  • Pylint passes (PYTHONPATH=app/python:tools/sim:tools/bench pylint app/python/open_fp tools/sim tools/bench --fail-under=9)
  • Hardware changes recorded in docs/VERIFIED.md (measurements, fit checks, pin changes) — none; stream figures are in the harness doc
  • Generated files regenerated and committed (e.g. docs/img/corner-assembly.svg, CAD STL/STEP) — n/a
  • Updated docs if needed
  • No unrelated changes mixed in

🤖 Generated with Claude Code

jewbetcha and others added 2 commits October 21, 2026 10:55
The production firmware and board-side main.py aren't written yet, so the
app's UI has only run on synthetic swings. This feeds it real plate data:

- bench/ads1220_stream: reads all 8 ADS1220s on L1's DRDY (~1 kHz),
  averages into 5 ms frames and prints 10 frames per Monitor line
  (B,<seq>,<t_first_us>,<period_us>,<flags>,<hex>), the fields of
  protocol.py's Batch.
- tools/bench/live_plates.py: parses those lines into Batches and runs
  them through open_fp.app.Core behind the simulator's WebUI stand-in,
  serving app/assets on :7070. Seeds bench/live/config.json with signs -1
  (these cells read lower under load) and zeroes the empty plates at start.
- firmware CI compiles the new sketch; docs/harness.md 5 shows how to run it.

On the plates: 197 frames/s by MCU time, no sequence gaps, ~5 % of frames
held while the sketch prints.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- stance.js showed only the newest sample of each live message, so the
  CoP dot moved in 20 Hz steps (one batch per 50 ms). Samples now queue
  and play out per animation frame, 70 ms behind the newest; the trail
  follows the sample shown. A stream restart (time going backwards)
  clears the queue.
- live_plates.py ticked the core every 10 ms whether or not frames had
  arrived; the empty `live` messages blanked the readouts (flicker). It
  now ticks only after new frames, like the simulator, and sends
  Cache-Control: no-cache so an edited page isn't served stale.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jewbetcha
jewbetcha merged commit 043765c into main Oct 28, 2026
18 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.

1 participant