From 552c43d04d7e95c8da87f1cd32e2809d2879cb36 Mon Sep 17 00:00:00 2001 From: Kai Chappell Date: Sat, 26 Jul 2025 19:22:27 +0000 Subject: [PATCH] Add instrument factory --- src/py_dvt_ate/instruments/__init__.py | 8 ++ src/py_dvt_ate/instruments/factory.py | 176 +++++++++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 src/py_dvt_ate/instruments/factory.py diff --git a/src/py_dvt_ate/instruments/__init__.py b/src/py_dvt_ate/instruments/__init__.py index 48892df..3e6d891 100644 --- a/src/py_dvt_ate/instruments/__init__.py +++ b/src/py_dvt_ate/instruments/__init__.py @@ -8,6 +8,11 @@ This package provides everything needed to communicate with lab instruments: - Factory for creating configured instrument sets """ +from py_dvt_ate.instruments.factory import ( + InstrumentConfig, + InstrumentFactory, + InstrumentSet, +) from py_dvt_ate.instruments.interfaces import ( IMultimeter, IPowerSupply, @@ -18,4 +23,7 @@ __all__ = [ "IThermalChamber", "IPowerSupply", "IMultimeter", + "InstrumentSet", + "InstrumentConfig", + "InstrumentFactory", ] diff --git a/src/py_dvt_ate/instruments/factory.py b/src/py_dvt_ate/instruments/factory.py new file mode 100644 index 0000000..7db631b --- /dev/null +++ b/src/py_dvt_ate/instruments/factory.py @@ -0,0 +1,176 @@ +"""Instrument factory for creating configured instrument sets. + +This module provides a factory pattern for creating sets of instruments +based on configuration. It abstracts away the choice between simulated +and real hardware, allowing test code to be written once and run against +either backend. +""" + +from dataclasses import dataclass +from typing import Literal + +from py_dvt_ate.instruments.interfaces import IMultimeter, IPowerSupply, IThermalChamber + + +@dataclass +class InstrumentSet: + """Container for a complete set of instruments. + + Holds all instruments needed for DVT testing. All instruments implement + the interface protocols (IThermalChamber, IPowerSupply, IMultimeter), + allowing them to be simulated or real hardware. + + Attributes: + chamber: Thermal chamber for temperature control. + psu: Programmable power supply for DUT power. + dmm: Digital multimeter for precision measurements. + """ + + chamber: IThermalChamber + psu: IPowerSupply + dmm: IMultimeter + + +@dataclass +class InstrumentConfig: + """Configuration for instrument connections. + + Defines how to connect to instruments. The backend determines whether + to use simulated instruments (TCP connections to virtual instruments) + or real hardware (PyVISA connections). + + Attributes: + backend: "simulator" for virtual instruments, "pyvisa" for real hardware. + + Simulator Settings: + simulator_host: Hostname/IP of simulation server. Default "localhost". + chamber_port: TCP port for thermal chamber simulator. Default 5001. + psu_port: TCP port for power supply simulator. Default 5002. + dmm_port: TCP port for multimeter simulator. Default 5003. + + PyVISA Settings (for real hardware): + chamber_visa: VISA resource string for thermal chamber (e.g., "TCPIP::192.168.1.10::INSTR"). + psu_visa: VISA resource string for power supply. + dmm_visa: VISA resource string for multimeter. + """ + + backend: Literal["simulator", "pyvisa"] + + # Simulator settings + simulator_host: str = "localhost" + chamber_port: int = 5001 + psu_port: int = 5002 + dmm_port: int = 5003 + + # PyVISA settings (for real hardware) + chamber_visa: str | None = None + psu_visa: str | None = None + dmm_visa: str | None = None + + +class InstrumentFactory: + """Factory for creating instrument sets from configuration. + + This factory encapsulates the creation logic for instrument sets, + hiding the complexity of instantiating transports and drivers based + on the chosen backend. + + Example: + >>> config = InstrumentConfig(backend="simulator") + >>> instruments = InstrumentFactory.create(config) + >>> instruments.chamber.set_temperature(85.0) + >>> instruments.psu.set_voltage(1, 3.3) + >>> voltage = instruments.dmm.measure_dc_voltage() + """ + + @staticmethod + def create(config: InstrumentConfig) -> InstrumentSet: + """Create instrument set based on configuration. + + Args: + config: Configuration specifying backend and connection details. + + Returns: + InstrumentSet containing all configured instruments. + + Raises: + ValueError: If backend is unknown or configuration is invalid. + ConnectionError: If unable to connect to instruments. + """ + if config.backend == "simulator": + return InstrumentFactory._create_simulated(config) + elif config.backend == "pyvisa": + return InstrumentFactory._create_pyvisa(config) + else: + raise ValueError(f"Unknown backend: {config.backend}") + + @staticmethod + def _create_simulated(config: InstrumentConfig) -> InstrumentSet: + """Create simulated instruments connected via TCP. + + Creates TCP transports for each virtual instrument and wraps them + in SCPI drivers. The simulation server must be running and listening + on the configured ports. + + Args: + config: Configuration with simulator_host and port settings. + + Returns: + InstrumentSet with simulated instruments. + + Raises: + ConnectionError: If unable to connect to simulation server. + """ + from py_dvt_ate.instruments.drivers.chamber import ThermalChamberDriver + from py_dvt_ate.instruments.drivers.multimeter import MultimeterDriver + from py_dvt_ate.instruments.drivers.power_supply import PowerSupplyDriver + from py_dvt_ate.instruments.transport.tcp import TCPTransport + + # Create transports for each instrument + chamber_transport = TCPTransport(config.simulator_host, config.chamber_port) + psu_transport = TCPTransport(config.simulator_host, config.psu_port) + dmm_transport = TCPTransport(config.simulator_host, config.dmm_port) + + # Wrap transports in drivers + return InstrumentSet( + chamber=ThermalChamberDriver(chamber_transport), + psu=PowerSupplyDriver(psu_transport), + dmm=MultimeterDriver(dmm_transport), + ) + + @staticmethod + def _create_pyvisa(config: InstrumentConfig) -> InstrumentSet: + """Create PyVISA instruments for real hardware. + + Creates VISA transports for each real instrument and wraps them + in SCPI drivers. Requires PyVISA to be installed and VISA resource + strings to be configured. + + Args: + config: Configuration with chamber_visa, psu_visa, dmm_visa settings. + + Returns: + InstrumentSet with real hardware instruments. + + Raises: + NotImplementedError: PyVISA backend not yet implemented. + ValueError: If required VISA resource strings are missing. + """ + # Future implementation would use pyvisa.ResourceManager + # to create VISA transports: + # + # import pyvisa + # from py_dvt_ate.instruments.transport.visa import VISATransport + # + # rm = pyvisa.ResourceManager() + # chamber_transport = VISATransport(rm.open_resource(config.chamber_visa)) + # psu_transport = VISATransport(rm.open_resource(config.psu_visa)) + # dmm_transport = VISATransport(rm.open_resource(config.dmm_visa)) + # + # return InstrumentSet( + # chamber=ThermalChamberDriver(chamber_transport), + # psu=PowerSupplyDriver(psu_transport), + # dmm=MultimeterDriver(dmm_transport), + # ) + + raise NotImplementedError("PyVISA backend not yet implemented")