/ hardware_simulation / SimulatedCTC100.py
SimulatedCTC100.py
1 import random 2 import time 3 4 5 class SimulatedCTC100Device: 6 """ 7 Simulated CTC100 Device for testing purposes. 8 Mimics the behavior of the actual CTC100Device class without hardware. 9 """ 10 11 def __init__(self, port, name = None): 12 """ 13 Initialize the simulated device. 14 15 :param port: The port parameter is ignored in simulation. 16 """ 17 self.name = name 18 self.port = port 19 self.address = port 20 self.input_channels = ['In1', 'In2', 'In3', 'In4'] 21 self.output_channels = ['Out1', 'Out2'] 22 self.aio_channels = ['AIO1', 'AIO2'] 23 self.temperatures = { 24 channel: 300.0 for channel in self.input_channels + self.aio_channels} 25 self.heater_outputs = { 26 channel: 0.0 for channel in self.output_channels} 27 self.heater_modes = { 28 channel: 'Off' for channel in self.output_channels} 29 self.setpoints = {channel: 300.0 for channel in self.output_channels} 30 self.pid_params = {channel: {'P': 1.0, 'I': 0.0, 'D': 0.0} 31 for channel in self.output_channels} 32 self.aio_iotypes = {channel: 'Input' for channel in self.aio_channels} 33 self.aio_voltages = {channel: 0.0 for channel in self.aio_channels} 34 self.pid_enabled = {channel: False for channel in self.output_channels} 35 self.heater_enabled = False 36 print( 37 f"Connected to Simulated CTC100 on {port} with input channels {self.input_channels}, " 38 f"output channels {self.output_channels}, and AIO channels {self.aio_channels}" 39 ) 40 41 # Communication methods are not needed for simulation 42 43 def get_input_channels(self): 44 return self.input_channels 45 46 def get_output_channels(self): 47 return self.output_channels 48 49 50 def set_heater_output(self, heater_number=1, heat_percent=0.0): 51 try: 52 if not (0 <= heat_percent <= 100): 53 raise ValueError("Heat percent must be between 0 and 100.") 54 channel = f"Out{heater_number}" 55 if not self.heater_enabled: 56 raise RuntimeError("Heater outputs are not enabled.") 57 self.heater_outputs[channel] = heat_percent 58 self.heater_modes[channel] = 'Manual' 59 print( 60 f"Heater output for {channel} set to {heat_percent}% in Manual mode.") 61 return True 62 except Exception as e: 63 print(f"Error setting heater output on Simulated CTC100: {e}") 64 return False 65 66 def enable_heater(self): 67 self.heater_enabled = True 68 print("Heater outputs enabled.") 69 70 def disable_heater(self): 71 self.heater_enabled = False 72 print("Heater outputs disabled.") 73 74 def set_control_mode(self, channel, mode): 75 if mode not in ['Off', 'Manual', 'PID']: 76 raise ValueError( 77 "Invalid control mode. Must be 'Off', 'Manual', or 'PID'.") 78 channel = f"Out{channel}" 79 self.heater_modes[channel] = mode 80 print(f"Control mode for {channel} set to {mode}.") 81 82 def link_heater_to_input(self, output_channel, input_channel): 83 # In simulation, we assume the link is successful 84 print( 85 f"Linked Out{output_channel} to In{input_channel} for PID control.") 86 87 def write_setpoint(self, channel, setpoint): 88 channel = f"Out{channel}" 89 self.setpoints[channel] = setpoint 90 print(f"Setpoint for {channel} set to {setpoint} K.") 91 92 def read_setpoint(self, channel): 93 channel = f"Out{channel}" 94 return self.setpoints.get(channel, None) 95 96 def enable_PID(self, channel): 97 channel = f"Out{channel}" 98 self.pid_enabled[channel] = True 99 self.heater_modes[channel] = 'PID' 100 print(f"PID control enabled on {channel}.") 101 102 def disable_PID(self, channel): 103 channel = f"Out{channel}" 104 self.pid_enabled[channel] = False 105 self.heater_modes[channel] = 'Off' 106 print(f"PID control disabled on {channel}.") 107 108 def set_PID_parameters(self, channel, P, I, D): 109 channel = f"Out{channel}" 110 self.pid_params[channel] = {'P': P, 'I': I, 'D': D} 111 print(f"PID parameters for {channel} set to P={P}, I={I}, D={D}.") 112 113 def read_PID_parameters(self, channel): 114 channel = f"Out{channel}" 115 return self.pid_params.get(channel, {'P': None, 'I': None, 'D': None}) 116 117 def get_aio_channels(self): 118 return self.aio_channels 119 120 def get_aio_iotype(self, channel): 121 if not isinstance(channel, str): 122 channel = f"AIO{channel}" 123 iotype = self.aio_iotypes.get(channel, None) 124 if iotype is None: 125 raise ValueError(f"AIO channel {channel} not found.") 126 return iotype 127 128 def set_aio_iotype(self, channel, iotype): 129 valid_iotypes = ['Input', 'Set out', 'Meas out'] 130 if iotype not in valid_iotypes: 131 raise ValueError( 132 f"Invalid IOType. Must be one of {valid_iotypes}.") 133 if not isinstance(channel, str): 134 channel = f"AIO{channel}" 135 self.aio_iotypes[channel] = iotype 136 print(f"{channel} IOType set to {iotype}.") 137 138 def get_aio_voltage(self, channel): 139 if not isinstance(channel, str): 140 channel = f"AIO{channel}" 141 iotype = self.get_aio_iotype(channel) 142 if iotype != 'Set out': 143 raise RuntimeError( 144 f"{channel} is not configured as 'Set out'. Current IOType: {iotype}") 145 voltage = self.aio_voltages.get(channel, None) 146 if voltage is None: 147 raise ValueError(f"AIO channel {channel} not found.") 148 return voltage 149 150 def set_aio_voltage(self, channel, voltage): 151 if not (-10.0 <= voltage <= 10.0): 152 raise ValueError("Voltage must be between -10 and +10 volts.") 153 if not isinstance(channel, str): 154 channel = f"AIO{channel}" 155 iotype = self.get_aio_iotype(channel) 156 if iotype != 'Set out': 157 raise RuntimeError( 158 f"{channel} is not configured as 'Set out'. Current IOType: {iotype}") 159 self.aio_voltages[channel] = voltage 160 print(f"{channel} voltage set to {voltage} V.") 161 162 def get_temperature(self, channel): 163 try: 164 if not isinstance(channel, str): 165 # Adjusted to handle In and AIO channels 166 if f"In{channel}" in self.input_channels: 167 channel = f"In{channel}" 168 elif f"AIO{channel}" in self.aio_channels: 169 channel = f"AIO{channel}" 170 else: 171 raise ValueError(f"Channel {channel} not found.") 172 temp = self.temperatures.get(channel, None) 173 if temp is None: 174 raise ValueError(f"Channel {channel} not found.") 175 176 # Simulate temperature changes if PID is enabled 177 if channel in self.input_channels or channel in self.aio_channels: 178 self._simulate_temperature(channel) 179 return temp 180 except Exception as e: 181 print( 182 f"Error reading temperature from Simulated CTC100 (Channel {channel}): {e}") 183 return None 184 185 def read_all_channels(self): 186 readings = {} 187 for channel in self.input_channels + self.aio_channels: 188 temp = self.get_temperature(channel) 189 readings[channel] = temp 190 return readings 191 192 # Simulate temperature changes based on PID control 193 def _simulate_temperature(self, channel): 194 # Simple simulation: Adjust temperature towards setpoint if PID is enabled 195 for out_channel in self.output_channels: 196 if self.pid_enabled[out_channel] and self.heater_enabled: 197 # Assume the heater is linked to the input channel 198 setpoint = self.setpoints[out_channel] 199 current_temp = self.temperatures[channel] 200 pid_params = self.pid_params[out_channel] 201 error = setpoint - current_temp 202 # Simplified PID control simulation 203 delta_temp = pid_params['P'] * error * \ 204 0.01 # Scale factor for simulation 205 self.temperatures[channel] += delta_temp 206 207 def setAlarm(self, channel, Tmin, Tmax): 208 # Simulate setting an alarm (no actual effect in simulation) 209 print(f"Alarm set on {channel} with Tmin={Tmin}, Tmax={Tmax}") 210 211 def disableAlarm(self, channel): 212 # Simulate disabling an alarm 213 print(f"Alarm disabled on {channel}") 214 215 def read_status(self): 216 # Simulate reading device status 217 return "Simulated CTC100 Device Status: All systems nominal." 218 219 def read_alarms(self): 220 # Simulate reading alarms 221 return "Simulated CTC100 Device Alarms: No active alarms." 222 223 # Simulate temperature changes based on PID control 224 225 def __del__(self): 226 # Cleanup if needed 227 pass