Fix SCPI server response handling and add TEMP:RAMP command
All checks were successful
CI / Lint (push) Successful in 4s
CI / Type Check (push) Successful in 18s
CI / Test (push) Successful in 8m29s
CI / Release (push) Has been skipped

- 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:
2025-12-03 01:40:38 +00:00
parent 3ea5f1ea32
commit e06cd34f6c
2 changed files with 43 additions and 6 deletions

View File

@@ -216,14 +216,13 @@ class InstrumentServer:
# Process command through instrument
response = instrument.process(command)
# Send response with newline terminator
# Always send a response, even if empty (for acknowledgment)
# Send response with newline terminator (only if non-empty)
# Per SCPI protocol: commands that complete successfully without
# output do not send a response. Only queries and errors respond.
if response:
writer.write(f"{response}\n".encode())
await writer.drain()
if response:
logger.debug("Port %d sent: %s", port, response)
else:
logger.debug("Port %d sent: <empty response>", port)
except asyncio.CancelledError:
logger.debug("Client handler cancelled for port %d", port)

View File

@@ -26,6 +26,8 @@ class ThermalChamberSim(BaseInstrument):
TEMP:SETPOINT? - Query current setpoint
TEMP:ACTUAL? - Query actual chamber temperature
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:
manufacturer: "PyDVTATE"
@@ -47,6 +49,7 @@ class ThermalChamberSim(BaseInstrument):
physics_engine: Reference to physics engine for temperature state.
"""
self._setpoint = 25.0 # Default setpoint
self._ramp_rate = 10.0 # Default ramp rate in degrees C/min
super().__init__(physics_engine)
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:ACTUAL", self._handle_temp_actual)
self.register_command("TEMP:STAB", self._handle_temp_stab)
self.register_command("TEMP:RAMP", self._handle_temp_ramp)
def reset(self) -> None:
"""Reset chamber to default state."""
self._setpoint = 25.0
self._ramp_rate = 10.0
if self._physics_engine is not None:
self._physics_engine.set_chamber_setpoint(self._setpoint)
@@ -141,3 +146,36 @@ class ThermalChamberSim(BaseInstrument):
if error <= self.STABILITY_THRESHOLD:
return "1"
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 ""