diff --git a/src/py_dvt_ate/instruments/drivers/__init__.py b/src/py_dvt_ate/instruments/drivers/__init__.py index d0a5f3b..0c5fe76 100644 --- a/src/py_dvt_ate/instruments/drivers/__init__.py +++ b/src/py_dvt_ate/instruments/drivers/__init__.py @@ -5,5 +5,6 @@ and handles responses from instruments. """ from py_dvt_ate.instruments.drivers.base import BaseDriver +from py_dvt_ate.instruments.drivers.chamber import ThermalChamberDriver -__all__ = ["BaseDriver"] +__all__ = ["BaseDriver", "ThermalChamberDriver"] diff --git a/src/py_dvt_ate/instruments/drivers/chamber.py b/src/py_dvt_ate/instruments/drivers/chamber.py new file mode 100644 index 0000000..dc45d4f --- /dev/null +++ b/src/py_dvt_ate/instruments/drivers/chamber.py @@ -0,0 +1,141 @@ +"""Thermal chamber SCPI driver. + +This module implements a client-side driver for thermal chambers that +communicate via SCPI commands. +""" + +import time + +from py_dvt_ate.instruments.drivers.base import BaseDriver + + +class ThermalChamberDriver(BaseDriver): + """SCPI driver for thermal chambers. + + Provides high-level Python API for controlling thermal chambers via + SCPI commands. Implements the IThermalChamber interface. + + SCPI Commands Used: + TEMP:SETPOINT - Set target temperature (°C) + TEMP:SETPOINT? - Query current setpoint + TEMP:ACTUAL? - Query actual chamber temperature + TEMP:STAB? - Query stability (1=stable, 0=settling) + TEMP:RAMP - Set temperature ramp rate (°C/min) + TEMP:RAMP? - Query ramp rate + + Example: + >>> transport = TCPTransport("localhost", 5001) + >>> chamber = ThermalChamberDriver(transport) + >>> chamber.connect() + >>> chamber.set_temperature(85.0) + >>> chamber.wait_until_stable(timeout=600.0) + >>> temp = chamber.get_temperature() + """ + + def set_temperature(self, setpoint: float) -> None: + """Set the chamber temperature setpoint. + + Args: + setpoint: Target temperature in degrees Celsius. + + Raises: + ConnectionError: If not connected. + IOError: If command fails. + """ + self.write(f"TEMP:SETPOINT {setpoint:.2f}") + + def get_temperature(self) -> float: + """Get the actual chamber temperature. + + Returns: + Current chamber temperature in degrees Celsius. + + Raises: + ConnectionError: If not connected. + IOError: If query fails. + """ + return self.query_float("TEMP:ACTUAL?") + + def get_setpoint(self) -> float: + """Get the current temperature setpoint. + + Returns: + Current setpoint in degrees Celsius. + + Raises: + ConnectionError: If not connected. + IOError: If query fails. + """ + return self.query_float("TEMP:SETPOINT?") + + 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. + + Returns: + True if temperature is stable, False if still settling. + + Raises: + ConnectionError: If not connected. + IOError: If query fails. + """ + return self.query_bool("TEMP:STAB?") + + 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. + + 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. + IOError: If communication fails. + ValueError: If timeout or poll_interval are negative. + """ + if timeout < 0: + raise ValueError("Timeout must be non-negative") + if poll_interval <= 0: + raise ValueError("Poll interval must be positive") + + start_time = time.time() + while time.time() - start_time < timeout: + if self.is_stable(): + return True + time.sleep(poll_interval) + + return False + + def set_ramp_rate(self, rate: float) -> None: + """Set the temperature ramp rate. + + Args: + rate: Ramp rate in degrees Celsius per minute. + + Raises: + ConnectionError: If not connected. + IOError: If command fails. + """ + self.write(f"TEMP:RAMP {rate:.2f}") + + def get_ramp_rate(self) -> float: + """Get the current temperature ramp rate. + + Returns: + Ramp rate in degrees Celsius per minute. + + Raises: + ConnectionError: If not connected. + IOError: If query fails. + """ + return self.query_float("TEMP:RAMP?")