"""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