Skip to content

W5: scope captures of several nodes on one trigger - #13

Merged
Tecnologic merged 4 commits into
mainfrom
claude/unimoc-debug-gui-lrgfap
Oct 25, 2026
Merged

Tecnologic merged 4 commits into
mainfrom
claude/unimoc-debug-gui-lrgfap

Conversation

@Tecnologic

Copy link
Copy Markdown
Owner

Phase W5 of docs/web_plan.md (old G5, docs/plan.md section on the scope).

How the shared trigger works

cymon-lib publishes cymon.TriggerEvent when a capture triggers. An armed node that subscribes to it triggers at its next sample. So ScopeClient.multi_capture:

  1. sets up every node with its channels; the period, length and pre-trigger part are the same on all nodes;
  2. arms the other nodes on a condition that never holds (rising above 3e38), so only the event can trigger them;
  3. arms the trigger node on the user's condition;
  4. waits for all captures, then reads them.

Each node's time axis is 0 at its own trigger sample.

The nodes need the trigger ports on one subject. UNIMOC disables uavcan.pub/sub.cymon_trigger.id by default:

  • trigger_ports checks the ports;
  • set_trigger_ports writes them (default subject 220, free on UNIMOC);
  • the API and UI then store and restart the nodes, because ports take effect at start.

Changes

  • core/scope.py: the capture is split into setup, arm, wait and read; added multi_capture, trigger_ports and set_trigger_ports.
  • API:
    • GET /api/scope/trigger?nodes=10,11;
    • POST /api/scope/trigger: operator only, refused while a node is engaged; stores and restarts the nodes;
    • POST /api/scope/capture: several nodes. Captures taken during a run are stored in it, one CSV per node with the variable names.
  • Scope view:
    • nodes with their variables, the trigger node and its condition;
    • a check of the shared trigger, with Set up the shared trigger;
    • all channels in one plot (uPlot.join across nodes), CSV export.
  • Demo device: trigger ports, publishes its trigger event, triggers on the events of others.
  • SimInstance.start() after a stop() now waits for the new pseudo terminals; it returned with the old ones.
  • README (Scope), web_plan.md status.

Tests

  • tests/unit/test_scope.py: two demo devices. Covers the refusal without a shared subject, then setup and restart, then a capture where the trigger node fires on its condition and the follower on the event.
  • tests/unit/test_web.py: the trigger endpoints (403 for viewers) and a two-node capture stored in a run.
  • tests/browser/test_scope_ui.py: the whole flow in Chromium.
  • tests/sim/test_scope_sim.py: the W5 acceptance test.
    • Two native_sim drives (nodes 10 and 11) with the trigger ports stored in their flash, joined into one bus by an slcan bridge in the test.
    • A capture on both with node 10 triggering.
    • The delay of the second drive, measured from the two trigger events: 3 to 21 ms here. Most of that is the Python bridge; on a real bus the plan expects 1 to 2 ms.
  • Locally: ruff, format and mypy clean; pytest 122 passed, 1 skipped, against the released v0.3.0 native_sim.

Generated by Claude Code

- ScopeClient.multi_capture: sets up every node, arms the other nodes on a
  condition that never holds and the trigger node on the user's condition;
  the others trigger on its cymon.TriggerEvent. trigger_ports checks that
  the nodes publish and subscribe the trigger on one subject;
  set_trigger_ports writes it (default 220).
- API: GET/POST /api/scope/trigger (the setup stores and restarts the
  nodes, operator only, refused while a node is engaged) and POST
  /api/scope/capture for several nodes; captures during a run are stored
  in it, one CSV per node.
- Scope view: nodes with their variables, the trigger node, the shared
  trigger check with "Set up the shared trigger", all channels in one plot
  on one time axis (each node on its own trigger sample), CSV export.
- Demo device: trigger ports, publishes its trigger event and triggers on
  the events of others.
- SimInstance.start after a stop: new pseudo terminals are waited for.
- Tests: two demo devices (core, API with a run, browser) and two
  native_sim drives joined by an slcan bridge, with the trigger delay of
  the second drive measured from the trigger events (3 to 20 ms over the
  Python bridge).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEh6hu8r51ZDjgfECmELvM
…ilure

On CI node 10 did not show up within 10 s: both drives' traffic goes over
one classic slcan terminal there; the other simulation tests wait 30 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEh6hu8r51ZDjgfECmELvM
…cypilot and the bridge saw

The two-drive test fails on CI with Python 3.13 only (node 10 not seen
on its own terminal); this separates the restart from the bridge.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEh6hu8r51ZDjgfECmELvM
Decoding and re-encoding every frame with python-can could not keep up
with two drives: the answers of the second drive came seconds late, so
its register requests timed out (reproduced locally once the bridge
started after cypilot; on CI with Python 3.13 also the other way). The
frame lines now pass through in chunks; only frame lines, so the
acknowledgements stay on their side. Locally 5 of 5 runs pass, in 14 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WEh6hu8r51ZDjgfECmELvM
@Tecnologic
Tecnologic merged commit 7c5c5c7 into main Oct 25, 2026
10 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.

2 participants