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.
- horiba_sdk/__init__.py +5 -3
- horiba_sdk/communication/websocket_communicator.py +2 -2
- horiba_sdk/core/__init__.py +0 -0
- horiba_sdk/core/acquisition_format.py +20 -0
- horiba_sdk/core/clean_count_mode.py +13 -0
- horiba_sdk/core/timer_resolution.py +14 -0
- horiba_sdk/core/x_axis_conversion_type.py +18 -0
- horiba_sdk/devices/ccd_discovery.py +10 -12
- horiba_sdk/devices/device_manager.py +24 -10
- horiba_sdk/devices/fake_responses/ccd.json +261 -12
- horiba_sdk/devices/fake_responses/monochromator.json +38 -10
- horiba_sdk/devices/monochromator_discovery.py +16 -9
- horiba_sdk/devices/single_devices/abstract_device.py +46 -1
- horiba_sdk/devices/single_devices/ccd.py +388 -143
- horiba_sdk/devices/single_devices/monochromator.py +87 -71
- horiba_sdk/sync/communication/abstract_communicator.py +2 -3
- horiba_sdk/sync/communication/websocket_communicator.py +38 -18
- horiba_sdk/sync/devices/device_discovery.py +13 -37
- horiba_sdk/sync/devices/device_manager.py +14 -10
- horiba_sdk/sync/devices/fake_icl_server.py +9 -6
- horiba_sdk/sync/devices/single_devices/abstract_device.py +11 -7
- horiba_sdk/sync/devices/single_devices/ccd.py +517 -62
- horiba_sdk/sync/devices/single_devices/monochromator.py +288 -25
- {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/METADATA +166 -92
- {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/RECORD +27 -22
- {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.5.2.dist-info}/LICENSE +0 -0
- {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
|
-
|
56
|
-
|
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
|
-
|
64
|
-
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
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},
|
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
|
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
|
-
|
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
|
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
|
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, '
|
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
|
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, '
|
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, '
|
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, '
|
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
|
-
|
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, '
|
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
|
-
|
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, '
|
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) ->
|
331
|
-
"""Returns the
|
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
|
-
|
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, '
|
360
|
+
'mono_getSlitStepPosition', {'index': self._id, 'locationId': slit.value}
|
348
361
|
)
|
349
|
-
return
|
362
|
+
return int(response.results['position'])
|
350
363
|
|
351
|
-
async def set_slit_step_position(self, slit: Slit, step_position:
|
352
|
-
"""
|
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 (
|
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, '
|
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
|
-
|
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,
|
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
|
-
|
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.
|
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
|
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
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
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.
|
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
|
-
|
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,
|
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
|
-
|
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
|
-
|
209
|
-
|
210
|
-
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
|
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
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
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
|
-
|
34
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
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.
|
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 =
|
59
|
-
|
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 =
|
62
|
-
|
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 =
|
65
|
-
|
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)
|