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.
Device lifecycle¶
When the DRIVER entry changes, a test UI shuts down the previous driver
instance before adopting the new one. Safety-relevant devices first receive
their corresponding stop command: RFOff for signal generators,
Standby for amplifiers, and Move(0) for motor controllers. A safe
shutdown failure aborts the switch.
Multi-channel UIs close every known channel instance before resetting shared controller state. The motor-controller UI does not send Stop and Quit in parallel with a running worker. A stop or close request made during a call is queued and executed after that call finishes. Nonzero error codes in driver results are treated like exceptions; explicitly defined NB pending states remain valid.
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.
Signal generator test UI¶
In mpylab.device.signalgenerator_ui, RF On, RF Off and the RF status
remain visible above all tabs. Red means ON, green OFF and amber an unknown
or failed state; text also identifies the state. RF On is disabled while a
driver operation is running. RF Off remains available: its request is queued
behind the active bounded bus call and runs next on the same serialized device
worker. This avoids concurrent transport access. RF Off on close remains in
place.
Values selected for a smoke test are copied in the GUI thread before the task is submitted. The worker never reads Qt widgets directly.
In the RF / Level tab, clicking dBm, dBuV, mW or µV immediately applies the
entered number in that unit. Actual level displays the driver readback
in the selected unit. Readback preserves the setpoint; match or
mismatch indicates the comparison. Unavailable or unconvertible values
are explicitly indicated. Conversion uses the existing UConv conventions.
The SMC100A driver reads RF state after Init and on Refresh using
OUTP:STAT?. Invalid replies are treated as errors; a previously known
state is then no longer displayed as current.
Display conversion between power and voltage units assumes 50 ohms, matching the signal-generator base driver SetLevel convention.
Active level units use the same blue highlight as frequency units.
Actual level shows dBm, dBµV, mW and µV together; individual conversions
that cannot be displayed are marked unavailable.
Receiver level display¶
The Measurement tab shows dBµV, dBm, µV and mW together. Power/voltage
conversion uses tools/uconv.py with an explicitly displayed 50-ohm
assumption. Logarithmic values have at most two decimal places; linear
values use four significant digits, with scientific notation for small
values where needed. The original Quantity, including uncertainty, remains
unchanged and is available in the tooltip. Displayed values are nominal.
Pending measurements, errors and missing readings have distinct status
messages. A previous valid value is explicitly labelled as the last valid
reading. Init clears the previous display. Unrepresentable conversions,
such as logarithmic levels at zero power, are marked unavailable.
Last Reading in the Status tab shows the same values.
Level readings also show standard uncertainties: u for linear units,
and asymmetric upper/lower deviations obtained by transforming nominal
value ± standard uncertainty with uconv.quantity_to_db for dB units.
Linear power/voltage uncertainty propagation is first order at an exact
assumed impedance of 50 ohms. A nonpositive lower bound is shown as
unbounded (−∞) in logarithmic units.
Device test UIs run driver tasks on exactly one persistent worker thread by
default. All calls from one UI are therefore serialized on the same thread
while the Qt event loop remains responsive. --no-threaded enables the
synchronous GUI-thread fallback when required. Slow instrument replies can
then trigger an operating-system “not responding” notification. Compatibility
with the VISA/GPIB backend must be checked on the actual setup. Refreshing
detector/preamp displays does not send setting commands.
Spectrum analyzer frequency axis¶
The spectrum view automatically selects Hz, kHz, MHz or GHz for its
frequency axis. Tick values are absolute frequencies in that unit; no
additional Matplotlib offset such as +1e8 is used. CSV export and the
driver interface continue to keep frequencies unchanged in hertz.
The Spectrum tab also provides compact quick controls for center, span,
start, stop, RBW, VBW, reference level, attenuation, preamplifier, detector,
and trace mode. Frequency values are applied with the unit buttons; the
other values have separate Apply and Read buttons. Readbacks and
match/mismatch state are synchronized with the full Controls tab and
use the same driver calls.
After an acquisition, a trace status line shows the frequency range, point count, RBW, detector, trace, and trace mode. It uses existing readbacks and does not trigger additional instrument queries.
Retrigger repeats GetSpectrumNB with a configurable delay. Calls do
not overlap: the next acquisition is scheduled only after the previous one
has completed. Turning retrigger off, leaving the Spectrum tab, Init, Quit,
and closing stop the sequence. The generic synchronous GetSpectrumNB
fallback runs on the persistent device worker by default. With
--no-threaded, slow hardware can block the UI until one acquisition ends.