horiba-sdk 0.3.3__py3-none-any.whl → 0.5.2__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.
Files changed (27) hide show
  1. horiba_sdk/__init__.py +5 -3
  2. horiba_sdk/communication/websocket_communicator.py +2 -2
  3. horiba_sdk/core/__init__.py +0 -0
  4. horiba_sdk/core/acquisition_format.py +20 -0
  5. horiba_sdk/core/clean_count_mode.py +13 -0
  6. horiba_sdk/core/timer_resolution.py +14 -0
  7. horiba_sdk/core/x_axis_conversion_type.py +18 -0
  8. horiba_sdk/devices/ccd_discovery.py +10 -12
  9. horiba_sdk/devices/device_manager.py +24 -10
  10. horiba_sdk/devices/fake_responses/ccd.json +261 -12
  11. horiba_sdk/devices/fake_responses/monochromator.json +38 -10
  12. horiba_sdk/devices/monochromator_discovery.py +16 -9
  13. horiba_sdk/devices/single_devices/abstract_device.py +46 -1
  14. horiba_sdk/devices/single_devices/ccd.py +388 -143
  15. horiba_sdk/devices/single_devices/monochromator.py +87 -71
  16. horiba_sdk/sync/communication/abstract_communicator.py +2 -3
  17. horiba_sdk/sync/communication/websocket_communicator.py +38 -18
  18. horiba_sdk/sync/devices/device_discovery.py +13 -37
  19. horiba_sdk/sync/devices/device_manager.py +14 -10
  20. horiba_sdk/sync/devices/fake_icl_server.py +9 -6
  21. horiba_sdk/sync/devices/single_devices/abstract_device.py +11 -7
  22. horiba_sdk/sync/devices/single_devices/ccd.py +517 -62
  23. horiba_sdk/sync/devices/single_devices/monochromator.py +288 -25
  24. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/METADATA +166 -92
  25. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/RECORD +27 -22
  26. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/LICENSE +0 -0
  27. {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/WHEEL +0 -0
@@ -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,29 +140,42 @@ 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'])
140
147
 
141
- async def home(self) -> None:
148
+ async def initialize(self) -> None:
142
149
  """Starts the monochromator initialization process called "homing".
143
150
 
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 is_initialized(self) -> bool:
159
+ """This command returns true when the mono is initialized. Otherwise, it returns false.
160
+ Note: This command may also return false when the mono is busy with another command.
161
+
162
+ Returns:
163
+ bool: If the monochromator is initialized or not
164
+
165
+ Raises:
166
+ Exception: When an error occurred on the device side
167
+ """
168
+ response: Response = await super()._execute_command('mono_isInitialized', {'index': self._id})
169
+ return bool(response.results['initialized'])
170
+
171
+ async def configuration(self) -> dict[str, Any]:
152
172
  """Returns the configuration of the monochromator.
153
173
 
154
174
  Returns:
155
175
  str: configuration of the monochromator
156
176
  """
157
177
  response: Response = await super()._execute_command('mono_getConfig', {'index': self._id, 'compact': False})
158
- return str(response.results)
178
+ return response.results['configuration']
159
179
 
160
180
  async def get_current_wavelength(self) -> float:
161
181
  """Current wavelength of the monochromator's position in nm.
@@ -194,16 +214,20 @@ class Monochromator(AbstractDevice):
194
214
  Raises:
195
215
  Exception: When an error occurred on the device side
196
216
  """
197
- await super()._execute_command('mono_moveToPosition', {'index': self._id, 'wavelength': wavelength}, 60)
217
+ await super()._execute_command('mono_moveToPosition', {'index': self._id, 'wavelength': wavelength}, 180)
198
218
 
199
219
  async def get_turret_grating(self) -> Grating:
200
- """Current grating of the turret
220
+ """Current grating of the turret.
221
+
222
+ .. note:: Prior to the initialization of the grating turret, this value may not reflect the actual position
223
+ of the turret. To read the current position of the grating turret, please run
224
+ :func:`Monochromator.home()` prior to running this command.
201
225
 
202
226
  Returns:
203
227
  Grating: current grating of turret. See :class:`Monochromator.Grating` for possible values.
204
228
 
205
229
  Raises:
206
- Exception: When an error occured on the device side
230
+ Exception: When an error occurred on the device side
207
231
  """
208
232
  response: Response = await super()._execute_command('mono_getGratingPosition', {'index': self._id})
209
233
  return self.Grating(response.results['position'])
@@ -211,15 +235,18 @@ class Monochromator(AbstractDevice):
211
235
  async def set_turret_grating(self, grating: Grating) -> None:
212
236
  """Select turret grating
213
237
 
238
+ .. note:: Note: The turret sensor does not re-read the position each time it is moved, therefore the position
239
+ may not be accurate prior to initialization. See note for get_turret_grating().
240
+
214
241
  Args:
215
- position (Grating): new grating of the turret. See :class:`Monochromator.Grating` for possible values.
242
+ grating (Grating): new grating of the turret. See :class:`Monochromator.Grating` for possible values.
216
243
 
217
244
  Raises:
218
- Exception: When an error occured on the device side
245
+ Exception: When an error occurred on the device side
219
246
  """
220
247
  await super()._execute_command('mono_moveGrating', {'index': self._id, 'position': grating.value})
221
248
 
222
- async def get_filter_wheel_position(self) -> FilterWheelPosition:
249
+ async def get_filter_wheel_position(self, filter_wheel: FilterWheel) -> FilterWheelPosition:
223
250
  """Current position of the filter wheel.
224
251
 
225
252
  Returns:
@@ -227,16 +254,14 @@ class Monochromator(AbstractDevice):
227
254
  for possible values.
228
255
 
229
256
  Raises:
230
- Exception: When an error occured on the device side
257
+ Exception: When an error occurred on the device side
231
258
  """
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
259
  response: Response = await super()._execute_command(
235
- 'mono_getFilterWheelPosition', {'index': self._id, 'type': 1}
260
+ 'mono_getFilterWheelPosition', {'index': self._id, 'locationId': filter_wheel.value}
236
261
  )
237
262
  return self.FilterWheelPosition(response.results['position'])
238
263
 
239
- async def set_filter_wheel_position(self, position: FilterWheelPosition) -> None:
264
+ async def set_filter_wheel_position(self, filter_wheel: FilterWheel, position: FilterWheelPosition) -> None:
240
265
  """Sets the current position of the filter wheel.
241
266
 
242
267
  Returns:
@@ -244,12 +269,10 @@ class Monochromator(AbstractDevice):
244
269
  for possible values.
245
270
 
246
271
  Raises:
247
- Exception: When an error occured on the device side
272
+ Exception: When an error occurred on the device side
248
273
  """
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
274
  await super()._execute_command(
252
- 'mono_moveFilterWheel', {'index': self._id, 'type': 1, 'position': position.value}
275
+ 'mono_moveFilterWheel', {'index': self._id, 'locationId': filter_wheel.value, 'position': position.value}
253
276
  )
254
277
 
255
278
  async def get_mirror_position(self, mirror: Mirror) -> MirrorPosition:
@@ -268,7 +291,7 @@ class Monochromator(AbstractDevice):
268
291
  Exception: When an error occurred on the device side
269
292
  """
270
293
  response: Response = await super()._execute_command(
271
- 'mono_getMirrorPosition', {'index': self._id, 'type': mirror.value}
294
+ 'mono_getMirrorPosition', {'index': self._id, 'locationId': mirror.value}
272
295
  )
273
296
  return self.MirrorPosition(response.results['position'])
274
297
 
@@ -286,18 +309,15 @@ class Monochromator(AbstractDevice):
286
309
  Exception: When an error occurred on the device side
287
310
  """
288
311
  await super()._execute_command(
289
- 'mono_moveMirror', {'index': self._id, 'type': mirror.value, 'position': position.value}
312
+ 'mono_moveMirror', {'index': self._id, 'locationId': mirror.value, 'position': position.value}
290
313
  )
291
314
 
292
315
  async def get_slit_position_in_mm(self, slit: Slit) -> float:
293
316
  """Returns the position in millimeters [mm] of the selected slit.
294
317
 
295
- .. todo:: Get more information about possible values and explain elements contained in monochromator at top
296
- of this class.
297
-
298
318
  Args:
299
319
  slit (Slit): desired slit to get the position from. See :class:`Monochromator.Slit` for possible
300
- values
320
+
301
321
  Returns:
302
322
  float: position in mm
303
323
 
@@ -306,64 +326,53 @@ class Monochromator(AbstractDevice):
306
326
  """
307
327
 
308
328
  response: Response = await super()._execute_command(
309
- 'mono_getSlitPositionInMM', {'index': self._id, 'type': slit.value}
329
+ 'mono_getSlitPositionInMM', {'index': self._id, 'locationId': slit.value}
310
330
  )
311
331
  return float(response.results['position'])
312
332
 
313
333
  async def set_slit_position(self, slit: Slit, position_in_mm: float) -> None:
314
334
  """Sets the position of the selected slit.
315
335
 
316
- .. todo:: Get more information about possible values and explain elements contained in monochromator at top
317
- of this class.
318
-
319
336
  Args:
320
337
  slit (Slit): desired slit to set the position. See :class:`Monochromator.Slit` for possible values.
321
- position (float): position to set in millimeters [mm].
338
+ position_in_mm (float): position to set in millimeters [mm].
322
339
 
323
340
  Raises:
324
341
  Exception: When an error occurred on the device side
325
342
  """
326
343
  await super()._execute_command(
327
- 'mono_moveSlitMM', {'index': self._id, 'type': slit.value, 'position': position_in_mm}
344
+ 'mono_moveSlitMM', {'index': self._id, 'locationId': slit.value, 'position': position_in_mm}
328
345
  )
329
346
 
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.
347
+ async def get_slit_step_position(self, slit: Slit) -> int:
348
+ """Returns the position of the specified slit in steps.
335
349
 
336
350
  Args:
337
351
  slit (Slit): desired slit to get the position from. See :class:`Monochromator.Slit` for possible
338
- values
339
352
  Returns:
340
- SlitStepPosition: step position. See :class:`Monochromator.SlitStepPosition` for possible values
353
+ int: step position.
341
354
 
342
355
  Raises:
343
356
  Exception: When an error occurred on the device side
344
357
  """
345
358
 
346
359
  response: Response = await super()._execute_command(
347
- 'mono_getSlitStepPosition', {'index': self._id, 'type': slit.value}
360
+ 'mono_getSlitStepPosition', {'index': self._id, 'locationId': slit.value}
348
361
  )
349
- return self.SlitStepPosition(response.results['position'])
362
+ return int(response.results['position'])
350
363
 
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.
364
+ async def set_slit_step_position(self, slit: Slit, step_position: int) -> None:
365
+ """Moves the specified slit to the position in steps.
356
366
 
357
367
  Args:
358
368
  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
369
+ step_position (int): the step position.
361
370
 
362
371
  Raises:
363
372
  Exception: When an error occurred on the device side
364
373
  """
365
374
  await super()._execute_command(
366
- 'mono_moveSlit', {'index': self._id, 'type': slit.value, 'position': step_position.value}
375
+ 'mono_moveSlit', {'index': self._id, 'locationId': slit.value, 'position': step_position}
367
376
  )
368
377
 
369
378
  async def open_shutter(self) -> None:
@@ -382,7 +391,7 @@ class Monochromator(AbstractDevice):
382
391
  """
383
392
  await super()._execute_command('mono_shutterClose', {'index': self._id})
384
393
 
385
- async def get_shutter_position(self) -> ShutterPosition:
394
+ async def get_shutter_position(self, shutter: Shutter) -> ShutterPosition:
386
395
  """Returns the shutter position.
387
396
 
388
397
  Returns:
@@ -392,4 +401,11 @@ class Monochromator(AbstractDevice):
392
401
  Exception: When an error occurred on the device side
393
402
  """
394
403
  response: Response = await super()._execute_command('mono_getShutterStatus', {'index': self._id})
395
- return self.ShutterPosition(response.results['position'])
404
+ # TODO: How many shutters are there?
405
+ if shutter == self.Shutter.FIRST:
406
+ return self.ShutterPosition(response.results['shutter 1'])
407
+ elif shutter == self.Shutter.SECOND:
408
+ return self.ShutterPosition(response.results['shutter 2'])
409
+ else:
410
+ logger.error(f'shutter {shutter} not implemented')
411
+ 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:
@@ -151,7 +169,7 @@ class WebsocketCommunicator(AbstractCommunicator):
151
169
  raise CommunicationException(None, 'Binary message callback already registered')
152
170
 
153
171
  self.binary_message_callback = callback
154
- logger.info('Binary message callback registered.')
172
+ logger.debug('Binary message callback registered.')
155
173
  self.running_binary_message_handling_thread = True
156
174
  self.binary_message_handling_thread = Thread(target=self._run_binary_message_callback)
157
175
  self.binary_message_handling_thread.start()
@@ -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]:
@@ -3,6 +3,7 @@ Synchronous Device Manager Module
3
3
 
4
4
  This Device Manager uses threads instead of asyncio
5
5
  """
6
+
6
7
  import importlib.resources
7
8
  import platform
8
9
  import subprocess
@@ -30,8 +31,8 @@ class DeviceManager(AbstractDeviceManager):
30
31
  def __init__(
31
32
  self,
32
33
  start_icl: bool = True,
33
- websocket_ip: str = '127.0.0.1',
34
- websocket_port: str = '25010',
34
+ icl_ip: str = '127.0.0.1',
35
+ icl_port: str = '25010',
35
36
  enable_binary_messages: bool = True,
36
37
  ):
37
38
  """
@@ -39,17 +40,15 @@ class DeviceManager(AbstractDeviceManager):
39
40
 
40
41
  Args:
41
42
  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
43
+ icl_ip (str) = '127.0.0.1': websocket IP
44
+ icl_port (str) = '25010': websocket port
44
45
  enable_binary_messages (bool) = True: If True, binary messages are enabled.
45
46
  """
46
47
  super().__init__()
47
48
  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
49
+ self._icl_communicator: WebsocketCommunicator = WebsocketCommunicator('ws://' + icl_ip + ':' + str(icl_port))
50
+ self._icl_websocket_ip: str = icl_ip
51
+ self._icl_websocket_port: str = icl_port
53
52
  self._icl_process: Optional[Popen[bytes]] = None
54
53
  self._binary_messages: bool = enable_binary_messages
55
54
  self._charge_coupled_devices: list[ChargeCoupledDevice] = []
@@ -76,7 +75,12 @@ class DeviceManager(AbstractDeviceManager):
76
75
 
77
76
  @override
78
77
  def stop(self) -> None:
79
- self.stop_icl()
78
+ if self._start_icl:
79
+ self.stop_icl()
80
+ return
81
+
82
+ if self._icl_communicator.opened():
83
+ self._icl_communicator.close()
80
84
 
81
85
  def start_icl(self) -> None:
82
86
  """
@@ -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)