Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" name: sweep-engine-design description: Architecture for measurement sweep engines — a headless plain-Python core (preflight, transactional bring-up, apply/settle/measure/store loop, abort, safe shutdown, partial emit) wrapped by a thin GUI-thread adapter, with a state machine and callback/signal surface. Use when writing or restructuring any measurement loop, sweep/scan engine, worker thread (QThread or threading.Thread), engine state machine (IDLE/RUNNING/MEASURING/COMPLETED/ERROR), or automation outer loop; when a GUI freezes during measurement; or when engine logic is untestable without hardware or a display. Ships a complete runnable headless engine with fault-injection tests and a Qt adapter snippet. Triggers include "sweep engine", "measurement loop", "QThread worker", "state machine", "abort handling", "automation loop", "scan engine".
Sweep Engine Design
The one architecture rule
The engine is plain Python; the GUI toolkit wraps it. Every decision — preflight, sequencing, settling, NaN policy, abort, shutdown ordering, partial emit — lives in a class with zero toolkit imports, exercised by threading.Thread in tests and by a ~30-line QThread adapter in production (adapter snippet included in the example file). What this buys:
- every safety/integrity guarantee is provable headless (no display, no
hardware, CI-friendly);
- the GUI cannot freeze (no measurement on the UI thread) and cannot corrupt
a run (no widget reads mid-loop);
- swapping toolkits (or running engine-only scripts) is trivial.
Runnable reference: examples/generic_sweep_engine.py (+ 12 fault-injection tests in examples/test_generic_sweep_engine.py).
The lifecycle
Details per stage: references/engine-lifecycle.md.
run():state RUNNINGpreflight() config.validate() fail-closed; required instrumentspresent; interlock closed (None ⇒ refuse)bring_up() limiter-first transactional apply, level 0, output ONstate MEASURINGfor cycle: for setpoint:abort? → break (partial kept)apply setpoint → sync (*OPC?) → settle (sliced ≤100 ms)→ measure (avg, NaN-tolerant) → store (pre-allocated buffer)→ emit point → track NaN (warn/abort)emit whole cycle (atomic granularity)state COMPLETED (or reason recorded)finally:_safe_shutdown() meters stop → ramp → zero → output OFF (each guarded)emit finished(data) ← fires on EVERY path (GUI re-enable depends on it)state IDLE
Contract points the tests enforce:
- Abort is an
Eventset from any thread; every wait is sliced; latency
≤ ~100 ms; shutdown still runs; partial data emitted completed=False.
- Per-step isolation: a failing point becomes NaN and the loop continues;
the consecutive-NaN tracker decides when flaky = dead (warn 3 / abort 10).
- Cycle-atomic emission: consumers receive whole cycles, or one explicit
partial on abort — never a torn cycle.
- finished-callback always fires (in finally) — the GUI's re-enable and
the exporter hang off it; an engine exception must not strand the UI.
- Readback stored, setpoint kept as metadata (integrity rule surfaced at
engine level).
- Run-level vs session-level failure: an automation campaign skips a
failed run (isolated by a dedicated internal exception) and continues — or aborts the session, per an explicit on_fail policy; never implicit.
State machine + control surface
States: IDLE / RUNNING / MEASURING / COMPLETED / ERROR — emitted as strings via on_state. GUI mapping and widget-locking rules (config widgets lock while ≠ IDLE) plus signal-naming conventions: references/state-machine.md.
Callback surface (→ Qt signals 1:1): on_state, on_point, on_cycle_done, on_progress, on_error, on_finished.
Cross-thread handshakes (e.g. asking the GUI thread to start a camera recording): request-callback + threading.Event ack with a timeout, waited via the sliced-abortable wait — never a blocking call into the GUI from the worker.
Automation outer loops
An automation layer is a loop over ENGINE RUNS, not a bigger engine: per-iteration config snapshots (dataclasses.replace, re-validated), state reset between runs (counters, flags — anything that must not leak), explicit re-initialization of physical history where required (e.g. re-saturation), and the same abort event threaded through all levels.
Companion skills
Safety patterns the skeleton encodes: fail-closed-safety. Wire discipline inside the loop: scpi-precision-io. NaN/config machinery: measurement- integrity. Proof recipes for the tests: instrument-mock-testing.