pymodaq 5.1.6__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 (154) hide show
  1. pymodaq/__init__.py +98 -0
  2. pymodaq/control_modules/__init__.py +1 -0
  3. pymodaq/control_modules/daq_move.py +1238 -0
  4. pymodaq/control_modules/daq_move_ui/__init__.py +0 -0
  5. pymodaq/control_modules/daq_move_ui/factory.py +48 -0
  6. pymodaq/control_modules/daq_move_ui/ui_base.py +359 -0
  7. pymodaq/control_modules/daq_move_ui/uis/__init__.py +0 -0
  8. pymodaq/control_modules/daq_move_ui/uis/binary.py +139 -0
  9. pymodaq/control_modules/daq_move_ui/uis/original.py +120 -0
  10. pymodaq/control_modules/daq_move_ui/uis/relative.py +124 -0
  11. pymodaq/control_modules/daq_move_ui/uis/simple.py +126 -0
  12. pymodaq/control_modules/daq_viewer.py +1517 -0
  13. pymodaq/control_modules/daq_viewer_ui.py +407 -0
  14. pymodaq/control_modules/mocks.py +57 -0
  15. pymodaq/control_modules/move_utility_classes.py +1141 -0
  16. pymodaq/control_modules/thread_commands.py +137 -0
  17. pymodaq/control_modules/ui_utils.py +72 -0
  18. pymodaq/control_modules/utils.py +591 -0
  19. pymodaq/control_modules/viewer_utility_classes.py +670 -0
  20. pymodaq/daq_utils/__init__.py +0 -0
  21. pymodaq/daq_utils/daq_utils.py +6 -0
  22. pymodaq/dashboard.py +2396 -0
  23. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.aliases +3 -0
  24. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvlps +3 -0
  25. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvproj +32 -0
  26. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.vi +0 -0
  27. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_1Dgaussian.vi +0 -0
  28. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_2Dgaussian.vi +0 -0
  29. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_cmd.vi +0 -0
  30. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_float.vi +0 -0
  31. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_int.vi +0 -0
  32. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_data.vi +0 -0
  33. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_int.vi +0 -0
  34. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_scalar.vi +0 -0
  35. pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_string.vi +0 -0
  36. pymodaq/examples/Labview_TCP_Client/client_state.ctl +0 -0
  37. pymodaq/examples/Labview_TCP_Client/cmd_types.ctl +0 -0
  38. pymodaq/examples/__init__.py +0 -0
  39. pymodaq/examples/function_plotter.py +160 -0
  40. pymodaq/examples/nonlinearscanner.py +126 -0
  41. pymodaq/examples/qt_less_standalone_module.py +165 -0
  42. pymodaq/examples/tcp_client.py +97 -0
  43. pymodaq/extensions/__init__.py +25 -0
  44. pymodaq/extensions/adaptive/__init__.py +2 -0
  45. pymodaq/extensions/adaptive/adaptive_optimization.py +179 -0
  46. pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
  47. pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +73 -0
  48. pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
  49. pymodaq/extensions/adaptive/loss_function/loss_factory.py +110 -0
  50. pymodaq/extensions/adaptive/utils.py +123 -0
  51. pymodaq/extensions/bayesian/__init__.py +2 -0
  52. pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
  53. pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +80 -0
  54. pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +105 -0
  55. pymodaq/extensions/bayesian/bayesian_optimization.py +143 -0
  56. pymodaq/extensions/bayesian/utils.py +180 -0
  57. pymodaq/extensions/console.py +73 -0
  58. pymodaq/extensions/daq_logger/__init__.py +1 -0
  59. pymodaq/extensions/daq_logger/abstract.py +52 -0
  60. pymodaq/extensions/daq_logger/daq_logger.py +519 -0
  61. pymodaq/extensions/daq_logger/db/__init__.py +0 -0
  62. pymodaq/extensions/daq_logger/db/db_logger.py +300 -0
  63. pymodaq/extensions/daq_logger/db/db_logger_models.py +100 -0
  64. pymodaq/extensions/daq_logger/h5logging.py +84 -0
  65. pymodaq/extensions/daq_scan.py +1218 -0
  66. pymodaq/extensions/daq_scan_ui.py +241 -0
  67. pymodaq/extensions/data_mixer/__init__.py +0 -0
  68. pymodaq/extensions/data_mixer/daq_0Dviewer_DataMixer.py +97 -0
  69. pymodaq/extensions/data_mixer/data_mixer.py +262 -0
  70. pymodaq/extensions/data_mixer/model.py +108 -0
  71. pymodaq/extensions/data_mixer/models/__init__.py +0 -0
  72. pymodaq/extensions/data_mixer/models/equation_model.py +91 -0
  73. pymodaq/extensions/data_mixer/models/gaussian_fit_model.py +65 -0
  74. pymodaq/extensions/data_mixer/parser.py +53 -0
  75. pymodaq/extensions/data_mixer/utils.py +23 -0
  76. pymodaq/extensions/h5browser.py +9 -0
  77. pymodaq/extensions/optimizers_base/__init__.py +0 -0
  78. pymodaq/extensions/optimizers_base/optimizer.py +1016 -0
  79. pymodaq/extensions/optimizers_base/thread_commands.py +22 -0
  80. pymodaq/extensions/optimizers_base/utils.py +427 -0
  81. pymodaq/extensions/pid/__init__.py +16 -0
  82. pymodaq/extensions/pid/actuator_controller.py +14 -0
  83. pymodaq/extensions/pid/daq_move_PID.py +154 -0
  84. pymodaq/extensions/pid/pid_controller.py +1016 -0
  85. pymodaq/extensions/pid/utils.py +189 -0
  86. pymodaq/extensions/utils.py +111 -0
  87. pymodaq/icon.ico +0 -0
  88. pymodaq/post_treatment/__init__.py +6 -0
  89. pymodaq/post_treatment/load_and_plot.py +352 -0
  90. pymodaq/resources/__init__.py +0 -0
  91. pymodaq/resources/config_template.toml +57 -0
  92. pymodaq/resources/preset_default.xml +1 -0
  93. pymodaq/resources/setup_plugin.py +73 -0
  94. pymodaq/splash.png +0 -0
  95. pymodaq/utils/__init__.py +0 -0
  96. pymodaq/utils/array_manipulation.py +6 -0
  97. pymodaq/utils/calibration_camera.py +180 -0
  98. pymodaq/utils/chrono_timer.py +203 -0
  99. pymodaq/utils/config.py +53 -0
  100. pymodaq/utils/conftests.py +5 -0
  101. pymodaq/utils/daq_utils.py +158 -0
  102. pymodaq/utils/data.py +128 -0
  103. pymodaq/utils/enums.py +6 -0
  104. pymodaq/utils/exceptions.py +38 -0
  105. pymodaq/utils/gui_utils/__init__.py +10 -0
  106. pymodaq/utils/gui_utils/loader_utils.py +75 -0
  107. pymodaq/utils/gui_utils/utils.py +18 -0
  108. pymodaq/utils/gui_utils/widgets/lcd.py +8 -0
  109. pymodaq/utils/h5modules/__init__.py +2 -0
  110. pymodaq/utils/h5modules/module_saving.py +526 -0
  111. pymodaq/utils/leco/__init__.py +25 -0
  112. pymodaq/utils/leco/daq_move_LECODirector.py +217 -0
  113. pymodaq/utils/leco/daq_xDviewer_LECODirector.py +163 -0
  114. pymodaq/utils/leco/director_utils.py +74 -0
  115. pymodaq/utils/leco/leco_director.py +166 -0
  116. pymodaq/utils/leco/pymodaq_listener.py +364 -0
  117. pymodaq/utils/leco/rpc_method_definitions.py +43 -0
  118. pymodaq/utils/leco/utils.py +74 -0
  119. pymodaq/utils/logger.py +6 -0
  120. pymodaq/utils/managers/__init__.py +0 -0
  121. pymodaq/utils/managers/batchscan_manager.py +346 -0
  122. pymodaq/utils/managers/modules_manager.py +589 -0
  123. pymodaq/utils/managers/overshoot_manager.py +242 -0
  124. pymodaq/utils/managers/preset_manager.py +229 -0
  125. pymodaq/utils/managers/preset_manager_utils.py +262 -0
  126. pymodaq/utils/managers/remote_manager.py +484 -0
  127. pymodaq/utils/math_utils.py +6 -0
  128. pymodaq/utils/messenger.py +6 -0
  129. pymodaq/utils/parameter/__init__.py +10 -0
  130. pymodaq/utils/parameter/utils.py +6 -0
  131. pymodaq/utils/scanner/__init__.py +5 -0
  132. pymodaq/utils/scanner/scan_config.py +16 -0
  133. pymodaq/utils/scanner/scan_factory.py +259 -0
  134. pymodaq/utils/scanner/scan_selector.py +477 -0
  135. pymodaq/utils/scanner/scanner.py +324 -0
  136. pymodaq/utils/scanner/scanners/_1d_scanners.py +174 -0
  137. pymodaq/utils/scanner/scanners/_2d_scanners.py +299 -0
  138. pymodaq/utils/scanner/scanners/__init__.py +1 -0
  139. pymodaq/utils/scanner/scanners/sequential.py +224 -0
  140. pymodaq/utils/scanner/scanners/tabular.py +319 -0
  141. pymodaq/utils/scanner/utils.py +110 -0
  142. pymodaq/utils/svg/__init__.py +6 -0
  143. pymodaq/utils/svg/svg_renderer.py +20 -0
  144. pymodaq/utils/svg/svg_view.py +35 -0
  145. pymodaq/utils/svg/svg_viewer2D.py +50 -0
  146. pymodaq/utils/tcp_ip/__init__.py +6 -0
  147. pymodaq/utils/tcp_ip/mysocket.py +12 -0
  148. pymodaq/utils/tcp_ip/serializer.py +13 -0
  149. pymodaq/utils/tcp_ip/tcp_server_client.py +772 -0
  150. pymodaq-5.1.6.dist-info/METADATA +238 -0
  151. pymodaq-5.1.6.dist-info/RECORD +154 -0
  152. pymodaq-5.1.6.dist-info/WHEEL +4 -0
  153. pymodaq-5.1.6.dist-info/entry_points.txt +7 -0
  154. pymodaq-5.1.6.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1238 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Created the 29/07/2022
4
+
5
+ @author: Sebastien Weber
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import numbers
11
+ from importlib import import_module
12
+ from numbers import Number
13
+
14
+ import sys
15
+ from typing import List, Union, Optional, Dict, TypeVar, TYPE_CHECKING
16
+ import numpy as np
17
+
18
+ from qtpy.QtCore import QObject, Signal, QThread, Slot, Qt, QTimer
19
+ from qtpy import QtWidgets
20
+
21
+ from easydict import EasyDict as edict
22
+
23
+ from pymodaq_utils.logger import set_logger, get_module_name
24
+ from pymodaq_utils.utils import find_keys_from_val
25
+ from pymodaq_utils import utils
26
+ from pymodaq.utils.gui_utils import get_splash_sc
27
+ from pymodaq_utils import config as config_mod
28
+ from pymodaq.utils.exceptions import ActuatorError
29
+ from pymodaq_utils.warnings import deprecation_msg
30
+ from pymodaq.utils.data import DataToExport, DataActuator
31
+ from pymodaq_data.h5modules.backends import Node
32
+
33
+ from pymodaq_gui.h5modules.saving import H5Saver
34
+ from pymodaq_gui.parameter import ioxml, Parameter
35
+ from pymodaq_gui.parameter import utils as putils
36
+ from pymodaq_gui.utils.utils import mkQApp
37
+
38
+ from pymodaq.utils.h5modules import module_saving
39
+ from pymodaq.control_modules.utils import ParameterControlModule
40
+ from pymodaq.control_modules.thread_commands import (
41
+ ThreadStatus,
42
+ ThreadStatusMove,
43
+ ControlToHardwareMove,
44
+ UiToMainMove,
45
+ )
46
+
47
+
48
+ from pymodaq.control_modules.move_utility_classes import (
49
+ ThreadCommand,
50
+ MoveCommand,
51
+ DAQ_Move_base,
52
+ DataActuatorType,
53
+ check_units,
54
+ DataUnitError,
55
+ )
56
+
57
+
58
+ from pymodaq.control_modules.move_utility_classes import params as daq_move_params
59
+ from pymodaq.utils.leco.pymodaq_listener import (
60
+ MoveActorListener,
61
+ LECOMoveCommands,
62
+ LECOCommands,
63
+ )
64
+
65
+ from pymodaq.utils.daq_utils import get_plugins
66
+ from pymodaq import Q_, Unit
67
+
68
+
69
+ from pymodaq.control_modules.daq_move_ui.factory import ActuatorUIFactory
70
+ from pymodaq.utils.config import Config as ControlModulesConfig
71
+
72
+ if TYPE_CHECKING:
73
+ from pymodaq.control_modules.daq_move_ui.ui_base import DAQ_Move_UI_Base
74
+
75
+ local_path = config_mod.get_set_local_dir()
76
+ sys.path.append(str(local_path))
77
+ logger = set_logger(get_module_name(__file__))
78
+
79
+ config_utils = config_mod.Config()
80
+ config = ControlModulesConfig()
81
+
82
+ HardwareController = TypeVar("HardwareController")
83
+
84
+ DAQ_Move_Actuators = get_plugins("daq_move")
85
+ ACTUATOR_TYPES = [mov["name"] for mov in DAQ_Move_Actuators]
86
+ if len(ACTUATOR_TYPES) == 0:
87
+ raise ActuatorError("No installed Actuator")
88
+
89
+
90
+ STATUS_WAIT_TIME = 1000
91
+
92
+
93
+ class DAQ_Move(ParameterControlModule):
94
+ """Main PyMoDAQ class to drive actuators
95
+
96
+ Qt object and generic UI to drive actuators.
97
+
98
+ Attributes
99
+ ----------
100
+ init_signal: Signal[bool]
101
+ This signal is emitted when the chosen actuator is correctly initialized
102
+ move_done_signal: Signal[str, DataActuator]
103
+ This signal is emitted when the chosen actuator finished its action. It gives the actuator's name and current
104
+ value
105
+ bounds_signal: Signal[bool]
106
+ This signal is emitted when the actuator reached defined limited boundaries.
107
+
108
+ See Also
109
+ --------
110
+ :class:`ControlModule`, :class:`ParameterManager`
111
+ """
112
+
113
+ settings_name = "daq_move_settings"
114
+
115
+ move_done_signal = Signal(DataActuator)
116
+ current_value_signal = Signal(DataActuator)
117
+ bounds_signal = Signal(bool)
118
+
119
+ params = daq_move_params + [
120
+ {'title': 'Saver Settings:', 'name': 'saver_settings', 'type': 'group',
121
+ 'visible': False, 'children': H5Saver.params}]
122
+
123
+ listener_class = MoveActorListener
124
+ ui: Optional[DAQ_Move_UI_Base]
125
+
126
+ def __init__(
127
+ self, parent=None, title="DAQ Move", ui_identifier: Optional[str] = None, **kwargs
128
+ ) -> None:
129
+ """
130
+
131
+ Parameters
132
+ ----------
133
+ parent: QWidget or None
134
+ parent: QWidget or None
135
+ if it is a valid QWidget, it will hold the user interface to drive it
136
+ title: str
137
+ The unique (should be unique) string identifier for the underlying actuator
138
+ """
139
+
140
+ self.logger = set_logger(f"{logger.name}.{title}")
141
+ self.logger.info(f"Initializing DAQ_Move: {title}")
142
+
143
+ super().__init__(action_list=("save", "update"), **kwargs)
144
+
145
+ if not (
146
+ ui_identifier is not None and ui_identifier in ActuatorUIFactory.keys()
147
+ ):
148
+ ui_identifier = config("actuator", "ui")
149
+ self.settings.child("main_settings", "ui_type").setValue(ui_identifier)
150
+ self.settings.child("main_settings", "ui_type").setOpts(readonly=True)
151
+
152
+ DAQ_Move_UI = ActuatorUIFactory.get(ui_identifier)
153
+
154
+ self.parent = parent
155
+ if parent is not None:
156
+ self.ui = DAQ_Move_UI(parent, title)
157
+ else:
158
+ self.ui = None
159
+
160
+ if self.ui is not None:
161
+ self.ui.actuators = ACTUATOR_TYPES
162
+ self.ui.set_settings_tree(self.settings_tree)
163
+ self.ui.command_sig.connect(self.process_ui_cmds)
164
+
165
+ self.splash_sc = get_splash_sc()
166
+ self._title = title
167
+ if len(ACTUATOR_TYPES) > 0: # will be 0 if no valid plugins are installed
168
+ self.actuator = kwargs.get("actuator", ACTUATOR_TYPES[0])
169
+
170
+ self._module_and_data_saver: module_saving.ActuatorTimeSaver = None
171
+ for hidden_param in ('custom_name',
172
+ 'current_scan_name',
173
+ 'current_scan_path',
174
+ 'current_h5_file',
175
+ 'new_file',
176
+ 'base_name'):
177
+ self.settings.child('saver_settings', hidden_param).setOpts(visible=False)
178
+
179
+ self._move_done_bool = True
180
+
181
+ self._current_value = DataActuator(title, units=self.units)
182
+ self._target_value = DataActuator(title, units=self.units)
183
+ self._relative_value = DataActuator(title, units=self.units)
184
+
185
+ self._refresh_timer = QTimer()
186
+ self._refresh_timer.timeout.connect(self.get_actuator_value)
187
+
188
+ def process_ui_cmds(self, cmd: utils.ThreadCommand):
189
+ """Process commands sent by actions done in the ui
190
+
191
+ Parameters
192
+ ----------
193
+ cmd: ThreadCommand
194
+ Possible values are :
195
+ * init
196
+ * quit
197
+ * get_value
198
+ * loop_get_value
199
+ * find_home
200
+ * stop
201
+ * move_abs
202
+ * move_rel
203
+ * show_log
204
+ * actuator_changed
205
+ * rel_value
206
+ * show_config
207
+ """
208
+ if cmd.command == UiToMainMove.INIT:
209
+ self.init_hardware(cmd.attribute[0])
210
+ elif cmd.command == UiToMainMove.QUIT:
211
+ self.quit_fun()
212
+ elif cmd.command == UiToMainMove.GET_VALUE:
213
+ self.get_actuator_value()
214
+ elif cmd.command == UiToMainMove.LOOP_GET_VALUE:
215
+ self.get_continuous_actuator_value(cmd.attribute)
216
+ elif cmd.command == UiToMainMove.FIND_HOME:
217
+ self.move_home()
218
+ elif cmd.command == UiToMainMove.STOP:
219
+ self.stop_motion()
220
+ elif cmd.command == UiToMainMove.MOVE_ABS:
221
+ data_act: DataActuator = cmd.attribute
222
+ if (
223
+ not Unit(data_act.units).is_compatible_with(self.units)
224
+ and data_act.units != ""
225
+ ):
226
+ data_act.force_units(self.units)
227
+ self.move_abs(data_act)
228
+ elif cmd.command == UiToMainMove.MOVE_REL:
229
+ data_act: DataActuator = cmd.attribute
230
+ if (
231
+ not Unit(data_act.units).is_compatible_with(self.units)
232
+ and data_act.units != ""
233
+ ):
234
+ data_act.force_units(self.units)
235
+ self.move_rel(data_act)
236
+ elif cmd.command == UiToMainMove.SHOW_LOG:
237
+ self.show_log()
238
+ elif cmd.command == UiToMainMove.SHOW_CONFIG:
239
+ self.config = self.show_config(self.config)
240
+ self.ui.config = self.config
241
+ elif cmd.command == UiToMainMove.ACTUATOR_CHANGED:
242
+ self.actuator = cmd.attribute
243
+ elif cmd.command == UiToMainMove.REL_VALUE:
244
+ self._relative_value = cmd.attribute
245
+
246
+ @property
247
+ def master(self) -> bool:
248
+ """Get/Set programmatically the Master/Slave status of an actuator"""
249
+ if self.initialized_state:
250
+ return (
251
+ self.settings["move_settings", "multiaxes", "multi_status"] == "Master"
252
+ )
253
+ else:
254
+ return True
255
+
256
+ @master.setter
257
+ def master(self, is_master: bool):
258
+ if self.initialized_state:
259
+ self.settings.child("move_settings", "multiaxes", "multi_status").setValue(
260
+ "Master" if is_master else "Slave"
261
+ )
262
+
263
+ def append_data(
264
+ self, dte: Optional[DataToExport] = None, where: Union[Node, str, None] = None
265
+ ):
266
+ """Appends current DataToExport to an ActuatorEnlargeableSaver
267
+
268
+ Parameters
269
+ ----------
270
+ dte: DataToExport, optional
271
+ where: Node or str
272
+ See Also
273
+ --------
274
+ ActuatorEnlargeableSaver
275
+ """
276
+ if dte is None:
277
+ dte = DataToExport(name=self.title, data=[self._current_value])
278
+ self._add_data_to_saver(dte, where=where)
279
+ self.settings.child('saver_settings', 'N_saved').setValue(self.settings['saver_settings', 'N_saved'] + 1)
280
+
281
+ def _add_data_to_saver(self, data: DataToExport, where=None, **kwargs):
282
+ """Adds DataToExport data to the current node using the declared module_and_data_saver
283
+
284
+ Filters the data to be saved by DataSource as specified in the current H5Saver (see self.module_and_data_saver)
285
+
286
+ Parameters
287
+ ----------
288
+ data: DataToExport
289
+ The data to be saved
290
+ kwargs: dict
291
+ Other named parameters to be passed as is to the module_and_data_saver
292
+
293
+ See Also
294
+ --------
295
+ DetectorSaver, DetectorEnlargeableSaver, DetectorExtendedSaver
296
+
297
+ """
298
+ # todo: test this for logging
299
+
300
+ node = self.module_and_data_saver.get_set_node(where)
301
+ self.module_and_data_saver.add_data(node, data, **kwargs)
302
+
303
+ def stop_motion(self):
304
+ """Stop any motion"""
305
+ try:
306
+ self.command_hardware.emit(ThreadCommand(ControlToHardwareMove.STOP_MOTION))
307
+ except Exception as e:
308
+ self.logger.exception(str(e))
309
+
310
+ def move(self, move_command: MoveCommand):
311
+ """Generic method to trigger the correct action on the actuator
312
+
313
+ Parameters
314
+ ----------
315
+ move_command: MoveCommand
316
+ MoveCommand with move_type attribute either:
317
+ * 'abs': performs an absolute action
318
+ * 'rel': performs a relative action
319
+ * 'home': find the actuator's home
320
+
321
+ See Also
322
+ --------
323
+ :meth:`move_abs`, :meth:`move_rel`, :meth:`move_home`, :class:`..utility_classes.MoveCommand`
324
+
325
+ """
326
+ if move_command.move_type == "abs":
327
+ self.move_abs(move_command.value)
328
+ elif move_command.move_type == "rel":
329
+ self.move_rel(move_command.value)
330
+ elif move_command.move_type == "home":
331
+ self.move_home(move_command.value)
332
+
333
+ def move_abs(self, value: Union[DataActuator, numbers.Number], send_to_tcpip=False):
334
+ """Move the connected hardware to the absolute value
335
+
336
+ Returns nothing but the move_done_signal will be send once the action is done
337
+
338
+ Parameters
339
+ ----------
340
+ value: ndarray
341
+ The value the actuator should reach
342
+ send_to_tcpip: bool
343
+ if True, this position is send through the TCP/IP communication canal
344
+ """
345
+ try:
346
+ if isinstance(value, Number):
347
+ value = DataActuator(
348
+ self.title, data=[np.array([value])], units=self.units
349
+ )
350
+ self._send_to_tcpip = send_to_tcpip
351
+ if value != self._current_value:
352
+ if self.ui is not None:
353
+ self.ui.move_done = False
354
+ self._move_done_bool = False
355
+ self._target_value = value
356
+ self.update_status("Moving")
357
+ self.command_hardware.emit(
358
+ ThreadCommand(ControlToHardwareMove.RESET_STOP_MOTION)
359
+ )
360
+ self.command_hardware.emit(
361
+ ThreadCommand(ControlToHardwareMove.MOVE_ABS, attribute=[value])
362
+ )
363
+
364
+ except Exception as e:
365
+ self.logger.exception(str(e))
366
+
367
+ def move_home(self, send_to_tcpip=False):
368
+ """Move the connected actuator to its home value (if any)
369
+
370
+ Parameters
371
+ ----------
372
+ send_to_tcpip: bool
373
+ if True, this position is send through the TCP/IP communication canal
374
+ """
375
+ self._send_to_tcpip = send_to_tcpip
376
+ try:
377
+ if self.ui is not None:
378
+ self.ui.move_done = False
379
+ self._move_done_bool = False
380
+ self.update_status("Moving")
381
+ self.command_hardware.emit(
382
+ ThreadCommand(ControlToHardwareMove.RESET_STOP_MOTION)
383
+ )
384
+ self.command_hardware.emit(ThreadCommand(ControlToHardwareMove.MOVE_HOME))
385
+
386
+ except Exception as e:
387
+ self.logger.exception(str(e))
388
+
389
+ def move_rel(
390
+ self, rel_value: Union[DataActuator, numbers.Number], send_to_tcpip=False
391
+ ):
392
+ """Move the connected hardware to the relative value
393
+
394
+ Returns nothing but the move_done_signal will be send once the action is done
395
+
396
+ Parameters
397
+ ----------
398
+ value: float
399
+ The relative value the actuator should reach
400
+ send_to_tcpip: bool
401
+ if True, this position is send through the TCP/IP communication canal
402
+ """
403
+
404
+ try:
405
+ if isinstance(rel_value, Number):
406
+ rel_value = DataActuator(
407
+ self.title, data=[np.array([rel_value])], units=self.units
408
+ )
409
+ self._send_to_tcpip = send_to_tcpip
410
+ if self.ui is not None:
411
+ self.ui.move_done = False
412
+ self._move_done_bool = False
413
+ self._target_value = self._current_value + rel_value
414
+ self.update_status("Moving")
415
+ self.command_hardware.emit(
416
+ ThreadCommand(ControlToHardwareMove.RESET_STOP_MOTION)
417
+ )
418
+ self.command_hardware.emit(
419
+ ThreadCommand(ControlToHardwareMove.MOVE_REL, attribute=[rel_value])
420
+ )
421
+
422
+ except Exception as e:
423
+ self.logger.exception(str(e))
424
+
425
+ def move_rel_p(self):
426
+ self.move_rel(self._relative_value)
427
+
428
+ def move_rel_m(self):
429
+ self.move_rel(-self._relative_value)
430
+
431
+ def quit_fun(self):
432
+ """Programmatic quitting of the current instance of DAQ_Move
433
+
434
+ Des-init the actuator then close the UI parent widget
435
+ """
436
+ # insert anything that needs to be closed before leaving
437
+
438
+ if self._initialized_state:
439
+ self.init_hardware(False)
440
+ self.quit_signal.emit()
441
+ if self.ui is not None:
442
+ self.ui.close()
443
+ # self.parent.close()
444
+
445
+ def init_hardware(self, do_init=True):
446
+ """Init or desinit the selected instrument plugin class"""
447
+ if not do_init:
448
+ try:
449
+ self.command_hardware.emit(ThreadCommand(ControlToHardwareMove.CLOSE))
450
+ if self.ui is not None:
451
+ self.ui.actuator_init = False
452
+ except Exception as e:
453
+ self.logger.exception(str(e))
454
+ else:
455
+ try:
456
+ hardware = DAQ_Move_Hardware(
457
+ self._actuator_type, self._current_value, self._title
458
+ )
459
+ self._hardware_thread = QThread()
460
+ hardware.moveToThread(self._hardware_thread)
461
+
462
+ self.command_hardware[ThreadCommand].connect(hardware.queue_command)
463
+ hardware.status_sig[ThreadCommand].connect(self.thread_status)
464
+ self._update_settings_signal[edict].connect(hardware.update_settings)
465
+
466
+ self._hardware_thread.hardware = hardware
467
+ self._hardware_thread.start()
468
+ self.command_hardware.emit(
469
+ ThreadCommand(
470
+ ControlToHardwareMove.INI_STAGE,
471
+ attribute=[
472
+ self.settings.child("move_settings").saveState(),
473
+ self.controller,
474
+ ],
475
+ )
476
+ )
477
+ except Exception as e:
478
+ self.logger.exception(str(e))
479
+
480
+ @property
481
+ def initialized_state(self):
482
+ """bool: status of the actuator's initialization (init or not)"""
483
+ return self._initialized_state
484
+
485
+ @property
486
+ def move_done_bool(self):
487
+ """bool: status of the actuator's status (done or not)"""
488
+ return self._move_done_bool
489
+
490
+ def value_changed(self, param: Parameter):
491
+ """Apply changes of value in the settings"""
492
+ super().value_changed(param=param)
493
+ path = self.settings.childPath(param)
494
+
495
+ if param.name() == "refresh_timeout":
496
+ self._refresh_timer.setInterval(param.value())
497
+
498
+ elif param.name() == 'continuous_saving_opt':
499
+ self.settings.child('saver_settings').setOpts(visible=param.value())
500
+
501
+ elif param.name() in putils.iter_children(self.settings.child('saver_settings'), []):
502
+ if param.name() == 'do_save':
503
+ self.setup_continuous_saving(param.value())
504
+ self.h5saver.settings.child(*path[1:]).setValue(param.value())
505
+
506
+ self._update_settings(param=param)
507
+
508
+ def setup_continuous_saving(self, init: bool = True):
509
+ """Configure the objects dealing with the continuous saving mode"""
510
+ if init:
511
+ self.module_and_data_saver = module_saving.ActuatorTimeSaver(self)
512
+ self.module_and_data_saver.h5saver = self.h5saver
513
+ self.h5saver.settings.child('do_save').sigValueChanged.connect(self._init_continuous_save)
514
+ else:
515
+ self.h5saver.close_file()
516
+
517
+ def _init_continuous_save(self):
518
+ """ Initialize the continuous saving H5Saver object
519
+
520
+ Update the module_and_data_saver attribute as :class:`DetectorTimeSaver` object
521
+ """
522
+ if self.settings.child('saver_settings', 'do_save').value():
523
+
524
+ self.settings.child('saver_settings', 'base_name').setValue('Data')
525
+ self.settings.child('saver_settings', 'N_saved').show()
526
+ self.settings.child('saver_settings', 'N_saved').setValue(0)
527
+ self.h5saver.init_file(update_h5=True)
528
+ else:
529
+ self.settings.child('saver_settings', 'N_saved').hide()
530
+
531
+ def param_deleted(self, param):
532
+ """Apply deletion of settings"""
533
+ if param.name() not in putils.iter_children(
534
+ self.settings.child("main_settings"), []
535
+ ):
536
+ self._update_settings_signal.emit(
537
+ edict(path=["move_settings"], param=param, change="parent")
538
+ )
539
+
540
+ def child_added(self, param, data):
541
+ """Apply addition of settings"""
542
+ path = self.settings.childPath(param)
543
+ if "main_settings" not in path:
544
+ self._update_settings_signal.emit(
545
+ edict(path=path, param=data[0].saveState(), change="childAdded")
546
+ )
547
+
548
+ def raise_timeout(self):
549
+ """Update status with "Timeout occurred" statement and change the timeout flag."""
550
+ self.update_status("Timeout occurred")
551
+ self.wait_position_flag = False
552
+
553
+ @Slot(ThreadCommand)
554
+ def thread_status(
555
+ self, status: ThreadCommand
556
+ ): # general function to get datas/infos from all threads back to the main
557
+ """Get back info (using the ThreadCommand object) from the hardware
558
+
559
+ And re-emit this ThreadCommand using the custom_sig signal if it should be used in a higher level module
560
+
561
+ Commands valid for all control modules are defined in the parent class, here are described only the specific
562
+ ones
563
+
564
+ Parameters
565
+ ----------
566
+ status: ThreadCommand
567
+ Possible values are:
568
+
569
+ * **ini_stage**: obtains info from the initialization
570
+ * **get_actuator_value**: update the UI current value
571
+ * **move_done**: update the UI current value and emits the move_done signal
572
+ * **outofbounds**: emits the bounds_signal signal with a True argument
573
+ * **set_allowed_values**: used to change the behaviour of the spinbox controlling absolute values (see
574
+ :meth:`daq_move_ui.set_abs_spinbox_properties`
575
+ * stop: stop the motion
576
+ """
577
+
578
+ super().thread_status(status, "move")
579
+
580
+ if status.command == ThreadStatusMove.INI_STAGE:
581
+ self.update_status(
582
+ f"Stage initialized: {status.attribute['initialized']} "
583
+ f"info: {status.attribute['info']}"
584
+ )
585
+ if status.attribute["initialized"]:
586
+ self.controller = status.attribute["controller"]
587
+ if self.ui is not None:
588
+ self.ui.actuator_init = True
589
+ self._initialized_state = True
590
+ else:
591
+ self._initialized_state = False
592
+ if self._initialized_state:
593
+ self.get_actuator_value()
594
+ self.init_signal.emit(self._initialized_state)
595
+
596
+ elif (
597
+ status.command == ThreadStatusMove.GET_ACTUATOR_VALUE
598
+ or status.command == "check_position"
599
+ ):
600
+ data_act = self._check_data_type(status.attribute)
601
+ if self.ui is not None:
602
+ self.ui.display_value(data_act)
603
+ if self.ui.has_action("show_graph") and self.ui.is_action_checked(
604
+ "show_graph"
605
+ ):
606
+ self.ui.show_data(DataToExport(name=self.title, data=[data_act]))
607
+
608
+ self._current_value = data_act
609
+ if self.settings['saver_settings', 'do_save']:
610
+ self.append_data()
611
+
612
+ self.current_value_signal.emit(self._current_value)
613
+ if (
614
+ self.settings["main_settings", "tcpip", "tcp_connected"]
615
+ and self._send_to_tcpip
616
+ ):
617
+ self._command_tcpip.emit(ThreadCommand("position_is", data_act))
618
+ if (
619
+ self.settings["main_settings", "leco", "leco_connected"]
620
+ and self._send_to_tcpip
621
+ ):
622
+ self._command_tcpip.emit(
623
+ ThreadCommand(LECOMoveCommands.POSITION, data_act)
624
+ )
625
+
626
+ elif status.command == ThreadStatusMove.MOVE_DONE:
627
+ data_act = self._check_data_type(status.attribute)
628
+ if self.ui is not None:
629
+ self.ui.display_value(data_act)
630
+ self.ui.move_done = True
631
+ self._current_value = data_act
632
+ self._move_done_bool = True
633
+ self.move_done_signal.emit(data_act)
634
+ if (
635
+ self.settings.child("main_settings", "tcpip", "tcp_connected").value()
636
+ and self._send_to_tcpip
637
+ ):
638
+ self._command_tcpip.emit(ThreadCommand("move_done", data_act))
639
+ if (
640
+ self.settings.child("main_settings", "leco", "leco_connected").value()
641
+ and self._send_to_tcpip
642
+ ):
643
+ self._command_tcpip.emit(
644
+ ThreadCommand(LECOMoveCommands.MOVE_DONE, data_act)
645
+ )
646
+
647
+ elif status.command == ThreadStatusMove.OUT_OF_BOUNDS:
648
+ logger.warning(f"The Actuator {self.title} has reached its defined bounds")
649
+ self.bounds_signal.emit(True)
650
+
651
+ elif status.command == ThreadStatusMove.SET_ALLOWED_VALUES:
652
+ if self.ui is not None:
653
+ self.ui.set_abs_spinbox_properties(**status.attribute)
654
+
655
+ elif status.command == ThreadStatusMove.STOP:
656
+ self.stop_motion()
657
+
658
+ elif status.command == ThreadStatusMove.UNITS:
659
+ self.units = status.attribute
660
+
661
+ def _check_data_type(
662
+ self, data_act: Union[list, np.ndarray, Number, DataActuator]
663
+ ) -> DataActuator:
664
+ """Make sure the data is a DataActuator
665
+
666
+ Mostly to make sure DAQ_Move is backcompatible with old style plugins
667
+ """
668
+ if isinstance(data_act, list): # backcompatibility
669
+ if isinstance(data_act[0], Number):
670
+ data_act = DataActuator(
671
+ data=[np.atleast_1d(val) for val in data_act], units=self.units
672
+ )
673
+ elif isinstance(data_act[0], np.ndarray):
674
+ data_act = DataActuator(data=data_act, units=self.units)
675
+ elif isinstance(data_act[0], DataActuator):
676
+ data_act = data_act[0]
677
+ else:
678
+ raise TypeError("Unknown data type")
679
+ elif isinstance(data_act, np.ndarray): # backcompatibility
680
+ data_act = DataActuator(data=[data_act], units=self.units)
681
+ data_act.name = (
682
+ self.title
683
+ ) # for the DataActuator name to be the title of the DAQ_Move
684
+ if (
685
+ not Unit(self.units).is_compatible_with(Unit(data_act.units))
686
+ and data_act.units == ""
687
+ ): # this happens if the units have not been specified in
688
+ # the plugin
689
+ data_act.force_units(self.units)
690
+ return data_act
691
+
692
+ def get_actuator_value(self):
693
+ """Get the current actuator value via the "get_actuator_value" command send to the hardware
694
+
695
+ Returns nothing but the `move_done_signal` will be send once the action is done
696
+ """
697
+ try:
698
+ self.command_hardware.emit(
699
+ ThreadCommand(ControlToHardwareMove.GET_ACTUATOR_VALUE)
700
+ )
701
+
702
+ except Exception as e:
703
+ self.logger.exception(str(e))
704
+
705
+ def grab(self):
706
+ if self.ui is not None:
707
+ self.manage_ui_actions("refresh_value", "setChecked", False)
708
+ self.get_continuous_actuator_value(False)
709
+
710
+ def stop_grab(self):
711
+ """Stop value polling. Mandatory
712
+
713
+ First uncheck the ui action if ui is not None, then stop the polling
714
+ """
715
+ if self.ui is not None:
716
+ self.manage_ui_actions("refresh_value", "setChecked", False)
717
+ self.get_continuous_actuator_value(False)
718
+
719
+ def get_continuous_actuator_value(self, get_value=True):
720
+ """Start the continuous getting of the actuator's value
721
+
722
+ Parameters
723
+ ----------
724
+ get_value: bool
725
+ if True start the timer to periodically fetch the actuator's value, else stop it
726
+
727
+ Notes
728
+ -----
729
+ The current timer period is set by the refresh value *'refresh_timeout'* in the actuator main settings.
730
+ """
731
+ if get_value:
732
+ self._refresh_timer.setInterval(
733
+ self.settings["main_settings", "refresh_timeout"]
734
+ )
735
+ self._refresh_timer.start()
736
+ else:
737
+ self._refresh_timer.stop()
738
+
739
+ @property
740
+ def actuator(self):
741
+ """str: the selected actuator's type
742
+
743
+ Returns
744
+ -------
745
+
746
+ """
747
+ return self._actuator_type
748
+
749
+ @actuator.setter
750
+ def actuator(self, act_type):
751
+ if act_type in ACTUATOR_TYPES:
752
+ self._actuator_type = act_type
753
+ self.update_plugin_config()
754
+ if self.ui is not None:
755
+ self.ui.actuator = act_type
756
+ self.update_settings()
757
+ else:
758
+ raise ActuatorError(
759
+ f"{act_type} is an invalid actuator, should be within {ACTUATOR_TYPES}"
760
+ )
761
+
762
+ @property
763
+ def actuators(self) -> List[str]:
764
+ """Get the list of possible actuators"""
765
+ return ACTUATOR_TYPES
766
+
767
+ def update_plugin_config(self):
768
+ parent_module = utils.find_dict_in_list_from_key_val(
769
+ DAQ_Move_Actuators, "name", self.actuator
770
+ )
771
+ mod = import_module(parent_module["module"].__package__.split(".")[0])
772
+ if hasattr(mod, "config"):
773
+ self.plugin_config = mod.config
774
+
775
+ @property
776
+ def units(self):
777
+ """Get/Set the units for the controller"""
778
+ return self.settings["move_settings", "units"]
779
+
780
+ @units.setter
781
+ def units(self, unit: str):
782
+ self.settings.child("move_settings", "units").setValue(unit)
783
+ if self.ui is not None and config("actuator", "display_units"):
784
+ unit = self.get_unit_to_display(unit)
785
+ self.ui.set_unit_as_suffix(unit)
786
+ self.ui.set_unit_prefix(
787
+ config("actuator", "siprefix")
788
+ and (unit != "" or config("actuator", "siprefix_even_without_units"))
789
+ )
790
+
791
+ @property
792
+ def axis_names(self) -> Union[List, Dict]:
793
+ """ Get the names of all possible axis"""
794
+ return self.settings.child('move_settings', 'multiaxes', 'axis').opts['limits']
795
+
796
+ @property
797
+ def axis_name(self) -> str:
798
+ """ Get/Set the current axis"""
799
+ limits = self.settings.child('move_settings', 'multiaxes', 'axis').opts['limits']
800
+ if isinstance(limits, list):
801
+ return self.settings['move_settings', 'multiaxes', 'axis']
802
+ elif isinstance(limits, dict):
803
+ return find_keys_from_val(limits,
804
+ val=self.settings['move_settings', 'multiaxes', 'axis'])[0]
805
+
806
+ @axis_name.setter
807
+ def axis_name(self, name: str):
808
+ """ Get/Set the current axis"""
809
+ limits = self.settings.child('move_settings', 'multiaxes', 'axis').opts['limits']
810
+ if name in limits:
811
+ if isinstance(limits, list):
812
+ self.settings.child('move_settings', 'multiaxes', 'axis').setValue(name)
813
+ elif isinstance(limits, dict):
814
+ self.settings.child('move_settings', 'multiaxes', 'axis').setValue(limits[name])
815
+
816
+ @property
817
+ def axis_names(self) -> Union[List, Dict]:
818
+ """ Get the names of all possible axis"""
819
+ return self.settings.child('move_settings', 'multiaxes', 'axis').opts['limits']
820
+
821
+ @property
822
+ def axis_name(self) -> str:
823
+ """ Get/Set the current axis"""
824
+ limits = self.settings.child('move_settings', 'multiaxes', 'axis').opts['limits']
825
+ if isinstance(limits, list):
826
+ return self.settings['move_settings', 'multiaxes', 'axis']
827
+ elif isinstance(limits, dict):
828
+ return find_keys_from_val(limits,
829
+ val=self.settings['move_settings', 'multiaxes', 'axis'])[0]
830
+
831
+ @axis_name.setter
832
+ def axis_name(self, name: str):
833
+ """ Get/Set the current axis"""
834
+ limits = self.settings.child('move_settings', 'multiaxes', 'axis').opts['limits']
835
+ if name in limits:
836
+ if isinstance(limits, list):
837
+ self.settings.child('move_settings', 'multiaxes', 'axis').setValue(name)
838
+ elif isinstance(limits, dict):
839
+ self.settings.child('move_settings', 'multiaxes', 'axis').setValue(limits[name])
840
+
841
+ @staticmethod
842
+ def get_unit_to_display(unit: str) -> str:
843
+ """Get the unit to be displayed in the UI
844
+
845
+ If the controller units are in mm the displayed unit will be m
846
+ because m is the base unit, then the user could ask for mm, km, µm...
847
+ only issue is when the usual displayed unit is not the base one, then add cases below
848
+
849
+ Parameters
850
+ ----------
851
+ unit: str
852
+
853
+ Returns
854
+ -------
855
+ str: the unit to be displayed on the ui
856
+ """
857
+ if ("°" in unit or "degree" in unit) and not "°C" in unit:
858
+ # special cas as pint base unit for angles are radians
859
+ return "°"
860
+ elif "°C" in unit:
861
+ return "°C"
862
+ else:
863
+ for key in config("actuator", "allowed_units"):
864
+ if key in unit:
865
+ return config("actuator", "allowed_units", key)
866
+ return str(Q_(1, unit).to_base_units().units)
867
+
868
+ def update_settings(self):
869
+ self.settings.child("main_settings", "move_type").setValue(self._actuator_type)
870
+ self.settings.child("main_settings", "module_name").setValue(self._title)
871
+ try:
872
+ for child in self.settings.child("move_settings").children():
873
+ child.remove()
874
+ parent_module = utils.find_dict_in_list_from_key_val(
875
+ DAQ_Move_Actuators, "name", self._actuator_type
876
+ )
877
+ class_ = getattr(
878
+ getattr(parent_module["module"], "daq_move_" + self._actuator_type),
879
+ "DAQ_Move_" + self._actuator_type,
880
+ )
881
+ params = getattr(class_, "params")
882
+ move_params = Parameter.create(
883
+ name="move_settings", type="group", children=params
884
+ )
885
+
886
+ self.settings.child("move_settings").addChildren(move_params.children())
887
+
888
+ except Exception as e:
889
+ self.logger.exception(str(e))
890
+
891
+ def connect_tcp_ip(self):
892
+ super().connect_tcp_ip(
893
+ params_state=self.settings.child("move_settings"), client_type="ACTUATOR"
894
+ )
895
+
896
+ def connect_leco(self, connect: bool) -> None:
897
+ super().connect_leco(connect)
898
+
899
+ @Slot(ThreadCommand)
900
+ def process_tcpip_cmds(self, status: ThreadCommand) -> None:
901
+ if super().process_tcpip_cmds(status=status) is None:
902
+ return
903
+ if LECOMoveCommands.MOVE_ABS == status.command:
904
+ self.move_abs(status.attribute, send_to_tcpip=True)
905
+
906
+ elif LECOMoveCommands.MOVE_REL == status.command:
907
+ self.move_rel(status.attribute, send_to_tcpip=True)
908
+
909
+ elif LECOMoveCommands.MOVE_HOME == status.command:
910
+ self.move_home(send_to_tcpip=True)
911
+
912
+ elif "check_position" in status.command:
913
+ deprecation_msg(
914
+ "check_position is deprecated, you should use get_actuator_value"
915
+ )
916
+ self._send_to_tcpip = True
917
+ self.get_actuator_value()
918
+
919
+ elif LECOMoveCommands.GET_ACTUATOR_VALUE in status.command:
920
+ self._send_to_tcpip = True
921
+ self.get_actuator_value()
922
+
923
+ elif status.command == LECOMoveCommands.STOP:
924
+ self.stop_motion()
925
+
926
+
927
+ class DAQ_Move_Hardware(QObject):
928
+ """
929
+ ================== ========================
930
+ **Attributes** **Type**
931
+ *status_sig* instance of Signal
932
+ *hardware* ???
933
+ *actuator_type* string
934
+ *current_position* float
935
+ *target_value* float
936
+ *hardware_adress* string
937
+ *axis_address* string
938
+ *motion_stoped* boolean
939
+ ================== ========================
940
+ """
941
+
942
+ status_sig = Signal(ThreadCommand)
943
+
944
+ def __init__(self, actuator_type, position: DataActuator, title="actuator"):
945
+ super().__init__()
946
+ self.logger = set_logger(f"{logger.name}.{title}.actuator")
947
+ self._title = title
948
+ self.hardware: Optional[DAQ_Move_base] = None
949
+ self.actuator_type = actuator_type
950
+ self.hardware_adress = None
951
+ self.axis_address = None
952
+ self.motion_stoped = False
953
+
954
+ @property
955
+ def title(self):
956
+ return self._title
957
+
958
+ def close(self):
959
+ """
960
+ Uninitialize the stage closing the hardware.
961
+
962
+ """
963
+ if self.hardware is not None and self.hardware.controller is not None:
964
+ self.hardware.close()
965
+
966
+ return "Stage uninitialized"
967
+
968
+ def get_actuator_value(self):
969
+ """Get the current position checking the hardware value."""
970
+ if self.hardware is not None:
971
+ pos = self.hardware.get_actuator_value()
972
+ if self.hardware.data_actuator_type == DataActuatorType.float:
973
+ pos = DataActuator(self._title, data=pos, units=self.hardware.axis_unit)
974
+ return pos
975
+
976
+ def check_position(self):
977
+ """Get the current position checking the hardware position (deprecated)"""
978
+ deprecation_msg("check_position is deprecated, use get_actuator_value")
979
+ pos = self.hardware.get_actuator_value()
980
+ return pos
981
+
982
+ def ini_stage(self, params_state=None, controller: Optional[HardwareController] = None) -> edict:
983
+ """
984
+ Init a stage updating the hardware and sending an hardware move_done signal.
985
+
986
+ =============== =================================== ==========================================================================================================================
987
+ **Parameters** **Type** **Description**
988
+
989
+ *params_state* ordered dictionary list The parameter state of the hardware class composed by a list representing the tree to keep a temporary save of the tree
990
+
991
+ *controller* one or many instance of DAQ_Move The controller id of the hardware
992
+
993
+ *stage* instance of DAQ_Move Defining axes and motors
994
+ =============== =================================== ==========================================================================================================================
995
+
996
+ See Also
997
+ --------
998
+ DAQ_utils.ThreadCommand, DAQ_Move
999
+ """
1000
+
1001
+ status = edict(initialized=False, info="")
1002
+ try:
1003
+ parent_module = utils.find_dict_in_list_from_key_val(
1004
+ DAQ_Move_Actuators, "name", self.actuator_type
1005
+ )
1006
+ class_ = getattr(
1007
+ getattr(parent_module["module"], "daq_move_" + self.actuator_type),
1008
+ "DAQ_Move_" + self.actuator_type,
1009
+ )
1010
+ self.hardware = class_(self, params_state)
1011
+ assert self.hardware is not None
1012
+ try:
1013
+ infos = self.hardware.ini_stage(
1014
+ controller
1015
+ ) # return edict(info="", controller=, stage=)
1016
+ except Exception as e:
1017
+ logger.exception("Hardware couldn't be initialized", exc_info=e)
1018
+ infos = str(e), False
1019
+
1020
+ if isinstance(infos, edict): # following old plugin templating
1021
+ status.update(infos)
1022
+ deprecation_msg(
1023
+ "Returns from init_stage should now be a string and a boolean,"
1024
+ " see pymodaq_plugins_template",
1025
+ stacklevel=3,
1026
+ )
1027
+ else:
1028
+ status.info = infos[0]
1029
+ status.initialized = infos[1]
1030
+ status.controller = self.hardware.controller
1031
+ self.hardware.move_done_signal.connect(self.move_done)
1032
+ if status.initialized:
1033
+ self.status_sig.emit(
1034
+ ThreadCommand(
1035
+ ThreadStatusMove.GET_ACTUATOR_VALUE, self.get_actuator_value()
1036
+ )
1037
+ )
1038
+
1039
+ return status
1040
+ except Exception as e:
1041
+ self.logger.exception(str(e))
1042
+ return status
1043
+
1044
+ def move_abs(self, position: DataActuator, polling: bool = True) -> None:
1045
+ """
1046
+
1047
+ """
1048
+ assert self.hardware is not None
1049
+ position = check_units(position, self.hardware.axis_unit)
1050
+ self.hardware.move_is_done = False
1051
+ self.hardware.ispolling = polling
1052
+ if self.hardware.data_actuator_type == self.hardware.data_actuator_type.float:
1053
+ self.hardware.move_abs(
1054
+ position.units_as(self.hardware.axis_unit).value()
1055
+ ) # convert to plugin controller current axis units
1056
+ else:
1057
+ position.units = (
1058
+ self.hardware.axis_unit
1059
+ ) # convert to plugin controller current axis units
1060
+ self.hardware.move_abs(position)
1061
+ self.hardware.poll_moving()
1062
+
1063
+ def move_rel(self, rel_position: DataActuator, polling: bool = True) -> None:
1064
+ """
1065
+
1066
+ """
1067
+ assert self.hardware is not None
1068
+ rel_position = check_units(rel_position, self.hardware.axis_unit)
1069
+ self.hardware.move_is_done = False
1070
+ self.hardware.ispolling = polling
1071
+
1072
+ if self.hardware.data_actuator_type.name == 'float':
1073
+ self.hardware.move_rel(rel_position.units_as(self.hardware.axis_unit).value())
1074
+ else:
1075
+ rel_position.units = (
1076
+ self.hardware.axis_unit
1077
+ ) # convert to plugin current axis units
1078
+ self.hardware.move_rel(rel_position)
1079
+
1080
+ self.hardware.poll_moving()
1081
+
1082
+ @Slot(float)
1083
+ def Move_Stoped(self, pos):
1084
+ """
1085
+ Send a "move_done" Thread Command with the given position as an attribute.
1086
+
1087
+ See Also
1088
+ --------
1089
+ DAQ_utils.ThreadCommand
1090
+ """
1091
+ self.status_sig.emit(ThreadCommand(ThreadStatusMove.MOVE_DONE, pos))
1092
+
1093
+ def move_home(self):
1094
+ """
1095
+ Make the hardware move to the init position.
1096
+
1097
+ """
1098
+ assert self.hardware is not None
1099
+ self.hardware.move_is_done = False
1100
+ self.hardware.move_home()
1101
+
1102
+ @Slot(DataActuator)
1103
+ def move_done(self, pos: DataActuator):
1104
+ """Send the move_done signal back to the main class"""
1105
+ self._current_value = pos
1106
+ self.status_sig.emit(
1107
+ ThreadCommand(command=ThreadStatusMove.MOVE_DONE, attribute=pos)
1108
+ )
1109
+
1110
+ @Slot(ThreadCommand)
1111
+ def queue_command(self, command: ThreadCommand):
1112
+ """Interpret command send by DAQ_Move class
1113
+ * **ini_stage** command, init a stage from command attribute.
1114
+ * **close** command, unitinalise the stage closing hardware and emitting the corresponding status signal
1115
+ * **move_abs** command, call the move_Abs method with position from command attribute
1116
+ * **move_rel** command, call the move_Rel method with the relative position from the command attribute.
1117
+ * **move_home** command, call the move_home method
1118
+ * **get_actuator_value** command, get the current position from the check_position method
1119
+ * **Stop_motion** command, stop any motion via the stop_Motion method
1120
+ * **reset_stop_motion** command, set the motion_stopped attribute to false
1121
+
1122
+ Parameters
1123
+ ----------
1124
+ command: ThreadCommand
1125
+ Possible commands are:
1126
+ * **ini_stage** command, init a stage from command attribute.
1127
+ * **close** command, unitinalise the stage closing hardware and emitting the corresponding status signal
1128
+ * **move_abs** command, call the move_abs method with position from command attribute
1129
+ * **move_rel** command, call the move_rel method with the relative position from the command attribute.
1130
+ * **move_home** command, call the move_home method
1131
+ * **get_actuator_value** command, get the current position from the check_position method
1132
+ * **stop_motion** command, stop any motion via the stop_Motion method
1133
+ * **reset_stop_motion** command, set the motion_stopped attribute to false
1134
+ """
1135
+ try:
1136
+ logger.debug(f"Threadcommand {command.command} sent to {self.title}")
1137
+ if command.command == ControlToHardwareMove.INI_STAGE:
1138
+ status: edict = self.ini_stage(*command.attribute)
1139
+ self.status_sig.emit(
1140
+ ThreadCommand(command=ThreadStatusMove.INI_STAGE, attribute=status)
1141
+ )
1142
+
1143
+ elif command.command == ControlToHardwareMove.CLOSE:
1144
+ status = self.close()
1145
+ self.status_sig.emit(
1146
+ ThreadCommand(command=ThreadStatus.CLOSE, attribute=[status])
1147
+ )
1148
+
1149
+ elif command.command == ControlToHardwareMove.MOVE_ABS:
1150
+ self.move_abs(*command.attribute)
1151
+
1152
+ elif command.command == ControlToHardwareMove.MOVE_REL:
1153
+ self.move_rel(*command.attribute)
1154
+
1155
+ elif command.command == ControlToHardwareMove.MOVE_HOME:
1156
+ self.move_home()
1157
+
1158
+ elif command.command == ControlToHardwareMove.GET_ACTUATOR_VALUE:
1159
+ pos = self.get_actuator_value()
1160
+ self.status_sig.emit(
1161
+ ThreadCommand(ThreadStatusMove.GET_ACTUATOR_VALUE, pos)
1162
+ )
1163
+
1164
+ elif command.command == ControlToHardwareMove.STOP_MOTION:
1165
+ self.stop_motion()
1166
+
1167
+ elif command.command == ControlToHardwareMove.RESET_STOP_MOTION:
1168
+ self.motion_stoped = False
1169
+
1170
+ else: # custom commands for particular plugins (see spectrometer module 'get_spectro_wl' for instance)
1171
+ if hasattr(self.hardware, command.command):
1172
+ cmd = getattr(self.hardware, command.command)
1173
+ if isinstance(command.attribute, list):
1174
+ cmd(*command.attribute)
1175
+ elif isinstance(command.attribute, dict):
1176
+ cmd(**command.attribute)
1177
+ except Exception as e:
1178
+ self.logger.exception(str(e))
1179
+
1180
+ def stop_motion(self):
1181
+ """
1182
+ stop hardware motion with motion_stopped attribute updtaed to True and a status signal sended with an "update_status" Thread Command
1183
+
1184
+ See Also
1185
+ --------
1186
+ DAQ_utils.ThreadCommand, stop_motion
1187
+ """
1188
+ self.status_sig.emit(
1189
+ ThreadCommand(command="Update_Status", attribute=["Motion stoping", "log"])
1190
+ )
1191
+ self.motion_stoped = True
1192
+ assert self.hardware is not None
1193
+ if self.hardware is not None and self.hardware.controller is not None:
1194
+ self.hardware.stop_motion()
1195
+ self.hardware.poll_timer.stop()
1196
+
1197
+ @Slot(edict)
1198
+ def update_settings(self, settings_parameter_dict):
1199
+ """
1200
+ Update settings of hardware with dictionary parameters in case of "Move_Settings" path, else update attribute with dictionnary parameters.
1201
+
1202
+ ========================= =========== ======================================================
1203
+ **Parameters** **Type** **Description**
1204
+
1205
+ *settings_parameter_dict* dictionary Dictionary containing the path and linked parameter
1206
+ ========================= =========== ======================================================
1207
+
1208
+ See Also
1209
+ --------
1210
+ update_settings
1211
+ """
1212
+ # settings_parameter_dict = edict(path=path,param=param)
1213
+ path = settings_parameter_dict["path"]
1214
+ param = settings_parameter_dict["param"]
1215
+ if path[0] == "main_settings":
1216
+ if hasattr(self, path[-1]):
1217
+ setattr(self, path[-1], param.value())
1218
+
1219
+ elif path[0] == "move_settings":
1220
+ if self.hardware is not None:
1221
+ self.hardware.update_settings(settings_parameter_dict)
1222
+
1223
+
1224
+ def main(init_qt=True):
1225
+ if init_qt: # used for the test suite
1226
+ app = mkQApp("PyMoDAQ Move")
1227
+
1228
+ widget = QtWidgets.QWidget()
1229
+ prog = DAQ_Move(widget, title="test")
1230
+ widget.show()
1231
+
1232
+ if init_qt:
1233
+ sys.exit(app.exec_())
1234
+ return prog, widget
1235
+
1236
+
1237
+ if __name__ == "__main__":
1238
+ main()