UI adapters and worker interface¶
Measurement classes are independent of a terminal or Qt presentation. A
MeasurementUIAdapter carries operator prompts, logging, and manual
interrupts across that boundary. Long-running measurement and instrument
operations are a separate concern and execute in a worker.
Responsibility boundaries¶
Component |
Owns |
Must not own |
|---|---|---|
Measurement class |
Workflow, autosave, resume, EUT assessment, device lifecycle, safety |
Widget state and Qt event processing |
UI adapter |
Prompts, log transport, operator events, interrupt requests |
Measurement decisions and direct device control |
Worker |
Blocking script or device call, cooperative cancellation, final state |
Direct widget modification |
GUI thread |
Widgets, dialogs, plots, and event loop |
Blocking measurement or communication calls |
Adapter methods¶
ask(msg, buttons, level, data) -> intPresent a message and return the zero-based index of the selected button.
buttonsmay be empty for an informational message.levelanddataare presentation hints; measurement logic must not depend on a specific widget representation.emit_log(block, *args) -> NoneForward one log block to the UI and configured log sinks. Preserve the complete text of exceptions and safety messages.
poll_key() -> int | NonePoll without blocking. Return one key code or
None. Measurement kernels call this at defined intervention and safety points.check_interrupt() -> int | NoneCompatibility name delegating to
poll_key().pre_user_event()andpost_user_event()Mark the beginning and end of an operator interaction, for example to update UI status or pause terminal handling.
run_interactive(obj, banner)Start an interactive terminal session where supported. A GUI may report that this operation is unavailable.
The concrete TUIAdapter additionally supports replacing its messenger,
logger, interrupt tester, pre/post callbacks, and interactive runner. A custom
adapter must provide the corresponding setter only if application code calls
the related Measure.set_* method after installing that adapter.
Binding to Measure¶
Measure creates a TUIAdapter by default. set_ui_adapter(adapter)
binds these compatibility attributes to the new adapter:
messenger -> ui.ask
UserInterruptTester -> ui.check_interrupt
PollKey -> ui.poll_key
PreUserEvent -> ui.pre_user_event
PostUserEvent -> ui.post_user_event
Use set_user_interrupt_tester in maintained code. The historical spelling
set_user_interrupt_Tester is only a compatibility alias. Pickle restore
recreates the default TUI adapter because UI objects and callbacks are not
serialized. A script or runner therefore attaches its desired adapter after
loading the history.
resolve_poll_key(handler, caller_locals) accepts an explicit callable and
retains caller-frame lookup only as a compatibility fallback. New kernels
pass the polling callable explicitly.
Qt adapter¶
QtUIAdapter communicates with MeasurementControlWidget through queued
Qt signals:
prompt_requestedTransfers prompt text, buttons, level, and data into the GUI thread.
log_requestedAppends complete log text without touching the widget from the worker.
status_changedUpdates the visible lifecycle state.
ask blocks the measurement worker on a threading.Condition while the
Qt event loop remains responsive. A button calls submit_answer in the GUI
thread and wakes the worker. Never call this blocking method from the GUI
thread itself.
The shared qt_runner performs the standard wiring:
load the text measurement script as a module;
copy and update its configuration;
record sources and changes of the effective configuration;
remove terminal-only
messengeranduser_interrupt_testercallbacks;attach the
QtUIAdapterand its manual EUT monitor;move
MeasurementTaskinto aQThread;return success, stop, and full exceptions through signals;
stop and delete worker objects when the thread finishes.
The control window separates measurement and effective configuration into tabs. The configuration tab is read-only and shows each value together with the last source that changed it. Stop / RF Off remains outside the tabs, so it stays immediately available while the operator inspects configuration.
Stop and RF-off¶
The Qt Stop / RF Off button follows two paths:
if an active prompt offers
Quit, it submits that answer;otherwise it records a thread-safe interrupt request.
poll_keyreturns the configured synthetic key exactly once.
The adapter does not access hardware. The measurement worker receives the request at its next polling point and runs the normal RF-off and finalization path. Responsiveness therefore depends on bounded instrument calls and short, documented polling intervals. A worker blocked indefinitely in a driver cannot be made safe merely by adding a GUI button.
Only use a separate immediate hardware safety path when the device and communication layer explicitly permit it. Serialize access to a shared transport; concurrent commands from GUI and worker threads can corrupt protocol state.
Manual and automatic EUT monitoring¶
Manual intervention remains available for every immunity exposure.
QtUIAdapter.manual_eut_monitor is attached to applicable measurement
parameters by qt_runner. During exposure, the operator can submit
degraded, failed, or not_evaluated. After exposure, state and
recovery are entered separately.
Automatic camera, communication, or measurement-channel monitors supplement
this manual path through CompositeEUTMonitor and may run through
ThreadedEUTMonitor. They do not replace operator input. The
EUT event structure and assessment rules define the data passed back to the measurement
kernel.
See Using and extending EUT monitoring for the complete usage and extension guide.
Worker design¶
A worker needs one start slot, bounded operations, cooperative cancellation, and exactly one terminal outcome for success, cancellation, or error. Qt signals should carry small immutable objects. High-rate scans must batch progress data; thousands of queued point signals can make the GUI appear blocked after the instrument has already finished.
Callbacks from ReceiverScanWorker execute in its Python worker thread.
GUI applications must bridge them through Qt signals or a thread-safe queue.
Its optional safety_stop is called at most once on cancellation or error.
Closing a window while work is active must request stop, wait for a bounded
period, and present a clear final state. Calling QThread.quit() only stops
the thread event loop; it does not interrupt arbitrary Python code already
running in the worker slot.
Implementation checklist¶
For a new UI:
implement every core adapter method with the return semantics above;
create and modify widgets only in the GUI thread;
execute the complete measurement script in one owned worker;
bridge prompts, logs, progress, cancellation, and exceptions explicitly;
make manual EUT reporting always reachable;
test stop during waits, scans, prompts, leveling, and error handling;
preserve the complete RF-off/finalization path and autosave state.
Tests¶
Run Qt tests with QT_QPA_PLATFORM=offscreen. Verify at least that the UI
remains responsive, prompts wake the worker, progress appears incrementally,
stop is consumed once and causes RF-off, errors retain their traceback, EUT
state and recovery remain distinct, and closing has bounded behavior.
The curated UI API lists the concrete adapter, runner, and worker entry points.