Add instrument interface protocols

This commit is contained in:
2025-07-19 14:08:46 +00:00
parent 5cd722664a
commit cf24462472
2 changed files with 374 additions and 0 deletions

View File

@@ -7,3 +7,15 @@ This package provides everything needed to communicate with lab instruments:
- Instrument drivers - Instrument drivers
- Factory for creating configured instrument sets - Factory for creating configured instrument sets
""" """
from py_dvt_ate.instruments.interfaces import (
IMultimeter,
IPowerSupply,
IThermalChamber,
)
__all__ = [
"IThermalChamber",
"IPowerSupply",
"IMultimeter",
]

View File

@@ -0,0 +1,362 @@
"""Instrument interface protocols.
This module defines the Hardware Abstraction Layer (HAL) interfaces for all
laboratory instruments used in DVT testing. These protocols allow test code
to be written against abstract interfaces rather than concrete implementations,
enabling seamless switching between simulated and real hardware.
The interfaces use ABC (Abstract Base Classes) for maximum type safety and
explicit interface implementation. All drivers must inherit from these base
classes and implement all abstract methods.
"""
from abc import ABC, abstractmethod
class IThermalChamber(ABC):
"""Hardware abstraction for thermal chambers.
Defines the interface for controlling environmental temperature during
thermal characterisation tests. Implementations may be virtual instruments
(simulators) or real hardware drivers.
Temperature units are always degrees Celsius.
"""
@abstractmethod
def set_temperature(self, setpoint: float) -> None:
"""Set the chamber temperature setpoint.
Args:
setpoint: Target temperature in degrees Celsius.
Raises:
ConnectionError: If not connected to instrument.
IOError: If command fails or instrument reports error.
"""
pass
@abstractmethod
def get_temperature(self) -> float:
"""Get the actual chamber temperature.
Returns:
Current chamber air temperature in degrees Celsius.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
"""
pass
@abstractmethod
def get_setpoint(self) -> float:
"""Get the current temperature setpoint.
Returns:
Current target temperature in degrees Celsius.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
"""
pass
@abstractmethod
def is_stable(self) -> bool:
"""Check if chamber temperature is stable.
Temperature is considered stable when it has settled within
the instrument's configured stability threshold of the setpoint
for a minimum dwell time.
Returns:
True if temperature is stable, False if still settling.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
"""
pass
@abstractmethod
def wait_until_stable(
self, timeout: float = 300.0, poll_interval: float = 1.0
) -> bool:
"""Wait until chamber temperature stabilises.
Polls the stability status at regular intervals until stable
or timeout is reached. This is a blocking call.
Args:
timeout: Maximum time to wait in seconds. Default 300s (5 minutes).
poll_interval: Time between stability checks in seconds. Default 1s.
Returns:
True if temperature stabilised within timeout, False if timed out.
Raises:
ConnectionError: If not connected to instrument.
IOError: If communication fails.
ValueError: If timeout or poll_interval are invalid.
"""
pass
@abstractmethod
def set_ramp_rate(self, rate: float) -> None:
"""Set the temperature ramp rate.
Controls how quickly the chamber changes temperature when moving
to a new setpoint. Slower ramp rates reduce thermal shock to DUT.
Args:
rate: Ramp rate in degrees Celsius per minute.
Raises:
ConnectionError: If not connected to instrument.
IOError: If command fails or instrument reports error.
ValueError: If rate is negative or exceeds instrument limits.
"""
pass
class IPowerSupply(ABC):
"""Hardware abstraction for programmable power supplies.
Defines the interface for controlling DC power supplies during electrical
characterisation tests. Implementations may be virtual instruments
(simulators) or real hardware drivers.
Voltage units are always volts (V).
Current units are always amps (A).
"""
@abstractmethod
def set_voltage(self, channel: int, voltage: float) -> None:
"""Set the output voltage setpoint.
Args:
channel: Output channel number (1-based indexing).
voltage: Target voltage in volts.
Raises:
ConnectionError: If not connected to instrument.
IOError: If command fails or instrument reports error.
ValueError: If channel is invalid or voltage out of range.
"""
pass
@abstractmethod
def get_voltage(self, channel: int) -> float:
"""Get the voltage setpoint.
Returns the programmed voltage, not the measured output voltage.
Use measure_voltage() to get the actual output voltage.
Args:
channel: Output channel number (1-based indexing).
Returns:
Current voltage setpoint in volts.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If channel is invalid.
"""
pass
@abstractmethod
def set_current_limit(self, channel: int, current: float) -> None:
"""Set the current limit.
The supply will operate in constant voltage mode until output current
reaches this limit, then transition to constant current mode.
Args:
channel: Output channel number (1-based indexing).
current: Current limit in amps.
Raises:
ConnectionError: If not connected to instrument.
IOError: If command fails or instrument reports error.
ValueError: If channel is invalid or current out of range.
"""
pass
@abstractmethod
def get_current_limit(self, channel: int) -> float:
"""Get the current limit.
Args:
channel: Output channel number (1-based indexing).
Returns:
Current limit in amps.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If channel is invalid.
"""
pass
@abstractmethod
def measure_voltage(self, channel: int) -> float:
"""Measure the actual output voltage.
Args:
channel: Output channel number (1-based indexing).
Returns:
Measured output voltage in volts.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If channel is invalid.
"""
pass
@abstractmethod
def measure_current(self, channel: int) -> float:
"""Measure the actual output current.
Args:
channel: Output channel number (1-based indexing).
Returns:
Measured output current in amps.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If channel is invalid.
"""
pass
@abstractmethod
def enable_output(self, channel: int, enable: bool) -> None:
"""Enable or disable the output.
Args:
channel: Output channel number (1-based indexing).
enable: True to enable output, False to disable.
Raises:
ConnectionError: If not connected to instrument.
IOError: If command fails or instrument reports error.
ValueError: If channel is invalid.
"""
pass
@abstractmethod
def is_output_enabled(self, channel: int) -> bool:
"""Check if output is enabled.
Args:
channel: Output channel number (1-based indexing).
Returns:
True if output is enabled, False if disabled.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If channel is invalid.
"""
pass
class IMultimeter(ABC):
"""Hardware abstraction for digital multimeters.
Defines the interface for making precision measurements with DMMs during
electrical characterisation tests. Implementations may be virtual instruments
(simulators) or real hardware drivers.
Voltage units are always volts (V).
Current units are always amps (A).
Resistance units are always ohms (Ω).
"""
@abstractmethod
def measure_dc_voltage(self, range: str = "AUTO") -> float:
"""Measure DC voltage.
Configures the meter for DC voltage and takes a measurement.
Args:
range: Measurement range. Default "AUTO" for auto-ranging.
Specific ranges depend on instrument capabilities.
Returns:
Measured voltage in volts.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If range is invalid for this instrument.
"""
pass
@abstractmethod
def measure_dc_current(self, range: str = "AUTO") -> float:
"""Measure DC current.
Configures the meter for DC current and takes a measurement.
Args:
range: Measurement range. Default "AUTO" for auto-ranging.
Specific ranges depend on instrument capabilities.
Returns:
Measured current in amps.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If range is invalid for this instrument.
"""
pass
@abstractmethod
def measure_resistance(self, range: str = "AUTO") -> float:
"""Measure resistance.
Configures the meter for resistance and takes a measurement.
Args:
range: Measurement range. Default "AUTO" for auto-ranging.
Specific ranges depend on instrument capabilities.
Returns:
Measured resistance in ohms.
Raises:
ConnectionError: If not connected to instrument.
IOError: If query fails or instrument reports error.
ValueError: If range is invalid for this instrument.
NotImplementedError: If instrument does not support resistance.
"""
pass
@abstractmethod
def set_integration_time(self, nplc: float) -> None:
"""Set the integration time.
Integration time affects measurement accuracy and speed. Higher
values (more power line cycles) provide better noise rejection
but take longer to measure.
Args:
nplc: Integration time in number of power line cycles (NPLC).
Typical values: 0.02, 0.2, 1, 10, 100.
Raises:
ConnectionError: If not connected to instrument.
IOError: If command fails or instrument reports error.
ValueError: If nplc is invalid for this instrument.
NotImplementedError: If instrument does not support integration time.
"""
pass