<< All versions

Skill v1.0.0

currentAutomated scan100/100
taewooopark/instrument-control-skills/sweep-engine-design
──Details
PublishedSeptember 27, 2026 at 08:17 AM
Content Hashsha256:aa9c29299612ca10...
Git SHAcefd86028e29
──Files
Files (1 file, 4.6 KB)
SKILL.md4.6 KBactive
SKILL.md · 93 lines · 4.6 KB

version: "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 RUNNING
preflight() config.validate() fail-closed; required instruments
present; interlock closed (None ⇒ refuse)
bring_up() limiter-first transactional apply, level 0, output ON
state MEASURING
for 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:

  1. Abort is an Event set from any thread; every wait is sliced; latency

≤ ~100 ms; shutdown still runs; partial data emitted completed=False.

  1. Per-step isolation: a failing point becomes NaN and the loop continues;

the consecutive-NaN tracker decides when flaky = dead (warn 3 / abort 10).

  1. Cycle-atomic emission: consumers receive whole cycles, or one explicit

partial on abort — never a torn cycle.

  1. 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.

  1. Readback stored, setpoint kept as metadata (integrity rule surfaced at

engine level).

  1. 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.

All versions