/ 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