Implement SCPI-based virtual DMM with DC voltage and current measurement. Supports MEAS, CONF, and READ commands. Integrates with physics engine for DUT output measurements.
214 lines
6.2 KiB
Python
214 lines
6.2 KiB
Python
"""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}"
|