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.
Files changed (36) hide show
  1. horiba_sdk/communication/messages.py +5 -2
  2. horiba_sdk/communication/websocket_communicator.py +1 -1
  3. horiba_sdk/core/stitching/__init__.py +6 -0
  4. horiba_sdk/core/stitching/labspec6_spectra_stitch.py +90 -0
  5. horiba_sdk/core/stitching/linear_spectra_stitch.py +107 -0
  6. horiba_sdk/core/stitching/simple_cut_spectra_stitch.py +84 -0
  7. horiba_sdk/core/stitching/spectra_stitch.py +16 -0
  8. horiba_sdk/core/stitching/y_displacement_spectra_stitch.py +87 -0
  9. horiba_sdk/core/trigger_input_polarity.py +6 -0
  10. horiba_sdk/devices/device_manager.py +19 -1
  11. horiba_sdk/devices/fake_icl_server.py +7 -0
  12. horiba_sdk/devices/fake_responses/spectracq3.json +217 -0
  13. horiba_sdk/devices/single_devices/__init__.py +2 -1
  14. horiba_sdk/devices/single_devices/ccd.py +4 -2
  15. horiba_sdk/devices/single_devices/spectracq3.py +392 -0
  16. horiba_sdk/devices/spectracq3_discovery.py +55 -0
  17. {horiba_sdk-0.5.4.dist-info → horiba_sdk-0.6.0.dist-info}/METADATA +2 -1
  18. {horiba_sdk-0.5.4.dist-info → horiba_sdk-0.6.0.dist-info}/RECORD +20 -26
  19. horiba_sdk/sync/__init__.py +0 -0
  20. horiba_sdk/sync/communication/__init__.py +0 -7
  21. horiba_sdk/sync/communication/abstract_communicator.py +0 -47
  22. horiba_sdk/sync/communication/test_client.py +0 -16
  23. horiba_sdk/sync/communication/websocket_communicator.py +0 -232
  24. horiba_sdk/sync/devices/__init__.py +0 -15
  25. horiba_sdk/sync/devices/abstract_device_discovery.py +0 -17
  26. horiba_sdk/sync/devices/abstract_device_manager.py +0 -68
  27. horiba_sdk/sync/devices/device_discovery.py +0 -58
  28. horiba_sdk/sync/devices/device_manager.py +0 -213
  29. horiba_sdk/sync/devices/fake_device_manager.py +0 -91
  30. horiba_sdk/sync/devices/fake_icl_server.py +0 -82
  31. horiba_sdk/sync/devices/single_devices/__init__.py +0 -5
  32. horiba_sdk/sync/devices/single_devices/abstract_device.py +0 -87
  33. horiba_sdk/sync/devices/single_devices/ccd.py +0 -674
  34. horiba_sdk/sync/devices/single_devices/monochromator.py +0 -413
  35. {horiba_sdk-0.5.4.dist-info → horiba_sdk-0.6.0.dist-info}/LICENSE +0 -0
  36. {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.5.4
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=Gxkw01JI0U5ShILANT0pezywcoSO2aHo06j1ixzZPEQ,2661
6
- horiba_sdk/communication/websocket_communicator.py,sha256=wQxGRtj3htQ8n_hMvcoBmZdD18iea0uiCEsTZYdR2Jg,8300
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=ZmZjQjTLxJvs5NqWQLTfDcCNb3PFwBYbu1mg_qmMCVo,10847
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=Yh9oh0YCbw-AXMnCHFWsZvJ7ZFjsnm1JG1y0ix1b-9Q,2348
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=Ai6HLstvMqT1c6A0yKLAH6227TUdS4ZMsC6zFrsmJic,192
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=mnx0keMePP7JAxCs8ZgCm_05tyu89efjAcBn2FPv4FI,29995
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/sync/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
35
- horiba_sdk/sync/communication/__init__.py,sha256=iVHHW0cQ9w_J5dGRkwdBnw7vYgRLn5MVsdIFWGZvJoA,186
36
- horiba_sdk/sync/communication/abstract_communicator.py,sha256=CEkGj2qVNoWYkFD9HOlxV82kls3gsJ517gWPK-9rUBw,1185
37
- horiba_sdk/sync/communication/test_client.py,sha256=7N5sM2Y63GSuFDmOEFRXeTHeAY7Nq7yX5MvbdPXeDko,651
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,,
File without changes
@@ -1,7 +0,0 @@
1
- from .abstract_communicator import AbstractCommunicator
2
- from .websocket_communicator import WebsocketCommunicator
3
-
4
- __all__ = [
5
- 'AbstractCommunicator',
6
- 'WebsocketCommunicator',
7
- ]
@@ -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