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.
- horiba_sdk/__init__.py +19 -0
- horiba_sdk/communication/__init__.py +44 -0
- horiba_sdk/communication/abstract_communicator.py +59 -0
- horiba_sdk/communication/communication_exception.py +19 -0
- horiba_sdk/communication/messages.py +87 -0
- horiba_sdk/communication/websocket_communicator.py +213 -0
- horiba_sdk/core/resolution.py +45 -0
- horiba_sdk/devices/__init__.py +11 -0
- horiba_sdk/devices/abstract_device_discovery.py +7 -0
- horiba_sdk/devices/abstract_device_manager.py +68 -0
- horiba_sdk/devices/ccd_discovery.py +57 -0
- horiba_sdk/devices/device_manager.py +250 -0
- horiba_sdk/devices/fake_device_manager.py +133 -0
- horiba_sdk/devices/fake_icl_server.py +56 -0
- horiba_sdk/devices/fake_responses/ccd.json +168 -0
- horiba_sdk/devices/fake_responses/icl.json +29 -0
- horiba_sdk/devices/fake_responses/monochromator.json +187 -0
- horiba_sdk/devices/monochromator_discovery.py +48 -0
- horiba_sdk/devices/single_devices/__init__.py +5 -0
- horiba_sdk/devices/single_devices/abstract_device.py +79 -0
- horiba_sdk/devices/single_devices/ccd.py +443 -0
- horiba_sdk/devices/single_devices/monochromator.py +395 -0
- horiba_sdk/icl_error/__init__.py +34 -0
- horiba_sdk/icl_error/abstract_error.py +65 -0
- horiba_sdk/icl_error/abstract_error_db.py +25 -0
- horiba_sdk/icl_error/error_list.json +265 -0
- horiba_sdk/icl_error/icl_error.py +30 -0
- horiba_sdk/icl_error/icl_error_db.py +81 -0
- horiba_sdk/sync/__init__.py +0 -0
- horiba_sdk/sync/communication/__init__.py +7 -0
- horiba_sdk/sync/communication/abstract_communicator.py +48 -0
- horiba_sdk/sync/communication/test_client.py +16 -0
- horiba_sdk/sync/communication/websocket_communicator.py +212 -0
- horiba_sdk/sync/devices/__init__.py +15 -0
- horiba_sdk/sync/devices/abstract_device_discovery.py +17 -0
- horiba_sdk/sync/devices/abstract_device_manager.py +68 -0
- horiba_sdk/sync/devices/device_discovery.py +82 -0
- horiba_sdk/sync/devices/device_manager.py +209 -0
- horiba_sdk/sync/devices/fake_device_manager.py +91 -0
- horiba_sdk/sync/devices/fake_icl_server.py +79 -0
- horiba_sdk/sync/devices/single_devices/__init__.py +5 -0
- horiba_sdk/sync/devices/single_devices/abstract_device.py +83 -0
- horiba_sdk/sync/devices/single_devices/ccd.py +219 -0
- horiba_sdk/sync/devices/single_devices/monochromator.py +150 -0
- horiba_sdk-0.3.2.dist-info/LICENSE +20 -0
- horiba_sdk-0.3.2.dist-info/METADATA +438 -0
- horiba_sdk-0.3.2.dist-info/RECORD +48 -0
- 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
|
+
}
|