diff --git a/src/py_dvt_ate/instruments/transport/server.py b/src/py_dvt_ate/instruments/transport/server.py index 94ee194..0e6d248 100644 --- a/src/py_dvt_ate/instruments/transport/server.py +++ b/src/py_dvt_ate/instruments/transport/server.py @@ -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) - writer.write(f"{response}\n".encode()) - await writer.drain() + # 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() logger.debug("Port %d sent: %s", port, response) - else: - logger.debug("Port %d sent: ", port) except asyncio.CancelledError: logger.debug("Client handler cancelled for port %d", port) diff --git a/src/py_dvt_ate/simulation/virtual/chamber.py b/src/py_dvt_ate/simulation/virtual/chamber.py index d8f697d..0459556 100644 --- a/src/py_dvt_ate/simulation/virtual/chamber.py +++ b/src/py_dvt_ate/simulation/virtual/chamber.py @@ -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 - 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 ""