Using and extending EUT monitoring ================================== EUT monitoring is part of every immunity test. mpylab separates three responsibilities: * monitors observe the EUT and produce structured events; * the measurement kernel decides whether to continue, retry, or stop; * the measurement worker immediately performs safety actions such as RF-off. A monitor therefore does not access ``MGraph`` or RF instruments directly. Manual intervention remains available alongside automatic monitoring. The complete :doc:`event structure and assessment rules <../results/immunity-results>` are documented separately. Using existing measurement classes ---------------------------------- ``MSC.Measure_Immunity`` and ``TEMCell.Measure_Immunity`` accept the same fundamental parameters: ``eut_monitor`` One automatic ``EUTMonitor`` or a sequence of monitors. ``manual_eut_monitor`` Optional manual monitor owned by a UI. When omitted, the measurement class creates a keyboard-backed fallback. ``performance_criterion`` Performance criterion ``"A"``, ``"B"``, or ``"C"``: * **A:** The EUT must operate as intended during and after exposure without unacceptable degradation. * **B:** Temporary degradation during exposure is permitted. Afterwards, the EUT must recover normal operation automatically; its operating mode and stored data must not be lost. * **C:** Temporary loss of function is permitted. Normal operation may be restored automatically, by operator intervention, or by reset. The applicable product standard may refine these general criteria or add further requirements. ``post_exposure_timeout`` Observation time after RF-off. This is particularly relevant when assessing recovery under criterion B or C. ``eut_event_policy`` Application decision for ``degraded``, ``failed``, and ``not_evaluated``. The ``safety_action="rf_off"`` request is handled independently of this policy. Use a reproducible virtual monitor for the first run: .. code-block:: python from mpylab.env.eut import VirtualEUTMonitor monitor = VirtualEUTMonitor([{ "status": "degraded", "reason": "virtual_output_tolerance", "safety_action": "none", }]) status = measurement.Measure_Immunity( description="EUT", eut_monitor=monitor, performance_criterion="B", post_exposure_timeout=10.0, eut_event_policy={ "degraded": "continue", "failed": "stop", "not_evaluated": "stop", "max_retries": 0, }, # further method-specific parameters ) The MSC threshold search receives its monitor in the ``ImmunityThresholdKernel`` parameter block. Qt applications pass their ``ManualEUTMonitor`` and automatic monitors to the measurement class. TEMField provides ``set_automatic_eut_monitor`` for this purpose. See the :doc:`MSC <../measurements/msc>` and :doc:`TEM guides <../measurements/tem>` for their complete workflows. Developing a specific monitor ----------------------------- An application-specific monitor derives from ``EUTMonitor``. Typical sources include camera images, communication responses, digital states, or process measurements. ``poll_event`` must be non-blocking or have a clearly bounded runtime: .. code-block:: python from mpylab.env.eut import EUTMonitor, make_eut_event class CommunicationEUTMonitor(EUTMonitor): def start_exposure(self, context): self.frequency = context.get("frequency") self.start_observation() def poll_event(self): observation = self.read_status_nonblocking() if observation is None: return None if observation == "ok": return make_eut_event( "passed", "communication_ok", source="eut_link", ) return make_eut_event( "failed", "communication_lost", details={"frequency": self.frequency}, safety_action="rf_off", source="eut_link", ) def stop_exposure(self): self.stop_observation() def close(self): self.close_connection() Wrap instrument access with significant latency: .. code-block:: python from mpylab.env.eut import ThreadedEUTMonitor monitor = ThreadedEUTMonitor( CommunicationEUTMonitor(), poll_interval=0.02, join_timeout=1.0, ) The wrapped ``poll_event`` call must still have a finite maximum runtime so that the thread can terminate safely. Failure of an automatic monitor is stored as ``monitor_diagnostic`` and must not be reported as an observed EUT failure. Integrating a new application ----------------------------- ``EUTMonitoringSession`` combines manual and automatic monitors and manages exposure lifecycle, phases, event validation, and diagnostics: .. code-block:: python from mpylab.env.eut import EUTMonitoringSession, ManualEUTMonitor manual = ManualEUTMonitor(poll_key=my_poll_key, keylist="sS") session = EUTMonitoringSession.from_monitors(manual, [monitor]) try: session.start_exposure({"frequency": frequency, "position": position}) event = session.poll_event() if event is not None and event["safety_action"] == "rf_off": measurement_worker.rf_off() # immediate safety path session.start_phase( "post_exposure", {"frequency": frequency, "position": position, "rf_on": False}, ) finally: session.close() The session never switches RF itself. A new measurement application must perform safety actions in its worker, preserve monitor events in the measurement history, and call ``close`` from a ``finally`` block. RF-off must precede monitor cleanup that might fail. Testing without hardware ------------------------ * ``VirtualEUTMonitor`` emits predefined events deterministically. * ``RandomEUTMonitor(seed=...)`` is useful for longer workflow and UI tests, but does not demonstrate real EUT behavior. * Tests must cover manual intervention, immediate RF-off, post-exposure, monitor diagnostics, and cleanup failures. * A virtual end-to-end run should use the same measurement-class parameters as the later hardware run. The curated :doc:`Python API <../../api/measurement>` documents every public monitor and session method. Thread and GUI rules are covered by :doc:`ui-workers`.