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.
- horiba_sdk/__init__.py +0 -4
- horiba_sdk/core/__init__.py +0 -0
- horiba_sdk/core/acquisition_format.py +10 -0
- horiba_sdk/core/clean_count_mode.py +11 -0
- horiba_sdk/core/timer_resolution.py +12 -0
- horiba_sdk/core/x_axis_conversion_type.py +13 -0
- horiba_sdk/devices/ccd_discovery.py +10 -12
- horiba_sdk/devices/device_manager.py +13 -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 +8 -0
- horiba_sdk/devices/single_devices/ccd.py +359 -153
- horiba_sdk/devices/single_devices/monochromator.py +73 -70
- horiba_sdk/sync/communication/abstract_communicator.py +2 -3
- horiba_sdk/sync/communication/websocket_communicator.py +37 -17
- horiba_sdk/sync/devices/device_discovery.py +13 -37
- horiba_sdk/sync/devices/device_manager.py +13 -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 +479 -62
- horiba_sdk/sync/devices/single_devices/monochromator.py +274 -24
- {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.4.0.dist-info}/METADATA +108 -13
- {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.4.0.dist-info}/RECORD +26 -21
- {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.4.0.dist-info}/LICENSE +0 -0
- {horiba_sdk-0.3.3.dist-info → horiba_sdk-0.4.0.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,7 +140,7 @@ 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'])
|
@@ -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
|
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
|
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},
|
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
|
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
|
-
|
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
|
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
|
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, '
|
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
|
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, '
|
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, '
|
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, '
|
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
|
-
|
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, '
|
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
|
-
|
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, '
|
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) ->
|
331
|
-
"""Returns the
|
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
|
-
|
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, '
|
347
|
+
'mono_getSlitStepPosition', {'index': self._id, 'locationId': slit.value}
|
348
348
|
)
|
349
|
-
return
|
349
|
+
return int(response.results['position'])
|
350
350
|
|
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.
|
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 (
|
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, '
|
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
|
-
|
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,
|
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:
|
@@ -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]:
|
@@ -30,8 +30,8 @@ class DeviceManager(AbstractDeviceManager):
|
|
30
30
|
def __init__(
|
31
31
|
self,
|
32
32
|
start_icl: bool = True,
|
33
|
-
|
34
|
-
|
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
|
-
|
43
|
-
|
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
|
-
|
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.
|
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 =
|
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)
|
@@ -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
|
-
|
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
|