Skip to content

feat(console): Analog In panel — live 10 Hz readout, strip chart, AO→AI loopback sweep (S1) — Studio v0.74 - #190

Open
mbreiser wants to merge 3 commits into
mainfrom
feat/console-analog-in
Open

feat(console): Analog In panel — live 10 Hz readout, strip chart, AO→AI loopback sweep (S1) — Studio v0.74#190
mbreiser wants to merge 3 commits into
mainfrom
feat/console-analog-in

Conversation

@mbreiser

@mbreiser mbreiser commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Studio half S1 of docs/development/analog-input-plan.md § 4 (plan PR #189; decisions 2026-09-07). Pairs with firmware F1 (reiserlab/LED-Display_G6_Firmware_Arena#46) but works against today's io_ext firmware too — the F1 flags byte is optional on the wire.

What changed (Studio v0.74)

New Console tool: "Analog In" (left-rail button + panel)

  • Live readout of both "Analog In (±10V)" BNCs: value, ±10 V bar with a centre mark, min/max since reset, and a 30 s strip chart (green = AI1, blue = AI2). Rate 10 / 5 / 2 Hz, Live/Paused toggle, Reset.
  • The poller never floods the log. It reads GET_ANALOG_IN via session.send directly (not the logging send()), is single-flight (a tick is skipped while a read is on the wire), and pauses itself with a visible reason whenever the gate says no: panel closed, not connected, no io_ext firmware, user pause, a run in progress, or a sweep in flight. Only state transitions are logged; it parks after 5 consecutive read errors with a resume hint.
  • AO → AI loopback self-test: cable "Analog Out (0-5V)" to an input, pick the input and step size, Run sweep → steps 0→5000 mV, three reads (median) per step, least-squares slope/offset, per-step error table, verdict ok (slope within 5 %, max error ≤ 150 mV) / check / fail, Copy CSV, and the previous AO level is restored. A flat reading near +10 V at every step is called out as the LAB-209 un-reworked-board signature. The sweep is also written to the bridge run log (analog_loopback_sweep).
  • The panel's status line shows what the controller reports about its reads (12-bit / calibration state from the F1 flags byte; "10-bit (pre-F1 fw)" otherwise).

Elsewhere

  • js/arena-wire-g6.js decodeAnalogIn returns {flags, cal1, cal2, bits12} (null/false on 4-byte replies).
  • Arena Trial gain tooltip states the G3-faithful scale: ×10, so 10 = unity = 100 frames/s per volt; 2–5 = 20–50 fps/V; negative reverses.
  • The I/O panel's one-shot Read appends the flags text.
  • HELP-map entries for the rail button and panel; release notes v0.74; CLAUDE.md Console convention (periodic reads bypass send(), reuse StudioAnalogIn.createPoller).

Code

  • js/studio-analog-in.js (new, classic dual-export, DOM-free): createMonitor, createPoller, sweepPlan, fitLinear, summarizeSweep, sweepCsv, describeFlags, median, barFraction, formatMv, drawStripChart.
  • arena_studio.html: rail button, panel section, CSS, poller/sweep wiring before the handler table, four data-cmd handlers (caipoll, caireset, caisweep, caisweepcsv).

Tests

  • tests/test-studio-analog-in.js (new, in pixi run test): 105 checks — monitor (window trim, min/max persistence, rate, cap), poller (single-flight skip, gate pause reported once, error streak → park → resume, gate reopen forgives errors, injected timers start/restart/stop), sweep math (exact fit, NaN rows, saturation → fail, 10 % slope → check, channel 2, CSV), wire flags decode, and HTML wiring (script order, rail, every panel id, every handler, quiet read, run gate, io_ext gate, HELP entries, gain tooltip, footer, AO restore).
  • tests/test-arena-wire-g6.js: flags decode cases (183 → 186).
  • Full pixi run test passes; touched JS Prettier-clean; all inline Studio script blocks parse.
  • Browser-verified offline (no controller): no console errors, module loads, the rail button opens the panel, the poller status goes "panel closed" → "connect to read" and issues no reads while disconnected.
  • Not bench-tested (no hardware for a few days): live values, rate, the sweep against a real board — plan § 5.4 / T6.

🤖 Generated with Claude Code

mbreiser and others added 2 commits September 9, 2026 08:25
…rt, AO→AI loopback sweep; 0xA4 flags decode; G3 gain tooltip (S1) — Studio v0.74

Studio half S1 of docs/development/analog-input-plan.md § 4 (decisions
2026-09-07). Works against today's io_ext firmware and reads the F1 flags byte
when present.

- js/studio-analog-in.js (new; classic dual-export, DOM-free): createMonitor
  (windowed ring buffer, min/max, rate), createPoller (single-flight ticker
  gated on panel-open / connected / io_ext / user pause / session.running /
  sweep; reports state CHANGES only, parks after N consecutive read errors),
  sweepPlan / fitLinear / summarizeSweep / sweepCsv (AO 0→5000 mV loopback
  self-test with ok/check/fail verdict — a flat +10 V reading is the LAB-209
  un-reworked-board signature), describeFlags (0xA4 flags byte), drawStripChart.
- arena_studio.html: left-rail "Analog In" button + panel (values, ±10 V bars,
  min/max, 30 s canvas trace, rate 10/5/2 Hz, Live/Paused, Reset, loopback
  sweep with table + Copy CSV). The poller reads via session.send directly —
  NOT the logging send() — so the Console log is not flooded at 10 Hz; state
  transitions and the sweep are logged; the sweep is also logged to the bridge
  run log (event analog_loopback_sweep) and restores the previous AO level.
  I/O panel's one-shot Read shows the flags text. Trial-panel gain tooltip now
  states the G3-faithful scale (×10; 10 = 100 fps/V; 2–5 = 20–50 fps/V).
  HELP-map entries; footer v0.74.
- js/arena-wire-g6.js: decodeAnalogIn returns {flags, cal1, cal2, bits12}
  (null/false on 4-byte pre-F1 replies); wire tests extended.
- tests/test-studio-analog-in.js (new, in pixi run test): 105 checks — monitor,
  single-flight poller with injected timers, sweep math incl. saturation and
  10 % slope verdicts, wire flags, and the HTML wiring (script order, rail,
  panel ids, handlers, quiet read, run gate, io_ext gate, HELP, tooltip, footer).
- Release notes v0.74; CLAUDE.md Console convention: periodic reads bypass
  send() and reuse StudioAnalogIn.createPoller.

Not bench-tested (no hardware); browser-verified offline: panel renders, poller
shows the gate reason and issues no reads while disconnected.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…VOLTAGE; pause the poller during the Console's FicTrac closed loop

Codex cross-review 2026-09-09. The Console send() helper returns the reply
frame even when the firmware answers a non-ok status (it logs the reason), so
the sweep's truthiness check only caught transport failures: with Analog Out in
frame_number mode every 0xA0 step is refused and the sweep would have recorded
AI readings against levels that were never applied. Decode the status, abort
with a hint naming the AO mode; report a refused/unknown restore too.

The live poller gated on session.running (runner trials) but not on the
Console's own FicTrac closed loop (bridge 'apply'), which streams 0x70 at
~100 Hz outside the runner — yield the link to it as well.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mbreiser
mbreiser force-pushed the feat/console-analog-in branch from 17e97b0 to e169159 Compare September 9, 2026 12:25
A perfectly linear channel reading AO + 1 V passed as 'ok' because the verdict
looked only at slope and scatter; for a loopback self-test the offset is the
diagnostic. Now ok = slope ±5 % AND |offset| ≤ 150 mV AND max |err| ≤ 150 mV;
'check' still means slope within ±15 %. Test added (Codex review item 4).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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