pymodaq_scripting 5.3.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.
@@ -0,0 +1 @@
1
+ from .devices import Actuator, Detector, Dashboard
@@ -0,0 +1,107 @@
1
+ from concurrent.futures import Future
2
+ from typing import Optional, TYPE_CHECKING, Union
3
+ from xml.etree.ElementTree import Element
4
+ from pymodaq_data.data import DataToExport
5
+ from pymodaq_scripting.utils import (
6
+ Device,
7
+ LECOActuatorWrapper,
8
+ LECODetectorWrapper,
9
+ LECODashboardWrapper,
10
+ )
11
+
12
+ if TYPE_CHECKING:
13
+ from pymodaq.utils.data import DataActuator
14
+
15
+
16
+ class Actuator(Device[LECOActuatorWrapper]):
17
+ """
18
+ Public interface to control an Actuator
19
+ """
20
+ def __init__(self, device: str, **kwargs) -> None:
21
+ super().__init__(LECOActuatorWrapper(device, **kwargs))
22
+
23
+ def get_actuator_value(self) -> Future['DataActuator']:
24
+ return self._wrapper.get_actuator_value()
25
+
26
+ def move_home(self) -> Future['DataActuator']:
27
+ return self._wrapper.move_home()
28
+
29
+ def move_abs(self, value: Union['DataActuator', int, float, str]) -> Future['DataActuator']:
30
+ return self._wrapper.move_abs(value)
31
+
32
+ def move_rel(self, value: Union['DataActuator', int, float, str]) -> Future['DataActuator']:
33
+ return self._wrapper.move_rel(value)
34
+
35
+ def stop_move(self) -> Future['DataActuator']:
36
+ return self._wrapper.stop_move()
37
+
38
+ def get_settings(self) -> Future[Element]:
39
+ return self._wrapper.get_settings()
40
+
41
+ def set_settings(self, settings: Element) -> None:
42
+ self._wrapper.set_settings(settings)
43
+
44
+ class Detector(Device[LECODetectorWrapper]):
45
+ """
46
+ Public interface to control a Detector
47
+ """
48
+ def __init__(self, device: str, **kwargs) -> None:
49
+ super().__init__(LECODetectorWrapper(device, **kwargs))
50
+
51
+ def snap(self) -> Optional[Future[DataToExport]]:
52
+ return self._wrapper.snap()
53
+
54
+ def grab(self, keep=False) -> Optional[list[DataToExport]]:
55
+ return self._wrapper.grab(keep=keep)
56
+
57
+ def stop_grab(self) -> None:
58
+ return self._wrapper.stop_grab()
59
+
60
+ def get_settings(self) -> Future[Element]:
61
+ return self._wrapper.get_settings()
62
+
63
+ def set_settings(self, settings: Element) -> None:
64
+ self._wrapper.set_settings(settings)
65
+
66
+
67
+ class Dashboard(Device[LECODashboardWrapper]):
68
+ """
69
+ Public interface to control a Dashboard
70
+ """
71
+
72
+ def __init__(self, **kwargs) -> None:
73
+ super().__init__(LECODashboardWrapper(**kwargs))
74
+
75
+ @property
76
+ def leco_name(self):
77
+ return self._wrapper.leco_name
78
+
79
+ @property
80
+ def name(self) -> str:
81
+ return self._wrapper.name
82
+
83
+ def sign_out(self) -> None:
84
+ self._wrapper.sign_out()
85
+
86
+ def get_devices(self) -> Future[dict[str, list[str]]]:
87
+ return self._wrapper.get_devices()
88
+
89
+ def get_scripting_devices(self) -> dict[str, dict[str, Actuator | Detector]]:
90
+ devices = self._wrapper.get_devices().result()
91
+
92
+ return {
93
+ 'actuators': {name: Actuator(name) for name in devices['actuators']},
94
+ 'detectors': {name: Detector(name) for name in devices['detectors']},
95
+ }
96
+
97
+ def get_states(self) -> Future[list[str]]:
98
+ return self._wrapper.get_states()
99
+
100
+ def apply_state(self, state: str) -> Future[bool]:
101
+ return self._wrapper.apply_state(state)
102
+
103
+ def get_experiments(self) -> Future[list[str]]:
104
+ return self._wrapper.get_experiments()
105
+
106
+ def apply_experiment(self, experiment: str) -> Future[bool]:
107
+ return self._wrapper.apply_experiment(experiment)
File without changes
@@ -0,0 +1,27 @@
1
+ from pymodaq_scripting import Detector, Actuator, Dashboard
2
+
3
+ # Once a dashboard is started:
4
+ dashboard = Dashboard()
5
+
6
+
7
+ print(dashboard.get_experiments().result())
8
+ # >>> ['default']
9
+ # Could make it so that it returns a future that completes when it's finished loading
10
+ dashboard.apply_experiment('default')
11
+
12
+ print(dashboard.get_states().result())
13
+ # >>> ['default', 'my_custom_config']
14
+
15
+ # Same remark as dashboard.apply_preset
16
+ dashboard.apply_state('default')
17
+
18
+ print(dashboard.get_devices().result()) # returns the names
19
+ # >>> {'actuators': ['Theta', 'Temperature', 'Power', 'Xaxis'], 'detectors': ['Det2D', 'Det0D', 'Det1D']}
20
+
21
+ devices = dashboard.get_scripting_devices()
22
+ print(devices)
23
+ # >>> {'actuators': {'Theta': <Actuator object>, 'Temperature': <Actuator object>, 'Power': <Actuator object>, 'Xaxis': <Actuator object>}, 'detectors': {'Det2D': <Detector object>, 'Det0D': <Detector object>, 'Det1D': <Detector object>}}
24
+
25
+
26
+ #Then each device can be used
27
+ devices['actuators']['Angle'].move_abs('90°')
@@ -0,0 +1,35 @@
1
+ from pathlib import Path
2
+
3
+ from pymodaq_data import Q_
4
+ from pymodaq_data.h5modules.backends import GroupType
5
+
6
+ from pymodaq_scripting import Detector, Actuator
7
+ from pymodaq_data.h5modules.data_saving import DataToExportEnlargeableSaver
8
+
9
+ theta = Actuator('Angle')
10
+ det0d = Detector('Det0D')
11
+ det1d = Detector('Det1D')
12
+
13
+ acquisitions = []
14
+
15
+ base = theta.get_actuator_value().result()
16
+ target = base + Q_('90°') # Or a DataActuator(data=90, units='°')
17
+ step = Q_('10°')
18
+
19
+ count = (target - base)/step
20
+ count = int(count.quantities[0].m[0]) + 1
21
+
22
+ filename = Path(r'C:\Data\2026\scripted_data1.h5')
23
+
24
+ with DataToExportEnlargeableSaver(filename, enl_axis_names=('count',), enl_axis_units=('',)) as saver:
25
+
26
+ group = saver.add_group('Script', group_type=GroupType.data ,where='/RawData/', title='scripted_data')
27
+ group0d = saver.add_det_group(where=group, title=det0d.name)
28
+ group1d = saver.add_det_group(where=group, title=det1d.name)
29
+ for idx in range(count):
30
+ value = theta.move_abs(base + idx * step).result() # taking directly the result makes it a blocking move
31
+ dte0d_future = det0d.snap() # async snap
32
+ dte1d_future = det1d.snap() # async snap
33
+ saver.add_data(group0d, dte0d_future.result(), (value.value(), ))
34
+ saver.add_data(group1d, dte1d_future.result(), (value.value(),))
35
+
@@ -0,0 +1,415 @@
1
+ import atexit
2
+ import xml.etree.ElementTree as ET
3
+ from abc import ABC
4
+
5
+ from concurrent.futures import Future, InvalidStateError
6
+ from functools import cached_property
7
+ from typing import Optional, cast, Union, Generic, TypeVar, TYPE_CHECKING
8
+ from xml.etree.ElementTree import Element
9
+
10
+ from pyleco.directors.director import Director
11
+ from pyleco.utils.listener import Listener
12
+ from serializall import SerializableFactory, utils
13
+
14
+ from pymodaq_data.data import DataToExport
15
+ from pymodaq_data import Q_, DataRaw
16
+
17
+ if TYPE_CHECKING:
18
+ from pymodaq.utils.data import DataActuator
19
+
20
+ sf = SerializableFactory()
21
+
22
+
23
+ def compare_xml_trees(base: Element, modified: Element) -> list[tuple[list[str], bytes]]:
24
+ """
25
+ Compare two XML trees and return changes.
26
+
27
+ Returns:
28
+ list of tuples: (path_list, changed_element_bytes)
29
+ """
30
+
31
+ changes = []
32
+
33
+ def compare_elements(base_elem, modified_elem, path):
34
+ """Recursively compare elements"""
35
+ # Compare text content (strip whitespace)
36
+ text1 = (base_elem.text or '').strip()
37
+ text2 = (modified_elem.text or '').strip()
38
+
39
+ if text1 != text2:
40
+ # Text changed - record this element
41
+ changed_bytes = ET.tostring(modified_elem)
42
+ changes.append((path.copy(), changed_bytes))
43
+ return # Don't recurse further into this branch
44
+
45
+ # Compare attributes (optional - if you want to detect attribute changes)
46
+ if base_elem.attrib != modified_elem.attrib:
47
+ changed_bytes = ET.tostring(modified_elem)
48
+ changes.append((path.copy(), changed_bytes))
49
+ return
50
+
51
+ # Compare children
52
+ children1 = list(base_elem)
53
+ children2 = list(modified_elem)
54
+
55
+ # Build tag-to-element mapping
56
+ children1_dict = {child.tag: child for child in children1}
57
+ children2_dict = {child.tag: child for child in children2}
58
+
59
+ # Recursively compare matching children
60
+ for tag in children1_dict.keys():
61
+ if tag in children2_dict:
62
+ compare_elements(children1_dict[tag], children2_dict[tag],
63
+ path + [tag])
64
+
65
+ # Start comparison with forced prefix: ['detector_settings', 'settings_client']
66
+ initial_path = [base.tag or modified.tag]
67
+ compare_elements(base, modified, initial_path)
68
+
69
+ return changes
70
+
71
+
72
+ def value_to_data_actuator(value: Union['DataActuator', int, float, str]) -> Union['DataActuator', DataRaw]:
73
+ """
74
+ Converts any kind of convertible value into a DataActuator
75
+ Parameters
76
+ ----------
77
+ value: The value to convert
78
+
79
+ Returns
80
+ -------
81
+ A DataActuator object containing the value
82
+ """
83
+ if any(isinstance(value, t) for t in (int, float)):
84
+ return DataRaw(name='actuator', data=value)
85
+ if isinstance(value, str):
86
+ value = Q_(value)
87
+ if isinstance(value, Q_):
88
+ return DataRaw(name='actuator', data=value.magnitude, units=str(value.units))
89
+ return value
90
+
91
+
92
+ class LECOBaseWrapper:
93
+ """
94
+ Common LECO infrastructure: listener, communicator, director setup.
95
+ Shared by all LECO device and dashboard wrappers.
96
+ """
97
+ def __init__(self, device_name: str, **kwargs) -> None:
98
+ self._device_name = device_name
99
+
100
+ self._listener = Listener(name=self.leco_name, timeout=None)
101
+ self._listener.start_listen()
102
+
103
+ self._communicator = self._listener.get_communicator()
104
+ self._director = Director(actor=self.name, communicator=self._communicator, **kwargs)
105
+ atexit.register(self._clean)
106
+
107
+ @cached_property
108
+ def leco_name(self) -> str:
109
+ return f'scripting_{self.name}'
110
+
111
+ @cached_property
112
+ def name(self) -> str:
113
+ return self._device_name
114
+
115
+ def _clean(self):
116
+ self.sign_out()
117
+ self._listener.close()
118
+ self._director.close()
119
+ self._communicator.close()
120
+
121
+ def sign_out(self):
122
+ self._director.ask_rpc('sign_out', actor='COORDINATOR')
123
+
124
+ def set_remote_name(self):
125
+ self._director.ask_rpc('set_remote_name', name=self.leco_name)
126
+
127
+
128
+ class LECODeviceWrapper(LECOBaseWrapper):
129
+ """
130
+ LECO wrapper with settings management, shared by actuator and detector devices.
131
+ """
132
+ def __init__(self, device: str, **kwargs) -> None:
133
+ self._base_settings: Optional[Element] = None
134
+ self._settings_future: Optional[Future[Element]] = None
135
+
136
+ super().__init__(device, **kwargs)
137
+
138
+ self._listener.register_rpc_method(self.set_director_settings)
139
+ self._listener.register_binary_rpc_method(self.set_director_info, accept_binary_input=True)
140
+
141
+ def get_settings(self) -> Future[Element]:
142
+ future = Future()
143
+ self._settings_future = future
144
+
145
+ self.set_remote_name()
146
+ self._director.ask_rpc("get_settings")
147
+
148
+ return future
149
+
150
+ def set_settings(self, settings: Element):
151
+ if self._base_settings is not None:
152
+ for (path, modified) in compare_xml_trees(self._base_settings, settings):
153
+ t_name, t_len = utils.str_len_to_bytes('ParameterWithPath')
154
+ data = t_len + t_name + sf.get_apply_serializer(path) + sf.get_apply_serializer(modified)
155
+ self._director.ask_rpc("set_info", parameter=None, additional_payload=[data])
156
+
157
+ def set_director_settings(self, settings: bytes):
158
+ # Important to have two creations so they can be compared
159
+ # for modifications later.
160
+ self._base_settings = ET.fromstring(settings)
161
+ try:
162
+ self._settings_future.set_result(ET.fromstring(settings))
163
+ self._settings_future = None
164
+ except (InvalidStateError, AttributeError):
165
+ pass
166
+
167
+ def set_director_info(self, parameter=None, additional_payload=None) -> None:
168
+ pass
169
+
170
+
171
+
172
+ class LECOActuatorWrapper(LECODeviceWrapper):
173
+ """
174
+ LECO wrapper for actuators: move commands and position callbacks.
175
+ """
176
+ def __init__(self, device: str, **kwargs) -> None:
177
+ self._send_position_future: Optional[Future['DataActuator']] = None
178
+ self._move_done_future: Optional[Future['DataActuator']] = None
179
+
180
+ super().__init__(device, **kwargs)
181
+
182
+ self._listener.register_binary_rpc_method(self.send_position, accept_binary_input=True)
183
+ self._listener.register_binary_rpc_method(self.set_move_done, accept_binary_input=True)
184
+
185
+ def get_actuator_value(self) -> Future['DataActuator']:
186
+ future = Future()
187
+ self._send_position_future = future
188
+
189
+ self.set_remote_name()
190
+ self._director.ask_rpc('get_actuator_value')
191
+
192
+ return future
193
+
194
+ def move_home(self) -> Future['DataActuator']:
195
+ future = Future()
196
+ self._move_done_future = future
197
+
198
+ self.set_remote_name()
199
+ self._director.ask_rpc("move_home")
200
+
201
+ return future
202
+
203
+ def move_abs(self, value: Union['DataActuator', int, float, str]) -> Future['DataActuator']:
204
+ future = Future()
205
+ self._move_done_future = future
206
+
207
+ value = value_to_data_actuator(value)
208
+ serialize = SerializableFactory().get_apply_serializer
209
+
210
+ self.set_remote_name()
211
+ self._director.ask_rpc("move_abs", position=None, additional_payload=[serialize(value)])
212
+
213
+ return future
214
+
215
+ def move_rel(self, value: 'DataActuator') -> Future['DataActuator']:
216
+ future = Future()
217
+ self._move_done_future = future
218
+
219
+ value = value_to_data_actuator(value)
220
+ serialize = SerializableFactory().get_apply_serializer
221
+
222
+ self.set_remote_name()
223
+ self._director.ask_rpc("move_rel", position=None, additional_payload=[serialize(value)])
224
+
225
+ return future
226
+
227
+ def stop_move(self) -> Future['DataActuator']:
228
+ future = Future()
229
+ self._move_done_future = future
230
+
231
+ self.set_remote_name()
232
+ self._director.ask_rpc("stop_motion")
233
+
234
+ return future
235
+
236
+ def send_position(self, data=None, additional_payload=None):
237
+ value: Union['DataActuator', DataRaw] = cast(DataRaw, sf.get_apply_deserializer(additional_payload[0]))
238
+ try:
239
+ self._send_position_future.set_result(value)
240
+ self._send_position_future = None
241
+ except (InvalidStateError, AttributeError):
242
+ pass
243
+
244
+ def set_move_done(self, data=None, additional_payload=None):
245
+ value: Union['DataActuator', DataRaw] = cast(DataRaw, sf.get_apply_deserializer(additional_payload[0]))
246
+ try:
247
+ self._move_done_future.set_result(value)
248
+ self._move_done_future = None
249
+ except (InvalidStateError, AttributeError):
250
+ pass
251
+
252
+
253
+ class LECODetectorWrapper(LECODeviceWrapper):
254
+ """
255
+ LECO wrapper for detectors: snap/grab commands and data callbacks.
256
+ """
257
+ def __init__(self, device: str, **kwargs) -> None:
258
+ self._snap_data_future: Optional[Future[DataToExport]] = None
259
+ self._grab_data_list: Optional[list[DataToExport]] = None
260
+ self._is_grabbing = False
261
+ super().__init__(device, **kwargs)
262
+ self._listener.register_binary_rpc_method(self.set_data, accept_binary_input=True)
263
+
264
+ def snap(self) -> Optional[Future[DataToExport]]:
265
+ if not self._is_grabbing:
266
+ future = Future()
267
+ self._snap_data_future = future
268
+ self.set_remote_name()
269
+ self._director.ask_rpc("send_data_snap")
270
+ return future
271
+ return None
272
+
273
+ def grab(self, keep=False) -> Optional[list[DataToExport]]:
274
+ was_grabbing = self._is_grabbing
275
+ if not was_grabbing:
276
+ self._grab_data_list = [] if keep else None
277
+
278
+ self.set_remote_name()
279
+ self._director.ask_rpc("send_data_grab")
280
+ self._is_grabbing = not was_grabbing
281
+
282
+ return None if was_grabbing else self._grab_data_list
283
+
284
+ def stop_grab(self):
285
+ self._is_grabbing = False
286
+ self.set_remote_name()
287
+ self._director.ask_rpc("stop_grab")
288
+
289
+ def set_data(self, data=None, additional_payload=None):
290
+ value: DataToExport = cast(DataToExport, sf.get_apply_deserializer(additional_payload[0]))
291
+ if self._is_grabbing:
292
+ if self._grab_data_list is not None:
293
+ self._grab_data_list.append(value)
294
+ else:
295
+ try:
296
+ self._snap_data_future.set_result(value)
297
+ self._snap_data_future = None
298
+ except (InvalidStateError, AttributeError):
299
+ pass
300
+
301
+
302
+ class LECODashboardWrapper(LECOBaseWrapper):
303
+ def __init__(self, **kwargs) -> None:
304
+ self._experiments_future: Optional[Future[list[str]]] = None
305
+ self._applied_experiment_future: Optional[Future[bool]] = None
306
+ self._states_future: Optional[Future[list[str]]] = None
307
+ self._applied_state_future: Optional[Future[bool]] = None
308
+ self._devices_list_future: Optional[Future[dict[str, list[str]]]] = None
309
+
310
+ super().__init__('dashboard', **kwargs)
311
+
312
+ self._listener.register_binary_rpc_method(self.send_devices, accept_binary_input=True)
313
+ self._listener.register_rpc_method(self.send_states)
314
+ self._listener.register_rpc_method(self.send_experiments)
315
+ self._listener.register_rpc_method(self.applied_experiment_done)
316
+ self._listener.register_rpc_method(self.applied_state_done)
317
+
318
+ def get_devices(self) -> Future[dict[str, list[str]]]:
319
+ future = Future()
320
+ self._devices_list_future = future
321
+
322
+ self.set_remote_name()
323
+ self._director.ask_rpc("get_devices")
324
+
325
+ return future
326
+
327
+ def get_states(self) -> Future[list[str]]:
328
+ future = Future()
329
+ self._states_future = future
330
+
331
+ self.set_remote_name()
332
+ self._director.ask_rpc("get_states")
333
+
334
+ return future
335
+
336
+ def apply_state(self, state: str) -> Future[bool]:
337
+ future = Future()
338
+ self._applied_state_future = future
339
+
340
+ self.set_remote_name()
341
+ self._director.ask_rpc("apply_state", state=state)
342
+
343
+ return future
344
+
345
+ def get_experiments(self) -> Future[list[str]]:
346
+ future = Future()
347
+ self._experiments_future = future
348
+
349
+ self.set_remote_name()
350
+ self._director.ask_rpc("get_experiments")
351
+
352
+ return future
353
+
354
+ def apply_experiment(self, experiment: str) -> Future[bool]:
355
+ future = Future()
356
+ self._applied_experiment_future = future
357
+
358
+ self.set_remote_name()
359
+ self._director.ask_rpc("apply_experiment", experiment=experiment)
360
+
361
+ return future
362
+
363
+ def send_devices(self, data=None, additional_payload=None):
364
+ value: dict[str, list[str]] = sf.get_apply_deserializer(additional_payload[0])
365
+ try:
366
+ self._devices_list_future.set_result(value)
367
+ self._devices_list_future = None
368
+ except (InvalidStateError, AttributeError):
369
+ pass
370
+
371
+ def send_states(self, states: list[str]):
372
+ try:
373
+ self._states_future.set_result(states)
374
+ self._states_future = None
375
+ except (InvalidStateError, AttributeError):
376
+ pass
377
+
378
+ def send_experiments(self, experiments: list[str]):
379
+ try:
380
+ self._experiments_future.set_result(experiments)
381
+ self._experiments_future = None
382
+ except (InvalidStateError, AttributeError):
383
+ pass
384
+
385
+ def applied_experiment_done(self, done: bool):
386
+ try:
387
+ self._applied_experiment_future.set_result(done)
388
+ self._applied_experiment_future = None
389
+ except (InvalidStateError, AttributeError):
390
+ pass
391
+
392
+ def applied_state_done(self, done: bool):
393
+ try:
394
+ self._applied_state_future.set_result(done)
395
+ self._applied_state_future = None
396
+ except (InvalidStateError, AttributeError):
397
+ pass
398
+
399
+ WrapperT = TypeVar('WrapperT', bound=LECOBaseWrapper)
400
+
401
+ class Device(ABC, Generic[WrapperT]):
402
+ def __init__(self, wrapper: WrapperT) -> None:
403
+ self._wrapper = wrapper
404
+
405
+ @property
406
+ def leco_name(self):
407
+ return self._wrapper.leco_name
408
+
409
+ @property
410
+ def name(self) -> str:
411
+ #Cast to avoid pycharm warning
412
+ return cast(str, cast(object, self._wrapper.name))
413
+
414
+ def sign_out(self) -> None:
415
+ self._wrapper.sign_out()
@@ -0,0 +1,140 @@
1
+ Metadata-Version: 2.5
2
+ Name: pymodaq_scripting
3
+ Version: 5.3.0
4
+ Summary: Objects and utilities to use PyMoDAQ from a script
5
+ Project-URL: Homepage, http://pymodaq.cnrs.fr
6
+ Project-URL: Source, https://github.com/PyMoDAQ/pymodaq_scripting
7
+ Project-URL: Tracker, https://github.com/PyMoDAQ/pymodaq_scripting/issues
8
+ Author-email: Sébastien Weber <sebastien.weber@cemes.fr>
9
+ License: The MIT License (MIT)
10
+
11
+ Copyright (c) 2021 Sebastien Weber <sebastien.weber@cemes.fr>
12
+
13
+ Permission is hereby granted, free of charge, to any person obtaining a copy
14
+ of this software and associated documentation files (the "Software"), to deal
15
+ in the Software without restriction, including without limitation the rights
16
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
+ copies of the Software, and to permit persons to whom the Software is
18
+ furnished to do so, subject to the following conditions:
19
+
20
+ The above copyright notice and this permission notice shall be included in
21
+ all copies or substantial portions of the Software.
22
+
23
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
29
+ THE SOFTWARE.
30
+ License-File: LICENSE
31
+ Classifier: Development Status :: 5 - Production/Stable
32
+ Classifier: Environment :: Other Environment
33
+ Classifier: Intended Audience :: Science/Research
34
+ Classifier: License :: OSI Approved :: MIT License
35
+ Classifier: Natural Language :: English
36
+ Classifier: Operating System :: OS Independent
37
+ Classifier: Programming Language :: Python :: 3.10
38
+ Classifier: Programming Language :: Python :: 3.11
39
+ Classifier: Programming Language :: Python :: 3.12
40
+ Classifier: Programming Language :: Python :: 3.13
41
+ Classifier: Topic :: Scientific/Engineering :: Human Machine Interfaces
42
+ Classifier: Topic :: Scientific/Engineering :: Visualization
43
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
44
+ Classifier: Topic :: Software Development :: User Interfaces
45
+ Requires-Python: >=3.10
46
+ Requires-Dist: pyleco
47
+ Requires-Dist: pymodaq-data>=5.0.13
48
+ Requires-Dist: pymodaq-utils>=0.0.8
49
+ Provides-Extra: dev
50
+ Requires-Dist: flake8; extra == 'dev'
51
+ Requires-Dist: hatch; extra == 'dev'
52
+ Requires-Dist: pytest; extra == 'dev'
53
+ Requires-Dist: pytest-cov; extra == 'dev'
54
+ Requires-Dist: pytest-xdist; extra == 'dev'
55
+ Description-Content-Type: text/x-rst
56
+
57
+ .. ############################################################
58
+ .. WARNING: This file is AUTO-GENERATED from README.rst.tpl.
59
+ .. ⚠️ DO NOT EDIT MANUALLY ⚠️
60
+ .. ############################################################
61
+
62
+
63
+ PyMoDAQ Scripting
64
+ #################
65
+
66
+ .. image:: https://img.shields.io/pypi/v/pymodaq_scripting.svg
67
+ :target: https://pypi.org/project/pymodaq_scripting/
68
+ :alt: Latest Version
69
+
70
+ .. image:: https://readthedocs.org/projects/pymodaq/badge/?version=patch-3
71
+ :target: https://pymodaq.readthedocs.io/en/stable/?badge=patch-3
72
+ :alt: Documentation Status
73
+
74
+ .. image:: https://codecov.io/gh/PyMoDAQ/PyMoDAQ/branch/patch-3/graph/badge.svg?token=IQNJRCQDM2
75
+ :target: https://codecov.io/gh/PyMoDAQ/PyMoDAQ
76
+
77
+ +-------------+-------------+---------------+
78
+ | | Linux | Windows |
79
+ +=============+=============+===============+
80
+ | Python 3.10 | |310-linux| | |310-windows| |
81
+ +-------------+-------------+---------------+
82
+ | Python 3.11 | |311-linux| | |311-windows| |
83
+ +-------------+-------------+---------------+
84
+ | Python 3.12 | |312-linux| | |312-windows| |
85
+ +-------------+-------------+---------------+
86
+ | Python 3.13 | |313-linux| | |313-windows| |
87
+ +-------------+-------------+---------------+
88
+
89
+
90
+
91
+
92
+
93
+ .. |310-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Linux_3.10.svg
94
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
95
+
96
+ .. |311-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Linux_3.11.svg
97
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
98
+
99
+ .. |312-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Linux_3.12.svg
100
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
101
+
102
+ .. |313-linux| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Linux_3.13.svg
103
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
104
+
105
+ .. |310-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Windows_3.10.svg
106
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
107
+
108
+ .. |311-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Windows_3.11.svg
109
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
110
+
111
+ .. |312-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Windows_3.12.svg
112
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
113
+
114
+ .. |313-windows| image:: https://raw.githubusercontent.com/PyMoDAQ/PyMoDAQ/badges/pymodaq_scripting/patch-3/tests_Windows_3.13.svg
115
+ :target: https://github.com/PyMoDAQ/PyMoDAQ/actions/workflows/tests-data.yml
116
+
117
+
118
+
119
+ .. figure:: http://pymodaq.cnrs.fr/en/latest/_static/splash.png
120
+ :alt: shortcut
121
+
122
+
123
+ PyMoDAQ__, Modular Data Acquisition with Python, is a set of **python** modules used to interface any kind of
124
+ experiments. It simplifies the interaction with detector and actuator hardware to go straight to the data acquisition
125
+ of interest.
126
+
127
+ __ https://pymodaq.readthedocs.io/en/stable/?badge=latest
128
+
129
+ `PyMoDAQ Scripting`__ is a set of utilities (mainly objects) that are used
130
+ to provide direct emulation of the DashBoard set of Control Modules but from a script in
131
+ another process. Communication is handled by the lecco protocol.
132
+
133
+ __ https://pymodaq.cnrs.fr/en/latest/user_folder/scripting.html
134
+
135
+
136
+ Published under the MIT FREE SOFTWARE LICENSE
137
+
138
+ GitHub repo: https://github.com/PyMoDAQ
139
+
140
+ Documentation: http://pymodaq.cnrs.fr/
@@ -0,0 +1,10 @@
1
+ pymodaq_scripting/__init__.py,sha256=bOKxPgKOT-TqS42lx8O8JY3d7cSfpskAUDADA1iYThM,50
2
+ pymodaq_scripting/devices.py,sha256=K7iPIubX84LBV8xosvtKBYTx15W18YEmoD-LpyeCbtA,3343
3
+ pymodaq_scripting/utils.py,sha256=yRdymXJq1e48YmHaPUDjBk300dvQzm27HzZo9CMByHQ,14116
4
+ pymodaq_scripting/examples/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
5
+ pymodaq_scripting/examples/dashboard_script.py,sha256=RbhDb4yziYywuo0wG5GvglGhGf80QrfPrgrozX8iMD8,1002
6
+ pymodaq_scripting/examples/simple_script.py,sha256=dTaVprhgG9lPh1ECo0xlbby9Yq7Gm4EJYDbcqNEOssQ,1320
7
+ pymodaq_scripting-5.3.0.dist-info/METADATA,sha256=V0Kf1F33TPRX4d8ctWcxo0v5PUekg0OVoz0ZBUOIAwk,6557
8
+ pymodaq_scripting-5.3.0.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
9
+ pymodaq_scripting-5.3.0.dist-info/licenses/LICENSE,sha256=VKOejxexXAe3XwfhAhcFGqeXQ12irxVHdeAojZwFEI8,1108
10
+ pymodaq_scripting-5.3.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.4
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2021 Sebastien Weber <sebastien.weber@cemes.fr>
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.