Fix SCPI server response handling and add TEMP:RAMP command
- Revert server to only send responses for non-empty strings Per SCPI protocol: successful commands with no output send nothing - Add TEMP:RAMP command support to thermal chamber simulator - Fixes test_multiple_commands and test_physics_engine_integration TCP server integration tests now passing (8/8). TempCo integration tests still need work due to async/sync mixing.
This commit is contained in:
@@ -216,14 +216,13 @@ class InstrumentServer:
|
|||||||
# Process command through instrument
|
# Process command through instrument
|
||||||
response = instrument.process(command)
|
response = instrument.process(command)
|
||||||
|
|
||||||
# Send response with newline terminator
|
# Send response with newline terminator (only if non-empty)
|
||||||
# Always send a response, even if empty (for acknowledgment)
|
# Per SCPI protocol: commands that complete successfully without
|
||||||
writer.write(f"{response}\n".encode())
|
# output do not send a response. Only queries and errors respond.
|
||||||
await writer.drain()
|
|
||||||
if response:
|
if response:
|
||||||
|
writer.write(f"{response}\n".encode())
|
||||||
|
await writer.drain()
|
||||||
logger.debug("Port %d sent: %s", port, response)
|
logger.debug("Port %d sent: %s", port, response)
|
||||||
else:
|
|
||||||
logger.debug("Port %d sent: <empty response>", port)
|
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.debug("Client handler cancelled for port %d", port)
|
logger.debug("Client handler cancelled for port %d", port)
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ class ThermalChamberSim(BaseInstrument):
|
|||||||
TEMP:SETPOINT? - Query current setpoint
|
TEMP:SETPOINT? - Query current setpoint
|
||||||
TEMP:ACTUAL? - Query actual chamber temperature
|
TEMP:ACTUAL? - Query actual chamber temperature
|
||||||
TEMP:STAB? - Query temperature stability (1=stable, 0=settling)
|
TEMP:STAB? - Query temperature stability (1=stable, 0=settling)
|
||||||
|
TEMP:RAMP <value> - Set temperature ramp rate in degrees C/min
|
||||||
|
TEMP:RAMP? - Query current ramp rate
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
manufacturer: "PyDVTATE"
|
manufacturer: "PyDVTATE"
|
||||||
@@ -47,6 +49,7 @@ class ThermalChamberSim(BaseInstrument):
|
|||||||
physics_engine: Reference to physics engine for temperature state.
|
physics_engine: Reference to physics engine for temperature state.
|
||||||
"""
|
"""
|
||||||
self._setpoint = 25.0 # Default setpoint
|
self._setpoint = 25.0 # Default setpoint
|
||||||
|
self._ramp_rate = 10.0 # Default ramp rate in degrees C/min
|
||||||
super().__init__(physics_engine)
|
super().__init__(physics_engine)
|
||||||
|
|
||||||
def _setup_commands(self) -> None:
|
def _setup_commands(self) -> None:
|
||||||
@@ -54,10 +57,12 @@ class ThermalChamberSim(BaseInstrument):
|
|||||||
self.register_command("TEMP:SETPOINT", self._handle_temp_setpoint)
|
self.register_command("TEMP:SETPOINT", self._handle_temp_setpoint)
|
||||||
self.register_command("TEMP:ACTUAL", self._handle_temp_actual)
|
self.register_command("TEMP:ACTUAL", self._handle_temp_actual)
|
||||||
self.register_command("TEMP:STAB", self._handle_temp_stab)
|
self.register_command("TEMP:STAB", self._handle_temp_stab)
|
||||||
|
self.register_command("TEMP:RAMP", self._handle_temp_ramp)
|
||||||
|
|
||||||
def reset(self) -> None:
|
def reset(self) -> None:
|
||||||
"""Reset chamber to default state."""
|
"""Reset chamber to default state."""
|
||||||
self._setpoint = 25.0
|
self._setpoint = 25.0
|
||||||
|
self._ramp_rate = 10.0
|
||||||
if self._physics_engine is not None:
|
if self._physics_engine is not None:
|
||||||
self._physics_engine.set_chamber_setpoint(self._setpoint)
|
self._physics_engine.set_chamber_setpoint(self._setpoint)
|
||||||
|
|
||||||
@@ -141,3 +146,36 @@ class ThermalChamberSim(BaseInstrument):
|
|||||||
if error <= self.STABILITY_THRESHOLD:
|
if error <= self.STABILITY_THRESHOLD:
|
||||||
return "1"
|
return "1"
|
||||||
return "0"
|
return "0"
|
||||||
|
|
||||||
|
def _handle_temp_ramp(self, command: SCPICommand) -> str:
|
||||||
|
"""Handle TEMP:RAMP command/query.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: Parsed SCPI command.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Ramp rate value for query, empty string for set command.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
ValueError: If ramp rate argument is invalid.
|
||||||
|
"""
|
||||||
|
if command.is_query:
|
||||||
|
return f"{self._ramp_rate:.2f}"
|
||||||
|
|
||||||
|
# Set command requires one argument
|
||||||
|
if not command.arguments:
|
||||||
|
raise ValueError("TEMP:RAMP requires a value")
|
||||||
|
|
||||||
|
try:
|
||||||
|
ramp_rate = float(command.arguments[0])
|
||||||
|
except ValueError as err:
|
||||||
|
raise ValueError(f"Invalid ramp rate value: {command.arguments[0]}") from err
|
||||||
|
|
||||||
|
if ramp_rate <= 0:
|
||||||
|
raise ValueError("Ramp rate must be positive")
|
||||||
|
|
||||||
|
self._ramp_rate = ramp_rate
|
||||||
|
# Note: Simulator doesn't currently model ramp rate dynamics
|
||||||
|
# The value is stored but not used in physics calculations
|
||||||
|
|
||||||
|
return ""
|
||||||
|
|||||||
Reference in New Issue
Block a user