"""Virtual digital multimeter (DMM) simulator. This module implements a SCPI-based virtual multimeter that interfaces with the physics engine to measure DUT electrical parameters. """ from __future__ import annotations from enum import Enum from typing import TYPE_CHECKING from py_dvt_ate.instruments.scpi import SCPICommand from py_dvt_ate.simulation.virtual.base import BaseInstrument if TYPE_CHECKING: from py_dvt_ate.simulation.physics.engine import PhysicsEngine class MeasurementFunction(Enum): """Available measurement functions.""" VOLTAGE_DC = "VOLT:DC" CURRENT_DC = "CURR:DC" class MultimeterSim(BaseInstrument): """Virtual digital multimeter simulator. Simulates a digital multimeter with SCPI control interface. The DMM measures DUT output voltage and load current via the physics engine. SCPI Commands: MEAS:VOLT:DC? - Measure DC voltage (shortcut) MEAS:CURR:DC? - Measure DC current (shortcut) CONF:VOLT:DC - Configure for DC voltage measurement CONF:CURR:DC - Configure for DC current measurement CONF? - Query current configuration READ? - Take measurement with current configuration Attributes: manufacturer: "PyDVTATE" model: "DMM-SIM-001" """ manufacturer = "PyDVTATE" model = "DMM-SIM-001" serial_number = "DMMSIM001" firmware_version = "1.0.0" def __init__(self, physics_engine: PhysicsEngine | None = None) -> None: """Initialise the multimeter simulator. Args: physics_engine: Reference to physics engine for measurement values. """ self._function = MeasurementFunction.VOLTAGE_DC super().__init__(physics_engine) def _setup_commands(self) -> None: """Register multimeter SCPI commands.""" self.register_command("MEAS:VOLT:DC", self._handle_meas_volt_dc) self.register_command("MEAS:CURR:DC", self._handle_meas_curr_dc) self.register_command("CONF:VOLT:DC", self._handle_conf_volt_dc) self.register_command("CONF:CURR:DC", self._handle_conf_curr_dc) self.register_command("CONF", self._handle_conf) self.register_command("READ", self._handle_read) def reset(self) -> None: """Reset multimeter to default state.""" self._function = MeasurementFunction.VOLTAGE_DC def _handle_meas_volt_dc(self, command: SCPICommand) -> str: """Handle MEAS:VOLT:DC? query. Configures for DC voltage and takes measurement in one command. Args: command: Parsed SCPI command. Returns: Measured DC voltage. Raises: ValueError: If used as command (not query). """ if not command.is_query: raise ValueError("MEAS:VOLT:DC is query only") self._function = MeasurementFunction.VOLTAGE_DC return self._measure_voltage_dc() def _handle_meas_curr_dc(self, command: SCPICommand) -> str: """Handle MEAS:CURR:DC? query. Configures for DC current and takes measurement in one command. Args: command: Parsed SCPI command. Returns: Measured DC current. Raises: ValueError: If used as command (not query). """ if not command.is_query: raise ValueError("MEAS:CURR:DC is query only") self._function = MeasurementFunction.CURRENT_DC return self._measure_current_dc() def _handle_conf_volt_dc(self, command: SCPICommand) -> str: """Handle CONF:VOLT:DC command. Configures multimeter for DC voltage measurement. Args: command: Parsed SCPI command. Returns: Empty string (no response for configuration). Raises: ValueError: If used as query. """ if command.is_query: raise ValueError("CONF:VOLT:DC is command only") self._function = MeasurementFunction.VOLTAGE_DC return "" def _handle_conf_curr_dc(self, command: SCPICommand) -> str: """Handle CONF:CURR:DC command. Configures multimeter for DC current measurement. Args: command: Parsed SCPI command. Returns: Empty string (no response for configuration). Raises: ValueError: If used as query. """ if command.is_query: raise ValueError("CONF:CURR:DC is command only") self._function = MeasurementFunction.CURRENT_DC return "" def _handle_conf(self, command: SCPICommand) -> str: """Handle CONF? query. Args: command: Parsed SCPI command. Returns: Current measurement configuration. Raises: ValueError: If used as command without subcommand. """ if not command.is_query: raise ValueError("CONF requires a function (e.g., CONF:VOLT:DC)") return f'"{self._function.value}"' def _handle_read(self, command: SCPICommand) -> str: """Handle READ? query. Takes measurement using current configuration. Args: command: Parsed SCPI command. Returns: Measured value. Raises: ValueError: If used as command (not query). """ if not command.is_query: raise ValueError("READ is query only") if self._function == MeasurementFunction.VOLTAGE_DC: return self._measure_voltage_dc() else: return self._measure_current_dc() def _measure_voltage_dc(self) -> str: """Measure DC voltage from physics engine. Returns: Formatted voltage reading. """ if self._physics_engine is None: return "0.000000" electrical_state = self._physics_engine.get_electrical_state() return f"{electrical_state.output_voltage:.6f}" def _measure_current_dc(self) -> str: """Measure DC current from physics engine. Returns: Formatted current reading. """ if self._physics_engine is None: return "0.000000" electrical_state = self._physics_engine.get_electrical_state() return f"{electrical_state.load_current:.6f}"