horiba-sdk 0.5.4__py3-none-any.whl → 0.6.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/communication/messages.py +5 -2
- horiba_sdk/communication/websocket_communicator.py +1 -1
- horiba_sdk/core/stitching/__init__.py +6 -0
- horiba_sdk/core/stitching/labspec6_spectra_stitch.py +90 -0
- horiba_sdk/core/stitching/linear_spectra_stitch.py +107 -0
- horiba_sdk/core/stitching/simple_cut_spectra_stitch.py +84 -0
- horiba_sdk/core/stitching/spectra_stitch.py +16 -0
- horiba_sdk/core/stitching/y_displacement_spectra_stitch.py +87 -0
- horiba_sdk/core/trigger_input_polarity.py +6 -0
- horiba_sdk/devices/device_manager.py +19 -1
- horiba_sdk/devices/fake_icl_server.py +7 -0
- horiba_sdk/devices/fake_responses/spectracq3.json +217 -0
- horiba_sdk/devices/single_devices/__init__.py +2 -1
- horiba_sdk/devices/single_devices/ccd.py +4 -2
- horiba_sdk/devices/single_devices/spectracq3.py +392 -0
- horiba_sdk/devices/spectracq3_discovery.py +55 -0
- {horiba_sdk-0.5.4.dist-info → horiba_sdk-0.6.0.dist-info}/METADATA +2 -1
- {horiba_sdk-0.5.4.dist-info → horiba_sdk-0.6.0.dist-info}/RECORD +20 -26
- horiba_sdk/sync/__init__.py +0 -0
- horiba_sdk/sync/communication/__init__.py +0 -7
- horiba_sdk/sync/communication/abstract_communicator.py +0 -47
- horiba_sdk/sync/communication/test_client.py +0 -16
- horiba_sdk/sync/communication/websocket_communicator.py +0 -232
- horiba_sdk/sync/devices/__init__.py +0 -15
- horiba_sdk/sync/devices/abstract_device_discovery.py +0 -17
- horiba_sdk/sync/devices/abstract_device_manager.py +0 -68
- horiba_sdk/sync/devices/device_discovery.py +0 -58
- horiba_sdk/sync/devices/device_manager.py +0 -213
- horiba_sdk/sync/devices/fake_device_manager.py +0 -91
- horiba_sdk/sync/devices/fake_icl_server.py +0 -82
- horiba_sdk/sync/devices/single_devices/__init__.py +0 -5
- horiba_sdk/sync/devices/single_devices/abstract_device.py +0 -87
- horiba_sdk/sync/devices/single_devices/ccd.py +0 -674
- horiba_sdk/sync/devices/single_devices/monochromator.py +0 -413
- {horiba_sdk-0.5.4.dist-info → horiba_sdk-0.6.0.dist-info}/LICENSE +0 -0
- {horiba_sdk-0.5.4.dist-info → horiba_sdk-0.6.0.dist-info}/WHEEL +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.1
|
2
2
|
Name: horiba-sdk
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.6.0
|
4
4
|
Summary: 'horiba-sdk' is a package that provides source code for the development with Horiba devices
|
5
5
|
Home-page: https://github.com/ThatsTheEnd/horiba-python-sdk
|
6
6
|
License: MIT
|
@@ -21,6 +21,7 @@ Requires-Dist: loguru (>=0.7.2,<0.8.0)
|
|
21
21
|
Requires-Dist: overrides (>=7.4.0,<8.0.0)
|
22
22
|
Requires-Dist: pint (>=0.23,<0.24)
|
23
23
|
Requires-Dist: psutil (>=5.9.7,<6.0.0)
|
24
|
+
Requires-Dist: pydantic (>=2.10.6,<3.0.0)
|
24
25
|
Requires-Dist: websockets (>=12.0,<13.0)
|
25
26
|
Project-URL: Repository, https://github.com/ThatsTheEnd/horiba-python-sdk
|
26
27
|
Description-Content-Type: text/markdown
|
@@ -2,52 +2,46 @@ horiba_sdk/__init__.py,sha256=J8jCV90BJ2SCDFSpacDeAAsJa8PFqBUDpREOJeAgXug,679
|
|
2
2
|
horiba_sdk/communication/__init__.py,sha256=nhS1rw1ZojM8zmRIx0VEFtYge0IH-B_L4zNBUNBJZYE,1564
|
3
3
|
horiba_sdk/communication/abstract_communicator.py,sha256=E80dfOjrfuNay3W5jeSn4T2tUas6vygRcF46kSoP5j8,1498
|
4
4
|
horiba_sdk/communication/communication_exception.py,sha256=d2ouOoVI6Q69_JL1bUEjjQOmjiO0CEm8K20WsaUuhB0,583
|
5
|
-
horiba_sdk/communication/messages.py,sha256=
|
6
|
-
horiba_sdk/communication/websocket_communicator.py,sha256=
|
5
|
+
horiba_sdk/communication/messages.py,sha256=NFwHD2UzIZx8htuUh7b75tjJ2bqgvuaKhJuXwHyoLZ8,2706
|
6
|
+
horiba_sdk/communication/websocket_communicator.py,sha256=IFP6dfuqiwGsNymPtT4Hqmii_8n2pO1Jv_k31GfrM8s,8331
|
7
7
|
horiba_sdk/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
8
|
horiba_sdk/core/acquisition_format.py,sha256=pHcHZck75BbJ4nn2FOZ9cUXq9juZUjoNxKdLUhIIINI,358
|
9
9
|
horiba_sdk/core/clean_count_mode.py,sha256=7guLd3eUV3Y2Cnj3w0JJ7ry74I8a6MFQiqbK5pTb3oU,185
|
10
10
|
horiba_sdk/core/resolution.py,sha256=VOQonjPBqZ2TbtiNVDBT3TI1Aymj1pPYWMoobFaXjG8,1029
|
11
|
+
horiba_sdk/core/stitching/__init__.py,sha256=pIafucQx6EuKVPWyvn4IIP6qf-FsNM7o9scKi3XE95g,336
|
12
|
+
horiba_sdk/core/stitching/labspec6_spectra_stitch.py,sha256=8oc1DoEtnBhYaOXfLFQBBc12DEh_wRVDI_lu9ELnP6Q,3093
|
13
|
+
horiba_sdk/core/stitching/linear_spectra_stitch.py,sha256=5ofmMUL7JFFsIO7dsn3AbJTlW7xVGRCQ5UEv48lh9QA,3974
|
14
|
+
horiba_sdk/core/stitching/simple_cut_spectra_stitch.py,sha256=yMqy1T_GwViG4pBwZIDg557gFPR8qMhJv8v1-9eQnTY,2920
|
15
|
+
horiba_sdk/core/stitching/spectra_stitch.py,sha256=Y63e4_sm2fU6GKYphopCWec1n29yMX-fDIyIrjKRuoc,447
|
16
|
+
horiba_sdk/core/stitching/y_displacement_spectra_stitch.py,sha256=YA1RefdJCe5U22gkqQQNi5mLgZ4A7dZxBiezkP0FsAI,3189
|
11
17
|
horiba_sdk/core/timer_resolution.py,sha256=V6pXEZMVvUUoqrJmLFFI45pFt_7po6-5r1DmC8jY6gs,300
|
18
|
+
horiba_sdk/core/trigger_input_polarity.py,sha256=UDns386W5ecKOHAdEru4dMObX9EF7pZetzY8IvmNufM,97
|
12
19
|
horiba_sdk/core/x_axis_conversion_type.py,sha256=SQrdbciTR8x5UWOqLDgxWPy12o0-yIVLrkm7N3gpWpg,449
|
13
20
|
horiba_sdk/devices/__init__.py,sha256=YYsJL2CSJwc2vsAjFQEKXjA5_QAnKlxSwlx32EgzFME,336
|
14
21
|
horiba_sdk/devices/abstract_device_discovery.py,sha256=04ZCEB5IZkUuJxSisS78eW6lAQNXG4uaDoPv-eBccBA,178
|
15
22
|
horiba_sdk/devices/abstract_device_manager.py,sha256=RXj5_pzFHpiolJe3ZfFsofwpD1hlwUMwYD1DyWMmlI0,1717
|
16
23
|
horiba_sdk/devices/ccd_discovery.py,sha256=nGuskZ07Y9myI__dU8LeLnrNiJEBpGPby21EEwgSVUk,2376
|
17
|
-
horiba_sdk/devices/device_manager.py,sha256=
|
24
|
+
horiba_sdk/devices/device_manager.py,sha256=Sof-VEfmEhSJ0WmnT72IGC3XhTuLuznBVayV8bx20jQ,11569
|
18
25
|
horiba_sdk/devices/fake_device_manager.py,sha256=Tr4Z067smYfy-ya29PO4PK4EWF6Sa1R2UQFZCWfPePE,5015
|
19
|
-
horiba_sdk/devices/fake_icl_server.py,sha256=
|
26
|
+
horiba_sdk/devices/fake_icl_server.py,sha256=aVmUZfBddz_w6HX2L6KS_9ei-a4SKtkh5Q8MuUV3szA,2757
|
20
27
|
horiba_sdk/devices/fake_responses/ccd.json,sha256=sMg-UqU6W1I01n2kEbkFc5J0XF8E3JVdOjB-1MhufzM,17020
|
21
28
|
horiba_sdk/devices/fake_responses/icl.json,sha256=ZPKhjx0BmwmqNGwbb7reXSStddgVZetCQQYVeNr9CSU,572
|
22
29
|
horiba_sdk/devices/fake_responses/monochromator.json,sha256=I9yBMsJyXXVCLF1J8mNPsfgXasIg60IL-4ToECcoGhw,4308
|
30
|
+
horiba_sdk/devices/fake_responses/spectracq3.json,sha256=gnGWGtz4MzifY4NaKtjQ6XaWL73d5lMJM3mOlpdmDtE,4099
|
23
31
|
horiba_sdk/devices/monochromator_discovery.py,sha256=KoZ8vfPsI6QRuiD4BiE0YvzO42dQzFZqvoxygDSllE8,2365
|
24
|
-
horiba_sdk/devices/single_devices/__init__.py,sha256=
|
32
|
+
horiba_sdk/devices/single_devices/__init__.py,sha256=JtQYr26ZrSqi3uaralLipw6u3e9f7z-QsXPIrSfdAoQ,241
|
25
33
|
horiba_sdk/devices/single_devices/abstract_device.py,sha256=CwCjHJWSp8WGrYsyq0ZhTQVnvYdoCG-kb4BU9SZXJm8,3994
|
26
|
-
horiba_sdk/devices/single_devices/ccd.py,sha256=
|
34
|
+
horiba_sdk/devices/single_devices/ccd.py,sha256=qtXMzEhYs3UJhOsDA2hIajdN1nyvP7eFZaiaBYZ3piY,30083
|
27
35
|
horiba_sdk/devices/single_devices/monochromator.py,sha256=r-9bI-ne_iGdfTWBvUnYSxxp1DQNlEt7wBN31cGfZ1E,14538
|
36
|
+
horiba_sdk/devices/single_devices/spectracq3.py,sha256=cFNFDbGLQB9HkKfg3TwYTEaLx8gcNu1O_nPjXOjH1hk,13667
|
37
|
+
horiba_sdk/devices/spectracq3_discovery.py,sha256=5mrjmZ30F35Jswl3u3yuMnu5Hu5LDOqYZ4rlOJeRvO8,2389
|
28
38
|
horiba_sdk/icl_error/__init__.py,sha256=EtqHaUeoaLilpb7L7juuVamJgIZ3Anfnca0AO2o46rg,1053
|
29
39
|
horiba_sdk/icl_error/abstract_error.py,sha256=5nQDI6MNfCi4xUx6taJcEWGwANOo8mPEb4kVxrO-DW8,1301
|
30
40
|
horiba_sdk/icl_error/abstract_error_db.py,sha256=IJ3wGb6DWxnY35VIUwZX0X80IPi5k4QoQXnVjDy8lH8,589
|
31
41
|
horiba_sdk/icl_error/error_list.json,sha256=wyDYZPeH2sYMRPoL-JBoDul4uaYvoqp7SJg0zypfl40,5977
|
32
42
|
horiba_sdk/icl_error/icl_error.py,sha256=IElsKl5nvLjprIV_ub9MacJmdSS327iNwOXhSiJXTc0,755
|
33
43
|
horiba_sdk/icl_error/icl_error_db.py,sha256=5Jbs_ZlMp8Bjr4u8reIxxfOSFYYkzWTfKIqOQ4oUSXQ,2603
|
34
|
-
horiba_sdk/
|
35
|
-
horiba_sdk/
|
36
|
-
horiba_sdk/
|
37
|
-
horiba_sdk/
|
38
|
-
horiba_sdk/sync/communication/websocket_communicator.py,sha256=rCa0RVwl9Evwtr8VJUhkhTiIX2yG6pVtblhV3BGwnxU,9234
|
39
|
-
horiba_sdk/sync/devices/__init__.py,sha256=NDHi3zfDC52aTO1RpaPIBfyp7PEXfTqiYdvY_ThkyRE,469
|
40
|
-
horiba_sdk/sync/devices/abstract_device_discovery.py,sha256=hKDWbI8-I3r5mrU3O1Y1ve6vV7sg6KpUa5d3EfcLIE8,447
|
41
|
-
horiba_sdk/sync/devices/abstract_device_manager.py,sha256=hF9BGgVqi6EK0n6hdUDu7dnKI1SfZ5mCweGvm6vCJ5o,1709
|
42
|
-
horiba_sdk/sync/devices/device_discovery.py,sha256=eLErCF3mFYK5Za0jRg2xBLQnWJTwce7LcWdm8OQcI0A,2723
|
43
|
-
horiba_sdk/sync/devices/device_manager.py,sha256=yGoijQnxJn7xyI3TpkBf-DpN3JGPNrQDkDtvvSLKNQk,8429
|
44
|
-
horiba_sdk/sync/devices/fake_device_manager.py,sha256=myj1GwxjcOmv9jZbaTk8QwC6qYluNaZfuP9xPXrw3do,2809
|
45
|
-
horiba_sdk/sync/devices/fake_icl_server.py,sha256=DlQOC54p7UCYBTdZOZCD9TgDdK4MarnbaWFUXRo7NP4,3298
|
46
|
-
horiba_sdk/sync/devices/single_devices/__init__.py,sha256=Ai6HLstvMqT1c6A0yKLAH6227TUdS4ZMsC6zFrsmJic,192
|
47
|
-
horiba_sdk/sync/devices/single_devices/abstract_device.py,sha256=ABQpU6wU5trH0j8OVjDP6MeGTj41e1ey5K5sw7tCCdQ,2883
|
48
|
-
horiba_sdk/sync/devices/single_devices/ccd.py,sha256=fHj3lU8pZJKCoo1W3xdLhucNd_7JM4XRRxbbHfAO5Os,28196
|
49
|
-
horiba_sdk/sync/devices/single_devices/monochromator.py,sha256=ur0scHOnnX0f_xhxh4Wzz4CNyeltFPuGg49PmPKEFwg,14351
|
50
|
-
horiba_sdk-0.5.4.dist-info/LICENSE,sha256=JD-7TpNZoT7emooLwaTU9bJZbFbeM1ba90b8gpCYxzM,1083
|
51
|
-
horiba_sdk-0.5.4.dist-info/METADATA,sha256=MGbBOP5E9ZWO8D-gGsU8TInWUUWannU3v1K5D1t4CY0,14349
|
52
|
-
horiba_sdk-0.5.4.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
53
|
-
horiba_sdk-0.5.4.dist-info/RECORD,,
|
44
|
+
horiba_sdk-0.6.0.dist-info/LICENSE,sha256=JD-7TpNZoT7emooLwaTU9bJZbFbeM1ba90b8gpCYxzM,1083
|
45
|
+
horiba_sdk-0.6.0.dist-info/METADATA,sha256=vE4RC5UDB0BpcldfDCLroIk4wjlWbJj3CuuH87gUTpc,14391
|
46
|
+
horiba_sdk-0.6.0.dist-info/WHEEL,sha256=Zb28QaM1gQi8f4VCBhsUklF61CTlNYfs9YAZn-TOGFk,88
|
47
|
+
horiba_sdk-0.6.0.dist-info/RECORD,,
|
horiba_sdk/sync/__init__.py
DELETED
File without changes
|
@@ -1,47 +0,0 @@
|
|
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, response_timeout_s: float = 5) -> 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
|
-
response_timeout_s (float, optional): Timeout in seconds. Defaults to 5.
|
36
|
-
|
37
|
-
Returns:
|
38
|
-
Response: The response corresponding to the sent command.
|
39
|
-
"""
|
40
|
-
pass
|
41
|
-
|
42
|
-
@abstractmethod
|
43
|
-
def close(self) -> None:
|
44
|
-
"""
|
45
|
-
Abstract method to close the connection.
|
46
|
-
"""
|
47
|
-
pass
|
@@ -1,16 +0,0 @@
|
|
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}')
|
@@ -1,232 +0,0 @@
|
|
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_dict: dict[int, JSONResponse] = {}
|
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, 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.
|
108
|
-
|
109
|
-
Returns:
|
110
|
-
Response: The response from the server
|
111
|
-
|
112
|
-
Raises:
|
113
|
-
CommunicationException: When the connection terminated with an error
|
114
|
-
"""
|
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
|
136
|
-
|
137
|
-
@override
|
138
|
-
def close(self) -> None:
|
139
|
-
"""
|
140
|
-
Closes the WebSocket connection.
|
141
|
-
|
142
|
-
Raises:
|
143
|
-
CommunicationException: When the websocket is already closed
|
144
|
-
"""
|
145
|
-
if not self.opened():
|
146
|
-
raise CommunicationException(None, 'cannot close already closed websocket')
|
147
|
-
if self.websocket:
|
148
|
-
logger.debug('Waiting websocket close...')
|
149
|
-
self.websocket.close()
|
150
|
-
|
151
|
-
if self.binary_message_handling_thread:
|
152
|
-
logger.debug('Canceling binary listening thread...')
|
153
|
-
self.running_binary_message_handling_thread = False
|
154
|
-
self.binary_message_handling_thread.join()
|
155
|
-
self.binary_message_handling_thread = None
|
156
|
-
|
157
|
-
if self.listen_thread:
|
158
|
-
logger.debug('Canceling listening thread...')
|
159
|
-
self.running_listen_thread = False
|
160
|
-
self.listen_thread.join()
|
161
|
-
self.listen_thread = None
|
162
|
-
|
163
|
-
self.websocket = None
|
164
|
-
logger.debug('Websocket connection closed')
|
165
|
-
|
166
|
-
def register_binary_message_callback(self, callback: Callable[[bytes], Any]) -> None:
|
167
|
-
"""Registers a callback to be called with every incoming binary message."""
|
168
|
-
if self.binary_message_callback:
|
169
|
-
raise CommunicationException(None, 'Binary message callback already registered')
|
170
|
-
|
171
|
-
self.binary_message_callback = callback
|
172
|
-
logger.debug('Binary message callback registered.')
|
173
|
-
self.running_binary_message_handling_thread = True
|
174
|
-
self.binary_message_handling_thread = Thread(target=self._run_binary_message_callback)
|
175
|
-
self.binary_message_handling_thread.start()
|
176
|
-
logger.info('Started binary message thread')
|
177
|
-
|
178
|
-
def _receive_data(self) -> None:
|
179
|
-
if not self.websocket:
|
180
|
-
raise CommunicationException(None, 'Websocket is not open')
|
181
|
-
|
182
|
-
while self.running_listen_thread:
|
183
|
-
try:
|
184
|
-
for message in self.websocket:
|
185
|
-
logger.debug(f'Received message: {message!r}')
|
186
|
-
if isinstance(message, str):
|
187
|
-
response: JSONResponse = JSONResponse(message)
|
188
|
-
self.json_message_dict[response.id] = response
|
189
|
-
elif isinstance(message, bytes) and self.binary_message_callback:
|
190
|
-
self.binary_message_queue.put(message)
|
191
|
-
else:
|
192
|
-
raise CommunicationException(None, f'Unknown type of message {type(message)}')
|
193
|
-
except websockets.ConnectionClosedOK:
|
194
|
-
logger.debug('websocket connection terminated properly')
|
195
|
-
except websockets.ConnectionClosedError as e:
|
196
|
-
raise CommunicationException(None, 'connection terminated with error') from e
|
197
|
-
except Exception as e:
|
198
|
-
raise CommunicationException(None, 'failure to process binary data') from e
|
199
|
-
|
200
|
-
def _run_binary_message_callback(self) -> None:
|
201
|
-
if not self.binary_message_callback:
|
202
|
-
raise CommunicationException(None, 'No binary message callback registered')
|
203
|
-
|
204
|
-
while self.running_binary_message_handling_thread:
|
205
|
-
while self.binary_message_queue.empty():
|
206
|
-
time.sleep(0.5)
|
207
|
-
if not self.running_binary_message_handling_thread:
|
208
|
-
return
|
209
|
-
|
210
|
-
binary_message = self.binary_message_queue.get()
|
211
|
-
self.binary_message_callback(binary_message)
|
212
|
-
|
213
|
-
@override
|
214
|
-
def request_with_response(self, command: Command, response_timeout_s: float = 5) -> Response:
|
215
|
-
"""
|
216
|
-
Concrete method to fetch a response from a command.
|
217
|
-
|
218
|
-
Args:
|
219
|
-
command (Command): Command for which a response is desired
|
220
|
-
response_timeout_s (float, optional): Timeout in seconds. Defaults to 5.
|
221
|
-
|
222
|
-
Returns:
|
223
|
-
Response: The response corresponding to the sent command.
|
224
|
-
"""
|
225
|
-
self.send(command)
|
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')
|
231
|
-
|
232
|
-
return response
|
@@ -1,15 +0,0 @@
|
|
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
|
-
]
|
@@ -1,17 +0,0 @@
|
|
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
|
@@ -1,68 +0,0 @@
|
|
1
|
-
from abc import ABC, abstractmethod
|
2
|
-
|
3
|
-
from horiba_sdk.sync.communication import AbstractCommunicator
|
4
|
-
from horiba_sdk.sync.devices.single_devices import ChargeCoupledDevice, Monochromator
|
5
|
-
|
6
|
-
|
7
|
-
class AbstractDeviceManager(ABC):
|
8
|
-
"""
|
9
|
-
DeviceManager class manages the lifecycle and interactions with devices.
|
10
|
-
|
11
|
-
"""
|
12
|
-
|
13
|
-
@abstractmethod
|
14
|
-
def start(self) -> None:
|
15
|
-
"""
|
16
|
-
Abstract method to start the device manager.
|
17
|
-
"""
|
18
|
-
pass
|
19
|
-
|
20
|
-
@abstractmethod
|
21
|
-
def stop(self) -> None:
|
22
|
-
"""
|
23
|
-
Abstract method to stop the device manager.
|
24
|
-
"""
|
25
|
-
pass
|
26
|
-
|
27
|
-
@abstractmethod
|
28
|
-
def discover_devices(self, error_on_no_device: bool = False) -> None:
|
29
|
-
"""
|
30
|
-
Abstract method that discovers and registers devices.
|
31
|
-
|
32
|
-
Args:
|
33
|
-
error_on_no_device (bool): If True, an exception is raised if no device is connected.
|
34
|
-
"""
|
35
|
-
pass
|
36
|
-
|
37
|
-
@property
|
38
|
-
@abstractmethod
|
39
|
-
def communicator(self) -> AbstractCommunicator:
|
40
|
-
"""
|
41
|
-
Abstract method to get the communicator attribute.
|
42
|
-
|
43
|
-
Returns:
|
44
|
-
AbstractCommunicator: Returns the internal communicator instance.
|
45
|
-
"""
|
46
|
-
pass
|
47
|
-
|
48
|
-
@property
|
49
|
-
@abstractmethod
|
50
|
-
def monochromators(self) -> list[Monochromator]:
|
51
|
-
"""
|
52
|
-
Abstract method to get the detected monochromators.
|
53
|
-
|
54
|
-
Returns:
|
55
|
-
List[Monochromator]: The detected monochromators
|
56
|
-
"""
|
57
|
-
pass
|
58
|
-
|
59
|
-
@property
|
60
|
-
@abstractmethod
|
61
|
-
def charge_coupled_devices(self) -> list[ChargeCoupledDevice]:
|
62
|
-
"""
|
63
|
-
Abstract method to get the detected CCDs.
|
64
|
-
|
65
|
-
Returns:
|
66
|
-
List[ChargeCoupledDevice]: The detected CCDS.
|
67
|
-
"""
|
68
|
-
pass
|
@@ -1,58 +0,0 @@
|
|
1
|
-
from typing import final
|
2
|
-
|
3
|
-
from loguru import logger
|
4
|
-
from overrides import override
|
5
|
-
|
6
|
-
from horiba_sdk.communication import Command, Response
|
7
|
-
from horiba_sdk.icl_error import AbstractErrorDB
|
8
|
-
from horiba_sdk.sync.communication import AbstractCommunicator
|
9
|
-
from horiba_sdk.sync.devices.abstract_device_discovery import AbstractDeviceDiscovery
|
10
|
-
from horiba_sdk.sync.devices.single_devices import ChargeCoupledDevice, Monochromator
|
11
|
-
|
12
|
-
|
13
|
-
@final
|
14
|
-
class DeviceDiscovery(AbstractDeviceDiscovery):
|
15
|
-
def __init__(self, communicator: AbstractCommunicator, error_db: AbstractErrorDB):
|
16
|
-
self._communicator: AbstractCommunicator = communicator
|
17
|
-
self._charge_coupled_devices: list[ChargeCoupledDevice] = []
|
18
|
-
self._monochromators: list[Monochromator] = []
|
19
|
-
self._error_db: AbstractErrorDB = error_db
|
20
|
-
self._discovered_devices: bool = False
|
21
|
-
|
22
|
-
@override
|
23
|
-
def execute(self, error_on_no_device: bool = False) -> None:
|
24
|
-
"""
|
25
|
-
Discovers the connected devices and saves them internally.
|
26
|
-
"""
|
27
|
-
if not self._communicator.opened():
|
28
|
-
self._communicator.open()
|
29
|
-
|
30
|
-
# Define the commands and device types in a list of tuples for iteration
|
31
|
-
commands_and_types = [('ccd_discover', 'ccd_list', 'CCD'), ('mono_discover', 'mono_list', 'Monochromator')]
|
32
|
-
|
33
|
-
for discover_command, list_command, device_type in commands_and_types:
|
34
|
-
response: Response = self._communicator.request_with_response(Command(discover_command, {}))
|
35
|
-
if response.results.get('count', 0) == 0 and error_on_no_device:
|
36
|
-
raise Exception(f'No {device_type} connected')
|
37
|
-
response = self._communicator.request_with_response(Command(list_command, {}))
|
38
|
-
|
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')
|
51
|
-
|
52
|
-
@override
|
53
|
-
def charge_coupled_devices(self) -> list[ChargeCoupledDevice]:
|
54
|
-
return self._charge_coupled_devices
|
55
|
-
|
56
|
-
@override
|
57
|
-
def monochromators(self) -> list[Monochromator]:
|
58
|
-
return self._monochromators
|