horiba-sdk 0.3.3__py3-none-any.whl → 0.4.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  from enum import Enum
2
2
  from types import TracebackType
3
- from typing import Optional, final
3
+ from typing import Any, Optional, final
4
4
 
5
5
  from loguru import logger
6
6
  from overrides import override
@@ -19,6 +19,13 @@ class Monochromator(AbstractDevice):
19
19
  should be used to access the detected Monochromators on the system.
20
20
  """
21
21
 
22
+ @final
23
+ class Shutter(Enum):
24
+ """Shutters installed in the monochromator."""
25
+
26
+ FIRST = 0
27
+ SECOND = 1
28
+
22
29
  @final
23
30
  class ShutterPosition(Enum):
24
31
  """Position of the shutter."""
@@ -34,6 +41,18 @@ class Monochromator(AbstractDevice):
34
41
  SECOND = 1
35
42
  THIRD = 2
36
43
 
44
+ @final
45
+ class FilterWheel(Enum):
46
+ """Filter wheels installed in the monochromator.
47
+
48
+ .. note:: the filter wheel is an optional module
49
+
50
+ """
51
+
52
+ # TODO: clarify naming of filter wheel
53
+ FIRST = 0
54
+ SECOND = 1
55
+
37
56
  @final
38
57
  class FilterWheelPosition(Enum):
39
58
  """Positions of the filter wheel installed in the monochromator.
@@ -52,17 +71,15 @@ class Monochromator(AbstractDevice):
52
71
  class Mirror(Enum):
53
72
  """Mirrors installed in the monochromator"""
54
73
 
55
- # TODO: clarify how the mirrors are called
56
- FIRST = 0
57
- SECOND = 1
74
+ ENTRANCE = 0
75
+ EXIT = 1
58
76
 
59
77
  @final
60
78
  class MirrorPosition(Enum):
61
79
  """Possible positions of the mirrors"""
62
80
 
63
- # TODO: clarify what possible position there are
64
- A = 0
65
- B = 1
81
+ AXIAL = 0
82
+ LATERAL = 1
66
83
 
67
84
  @final
68
85
  class Slit(Enum):
@@ -74,16 +91,6 @@ class Monochromator(AbstractDevice):
74
91
  C = 2
75
92
  D = 3
76
93
 
77
- @final
78
- class SlitStepPosition(Enum):
79
- """Slits steps available on the monochromator."""
80
-
81
- # TODO: clarify how the slits are called
82
- A = 0
83
- B = 1
84
- C = 2
85
- D = 3
86
-
87
94
  def __init__(self, device_id: int, communicator: AbstractCommunicator, error_db: AbstractErrorDB) -> None:
88
95
  super().__init__(device_id, communicator, error_db)
89
96
 
@@ -106,7 +113,7 @@ class Monochromator(AbstractDevice):
106
113
  """Opens the connection to the Monochromator
107
114
 
108
115
  Raises:
109
- Exception: When an error occured on the device side
116
+ Exception: When an error occurred on the device side
110
117
  """
111
118
  await super().open()
112
119
  await super()._execute_command('mono_open', {'index': self._id})
@@ -116,7 +123,7 @@ class Monochromator(AbstractDevice):
116
123
  """Closes the connection to the Monochromator
117
124
 
118
125
  Raises:
119
- Exception: When an error occured on the device side
126
+ Exception: When an error occurred on the device side
120
127
  """
121
128
  await super()._execute_command('mono_close', {'index': self._id})
122
129
 
@@ -124,7 +131,7 @@ class Monochromator(AbstractDevice):
124
131
  """Checks if the connection to the monochromator is open.
125
132
 
126
133
  Raises:
127
- Exception: When an error occured on the device side
134
+ Exception: When an error occurred on the device side
128
135
  """
129
136
  response: Response = await super()._execute_command('mono_isOpen', {'index': self._id})
130
137
  return bool(response.results['open'])
@@ -133,7 +140,7 @@ class Monochromator(AbstractDevice):
133
140
  """Checks if the monochromator is busy.
134
141
 
135
142
  Raises:
136
- Exception: When an error occured on the device side
143
+ Exception: When an error occurred on the device side
137
144
  """
138
145
  response: Response = await super()._execute_command('mono_isBusy', {'index': self._id})
139
146
  return bool(response.results['busy'])
@@ -144,18 +151,18 @@ class Monochromator(AbstractDevice):
144
151
  Use :func:`Monochromator.is_busy()` to know if the operation is still taking place.
145
152
 
146
153
  Raises:
147
- Exception: When an error occured on the device side
154
+ Exception: When an error occurred on the device side
148
155
  """
149
156
  await super()._execute_command('mono_init', {'index': self._id})
150
157
 
151
- async def configuration(self) -> str:
158
+ async def configuration(self) -> dict[str, Any]:
152
159
  """Returns the configuration of the monochromator.
153
160
 
154
161
  Returns:
155
162
  str: configuration of the monochromator
156
163
  """
157
164
  response: Response = await super()._execute_command('mono_getConfig', {'index': self._id, 'compact': False})
158
- return str(response.results)
165
+ return response.results['configuration']
159
166
 
160
167
  async def get_current_wavelength(self) -> float:
161
168
  """Current wavelength of the monochromator's position in nm.
@@ -194,16 +201,20 @@ class Monochromator(AbstractDevice):
194
201
  Raises:
195
202
  Exception: When an error occurred on the device side
196
203
  """
197
- await super()._execute_command('mono_moveToPosition', {'index': self._id, 'wavelength': wavelength}, 60)
204
+ await super()._execute_command('mono_moveToPosition', {'index': self._id, 'wavelength': wavelength}, 180)
198
205
 
199
206
  async def get_turret_grating(self) -> Grating:
200
- """Current grating of the turret
207
+ """Current grating of the turret.
208
+
209
+ .. note:: Prior to the initialization of the grating turret, this value may not reflect the actual position
210
+ of the turret. To read the current position of the grating turret, please run
211
+ :func:`Monochromator.home()` prior to running this command.
201
212
 
202
213
  Returns:
203
214
  Grating: current grating of turret. See :class:`Monochromator.Grating` for possible values.
204
215
 
205
216
  Raises:
206
- Exception: When an error occured on the device side
217
+ Exception: When an error occurred on the device side
207
218
  """
208
219
  response: Response = await super()._execute_command('mono_getGratingPosition', {'index': self._id})
209
220
  return self.Grating(response.results['position'])
@@ -211,15 +222,18 @@ class Monochromator(AbstractDevice):
211
222
  async def set_turret_grating(self, grating: Grating) -> None:
212
223
  """Select turret grating
213
224
 
225
+ .. note:: Note: The turret sensor does not re-read the position each time it is moved, therefore the position
226
+ may not be accurate prior to initialization. See note for get_turret_grating().
227
+
214
228
  Args:
215
- position (Grating): new grating of the turret. See :class:`Monochromator.Grating` for possible values.
229
+ grating (Grating): new grating of the turret. See :class:`Monochromator.Grating` for possible values.
216
230
 
217
231
  Raises:
218
- Exception: When an error occured on the device side
232
+ Exception: When an error occurred on the device side
219
233
  """
220
234
  await super()._execute_command('mono_moveGrating', {'index': self._id, 'position': grating.value})
221
235
 
222
- async def get_filter_wheel_position(self) -> FilterWheelPosition:
236
+ async def get_filter_wheel_position(self, filter_wheel: FilterWheel) -> FilterWheelPosition:
223
237
  """Current position of the filter wheel.
224
238
 
225
239
  Returns:
@@ -227,16 +241,14 @@ class Monochromator(AbstractDevice):
227
241
  for possible values.
228
242
 
229
243
  Raises:
230
- Exception: When an error occured on the device side
244
+ Exception: When an error occurred on the device side
231
245
  """
232
- # TODO: refactor in case there can be more than one filter wheel. What should be done if no filter wheel is
233
- # installed?
234
246
  response: Response = await super()._execute_command(
235
- 'mono_getFilterWheelPosition', {'index': self._id, 'type': 1}
247
+ 'mono_getFilterWheelPosition', {'index': self._id, 'locationId': filter_wheel.value}
236
248
  )
237
249
  return self.FilterWheelPosition(response.results['position'])
238
250
 
239
- async def set_filter_wheel_position(self, position: FilterWheelPosition) -> None:
251
+ async def set_filter_wheel_position(self, filter_wheel: FilterWheel, position: FilterWheelPosition) -> None:
240
252
  """Sets the current position of the filter wheel.
241
253
 
242
254
  Returns:
@@ -244,12 +256,10 @@ class Monochromator(AbstractDevice):
244
256
  for possible values.
245
257
 
246
258
  Raises:
247
- Exception: When an error occured on the device side
259
+ Exception: When an error occurred on the device side
248
260
  """
249
- # TODO: refactor in case there can be more than one filter wheel. What should be done if no filter wheel is
250
- # installed?
251
261
  await super()._execute_command(
252
- 'mono_moveFilterWheel', {'index': self._id, 'type': 1, 'position': position.value}
262
+ 'mono_moveFilterWheel', {'index': self._id, 'locationId': filter_wheel.value, 'position': position.value}
253
263
  )
254
264
 
255
265
  async def get_mirror_position(self, mirror: Mirror) -> MirrorPosition:
@@ -268,7 +278,7 @@ class Monochromator(AbstractDevice):
268
278
  Exception: When an error occurred on the device side
269
279
  """
270
280
  response: Response = await super()._execute_command(
271
- 'mono_getMirrorPosition', {'index': self._id, 'type': mirror.value}
281
+ 'mono_getMirrorPosition', {'index': self._id, 'locationId': mirror.value}
272
282
  )
273
283
  return self.MirrorPosition(response.results['position'])
274
284
 
@@ -286,18 +296,15 @@ class Monochromator(AbstractDevice):
286
296
  Exception: When an error occurred on the device side
287
297
  """
288
298
  await super()._execute_command(
289
- 'mono_moveMirror', {'index': self._id, 'type': mirror.value, 'position': position.value}
299
+ 'mono_moveMirror', {'index': self._id, 'locationId': mirror.value, 'position': position.value}
290
300
  )
291
301
 
292
302
  async def get_slit_position_in_mm(self, slit: Slit) -> float:
293
303
  """Returns the position in millimeters [mm] of the selected slit.
294
304
 
295
- .. todo:: Get more information about possible values and explain elements contained in monochromator at top
296
- of this class.
297
-
298
305
  Args:
299
306
  slit (Slit): desired slit to get the position from. See :class:`Monochromator.Slit` for possible
300
- values
307
+
301
308
  Returns:
302
309
  float: position in mm
303
310
 
@@ -306,64 +313,53 @@ class Monochromator(AbstractDevice):
306
313
  """
307
314
 
308
315
  response: Response = await super()._execute_command(
309
- 'mono_getSlitPositionInMM', {'index': self._id, 'type': slit.value}
316
+ 'mono_getSlitPositionInMM', {'index': self._id, 'locationId': slit.value}
310
317
  )
311
318
  return float(response.results['position'])
312
319
 
313
320
  async def set_slit_position(self, slit: Slit, position_in_mm: float) -> None:
314
321
  """Sets the position of the selected slit.
315
322
 
316
- .. todo:: Get more information about possible values and explain elements contained in monochromator at top
317
- of this class.
318
-
319
323
  Args:
320
324
  slit (Slit): desired slit to set the position. See :class:`Monochromator.Slit` for possible values.
321
- position (float): position to set in millimeters [mm].
325
+ position_in_mm (float): position to set in millimeters [mm].
322
326
 
323
327
  Raises:
324
328
  Exception: When an error occurred on the device side
325
329
  """
326
330
  await super()._execute_command(
327
- 'mono_moveSlitMM', {'index': self._id, 'type': slit.value, 'position': position_in_mm}
331
+ 'mono_moveSlitMM', {'index': self._id, 'locationId': slit.value, 'position': position_in_mm}
328
332
  )
329
333
 
330
- async def get_slit_step_position(self, slit: Slit) -> SlitStepPosition:
331
- """Returns the step position of the selected slit.
332
-
333
- .. todo:: Get more information about possible values and explain elements contained in monochromator at top
334
- of this class.
334
+ async def get_slit_step_position(self, slit: Slit) -> int:
335
+ """Returns the position of the specified slit in steps.
335
336
 
336
337
  Args:
337
338
  slit (Slit): desired slit to get the position from. See :class:`Monochromator.Slit` for possible
338
- values
339
339
  Returns:
340
- SlitStepPosition: step position. See :class:`Monochromator.SlitStepPosition` for possible values
340
+ int: step position.
341
341
 
342
342
  Raises:
343
343
  Exception: When an error occurred on the device side
344
344
  """
345
345
 
346
346
  response: Response = await super()._execute_command(
347
- 'mono_getSlitStepPosition', {'index': self._id, 'type': slit.value}
347
+ 'mono_getSlitStepPosition', {'index': self._id, 'locationId': slit.value}
348
348
  )
349
- return self.SlitStepPosition(response.results['position'])
349
+ return int(response.results['position'])
350
350
 
351
- async def set_slit_step_position(self, slit: Slit, step_position: SlitStepPosition) -> None:
352
- """Sets the step position of the selected slit.
353
-
354
- .. todo:: Get more information about possible values and explain elements contained in monochromator at top
355
- of this class.
351
+ async def set_slit_step_position(self, slit: Slit, step_position: int) -> None:
352
+ """Moves the specified slit to the position in steps.
356
353
 
357
354
  Args:
358
355
  slit (Slit): desired slit to set the step position. See :class:`Monochromator.Slit` for possible values.
359
- step_position (SlitStepPosition): the step position. See :class:`Monochromator.SlitStepPosition` for
360
- possible values
356
+ step_position (int): the step position.
361
357
 
362
358
  Raises:
363
359
  Exception: When an error occurred on the device side
364
360
  """
365
361
  await super()._execute_command(
366
- 'mono_moveSlit', {'index': self._id, 'type': slit.value, 'position': step_position.value}
362
+ 'mono_moveSlit', {'index': self._id, 'locationId': slit.value, 'position': step_position}
367
363
  )
368
364
 
369
365
  async def open_shutter(self) -> None:
@@ -382,7 +378,7 @@ class Monochromator(AbstractDevice):
382
378
  """
383
379
  await super()._execute_command('mono_shutterClose', {'index': self._id})
384
380
 
385
- async def get_shutter_position(self) -> ShutterPosition:
381
+ async def get_shutter_position(self, shutter: Shutter) -> ShutterPosition:
386
382
  """Returns the shutter position.
387
383
 
388
384
  Returns:
@@ -392,4 +388,11 @@ class Monochromator(AbstractDevice):
392
388
  Exception: When an error occurred on the device side
393
389
  """
394
390
  response: Response = await super()._execute_command('mono_getShutterStatus', {'index': self._id})
395
- return self.ShutterPosition(response.results['position'])
391
+ # TODO: How many shutters are there?
392
+ if shutter == self.Shutter.FIRST:
393
+ return self.ShutterPosition(response.results['shutter 1'])
394
+ elif shutter == self.Shutter.SECOND:
395
+ return self.ShutterPosition(response.results['shutter 2'])
396
+ else:
397
+ logger.error(f'shutter {shutter} not implemented')
398
+ raise Exception('shutter not implemented')
@@ -26,14 +26,13 @@ class AbstractCommunicator(ABC):
26
26
  pass
27
27
 
28
28
  @abstractmethod
29
- def request_with_response(self, command: Command, time_to_wait_for_response_in_s: float = 0.1) -> Response:
29
+ def request_with_response(self, command: Command, response_timeout_s: float = 5) -> Response:
30
30
  """
31
31
  Abstract method to fetch a response from a command.
32
32
 
33
33
  Args:
34
34
  command (Command): Command for which a response is desired
35
- time_to_wait_for_response_in_s (float, optional): Time, in seconds, to wait between request and response.
36
- Defaults to 0.1s
35
+ response_timeout_s (float, optional): Timeout in seconds. Defaults to 5.
37
36
 
38
37
  Returns:
39
38
  Response: The response corresponding to the sent command.
@@ -28,7 +28,7 @@ class WebsocketCommunicator(AbstractCommunicator):
28
28
  self.listen_thread: Optional[Thread] = None
29
29
  self.running_binary_message_handling_thread: bool = False
30
30
  self.binary_message_handling_thread: Optional[Thread] = None
31
- self.json_message_queue: Queue[str] = Queue()
31
+ self.json_message_dict: dict[int, JSONResponse] = {}
32
32
  self.binary_message_queue: Queue[bytes] = Queue()
33
33
  self.binary_message_callback: Optional[Callable[[bytes], Any]] = None
34
34
  self.icl_info: dict[str, Any] = {}
@@ -99,8 +99,12 @@ class WebsocketCommunicator(AbstractCommunicator):
99
99
  """
100
100
  return self.websocket is not None
101
101
 
102
- def response(self) -> Response:
103
- """Fetches the next response
102
+ def response(self, command_id: int, timeout_s: float = 5.0) -> Response:
103
+ """Fetches the response belonging to the command_id.
104
+
105
+ Args:
106
+ command_id (int): The command id of the command.
107
+ timeout_s (float): The timeout in seconds.
104
108
 
105
109
  Returns:
106
110
  Response: The response from the server
@@ -108,13 +112,27 @@ class WebsocketCommunicator(AbstractCommunicator):
108
112
  Raises:
109
113
  CommunicationException: When the connection terminated with an error
110
114
  """
111
- if not self.json_message_queue or self.json_message_queue.empty():
112
- raise CommunicationException(None, 'No message to be received.')
113
-
114
- logger.debug(f'#{self.json_message_queue.qsize()} messages in the queue, taking first')
115
- response: str = self.json_message_queue.get()
116
- logger.debug('retrieved message in queue')
117
- return JSONResponse(response)
115
+ waited_time_in_s: float = 0.0
116
+ sleep_time_in_s: float = 0.1
117
+ while waited_time_in_s < timeout_s and (
118
+ not self.json_message_dict
119
+ or len(self.json_message_dict) == 0
120
+ or self.json_message_dict.get(command_id) is None
121
+ ):
122
+ time.sleep(sleep_time_in_s)
123
+ waited_time_in_s += sleep_time_in_s
124
+
125
+ if not self.json_message_dict or len(self.json_message_dict) == 0:
126
+ raise CommunicationException(None, 'no message to be received.')
127
+
128
+ if self.json_message_dict.get(command_id) is None:
129
+ raise CommunicationException(None, f'no response with id {command_id}')
130
+
131
+ logger.debug(f'#{len(self.json_message_dict)} messages, taking the one with id:{command_id}')
132
+ response: JSONResponse = self.json_message_dict[command_id]
133
+ del self.json_message_dict[command_id]
134
+ logger.debug('retrieved message in dict')
135
+ return response
118
136
 
119
137
  @override
120
138
  def close(self) -> None:
@@ -166,7 +184,8 @@ class WebsocketCommunicator(AbstractCommunicator):
166
184
  for message in self.websocket:
167
185
  logger.debug(f'Received message: {message!r}')
168
186
  if isinstance(message, str):
169
- self.json_message_queue.put(message)
187
+ response: JSONResponse = JSONResponse(message)
188
+ self.json_message_dict[response.id] = response
170
189
  elif isinstance(message, bytes) and self.binary_message_callback:
171
190
  self.binary_message_queue.put(message)
172
191
  else:
@@ -192,21 +211,22 @@ class WebsocketCommunicator(AbstractCommunicator):
192
211
  self.binary_message_callback(binary_message)
193
212
 
194
213
  @override
195
- def request_with_response(self, command: Command, time_to_wait_for_response_in_s: float = 0.1) -> Response:
214
+ def request_with_response(self, command: Command, response_timeout_s: float = 5) -> Response:
196
215
  """
197
216
  Concrete method to fetch a response from a command.
198
217
 
199
218
  Args:
200
219
  command (Command): Command for which a response is desired
201
- time_to_wait_for_response_in_s (float, optional): Time, in seconds, to wait between request and response.
202
- Defaults to 0.1s
220
+ response_timeout_s (float, optional): Timeout in seconds. Defaults to 5.
203
221
 
204
222
  Returns:
205
223
  Response: The response corresponding to the sent command.
206
224
  """
207
225
  self.send(command)
208
- logger.debug('sent command, waiting for response')
209
- time.sleep(time_to_wait_for_response_in_s)
210
- response: Response = self.response()
226
+ response: Response = self.response(command.id, response_timeout_s)
227
+
228
+ if response.id != command.id:
229
+ logger.error(f'got wrong response id: {response.id}, command id: {command.id}')
230
+ raise Exception('got wrong response id')
211
231
 
212
232
  return response
@@ -1,5 +1,4 @@
1
- import re
2
- from typing import Any, final
1
+ from typing import final
3
2
 
4
3
  from loguru import logger
5
4
  from overrides import override
@@ -37,41 +36,18 @@ class DeviceDiscovery(AbstractDeviceDiscovery):
37
36
  raise Exception(f'No {device_type} connected')
38
37
  response = self._communicator.request_with_response(Command(list_command, {}))
39
38
 
40
- # as the responses of ccd_list and mono_list differ, we need to parse them separately
41
- if device_type == 'CCD':
42
- raw_device_list = response.results
43
- self._charge_coupled_devices = self._parse_ccds(raw_device_list)
44
- logger.info(f'Found {len(self._charge_coupled_devices)} CCD devices: {self._charge_coupled_devices}')
45
- elif device_type == 'Monochromator':
46
- raw_device_list = response.results['list']
47
- self._monochromators = self._parse_monos(raw_device_list)
48
- logger.info(f'Found {len(self._monochromators)} Monochromator devices: {self._monochromators}')
49
-
50
- def _parse_ccds(self, raw_device_list: dict[str, Any]) -> list[ChargeCoupledDevice]:
51
- detected_ccds: list[ChargeCoupledDevice] = []
52
- for key, value in raw_device_list.items():
53
- logger.debug(f'Parsing CCD: {key} - {value}')
54
- ccd_index: int = int(key.split(':')[0].replace('index', '').strip())
55
- ccd_type_match = re.search(r'deviceType: (.*?),', value)
56
- if not ccd_type_match:
57
- raise Exception(f'Failed to find ccd type "deviceType" in string "{value}"')
58
- ccd_type: str = str(ccd_type_match.group(1).strip())
59
-
60
- logger.info(f'Detected CCD: {ccd_type}')
61
- detected_ccds.append(ChargeCoupledDevice(ccd_index, self._communicator, self._error_db))
62
-
63
- return detected_ccds
64
-
65
- def _parse_monos(self, raw_device_list: dict[str, Any]) -> list[Monochromator]:
66
- detected_monos = []
67
- for device_string in raw_device_list:
68
- mono_index: int = int(device_string.split(';')[0])
69
- mono_type: str = device_string.split(';')[1]
70
-
71
- logger.info(f'Detected Monochromator: {mono_type}')
72
- detected_monos.append(Monochromator(mono_index, self._communicator, self._error_db))
73
-
74
- return detected_monos
39
+ for device in response.results['devices']:
40
+ if device_type == 'CCD':
41
+ ccd = ChargeCoupledDevice(device['index'], self._communicator, self._error_db)
42
+ logger.info(f'Detected CCD: {device["deviceType"]}')
43
+ self._charge_coupled_devices.append(ccd)
44
+ elif device_type == 'Monochromator':
45
+ mono = Monochromator(device['index'], self._communicator, self._error_db)
46
+ logger.info(f'Detected Monochromator: {device["deviceType"]}')
47
+ self._monochromators.append(mono)
48
+
49
+ logger.info(f'Found {len(self._monochromators)} Monochromator devices')
50
+ logger.info(f'Found {len(self._charge_coupled_devices)} CCD devices')
75
51
 
76
52
  @override
77
53
  def charge_coupled_devices(self) -> list[ChargeCoupledDevice]:
@@ -30,8 +30,8 @@ class DeviceManager(AbstractDeviceManager):
30
30
  def __init__(
31
31
  self,
32
32
  start_icl: bool = True,
33
- websocket_ip: str = '127.0.0.1',
34
- websocket_port: str = '25010',
33
+ icl_ip: str = '127.0.0.1',
34
+ icl_port: str = '25010',
35
35
  enable_binary_messages: bool = True,
36
36
  ):
37
37
  """
@@ -39,17 +39,15 @@ class DeviceManager(AbstractDeviceManager):
39
39
 
40
40
  Args:
41
41
  start_icl (bool) = True: If True, the ICL software is started and communication is established.
42
- websocket_ip (str) = '127.0.0.1': websocket IP
43
- websocket_port (str) = '25010': websocket port
42
+ icl_ip (str) = '127.0.0.1': websocket IP
43
+ icl_port (str) = '25010': websocket port
44
44
  enable_binary_messages (bool) = True: If True, binary messages are enabled.
45
45
  """
46
46
  super().__init__()
47
47
  self._start_icl = start_icl
48
- self._icl_communicator: WebsocketCommunicator = WebsocketCommunicator(
49
- 'ws://' + websocket_ip + ':' + str(websocket_port)
50
- )
51
- self._icl_websocket_ip: str = websocket_ip
52
- self._icl_websocket_port: str = websocket_port
48
+ self._icl_communicator: WebsocketCommunicator = WebsocketCommunicator('ws://' + icl_ip + ':' + str(icl_port))
49
+ self._icl_websocket_ip: str = icl_ip
50
+ self._icl_websocket_port: str = icl_port
53
51
  self._icl_process: Optional[Popen[bytes]] = None
54
52
  self._binary_messages: bool = enable_binary_messages
55
53
  self._charge_coupled_devices: list[ChargeCoupledDevice] = []
@@ -76,7 +74,12 @@ class DeviceManager(AbstractDeviceManager):
76
74
 
77
75
  @override
78
76
  def stop(self) -> None:
79
- self.stop_icl()
77
+ if self._start_icl:
78
+ self.stop_icl()
79
+ return
80
+
81
+ if self._icl_communicator.opened():
82
+ self._icl_communicator.close()
80
83
 
81
84
  def start_icl(self) -> None:
82
85
  """
@@ -55,14 +55,17 @@ class FakeICLServer:
55
55
  websocket.send(message)
56
56
  continue
57
57
  if command['command'].startswith('icl_'):
58
- response = json.dumps(self.icl_responses[command['command']])
59
- websocket.send(response)
58
+ response = self.icl_responses[command['command']]
59
+ response['id'] = command['id']
60
+ websocket.send(json.dumps(response))
60
61
  elif command['command'].startswith('mono_'):
61
- response = json.dumps(self.monochromator_responses[command['command']])
62
- websocket.send(response)
62
+ response = self.monochromator_responses[command['command']]
63
+ response['id'] = command['id']
64
+ websocket.send(json.dumps(response))
63
65
  elif command['command'].startswith('ccd_'):
64
- response = json.dumps(self.ccd_responses[command['command']])
65
- websocket.send(response)
66
+ response = self.ccd_responses[command['command']]
67
+ response['id'] = command['id']
68
+ websocket.send(json.dumps(response))
66
69
  else:
67
70
  logger.info('unknown command, responding with message')
68
71
  websocket.send(message)
@@ -23,6 +23,14 @@ class AbstractDevice(ABC):
23
23
  self._error_db: AbstractErrorDB = error_db
24
24
  self._communicator: AbstractCommunicator = communicator
25
25
 
26
+ def id(self) -> int:
27
+ """Return the ID of the device.
28
+
29
+ Returns:
30
+ int: ID of the device.
31
+ """
32
+ return self._id
33
+
26
34
  @abstractmethod
27
35
  def open(self) -> None:
28
36
  """
@@ -44,9 +52,7 @@ class AbstractDevice(ABC):
44
52
  """
45
53
  pass
46
54
 
47
- def _execute_command(
48
- self, command_name: str, parameters: dict[Any, Any], time_to_wait_for_response_in_s: float = 0.1
49
- ) -> Response:
55
+ def _execute_command(self, command_name: str, parameters: dict[Any, Any], timeout_in_s: float = 5) -> Response:
50
56
  """
51
57
  Creates a command from the command name, and it's parameters
52
58
  Executes a command and handles the response.
@@ -54,7 +60,7 @@ class AbstractDevice(ABC):
54
60
  Args:
55
61
  command_name (str): The name of the command to execute.
56
62
  parameters (dict): The parameters for the command.
57
- time_to_wait_for_response_in_s (float, optional): The time to wait for the response. Defaults to 0.1
63
+ timeout_in_s (float, optional): The timeout in seconds.
58
64
 
59
65
  Returns:
60
66
  Response: The response from the device.
@@ -62,9 +68,7 @@ class AbstractDevice(ABC):
62
68
  Raises:
63
69
  Exception: When an error occurred on the device side.
64
70
  """
65
- response: Response = self._communicator.request_with_response(
66
- Command(command_name, parameters), time_to_wait_for_response_in_s
67
- )
71
+ response: Response = self._communicator.request_with_response(Command(command_name, parameters), timeout_in_s)
68
72
  if response.errors:
69
73
  self._handle_errors(response.errors)
70
74
  return response