horiba-sdk 0.3.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 (48) hide show
  1. horiba_sdk/__init__.py +19 -0
  2. horiba_sdk/communication/__init__.py +44 -0
  3. horiba_sdk/communication/abstract_communicator.py +59 -0
  4. horiba_sdk/communication/communication_exception.py +19 -0
  5. horiba_sdk/communication/messages.py +87 -0
  6. horiba_sdk/communication/websocket_communicator.py +213 -0
  7. horiba_sdk/core/resolution.py +45 -0
  8. horiba_sdk/devices/__init__.py +11 -0
  9. horiba_sdk/devices/abstract_device_discovery.py +7 -0
  10. horiba_sdk/devices/abstract_device_manager.py +68 -0
  11. horiba_sdk/devices/ccd_discovery.py +57 -0
  12. horiba_sdk/devices/device_manager.py +250 -0
  13. horiba_sdk/devices/fake_device_manager.py +133 -0
  14. horiba_sdk/devices/fake_icl_server.py +56 -0
  15. horiba_sdk/devices/fake_responses/ccd.json +168 -0
  16. horiba_sdk/devices/fake_responses/icl.json +29 -0
  17. horiba_sdk/devices/fake_responses/monochromator.json +187 -0
  18. horiba_sdk/devices/monochromator_discovery.py +48 -0
  19. horiba_sdk/devices/single_devices/__init__.py +5 -0
  20. horiba_sdk/devices/single_devices/abstract_device.py +79 -0
  21. horiba_sdk/devices/single_devices/ccd.py +443 -0
  22. horiba_sdk/devices/single_devices/monochromator.py +395 -0
  23. horiba_sdk/icl_error/__init__.py +34 -0
  24. horiba_sdk/icl_error/abstract_error.py +65 -0
  25. horiba_sdk/icl_error/abstract_error_db.py +25 -0
  26. horiba_sdk/icl_error/error_list.json +265 -0
  27. horiba_sdk/icl_error/icl_error.py +30 -0
  28. horiba_sdk/icl_error/icl_error_db.py +81 -0
  29. horiba_sdk/sync/__init__.py +0 -0
  30. horiba_sdk/sync/communication/__init__.py +7 -0
  31. horiba_sdk/sync/communication/abstract_communicator.py +48 -0
  32. horiba_sdk/sync/communication/test_client.py +16 -0
  33. horiba_sdk/sync/communication/websocket_communicator.py +212 -0
  34. horiba_sdk/sync/devices/__init__.py +15 -0
  35. horiba_sdk/sync/devices/abstract_device_discovery.py +17 -0
  36. horiba_sdk/sync/devices/abstract_device_manager.py +68 -0
  37. horiba_sdk/sync/devices/device_discovery.py +82 -0
  38. horiba_sdk/sync/devices/device_manager.py +209 -0
  39. horiba_sdk/sync/devices/fake_device_manager.py +91 -0
  40. horiba_sdk/sync/devices/fake_icl_server.py +79 -0
  41. horiba_sdk/sync/devices/single_devices/__init__.py +5 -0
  42. horiba_sdk/sync/devices/single_devices/abstract_device.py +83 -0
  43. horiba_sdk/sync/devices/single_devices/ccd.py +219 -0
  44. horiba_sdk/sync/devices/single_devices/monochromator.py +150 -0
  45. horiba_sdk-0.3.2.dist-info/LICENSE +20 -0
  46. horiba_sdk-0.3.2.dist-info/METADATA +438 -0
  47. horiba_sdk-0.3.2.dist-info/RECORD +48 -0
  48. horiba_sdk-0.3.2.dist-info/WHEEL +4 -0
@@ -0,0 +1,265 @@
1
+ {
2
+ "errors": [
3
+ {
4
+ "number": -1,
5
+ "text": "ICL error: no parser found",
6
+ "level": "fatal"
7
+ },
8
+ {
9
+ "number": -2,
10
+ "text": "ICL error: unknown command",
11
+ "level": "fatal"
12
+ },
13
+ {
14
+ "number": -3,
15
+ "text": "ICL error: invalid bin mode",
16
+ "level": "fatal"
17
+ },
18
+ {
19
+ "number": -300,
20
+ "text": "CCD error: already initialized",
21
+ "level": "fatal"
22
+ },
23
+ {
24
+ "number": -301,
25
+ "text": "CCD error: already open",
26
+ "level": "fatal"
27
+ },
28
+ {
29
+ "number": -302,
30
+ "text": "CCD error: already closed",
31
+ "level": "fatal"
32
+ },
33
+ {
34
+ "number": -303,
35
+ "text": "CCD error: already uninitialized",
36
+ "level": "fatal"
37
+ },
38
+ {
39
+ "number": -304,
40
+ "text": "CCD error: not initialized",
41
+ "level": "fatal"
42
+ },
43
+ {
44
+ "number": -305,
45
+ "text": "CCD error: not open",
46
+ "level": "fatal"
47
+ },
48
+ {
49
+ "number": -306,
50
+ "text": "CCD error: not found",
51
+ "level": "fatal"
52
+ },
53
+ {
54
+ "number": -307,
55
+ "text": "CCD error: invalid device index",
56
+ "level": "fatal"
57
+ },
58
+ {
59
+ "number": -308,
60
+ "text": "CCD error: initialization failure",
61
+ "level": "fatal"
62
+ },
63
+ {
64
+ "number": -309,
65
+ "text": "CCD error: acquiring",
66
+ "level": "fatal"
67
+ },
68
+ {
69
+ "number": -310,
70
+ "text": "CCD error: acquisition preparation failed",
71
+ "level": "fatal"
72
+ },
73
+ {
74
+ "number": -311,
75
+ "text": "CCD error: not ready for acquisition",
76
+ "level": "fatal"
77
+ },
78
+ {
79
+ "number": -312,
80
+ "text": "CCD error: get spectra failed",
81
+ "level": "fatal"
82
+ },
83
+ {
84
+ "number": -313,
85
+ "text": "CCD error: go failed",
86
+ "level": "fatal"
87
+ },
88
+ {
89
+ "number": -314,
90
+ "text": "CCD error: no free packet",
91
+ "level": "fatal"
92
+ },
93
+ {
94
+ "number": -315,
95
+ "text": "CCD error: command not supported",
96
+ "level": "fatal"
97
+ },
98
+ {
99
+ "number": -316,
100
+ "text": "CCD error: command failed",
101
+ "level": "fatal"
102
+ },
103
+ {
104
+ "number": -317,
105
+ "text": "CCD error: invalid token",
106
+ "level": "fatal"
107
+ },
108
+ {
109
+ "number": -318,
110
+ "text": "CCD error: invalid value",
111
+ "level": "fatal"
112
+ },
113
+ {
114
+ "number": -319,
115
+ "text": "CCD error: capabilities read error",
116
+ "level": "fatal"
117
+ },
118
+ {
119
+ "number": -320,
120
+ "text": "CCD error: acquisition already running",
121
+ "level": "fatal"
122
+ },
123
+ {
124
+ "number": -321,
125
+ "text": "CCD error: acquisition data format error",
126
+ "level": "fatal"
127
+ },
128
+ {
129
+ "number": -322,
130
+ "text": "CCD error: unsupported acquisition format",
131
+ "level": "fatal"
132
+ },
133
+ {
134
+ "number": -323,
135
+ "text": "CCD error: command execution exception",
136
+ "level": "fatal"
137
+ },
138
+ {
139
+ "number": -324,
140
+ "text": "CCD error: missing parameter",
141
+ "level": "fatal"
142
+ },
143
+ {
144
+ "number": -500,
145
+ "text": "MONO error: already initialized",
146
+ "level": "fatal"
147
+ },
148
+ {
149
+ "number": -501,
150
+ "text": "MONO error: already open",
151
+ "level": "fatal"
152
+ },
153
+ {
154
+ "number": -502,
155
+ "text": "MONO error: already opening",
156
+ "level": "fatal"
157
+ },
158
+ {
159
+ "number": -503,
160
+ "text": "MONO error: already closed",
161
+ "level": "fatal"
162
+ },
163
+ {
164
+ "number": -504,
165
+ "text": "MONO error: already uninitialized",
166
+ "level": "fatal"
167
+ },
168
+ {
169
+ "number": -505,
170
+ "text": "MONO error: not initialized",
171
+ "level": "fatal"
172
+ },
173
+ {
174
+ "number": -506,
175
+ "text": "MONO error: not open",
176
+ "level": "fatal"
177
+ },
178
+ {
179
+ "number": -507,
180
+ "text": "MONO error: not found",
181
+ "level": "fatal"
182
+ },
183
+ {
184
+ "number": -508,
185
+ "text": "MONO error: invalid device index",
186
+ "level": "fatal"
187
+ },
188
+ {
189
+ "number": -509,
190
+ "text": "MONO error: initialization failure",
191
+ "level": "fatal"
192
+ },
193
+ {
194
+ "number": -510,
195
+ "text": "MONO error: command not supported",
196
+ "level": "fatal"
197
+ },
198
+ {
199
+ "number": -511,
200
+ "text": "MONO error: discovery",
201
+ "level": "fatal"
202
+ },
203
+ {
204
+ "number": -512,
205
+ "text": "MONO error: communication error",
206
+ "level": "fatal"
207
+ },
208
+ {
209
+ "number": -513,
210
+ "text": "MONO error: invalid parameter",
211
+ "level": "fatal"
212
+ },
213
+ {
214
+ "number": -514,
215
+ "text": "MONO error: lost USB connection",
216
+ "level": "fatal"
217
+ },
218
+ {
219
+ "number": -515,
220
+ "text": "MONO error: open error",
221
+ "level": "fatal"
222
+ },
223
+ {
224
+ "number": -516,
225
+ "text": "MONO error: error log",
226
+ "level": "fatal"
227
+ },
228
+ {
229
+ "number": -517,
230
+ "text": "MONO error: initialization error",
231
+ "level": "fatal"
232
+ },
233
+ {
234
+ "number": -518,
235
+ "text": "MONO error: get configuration",
236
+ "level": "fatal"
237
+ },
238
+ {
239
+ "number": -519,
240
+ "text": "MONO error: command error",
241
+ "level": "fatal"
242
+ },
243
+ {
244
+ "number": -520,
245
+ "text": "MONO error: communication failed",
246
+ "level": "fatal"
247
+ },
248
+ {
249
+ "number": -521,
250
+ "text": "MONO error: missing parameter",
251
+ "level": "fatal"
252
+ },
253
+ {
254
+ "number": -600,
255
+ "text": "SCD error: command not supported",
256
+ "level": "fatal"
257
+ },
258
+ {
259
+ "number": -729,
260
+ "text": "Missing parameter argument: ",
261
+ "level": "fatal"
262
+ }
263
+
264
+ ]
265
+ }
@@ -0,0 +1,30 @@
1
+ from typing import final
2
+
3
+ from loguru import logger
4
+ from overrides import override
5
+
6
+ from horiba_sdk.icl_error import AbstractError, Severity
7
+
8
+
9
+ @final
10
+ class ICLError(AbstractError):
11
+ """Represents an Error from the ICL. It has an error code, a message and severity."""
12
+
13
+ def __init__(self, code: int, message: str, severity: Severity) -> None:
14
+ self._code = code
15
+ self._message = message
16
+ self._severity = severity
17
+
18
+ @override
19
+ def log(self) -> None:
20
+ """Logs the error with the appropriate severity"""
21
+ logger.log(self._severity.name, self._message)
22
+
23
+ @override
24
+ def message(self) -> str:
25
+ """Message of error.
26
+
27
+ Returns:
28
+ str: message
29
+ """
30
+ return self._message
@@ -0,0 +1,81 @@
1
+ import json
2
+ from pathlib import Path
3
+ from typing import final
4
+
5
+ from loguru import logger
6
+ from overrides import override
7
+
8
+ from horiba_sdk.icl_error import AbstractError, AbstractErrorDB, ICLError, Severity, StringAsSeverity
9
+
10
+
11
+ @final
12
+ class ICLErrorDB(AbstractErrorDB):
13
+ """ICL Error Database
14
+
15
+ This class loads a error database in json format. Based on an error string from the ICL, it returns a
16
+ `horiba_sdk.icl_error.ICLError`
17
+ object.
18
+
19
+ The json databse has to look like the following example:
20
+
21
+ .. code-block:: json
22
+
23
+ {
24
+ "errors": [
25
+ {
26
+ "number": -1,
27
+ "text": "ICL error: no parser found",
28
+ "level": "fatal"
29
+ },
30
+ {
31
+ "number": -2,
32
+ "text": "ICL error: unknown command",
33
+ "level": "fatal"
34
+ }
35
+ }
36
+
37
+ A database exists in this module under :code:`horiba_sdk/icl_error/error_list.json`
38
+
39
+ """
40
+
41
+ def __init__(self, json_db_path: Path) -> None:
42
+ if not json_db_path.is_file():
43
+ raise FileNotFoundError(f'ICL Json DB does not exist at {json_db_path}')
44
+
45
+ with open(json_db_path) as file:
46
+ self._icl_error_db = json.load(file)
47
+
48
+ @override
49
+ def error_from(self, string: str) -> AbstractError:
50
+ """Searches an error in the database and when successfull returns a corresponding
51
+ `horiba_sdk.icl_error.ICLError`.
52
+
53
+ Args:
54
+ string (str): ICL error string in the format :code:`'[E];<error code>;<error string>'`
55
+
56
+ Returns:
57
+ ICLError: the corresponding error
58
+
59
+ Raises:
60
+ Exception: when the error string is not formatted as explained above or when no error is found with the
61
+ given error code.
62
+ """
63
+ parsed_error = string.split(';')
64
+
65
+ if len(parsed_error) != 3:
66
+ raise Exception(f'Invalid length of ICL error string, was {len(parsed_error)} should be 3')
67
+
68
+ error_code: int = int(parsed_error[1])
69
+ found_error = next(
70
+ (error for error in self._icl_error_db.get('errors', []) if error.get('number') == error_code), None
71
+ )
72
+
73
+ if found_error is None:
74
+ logger.error(f'Error with number #{error_code} not found in error db')
75
+ text: str = parsed_error[2]
76
+ return ICLError(error_code, f'Unknown error: {text}', Severity.CRITICAL)
77
+
78
+ level: str = found_error.get('level')
79
+ severity: Severity = StringAsSeverity(level).to_severity()
80
+ message: str = found_error.get('text')
81
+ return ICLError(error_code, message, severity)
File without changes
@@ -0,0 +1,7 @@
1
+ from .abstract_communicator import AbstractCommunicator
2
+ from .websocket_communicator import WebsocketCommunicator
3
+
4
+ __all__ = [
5
+ 'AbstractCommunicator',
6
+ 'WebsocketCommunicator',
7
+ ]
@@ -0,0 +1,48 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from horiba_sdk.communication.messages import Command, Response
4
+
5
+
6
+ class AbstractCommunicator(ABC):
7
+ """
8
+ Abstract base class for communication protocols.
9
+ """
10
+
11
+ @abstractmethod
12
+ def open(self) -> None:
13
+ """
14
+ Abstract method to establish a connection.
15
+ """
16
+ pass
17
+
18
+ @abstractmethod
19
+ def opened(self) -> bool:
20
+ """
21
+ Abstract method that says if the connection is open
22
+
23
+ Returns:
24
+ bool: True if the connection is open, False otherwise
25
+ """
26
+ pass
27
+
28
+ @abstractmethod
29
+ def request_with_response(self, command: Command, time_to_wait_for_response_in_s: float = 0.1) -> Response:
30
+ """
31
+ Abstract method to fetch a response from a command.
32
+
33
+ Args:
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
37
+
38
+ Returns:
39
+ Response: The response corresponding to the sent command.
40
+ """
41
+ pass
42
+
43
+ @abstractmethod
44
+ def close(self) -> None:
45
+ """
46
+ Abstract method to close the connection.
47
+ """
48
+ pass
@@ -0,0 +1,16 @@
1
+ # Closing frame is apparently not sent. Here is a minimal example to prove the point:
2
+ # 1. First run icl.exe
3
+ # 2. Then execute this script:
4
+ # poetry run python ./horiba_sdk/sync/communication/test_client.py
5
+ from websockets.exceptions import ConnectionClosedError, ConnectionClosedOK
6
+ from websockets.sync.client import connect
7
+
8
+ websocket = connect(uri='ws://localhost:25010')
9
+ try:
10
+ websocket.send('{"command":"icl_shutdown"}')
11
+ for message in websocket:
12
+ print(message)
13
+ except ConnectionClosedOK as e:
14
+ print(f'Connection normally closed: {e}')
15
+ except ConnectionClosedError as e:
16
+ print(f'Protocol error or network failure: {e}')
@@ -0,0 +1,212 @@
1
+ import time
2
+ from queue import Queue
3
+ from threading import Thread
4
+ from types import TracebackType
5
+ from typing import Any, Callable, Optional, final
6
+
7
+ import websockets
8
+ from loguru import logger
9
+ from overrides import override
10
+ from websockets.sync.client import ClientConnection
11
+
12
+ from horiba_sdk.communication.communication_exception import CommunicationException
13
+ from horiba_sdk.communication.messages import Command, JSONResponse, Response
14
+ from horiba_sdk.sync.communication.abstract_communicator import AbstractCommunicator
15
+
16
+
17
+ @final
18
+ class WebsocketCommunicator(AbstractCommunicator):
19
+ """
20
+ The WebsocketCommunicator implements the `horiba_sdk.sync.communication.AbstractCommunicator` via websockets.
21
+ A background thread listens continuously for incoming binary data.
22
+ """
23
+
24
+ def __init__(self, uri: str = 'ws://127.0.0.1:25010') -> None:
25
+ self.uri: str = uri
26
+ self.websocket: Optional[ClientConnection] = None
27
+ self.running_listen_thread: bool = False
28
+ self.listen_thread: Optional[Thread] = None
29
+ self.running_binary_message_handling_thread: bool = False
30
+ self.binary_message_handling_thread: Optional[Thread] = None
31
+ self.json_message_queue: Queue[str] = Queue()
32
+ self.binary_message_queue: Queue[bytes] = Queue()
33
+ self.binary_message_callback: Optional[Callable[[bytes], Any]] = None
34
+ self.icl_info: dict[str, Any] = {}
35
+
36
+ def __enter__(self) -> 'WebsocketCommunicator':
37
+ self.open()
38
+ return self
39
+
40
+ def __exit__(
41
+ self,
42
+ exc_type: Optional[type[BaseException]],
43
+ exc_value: Optional[BaseException],
44
+ traceback: Optional[TracebackType],
45
+ ) -> None:
46
+ self.close()
47
+
48
+ @override
49
+ def open(self) -> None:
50
+ """
51
+ Opens the WebSocket connection and starts listening for binary data.
52
+
53
+ Raises:
54
+ CommunicationException: When the websocket is already opened or
55
+ there is an issue with the underlying websockets connection attempt.
56
+ """
57
+ if self.opened():
58
+ raise CommunicationException(None, 'websocket already opened')
59
+
60
+ try:
61
+ self.websocket = websockets.sync.client.connect(self.uri)
62
+ except websockets.WebSocketException as e:
63
+ raise CommunicationException(None, 'websocket connection issue') from e
64
+
65
+ self.running_listen_thread = True
66
+ self.listen_thread = Thread(target=self._receive_data)
67
+ self.listen_thread.start()
68
+
69
+ logger.debug(f'Websocket connection established to {self.uri}')
70
+
71
+ def send(self, command: Command) -> None:
72
+ """
73
+ Sends a command to the WebSocket server.
74
+
75
+ Args:
76
+ command (Command): The command to send to the server.
77
+
78
+ Raises:
79
+ CommunicationException: When trying to send a command while the websocket is closed.
80
+
81
+ """
82
+ if not self.opened():
83
+ raise CommunicationException(None, 'WebSocket is not opened.')
84
+
85
+ try:
86
+ # mypy cannot infer the check from self.opened() done above
87
+ logger.debug(f'Sending JSON command: {command.json()}')
88
+ self.websocket.send(command.json()) # type: ignore
89
+ except websockets.exceptions.ConnectionClosed as e:
90
+ raise CommunicationException(None, 'Trying to send data while websocket is closed') from e
91
+
92
+ @override
93
+ def opened(self) -> bool:
94
+ """
95
+ Returns if the websocket connection is open or not
96
+
97
+ Returns:
98
+ bool: True if the websocket connection is open, False otherwise
99
+ """
100
+ return self.websocket is not None
101
+
102
+ def response(self) -> Response:
103
+ """Fetches the next response
104
+
105
+ Returns:
106
+ Response: The response from the server
107
+
108
+ Raises:
109
+ CommunicationException: When the connection terminated with an error
110
+ """
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)
118
+
119
+ @override
120
+ def close(self) -> None:
121
+ """
122
+ Closes the WebSocket connection.
123
+
124
+ Raises:
125
+ CommunicationException: When the websocket is already closed
126
+ """
127
+ if not self.opened():
128
+ raise CommunicationException(None, 'cannot close already closed websocket')
129
+ if self.websocket:
130
+ logger.debug('Waiting websocket close...')
131
+ self.websocket.close()
132
+
133
+ if self.binary_message_handling_thread:
134
+ logger.debug('Canceling binary listening thread...')
135
+ self.running_binary_message_handling_thread = False
136
+ self.binary_message_handling_thread.join()
137
+ self.binary_message_handling_thread = None
138
+
139
+ if self.listen_thread:
140
+ logger.debug('Canceling listening thread...')
141
+ self.running_listen_thread = False
142
+ self.listen_thread.join()
143
+ self.listen_thread = None
144
+
145
+ self.websocket = None
146
+ logger.debug('Websocket connection closed')
147
+
148
+ def register_binary_message_callback(self, callback: Callable[[bytes], Any]) -> None:
149
+ """Registers a callback to be called with every incoming binary message."""
150
+ if self.binary_message_callback:
151
+ raise CommunicationException(None, 'Binary message callback already registered')
152
+
153
+ self.binary_message_callback = callback
154
+ logger.info('Binary message callback registered.')
155
+ self.running_binary_message_handling_thread = True
156
+ self.binary_message_handling_thread = Thread(target=self._run_binary_message_callback)
157
+ self.binary_message_handling_thread.start()
158
+ logger.info('Started binary message thread')
159
+
160
+ def _receive_data(self) -> None:
161
+ if not self.websocket:
162
+ raise CommunicationException(None, 'Websocket is not open')
163
+
164
+ while self.running_listen_thread:
165
+ try:
166
+ for message in self.websocket:
167
+ logger.debug(f'Received message: {message!r}')
168
+ if isinstance(message, str):
169
+ self.json_message_queue.put(message)
170
+ elif isinstance(message, bytes) and self.binary_message_callback:
171
+ self.binary_message_queue.put(message)
172
+ else:
173
+ raise CommunicationException(None, f'Unknown type of message {type(message)}')
174
+ except websockets.ConnectionClosedOK:
175
+ logger.debug('websocket connection terminated properly')
176
+ except websockets.ConnectionClosedError as e:
177
+ raise CommunicationException(None, 'connection terminated with error') from e
178
+ except Exception as e:
179
+ raise CommunicationException(None, 'failure to process binary data') from e
180
+
181
+ def _run_binary_message_callback(self) -> None:
182
+ if not self.binary_message_callback:
183
+ raise CommunicationException(None, 'No binary message callback registered')
184
+
185
+ while self.running_binary_message_handling_thread:
186
+ while self.binary_message_queue.empty():
187
+ time.sleep(0.5)
188
+ if not self.running_binary_message_handling_thread:
189
+ return
190
+
191
+ binary_message = self.binary_message_queue.get()
192
+ self.binary_message_callback(binary_message)
193
+
194
+ @override
195
+ def request_with_response(self, command: Command, time_to_wait_for_response_in_s: float = 0.1) -> Response:
196
+ """
197
+ Concrete method to fetch a response from a command.
198
+
199
+ Args:
200
+ 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
203
+
204
+ Returns:
205
+ Response: The response corresponding to the sent command.
206
+ """
207
+ 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()
211
+
212
+ return response
@@ -0,0 +1,15 @@
1
+ from .abstract_device_discovery import AbstractDeviceDiscovery
2
+ from .abstract_device_manager import AbstractDeviceManager
3
+ from .device_discovery import DeviceDiscovery
4
+ from .device_manager import DeviceManager
5
+ from .fake_device_manager import FakeDeviceManager
6
+ from .fake_icl_server import FakeICLServer
7
+
8
+ __all__ = [
9
+ 'AbstractDeviceDiscovery',
10
+ 'AbstractDeviceManager',
11
+ 'DeviceDiscovery',
12
+ 'DeviceManager',
13
+ 'FakeDeviceManager',
14
+ 'FakeICLServer',
15
+ ]
@@ -0,0 +1,17 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from horiba_sdk.sync.devices.single_devices import ChargeCoupledDevice, Monochromator
4
+
5
+
6
+ class AbstractDeviceDiscovery(ABC):
7
+ @abstractmethod
8
+ def execute(self, error_on_no_device: bool = False) -> None:
9
+ pass
10
+
11
+ @abstractmethod
12
+ def charge_coupled_devices(self) -> list[ChargeCoupledDevice]:
13
+ pass
14
+
15
+ @abstractmethod
16
+ def monochromators(self) -> list[Monochromator]:
17
+ pass