Move InstrumentServer to instruments/transport
InstrumentServer is a general-purpose SCPI-over-TCP server that can host any device implementing the SCPIDevice protocol (process method). Moving it from simulation/ to instruments/transport/ reflects this: - simulation package now depends on instruments package - InstrumentServer can host both virtual and real instrument adapters - Added SCPIDevice Protocol for type-safe device registration
This commit is contained in:
@@ -1,6 +1,11 @@
|
||||
"""Transport layer for instrument communication.
|
||||
|
||||
Provides connection abstractions for different backends:
|
||||
- TCP server for hosting SCPI instruments
|
||||
- TCP sockets (for simulation server)
|
||||
- PyVISA (for real instruments)
|
||||
"""
|
||||
|
||||
from py_dvt_ate.instruments.transport.server import InstrumentServer, SCPIDevice
|
||||
|
||||
__all__ = ["InstrumentServer", "SCPIDevice"]
|
||||
|
||||
@@ -1,32 +1,55 @@
|
||||
"""Async TCP server for exposing virtual instruments over network.
|
||||
"""Async TCP server for exposing instruments over network.
|
||||
|
||||
This module provides the InstrumentServer class that hosts virtual SCPI
|
||||
This module provides the InstrumentServer class that hosts SCPI
|
||||
instruments over TCP, allowing client applications to communicate using
|
||||
standard SCPI commands over a network connection.
|
||||
|
||||
This is a general-purpose server that works with any object implementing
|
||||
the SCPIDevice protocol (having a process(command) -> str method).
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
from typing import TYPE_CHECKING
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from py_dvt_ate.simulation.virtual.base import BaseInstrument
|
||||
|
||||
# Re-export for type checking - actual import happens at runtime via registration
|
||||
__all__ = ["InstrumentServer"]
|
||||
__all__ = ["InstrumentServer", "SCPIDevice"]
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class SCPIDevice(Protocol):
|
||||
"""Protocol for SCPI-compatible devices.
|
||||
|
||||
Any object with a process method matching this signature can be
|
||||
served by InstrumentServer.
|
||||
"""
|
||||
|
||||
def process(self, command: str) -> str:
|
||||
"""Process a SCPI command and return the response.
|
||||
|
||||
Args:
|
||||
command: SCPI command string to process.
|
||||
|
||||
Returns:
|
||||
Response string (may be empty for commands with no response).
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class InstrumentServer:
|
||||
"""Async TCP server hosting virtual SCPI instruments.
|
||||
"""Async TCP server hosting SCPI instruments.
|
||||
|
||||
Each instrument is assigned a port. Clients connect via TCP and send
|
||||
SCPI commands as newline-terminated strings. Responses are also
|
||||
newline-terminated.
|
||||
|
||||
This server can host any device implementing the SCPIDevice protocol,
|
||||
including both virtual instruments (simulators) and adapters for
|
||||
real hardware.
|
||||
|
||||
Attributes:
|
||||
host: Host address to bind to.
|
||||
"""
|
||||
@@ -38,7 +61,7 @@ class InstrumentServer:
|
||||
host: Host address to bind to. Defaults to localhost.
|
||||
"""
|
||||
self._host = host
|
||||
self._instruments: dict[int, BaseInstrument] = {}
|
||||
self._instruments: dict[int, SCPIDevice] = {}
|
||||
self._servers: list[asyncio.Server] = []
|
||||
self._running = False
|
||||
|
||||
@@ -52,12 +75,12 @@ class InstrumentServer:
|
||||
"""Check if server is currently running."""
|
||||
return self._running
|
||||
|
||||
def register_instrument(self, port: int, instrument: BaseInstrument) -> None:
|
||||
def register_instrument(self, port: int, instrument: SCPIDevice) -> None:
|
||||
"""Register an instrument to be served on a specific port.
|
||||
|
||||
Args:
|
||||
port: TCP port number to serve the instrument on.
|
||||
instrument: Virtual instrument to serve.
|
||||
instrument: SCPI device to serve (any object with process method).
|
||||
|
||||
Raises:
|
||||
ValueError: If port is already registered.
|
||||
@@ -76,7 +99,7 @@ class InstrumentServer:
|
||||
port,
|
||||
)
|
||||
|
||||
def get_instrument(self, port: int) -> BaseInstrument | None:
|
||||
def get_instrument(self, port: int) -> SCPIDevice | None:
|
||||
"""Get the instrument registered on a port.
|
||||
|
||||
Args:
|
||||
@@ -154,7 +177,7 @@ class InstrumentServer:
|
||||
self,
|
||||
reader: asyncio.StreamReader,
|
||||
writer: asyncio.StreamWriter,
|
||||
instrument: BaseInstrument,
|
||||
instrument: SCPIDevice,
|
||||
port: int,
|
||||
) -> None:
|
||||
"""Handle a client connection.
|
||||
@@ -2,9 +2,10 @@
|
||||
|
||||
Provides virtual instruments backed by a coupled thermal-electrical
|
||||
physics engine. Used for development and testing without real hardware.
|
||||
|
||||
Note: InstrumentServer has moved to py_dvt_ate.instruments.transport
|
||||
"""
|
||||
|
||||
from py_dvt_ate.simulation.server import ServerConfig, SimulationServer
|
||||
from py_dvt_ate.simulation.tcp_server import InstrumentServer
|
||||
|
||||
__all__ = ["InstrumentServer", "ServerConfig", "SimulationServer"]
|
||||
__all__ = ["ServerConfig", "SimulationServer"]
|
||||
|
||||
@@ -11,8 +11,8 @@ import logging
|
||||
import signal
|
||||
from dataclasses import dataclass
|
||||
|
||||
from py_dvt_ate.instruments.transport import InstrumentServer
|
||||
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
|
||||
from py_dvt_ate.simulation.tcp_server import InstrumentServer
|
||||
from py_dvt_ate.simulation.virtual.chamber import ThermalChamberSim
|
||||
from py_dvt_ate.simulation.virtual.multimeter import MultimeterSim
|
||||
from py_dvt_ate.simulation.virtual.power_supply import PowerSupplySim
|
||||
|
||||
@@ -9,9 +9,9 @@ import asyncio
|
||||
|
||||
import pytest
|
||||
|
||||
from py_dvt_ate.instruments.transport import InstrumentServer
|
||||
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
|
||||
from py_dvt_ate.simulation.server import ServerConfig, SimulationServer
|
||||
from py_dvt_ate.simulation.tcp_server import InstrumentServer
|
||||
from py_dvt_ate.simulation.virtual.chamber import ThermalChamberSim
|
||||
from py_dvt_ate.simulation.virtual.multimeter import MultimeterSim
|
||||
from py_dvt_ate.simulation.virtual.power_supply import PowerSupplySim
|
||||
|
||||
Reference in New Issue
Block a user