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.
|
"""Transport layer for instrument communication.
|
||||||
|
|
||||||
Provides connection abstractions for different backends:
|
Provides connection abstractions for different backends:
|
||||||
|
- TCP server for hosting SCPI instruments
|
||||||
- TCP sockets (for simulation server)
|
- TCP sockets (for simulation server)
|
||||||
- PyVISA (for real instruments)
|
- 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
|
instruments over TCP, allowing client applications to communicate using
|
||||||
standard SCPI commands over a network connection.
|
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
|
from __future__ import annotations
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import logging
|
import logging
|
||||||
from typing import TYPE_CHECKING
|
from typing import Protocol, runtime_checkable
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
__all__ = ["InstrumentServer", "SCPIDevice"]
|
||||||
from py_dvt_ate.simulation.virtual.base import BaseInstrument
|
|
||||||
|
|
||||||
# Re-export for type checking - actual import happens at runtime via registration
|
|
||||||
__all__ = ["InstrumentServer"]
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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:
|
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
|
Each instrument is assigned a port. Clients connect via TCP and send
|
||||||
SCPI commands as newline-terminated strings. Responses are also
|
SCPI commands as newline-terminated strings. Responses are also
|
||||||
newline-terminated.
|
newline-terminated.
|
||||||
|
|
||||||
|
This server can host any device implementing the SCPIDevice protocol,
|
||||||
|
including both virtual instruments (simulators) and adapters for
|
||||||
|
real hardware.
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
host: Host address to bind to.
|
host: Host address to bind to.
|
||||||
"""
|
"""
|
||||||
@@ -38,7 +61,7 @@ class InstrumentServer:
|
|||||||
host: Host address to bind to. Defaults to localhost.
|
host: Host address to bind to. Defaults to localhost.
|
||||||
"""
|
"""
|
||||||
self._host = host
|
self._host = host
|
||||||
self._instruments: dict[int, BaseInstrument] = {}
|
self._instruments: dict[int, SCPIDevice] = {}
|
||||||
self._servers: list[asyncio.Server] = []
|
self._servers: list[asyncio.Server] = []
|
||||||
self._running = False
|
self._running = False
|
||||||
|
|
||||||
@@ -52,12 +75,12 @@ class InstrumentServer:
|
|||||||
"""Check if server is currently running."""
|
"""Check if server is currently running."""
|
||||||
return self._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.
|
"""Register an instrument to be served on a specific port.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
port: TCP port number to serve the instrument on.
|
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:
|
Raises:
|
||||||
ValueError: If port is already registered.
|
ValueError: If port is already registered.
|
||||||
@@ -76,7 +99,7 @@ class InstrumentServer:
|
|||||||
port,
|
port,
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_instrument(self, port: int) -> BaseInstrument | None:
|
def get_instrument(self, port: int) -> SCPIDevice | None:
|
||||||
"""Get the instrument registered on a port.
|
"""Get the instrument registered on a port.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -154,7 +177,7 @@ class InstrumentServer:
|
|||||||
self,
|
self,
|
||||||
reader: asyncio.StreamReader,
|
reader: asyncio.StreamReader,
|
||||||
writer: asyncio.StreamWriter,
|
writer: asyncio.StreamWriter,
|
||||||
instrument: BaseInstrument,
|
instrument: SCPIDevice,
|
||||||
port: int,
|
port: int,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Handle a client connection.
|
"""Handle a client connection.
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
|
|
||||||
Provides virtual instruments backed by a coupled thermal-electrical
|
Provides virtual instruments backed by a coupled thermal-electrical
|
||||||
physics engine. Used for development and testing without real hardware.
|
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.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
|
import signal
|
||||||
from dataclasses import dataclass
|
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.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.chamber import ThermalChamberSim
|
||||||
from py_dvt_ate.simulation.virtual.multimeter import MultimeterSim
|
from py_dvt_ate.simulation.virtual.multimeter import MultimeterSim
|
||||||
from py_dvt_ate.simulation.virtual.power_supply import PowerSupplySim
|
from py_dvt_ate.simulation.virtual.power_supply import PowerSupplySim
|
||||||
|
|||||||
@@ -9,9 +9,9 @@ import asyncio
|
|||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
from py_dvt_ate.instruments.transport import InstrumentServer
|
||||||
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
|
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
|
||||||
from py_dvt_ate.simulation.server import ServerConfig, SimulationServer
|
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.chamber import ThermalChamberSim
|
||||||
from py_dvt_ate.simulation.virtual.multimeter import MultimeterSim
|
from py_dvt_ate.simulation.virtual.multimeter import MultimeterSim
|
||||||
from py_dvt_ate.simulation.virtual.power_supply import PowerSupplySim
|
from py_dvt_ate.simulation.virtual.power_supply import PowerSupplySim
|
||||||
|
|||||||
Reference in New Issue
Block a user