- TEMP:SETPOINT: Set/query target temperature - TEMP:ACTUAL?: Query actual chamber temperature from physics engine - TEMP:STAB?: Query temperature stability (within 0.5°C threshold)
144 lines
4.4 KiB
Python
144 lines
4.4 KiB
Python
"""Virtual thermal chamber simulator.
|
|
|
|
This module implements a SCPI-based virtual thermal chamber that interfaces
|
|
with the physics engine to provide realistic temperature control simulation.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import TYPE_CHECKING
|
|
|
|
from py_dvt_ate.instruments.scpi import SCPICommand
|
|
from py_dvt_ate.simulation.virtual.base import BaseInstrument
|
|
|
|
if TYPE_CHECKING:
|
|
from py_dvt_ate.simulation.physics.engine import PhysicsEngine
|
|
|
|
|
|
class ThermalChamberSim(BaseInstrument):
|
|
"""Virtual thermal chamber simulator.
|
|
|
|
Simulates a thermal chamber with SCPI control interface. The chamber
|
|
temperature behaviour is driven by the physics engine.
|
|
|
|
SCPI Commands:
|
|
TEMP:SETPOINT <value> - Set target temperature in degrees C
|
|
TEMP:SETPOINT? - Query current setpoint
|
|
TEMP:ACTUAL? - Query actual chamber temperature
|
|
TEMP:STAB? - Query temperature stability (1=stable, 0=settling)
|
|
|
|
Attributes:
|
|
manufacturer: "PyDVTATE"
|
|
model: "TC-SIM-001"
|
|
"""
|
|
|
|
manufacturer = "PyDVTATE"
|
|
model = "TC-SIM-001"
|
|
serial_number = "TCSIM001"
|
|
firmware_version = "1.0.0"
|
|
|
|
# Stability threshold in degrees C
|
|
STABILITY_THRESHOLD = 0.5
|
|
|
|
def __init__(self, physics_engine: PhysicsEngine | None = None) -> None:
|
|
"""Initialise the thermal chamber simulator.
|
|
|
|
Args:
|
|
physics_engine: Reference to physics engine for temperature state.
|
|
"""
|
|
self._setpoint = 25.0 # Default setpoint
|
|
super().__init__(physics_engine)
|
|
|
|
def _setup_commands(self) -> None:
|
|
"""Register thermal chamber SCPI commands."""
|
|
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)
|
|
|
|
def reset(self) -> None:
|
|
"""Reset chamber to default state."""
|
|
self._setpoint = 25.0
|
|
if self._physics_engine is not None:
|
|
self._physics_engine.set_chamber_setpoint(self._setpoint)
|
|
|
|
def _handle_temp_setpoint(self, command: SCPICommand) -> str:
|
|
"""Handle TEMP:SETPOINT command/query.
|
|
|
|
Args:
|
|
command: Parsed SCPI command.
|
|
|
|
Returns:
|
|
Setpoint value for query, empty string for set command.
|
|
|
|
Raises:
|
|
ValueError: If setpoint argument is invalid.
|
|
"""
|
|
if command.is_query:
|
|
return f"{self._setpoint:.2f}"
|
|
|
|
# Set command requires one argument
|
|
if not command.arguments:
|
|
raise ValueError("TEMP:SETPOINT requires a value")
|
|
|
|
try:
|
|
setpoint = float(command.arguments[0])
|
|
except ValueError:
|
|
raise ValueError(f"Invalid temperature value: {command.arguments[0]}")
|
|
|
|
self._setpoint = setpoint
|
|
if self._physics_engine is not None:
|
|
self._physics_engine.set_chamber_setpoint(setpoint)
|
|
|
|
return ""
|
|
|
|
def _handle_temp_actual(self, command: SCPICommand) -> str:
|
|
"""Handle TEMP:ACTUAL? query.
|
|
|
|
Args:
|
|
command: Parsed SCPI command.
|
|
|
|
Returns:
|
|
Actual chamber temperature.
|
|
|
|
Raises:
|
|
ValueError: If used as command (not query).
|
|
"""
|
|
if not command.is_query:
|
|
raise ValueError("TEMP:ACTUAL is query only")
|
|
|
|
if self._physics_engine is None:
|
|
# Return setpoint if no physics engine connected
|
|
return f"{self._setpoint:.2f}"
|
|
|
|
thermal_state = self._physics_engine.get_thermal_state()
|
|
return f"{thermal_state.chamber_temperature:.2f}"
|
|
|
|
def _handle_temp_stab(self, command: SCPICommand) -> str:
|
|
"""Handle TEMP:STAB? stability query.
|
|
|
|
Temperature is considered stable when the actual chamber temperature
|
|
is within STABILITY_THRESHOLD of the setpoint.
|
|
|
|
Args:
|
|
command: Parsed SCPI command.
|
|
|
|
Returns:
|
|
"1" if stable, "0" if settling.
|
|
|
|
Raises:
|
|
ValueError: If used as command (not query).
|
|
"""
|
|
if not command.is_query:
|
|
raise ValueError("TEMP:STAB is query only")
|
|
|
|
if self._physics_engine is None:
|
|
# Assume stable if no physics engine connected
|
|
return "1"
|
|
|
|
thermal_state = self._physics_engine.get_thermal_state()
|
|
error = abs(thermal_state.chamber_temperature - self._setpoint)
|
|
|
|
if error <= self.STABILITY_THRESHOLD:
|
|
return "1"
|
|
return "0"
|