Add instrument interface protocols
This commit is contained in:
@@ -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",
|
||||||
|
]
|
||||||
|
|||||||
362
src/py_dvt_ate/instruments/interfaces.py
Normal file
362
src/py_dvt_ate/instruments/interfaces.py
Normal 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
|
||||||
Reference in New Issue
Block a user