Add multimeter simulator
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.
This commit is contained in:
213
src/py_dvt_ate/simulation/virtual/multimeter.py
Normal file
213
src/py_dvt_ate/simulation/virtual/multimeter.py
Normal file
@@ -0,0 +1,213 @@
|
||||
"""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}"
|
||||
Reference in New Issue
Block a user