.. index:: UI adapter, Qt worker, measurement worker, RF-off 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 ------------------------- .. list-table:: UI and worker responsibilities :header-rows: 1 :widths: 24 38 38 * - 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) -> int`` Present a message and return the zero-based index of the selected button. ``buttons`` may be empty for an informational message. ``level`` and ``data`` are presentation hints; measurement logic must not depend on a specific widget representation. ``emit_log(block, *args) -> None`` Forward one log block to the UI and configured log sinks. Preserve the complete text of exceptions and safety messages. ``poll_key() -> int | None`` Poll without blocking. Return one key code or ``None``. Measurement kernels call this at defined intervention and safety points. ``check_interrupt() -> int | None`` Compatibility name delegating to ``poll_key()``. ``pre_user_event()`` and ``post_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: .. code-block:: text 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_requested`` Transfers prompt text, buttons, level, and data into the GUI thread. ``log_requested`` Appends complete log text without touching the widget from the worker. ``status_changed`` Updates 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 ``messenger`` and ``user_interrupt_tester`` callbacks; #. attach the ``QtUIAdapter`` and its manual EUT monitor; #. move ``MeasurementTask`` into a ``QThread``; #. 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_key`` returns 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 :doc:`EUT event structure and assessment rules <../results/immunity-results>` define the data passed back to the measurement kernel. See :doc:`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 :doc:`curated UI API <../../api/ui>` lists the concrete adapter, runner, and worker entry points.