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,250 @@
1
+ """
2
+ Device Manager Module
3
+
4
+ This module provides the DeviceManager class, responsible for managing the lifecycle and interactions with devices.
5
+
6
+ Import Structure Explanation:
7
+ -----------------------------
8
+ Due to the interdependence between the AbstractDevice class and the DeviceManager class, we face a potential circular
9
+ import issue. Specifically, the AbstractDevice class needs knowledge about the DeviceManager, and vice versa.
10
+
11
+ To handle this challenge while still allowing type checking tools like mypy to function correctly, we've made use of
12
+ Python's TYPE_CHECKING constant available in the `typing` module. This constant is `True` only when type checking is
13
+ performed, and not during runtime. This ensures that:
14
+
15
+ 1. At runtime, there's no circular import, as the import statement within the TYPE_CHECKING block is effectively
16
+ ignored.
17
+
18
+ 2. During type checking (like when running mypy), the import statement is executed, allowing mypy to recognize and
19
+ validate types.
20
+
21
+ Therefore, the `if TYPE_CHECKING:` block is a solution to maintain clean architecture while still benefiting from type
22
+ checking capabilities.
23
+
24
+ Notes:
25
+ ------
26
+ The forward declaration method ("ClassName") is used for type hints within the module to further prevent any runtime
27
+ circular dependencies.
28
+
29
+ For more details on the TYPE_CHECKING constant and its usage, refer to:
30
+ https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING
31
+ """
32
+
33
+ import asyncio
34
+ import importlib.resources
35
+ import platform
36
+ from pathlib import Path
37
+ from typing import Optional, final
38
+
39
+ import psutil
40
+ from loguru import logger
41
+ from overrides import override
42
+
43
+ from horiba_sdk.communication import (
44
+ AbstractCommunicator,
45
+ Command,
46
+ CommunicationException,
47
+ Response,
48
+ WebsocketCommunicator,
49
+ )
50
+ from horiba_sdk.devices import AbstractDeviceManager
51
+ from horiba_sdk.devices.ccd_discovery import ChargeCoupledDevicesDiscovery
52
+ from horiba_sdk.devices.monochromator_discovery import MonochromatorsDiscovery
53
+ from horiba_sdk.devices.single_devices import ChargeCoupledDevice, Monochromator
54
+ from horiba_sdk.icl_error import AbstractError, AbstractErrorDB, ICLErrorDB
55
+
56
+
57
+ @final
58
+ class DeviceManager(AbstractDeviceManager):
59
+ """
60
+ DeviceManager class manages the lifecycle and interactions with devices.
61
+ """
62
+
63
+ def __init__(
64
+ self,
65
+ start_icl: bool = True,
66
+ websocket_ip: str = '127.0.0.1',
67
+ websocket_port: str = '25010',
68
+ enable_binary_messages: bool = True,
69
+ ):
70
+ """
71
+ Initializes the DeviceManager with the specified communicator class.
72
+
73
+ Args:
74
+ start_icl (bool) = True: If True, the ICL software is started and communication is established.
75
+ websocket_ip (str) = '127.0.0.1': websocket IP
76
+ websocket_port (str) = '25010': websocket port
77
+ enable_binary_messages (bool) = True: If True, binary messages are enabled.
78
+ """
79
+ super().__init__()
80
+ self._start_icl = start_icl
81
+ self._icl_communicator: WebsocketCommunicator = WebsocketCommunicator(
82
+ 'ws://' + websocket_ip + ':' + str(websocket_port)
83
+ )
84
+ self._icl_communicator.register_binary_message_callback(self._binary_message_callback)
85
+ self._icl_websocket_ip: str = websocket_ip
86
+ self._icl_websocket_port: str = websocket_port
87
+ self._icl_process: Optional[asyncio.subprocess.Process] = None
88
+ self._binary_messages: bool = enable_binary_messages
89
+ self._charge_coupled_devices: list[ChargeCoupledDevice] = []
90
+ self._monochromators: list[Monochromator] = []
91
+
92
+ error_list_path: Path = Path(str(importlib.resources.files('horiba_sdk.icl_error') / 'error_list.json'))
93
+ self._icl_error_db: AbstractErrorDB = ICLErrorDB(error_list_path)
94
+
95
+ @override
96
+ async def start(self) -> None:
97
+ if self._start_icl:
98
+ await self.start_icl()
99
+
100
+ await self._icl_communicator.open()
101
+
102
+ icl_info: Response = await self._icl_communicator.request_with_response(Command('icl_info', {}))
103
+ logger.info(f'ICL info: {icl_info.results}')
104
+
105
+ if self._binary_messages:
106
+ await self._enable_binary_messages()
107
+
108
+ await self.discover_devices()
109
+
110
+ @override
111
+ async def stop(self) -> None:
112
+ await self.stop_icl()
113
+
114
+ async def start_icl(self) -> None:
115
+ """
116
+ Starts the ICL software and establishes communication.
117
+ """
118
+ logger.info('Starting ICL software...')
119
+ # try:
120
+ if platform.system() != 'Windows':
121
+ logger.info('Only Windows is supported for ICL software. Skip starting of ICL...')
122
+ return
123
+
124
+ icl_running = 'icl.exe' in (p.name() for p in psutil.process_iter())
125
+ if not icl_running:
126
+ logger.info('icl not running, starting it...')
127
+ # subprocess.Popen([r'C:\Program Files\HORIBA Scientific\SDK\icl.exe'])
128
+ self._icl_process = await asyncio.create_subprocess_exec(r'C:\Program Files\HORIBA Scientific\SDK\icl.exe')
129
+ # except subprocess.CalledProcessError:
130
+ # logger.error('Failed to start ICL software.')
131
+ # TODO: [saga] is this the best way handle exceptions?
132
+ # except Exception as e: # pylint: disable=broad-exception-caught
133
+ # logger.error('Unexpected error: %s', e)
134
+
135
+ async def _enable_binary_messages(self) -> None:
136
+ bin_mode_command: Command = Command('icl_binMode', {'mode': 'all'})
137
+ response: Response = await self._icl_communicator.request_with_response(bin_mode_command)
138
+
139
+ if response.errors:
140
+ self._handle_errors(response.errors)
141
+
142
+ def _handle_errors(self, errors: list[str]) -> None:
143
+ for error in errors:
144
+ icl_error: AbstractError = self._icl_error_db.error_from(error)
145
+ icl_error.log()
146
+ # TODO: [saga] only throw depending on the log level, tbd
147
+ raise Exception(f'Error from the ICL: {icl_error.message()}')
148
+
149
+ async def stop_icl(self) -> None:
150
+ """
151
+ Stops the communication and cleans up resources.
152
+ """
153
+ logger.info('Requesting shutdown of ICL...')
154
+
155
+ if not self._icl_communicator.opened():
156
+ await self._icl_communicator.open()
157
+
158
+ try:
159
+ info_command: Command = Command('icl_info', {})
160
+ response: Response = await self._icl_communicator.request_with_response(info_command)
161
+
162
+ logger.info(f'ICL info: {response.results}')
163
+ shutdown_command: Command = Command('icl_shutdown', {})
164
+ _response: Response = await self._icl_communicator.request_with_response(shutdown_command, timeout=10)
165
+ except CommunicationException as e:
166
+ logger.debug(f'CommunicationException: {e.message}')
167
+
168
+ if self._icl_communicator.opened():
169
+ await self._icl_communicator.close()
170
+
171
+ if self._icl_process is not None:
172
+ await self._icl_process.wait()
173
+ icl_running = 'icl.exe' in (p.name() for p in psutil.process_iter())
174
+ if icl_running:
175
+ raise Exception('Failed to shutdown ICL software.')
176
+
177
+ logger.info('icl_shutdown command sent')
178
+
179
+ def _format_icl_binary_to_string(self, message: bytes) -> str:
180
+ return ' '.join(format(byte, '02x') for byte in message[::-1])
181
+
182
+ async def _binary_message_callback(self, message: bytes) -> None:
183
+ # hex_data = ' '.join(format(byte, '02x') for byte in message)
184
+ # if len(message) < 18:
185
+ # logger.warning(f'binary message not valid: {len(message)} < 16')
186
+ # logger.info(f'Received binary message: {hex_data}')
187
+ # logger.info(f'magic number: {self._format_icl_binary_to_string(message[:2])}')
188
+ # logger.info(f'message type: {self._format_icl_binary_to_string(message[2:4])}')
189
+ # logger.info(f'element type: {self._format_icl_binary_to_string(message[4:6])}')
190
+ # logger.info(f'element count: {self._format_icl_binary_to_string(message[6:10])}')
191
+ # logger.info(f'tag 1: {self._format_icl_binary_to_string(message[10:12])}')
192
+ # logger.info(f'tag 2: {self._format_icl_binary_to_string(message[12:14])}')
193
+ # logger.info(f'tag 3: {self._format_icl_binary_to_string(message[14:16])}')
194
+ # logger.info(f'tag 4: {self._format_icl_binary_to_string(message[16:18])}')
195
+ # logger.info(f'payload: {self._format_icl_binary_to_string(message[18:])}')
196
+ # logger.info(f'payload as string: {str(message[18:])}')
197
+ pass
198
+
199
+ @override
200
+ async def discover_devices(self, error_on_no_device: bool = False) -> None:
201
+ """
202
+ Discovers the connected devices and saves them internally.
203
+
204
+ Args:
205
+ error_on_no_device (bool): If True, an exception is raised if no device is connected.
206
+ """
207
+ ccds_discovery: ChargeCoupledDevicesDiscovery = ChargeCoupledDevicesDiscovery(
208
+ self._icl_communicator, self._icl_error_db
209
+ )
210
+ await ccds_discovery.execute(error_on_no_device)
211
+ self._charge_coupled_devices = ccds_discovery.charge_coupled_devices()
212
+
213
+ monochromators_discovery: MonochromatorsDiscovery = MonochromatorsDiscovery(
214
+ self._icl_communicator, self._icl_error_db
215
+ )
216
+ await monochromators_discovery.execute(error_on_no_device)
217
+ self._monochromators = monochromators_discovery.monochromators()
218
+
219
+ @property
220
+ @override
221
+ def communicator(self) -> AbstractCommunicator:
222
+ """
223
+ Getter method for the communicator attribute.
224
+
225
+ Returns:
226
+ horiba_sdk.communication.AbstractCommunicator: Returns a new communicator instance.
227
+ """
228
+ return self._icl_communicator
229
+
230
+ @property
231
+ @override
232
+ def monochromators(self) -> list[Monochromator]:
233
+ """
234
+ The detected monochromators, should be called after :meth:`discover_devices`
235
+
236
+ Returns:
237
+ List[Monochromator]: The detected monochromators
238
+ """
239
+ return self._monochromators
240
+
241
+ @property
242
+ @override
243
+ def charge_coupled_devices(self) -> list[ChargeCoupledDevice]:
244
+ """
245
+ The detected CCDs, should be called after :meth:`discover_devices`
246
+
247
+ Returns:
248
+ List[ChargeCoupledDevice]: The detected CCDS.
249
+ """
250
+ return self._charge_coupled_devices
@@ -0,0 +1,133 @@
1
+ from typing import final
2
+
3
+ from overrides import override
4
+
5
+ from horiba_sdk.communication.websocket_communicator import WebsocketCommunicator
6
+ from horiba_sdk.devices.single_devices import ChargeCoupledDevice, Monochromator
7
+ from horiba_sdk.icl_error import FakeErrorDB
8
+
9
+ from .abstract_device_manager import AbstractDeviceManager
10
+
11
+
12
+ @final
13
+ class FakeDeviceManager(AbstractDeviceManager):
14
+ """
15
+ The FakeDeviceManager represents a `horiba_sdk.devices.DeviceManager` that can be used in the unit tests. It starts
16
+ a local websocket server and returns the predefined response located in `horiba_sdk/devices/fake_responses/*.json`.
17
+ Currently supported devices for fake responses are:
18
+ - The ICL itself
19
+ - The :class:`Monochromator`
20
+
21
+ For other unsupported devices, it just responds with the sent command.
22
+
23
+ The class should be used in a pytest fixture as follows::
24
+
25
+ fake_icl_host: str = 'localhost'
26
+ fake_icl_port: int = 8765
27
+
28
+ @pytest.fixture(scope='module')
29
+ def fake_device_manager():
30
+ fake_device_manager = FakeDeviceManager(fake_icl_host, fake_icl_port)
31
+ return fake_device_manager
32
+
33
+
34
+ @pytest.fixture(scope='module')
35
+ def _run_fake_icl_server(fake_device_manager):
36
+ thread = threading.Thread(target=fake_device_manager.start_icl)
37
+ thread.start()
38
+ yield
39
+ fake_device_manager.loop.call_soon_threadsafe(fake_device_manager.server.cancel)
40
+ thread.join()
41
+
42
+ @pytest.mark.asyncio
43
+ async def your_unit_test(fake_device_manager, _run_fake_icl_server):
44
+ pass
45
+
46
+ """
47
+
48
+ def __init__(self, host: str = '127.0.0.1', port: int = 25011):
49
+ self.host = host
50
+ self.port = port
51
+ # self.websocket: Optional[WebSocketServerProtocol] = None
52
+ self.error_db: FakeErrorDB = FakeErrorDB()
53
+ self.websocket_communicator = WebsocketCommunicator('ws://' + self.host + ':' + str(self.port))
54
+
55
+ # current_directory = os.path.dirname(__file__)
56
+ # fake_responses_path = os.path.join(current_directory, 'fake_responses')
57
+
58
+ # icl_fake_responses_path = os.path.join(fake_responses_path, 'icl.json')
59
+ # with open(icl_fake_responses_path) as json_file:
60
+ # self.icl_responses = json.load(json_file)
61
+
62
+ # monochromator_fake_responses_path = os.path.join(fake_responses_path, 'monochromator.json')
63
+ # with open(monochromator_fake_responses_path) as json_file:
64
+ # self.monochromator_responses = json.load(json_file)
65
+
66
+ # ccd_fake_responses_path = os.path.join(fake_responses_path, 'ccd.json')
67
+ # with open(ccd_fake_responses_path) as json_file:
68
+ # self.ccd_responses = json.load(json_file)
69
+
70
+ async def start(self) -> None:
71
+ """
72
+ Does nothing.
73
+ """
74
+ await self.websocket_communicator.open()
75
+
76
+ async def stop(self) -> None:
77
+ """
78
+ Does nothing.
79
+ """
80
+ await self.websocket_communicator.close()
81
+
82
+ @override
83
+ async def discover_devices(self, error_on_no_device: bool = False) -> None:
84
+ """
85
+ Does nothing.
86
+ """
87
+ pass
88
+
89
+ @property
90
+ @override
91
+ def communicator(self) -> WebsocketCommunicator:
92
+ """Communicator"""
93
+ # return WebsocketCommunicator('ws://' + self.host + ':' + str(self.port))
94
+ return self.websocket_communicator
95
+
96
+ @property
97
+ @override
98
+ def monochromators(self) -> list[Monochromator]:
99
+ """
100
+ Abstract method to get the detected monochromators.
101
+
102
+ Returns:
103
+ List[Monochromator]: The detected monochromators
104
+ """
105
+ return [Monochromator(0, self.communicator, self.error_db)]
106
+
107
+ @property
108
+ @override
109
+ def charge_coupled_devices(self) -> list[ChargeCoupledDevice]:
110
+ """
111
+ Abstract method to get the detected CCDs.
112
+
113
+ Returns:
114
+ List[ChargeCoupledDevice]: The detected CCDS.
115
+ """
116
+ return [ChargeCoupledDevice(0, self.communicator, self.error_db)]
117
+
118
+ # async def _echo_handler(self, websocket: WebSocketServerProtocol) -> None:
119
+ # async for message in websocket:
120
+ # logger.info('received: {message}', message=message)
121
+ # command = json.loads(message)
122
+ # if command['command'].startswith('icl_'):
123
+ # response = json.dumps(self.icl_responses[command['command']])
124
+ # await websocket.send(response)
125
+ # elif command['command'].startswith('mono_'):
126
+ # response = json.dumps(self.monochromator_responses[command['command']])
127
+ # await websocket.send(response)
128
+ # elif command['command'].startswith('ccd_'):
129
+ # response = json.dumps(self.ccd_responses[command['command']])
130
+ # await websocket.send(response)
131
+ # else:
132
+ # logger.info('unknown command, responding with message')
133
+ # await websocket.send(message)
@@ -0,0 +1,56 @@
1
+ import json
2
+ import os
3
+
4
+ import websockets
5
+ from loguru import logger
6
+
7
+
8
+ class FakeICLServer:
9
+ def __init__(self, fake_icl_host: str = 'localhost', fake_icl_port: int = 8765):
10
+ self._fake_icl_host: str = fake_icl_host
11
+ self._fake_icl_port: int = fake_icl_port
12
+ self._server = None
13
+
14
+ current_directory = os.path.dirname(__file__)
15
+ fake_responses_path = os.path.join(current_directory, 'fake_responses')
16
+
17
+ icl_fake_responses_path = os.path.join(fake_responses_path, 'icl.json')
18
+ with open(icl_fake_responses_path) as json_file:
19
+ self.icl_responses = json.load(json_file)
20
+
21
+ monochromator_fake_responses_path = os.path.join(fake_responses_path, 'monochromator.json')
22
+ with open(monochromator_fake_responses_path) as json_file:
23
+ self.monochromator_responses = json.load(json_file)
24
+
25
+ ccd_fake_responses_path = os.path.join(fake_responses_path, 'ccd.json')
26
+ with open(ccd_fake_responses_path) as json_file:
27
+ self.ccd_responses = json.load(json_file)
28
+
29
+ async def echo(self, websocket):
30
+ async for message in websocket:
31
+ logger.info('received: {message}', message=message)
32
+ command = json.loads(message)
33
+ if 'command' not in command:
34
+ logger.info('unknown message format, responding with message')
35
+ await websocket.send(message)
36
+
37
+ if command['command'].startswith('icl_'):
38
+ response = json.dumps(self.icl_responses[command['command']])
39
+ await websocket.send(response)
40
+ elif command['command'].startswith('mono_'):
41
+ response = json.dumps(self.monochromator_responses[command['command']])
42
+ await websocket.send(response)
43
+ elif command['command'].startswith('ccd_'):
44
+ response = json.dumps(self.ccd_responses[command['command']])
45
+ await websocket.send(response)
46
+ else:
47
+ logger.info('unknown command, responding with message')
48
+ await websocket.send(message)
49
+
50
+ async def start(self):
51
+ self._server = await websockets.serve(self.echo, self._fake_icl_host, self._fake_icl_port)
52
+
53
+ async def stop(self):
54
+ if self._server:
55
+ self._server.close()
56
+ await self._server.wait_closed()
@@ -0,0 +1,168 @@
1
+ {
2
+ "ccd_discover": {
3
+ "id": 1234,
4
+ "command": "ccd_discover",
5
+ "results": {
6
+ "count": 1
7
+ },
8
+ "errors": []
9
+ },
10
+ "ccd_list": {
11
+ "id": 1234,
12
+ "command": "ccd_list",
13
+ "results": {
14
+ "index0: %2" : "{ productId: 13, deviceType: HORIBA Scientific Syncerity, serialNumber: Camera SN: 2244}"
15
+ },
16
+ "errors": []
17
+ },
18
+ "ccd_listCount": {
19
+ "id": 1234,
20
+ "command": "ccd_listCount",
21
+ "results": {
22
+ "count": 2
23
+ },
24
+ "errors": []
25
+ },
26
+ "ccd_open": {
27
+ "id": 1234,
28
+ "command": "ccd_open",
29
+ "errors": [
30
+ ]
31
+ },
32
+ "ccd_close": {
33
+ "id": 1234,
34
+ "command": "ccd_close",
35
+ "errors": [
36
+ ]
37
+ },
38
+ "ccd_isOpen": {
39
+ "id": 1234,
40
+ "command": "ccd_isOpen",
41
+ "results":{
42
+ "open": true
43
+ },
44
+ "errors": [
45
+ ]
46
+ },
47
+ "ccd_restart": {},
48
+ "ccd_getConfig": {},
49
+ "ccd_getChipSize": {
50
+ "id": 1234,
51
+ "command": "ccd_getChipSize",
52
+ "errors": [],
53
+ "results": {
54
+ "x": 1024,
55
+ "y": 256
56
+ }
57
+ },
58
+ "ccd_getChipTemperature": {
59
+ "id": 1234,
60
+ "command": "ccd_getChipTemperature",
61
+ "errors": [],
62
+ "results": {
63
+ "temperature": -31.219999313354492
64
+ }
65
+ },
66
+ "ccd_getNumberOfAvgs": {},
67
+ "ccd_setNumberOfAvgs": {},
68
+ "ccd_getGain": {
69
+ "id": 1234,
70
+ "command": "ccd_getGain",
71
+ "errors": [],
72
+ "results": {
73
+ "info": "High Light",
74
+ "token": 0
75
+ }
76
+ },
77
+ "ccd_setGain": {},
78
+ "ccd_getSpeed": {
79
+ "id": 1234,
80
+ "command": "ccd_getSpeed",
81
+ "errors": [],
82
+ "results": {
83
+ "info": "45 kHz ",
84
+ "token": 0
85
+ }
86
+ },
87
+ "ccd_setSpeed": {},
88
+ "ccd_getFitParams": {
89
+ "id": 1234,
90
+ "command": "ccd_getFitParams",
91
+ "errors": [],
92
+ "results": {
93
+ "params": "0,1,0,0,0"
94
+ }
95
+ },
96
+ "ccd_setFitParams": {},
97
+ "ccd_getExposureTime": {
98
+ "id": 1234,
99
+ "command": "ccd_getExposureTime",
100
+ "errors": [],
101
+ "results": {
102
+ "time": 0
103
+ }
104
+ },
105
+ "ccd_setExposureTime": {},
106
+ "ccd_getTimerResolution": {
107
+ "id": 1234,
108
+ "command": "ccd_getTimerResolution",
109
+ "errors": [],
110
+ "results": {
111
+ "resolution": 1000
112
+ }
113
+ },
114
+ "ccd_setTimerResolution": {},
115
+ "ccd_setAcqFormat": {},
116
+ "ccd_setRoi": {},
117
+ "ccd_getXAxisConversionType": {
118
+ "command": "ccd_getXAxisConversionType",
119
+ "errors": [],
120
+ "id": 0,
121
+ "results": {
122
+ "type": 0
123
+ }
124
+ },
125
+ "ccd_setXAxisConversionType": {},
126
+ "ccd_getDataRetrievalMethod": {},
127
+ "ccd_setDataRetrievalMethod": {},
128
+ "ccd_getAcqCount": {
129
+ "command": "ccd_getAcqCount",
130
+ "errors": [],
131
+ "id": 0,
132
+ "results": {
133
+ "count": 1
134
+ }
135
+ },
136
+ "ccd_setAcqCount": {},
137
+ "ccd_getCleanCount": {
138
+ "command": "ccd_getCleanCount",
139
+ "errors": [],
140
+ "id": 0,
141
+ "results": {
142
+ "count": 1,
143
+ "mode": 238
144
+ }
145
+ },
146
+ "ccd_setCleanCount": {},
147
+ "ccd_getDataSize": {
148
+ "command": "ccd_getDataSize",
149
+ "errors": [],
150
+ "id": 0,
151
+ "results": {
152
+ "size": 1024
153
+ }
154
+ },
155
+ "ccd_getTriggerIn": {
156
+ "command": "ccd_getTriggerIn",
157
+ "errors": [],
158
+ "id": 0,
159
+ "results": {
160
+ "addressWhere": -1,
161
+ "eventWhen": -1,
162
+ "sigTypeHow": -1
163
+ }
164
+ },
165
+ "ccd_setTriggerIn": {},
166
+ "ccd_getSignalOut": {},
167
+ "ccd_setSignalOut": {}
168
+ }
@@ -0,0 +1,29 @@
1
+ {
2
+ "icl_info": {
3
+ "command": "icl_info",
4
+ "errors": [],
5
+ "id": 1234,
6
+ "results": {
7
+ "nodeAlias": "ICL",
8
+ "nodeApiVersion": 300,
9
+ "nodeBuilt": "Dec 5 2023-13:17:00",
10
+ "nodeDescription": "Instrument Control Library",
11
+ "nodeId": 1,
12
+ "nodeVersion": "2.0.0.108.d762232a"
13
+ }
14
+ },
15
+ "icl_shutdown": {
16
+ "id": 1234,
17
+ "command": "icl_shutdown",
18
+ "results": {
19
+ "state": "Shutting down"
20
+ },
21
+ "errors": []
22
+ },
23
+ "icl_binMode": {
24
+ "id": 1234,
25
+ "command": "icl_binMode",
26
+ "results": {},
27
+ "errors": []
28
+ }
29
+ }