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.
- pymodaq/__init__.py +98 -0
- pymodaq/control_modules/__init__.py +1 -0
- pymodaq/control_modules/daq_move.py +1238 -0
- pymodaq/control_modules/daq_move_ui/__init__.py +0 -0
- pymodaq/control_modules/daq_move_ui/factory.py +48 -0
- pymodaq/control_modules/daq_move_ui/ui_base.py +359 -0
- pymodaq/control_modules/daq_move_ui/uis/__init__.py +0 -0
- pymodaq/control_modules/daq_move_ui/uis/binary.py +139 -0
- pymodaq/control_modules/daq_move_ui/uis/original.py +120 -0
- pymodaq/control_modules/daq_move_ui/uis/relative.py +124 -0
- pymodaq/control_modules/daq_move_ui/uis/simple.py +126 -0
- pymodaq/control_modules/daq_viewer.py +1517 -0
- pymodaq/control_modules/daq_viewer_ui.py +407 -0
- pymodaq/control_modules/mocks.py +57 -0
- pymodaq/control_modules/move_utility_classes.py +1141 -0
- pymodaq/control_modules/thread_commands.py +137 -0
- pymodaq/control_modules/ui_utils.py +72 -0
- pymodaq/control_modules/utils.py +591 -0
- pymodaq/control_modules/viewer_utility_classes.py +670 -0
- pymodaq/daq_utils/__init__.py +0 -0
- pymodaq/daq_utils/daq_utils.py +6 -0
- pymodaq/dashboard.py +2396 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.aliases +3 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvlps +3 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.lvproj +32 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Client.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_1Dgaussian.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_Server_2Dgaussian.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_cmd.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_float.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_read_int.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_data.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_int.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_scalar.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/DAQ_TCP_send_string.vi +0 -0
- pymodaq/examples/Labview_TCP_Client/client_state.ctl +0 -0
- pymodaq/examples/Labview_TCP_Client/cmd_types.ctl +0 -0
- pymodaq/examples/__init__.py +0 -0
- pymodaq/examples/function_plotter.py +160 -0
- pymodaq/examples/nonlinearscanner.py +126 -0
- pymodaq/examples/qt_less_standalone_module.py +165 -0
- pymodaq/examples/tcp_client.py +97 -0
- pymodaq/extensions/__init__.py +25 -0
- pymodaq/extensions/adaptive/__init__.py +2 -0
- pymodaq/extensions/adaptive/adaptive_optimization.py +179 -0
- pymodaq/extensions/adaptive/loss_function/_1d_loss_functions.py +73 -0
- pymodaq/extensions/adaptive/loss_function/_2d_loss_functions.py +73 -0
- pymodaq/extensions/adaptive/loss_function/__init__.py +3 -0
- pymodaq/extensions/adaptive/loss_function/loss_factory.py +110 -0
- pymodaq/extensions/adaptive/utils.py +123 -0
- pymodaq/extensions/bayesian/__init__.py +2 -0
- pymodaq/extensions/bayesian/acquisition/__init__.py +2 -0
- pymodaq/extensions/bayesian/acquisition/acquisition_function_factory.py +80 -0
- pymodaq/extensions/bayesian/acquisition/base_acquisition_function.py +105 -0
- pymodaq/extensions/bayesian/bayesian_optimization.py +143 -0
- pymodaq/extensions/bayesian/utils.py +180 -0
- pymodaq/extensions/console.py +73 -0
- pymodaq/extensions/daq_logger/__init__.py +1 -0
- pymodaq/extensions/daq_logger/abstract.py +52 -0
- pymodaq/extensions/daq_logger/daq_logger.py +519 -0
- pymodaq/extensions/daq_logger/db/__init__.py +0 -0
- pymodaq/extensions/daq_logger/db/db_logger.py +300 -0
- pymodaq/extensions/daq_logger/db/db_logger_models.py +100 -0
- pymodaq/extensions/daq_logger/h5logging.py +84 -0
- pymodaq/extensions/daq_scan.py +1218 -0
- pymodaq/extensions/daq_scan_ui.py +241 -0
- pymodaq/extensions/data_mixer/__init__.py +0 -0
- pymodaq/extensions/data_mixer/daq_0Dviewer_DataMixer.py +97 -0
- pymodaq/extensions/data_mixer/data_mixer.py +262 -0
- pymodaq/extensions/data_mixer/model.py +108 -0
- pymodaq/extensions/data_mixer/models/__init__.py +0 -0
- pymodaq/extensions/data_mixer/models/equation_model.py +91 -0
- pymodaq/extensions/data_mixer/models/gaussian_fit_model.py +65 -0
- pymodaq/extensions/data_mixer/parser.py +53 -0
- pymodaq/extensions/data_mixer/utils.py +23 -0
- pymodaq/extensions/h5browser.py +9 -0
- pymodaq/extensions/optimizers_base/__init__.py +0 -0
- pymodaq/extensions/optimizers_base/optimizer.py +1016 -0
- pymodaq/extensions/optimizers_base/thread_commands.py +22 -0
- pymodaq/extensions/optimizers_base/utils.py +427 -0
- pymodaq/extensions/pid/__init__.py +16 -0
- pymodaq/extensions/pid/actuator_controller.py +14 -0
- pymodaq/extensions/pid/daq_move_PID.py +154 -0
- pymodaq/extensions/pid/pid_controller.py +1016 -0
- pymodaq/extensions/pid/utils.py +189 -0
- pymodaq/extensions/utils.py +111 -0
- pymodaq/icon.ico +0 -0
- pymodaq/post_treatment/__init__.py +6 -0
- pymodaq/post_treatment/load_and_plot.py +352 -0
- pymodaq/resources/__init__.py +0 -0
- pymodaq/resources/config_template.toml +57 -0
- pymodaq/resources/preset_default.xml +1 -0
- pymodaq/resources/setup_plugin.py +73 -0
- pymodaq/splash.png +0 -0
- pymodaq/utils/__init__.py +0 -0
- pymodaq/utils/array_manipulation.py +6 -0
- pymodaq/utils/calibration_camera.py +180 -0
- pymodaq/utils/chrono_timer.py +203 -0
- pymodaq/utils/config.py +53 -0
- pymodaq/utils/conftests.py +5 -0
- pymodaq/utils/daq_utils.py +158 -0
- pymodaq/utils/data.py +128 -0
- pymodaq/utils/enums.py +6 -0
- pymodaq/utils/exceptions.py +38 -0
- pymodaq/utils/gui_utils/__init__.py +10 -0
- pymodaq/utils/gui_utils/loader_utils.py +75 -0
- pymodaq/utils/gui_utils/utils.py +18 -0
- pymodaq/utils/gui_utils/widgets/lcd.py +8 -0
- pymodaq/utils/h5modules/__init__.py +2 -0
- pymodaq/utils/h5modules/module_saving.py +526 -0
- pymodaq/utils/leco/__init__.py +25 -0
- pymodaq/utils/leco/daq_move_LECODirector.py +217 -0
- pymodaq/utils/leco/daq_xDviewer_LECODirector.py +163 -0
- pymodaq/utils/leco/director_utils.py +74 -0
- pymodaq/utils/leco/leco_director.py +166 -0
- pymodaq/utils/leco/pymodaq_listener.py +364 -0
- pymodaq/utils/leco/rpc_method_definitions.py +43 -0
- pymodaq/utils/leco/utils.py +74 -0
- pymodaq/utils/logger.py +6 -0
- pymodaq/utils/managers/__init__.py +0 -0
- pymodaq/utils/managers/batchscan_manager.py +346 -0
- pymodaq/utils/managers/modules_manager.py +589 -0
- pymodaq/utils/managers/overshoot_manager.py +242 -0
- pymodaq/utils/managers/preset_manager.py +229 -0
- pymodaq/utils/managers/preset_manager_utils.py +262 -0
- pymodaq/utils/managers/remote_manager.py +484 -0
- pymodaq/utils/math_utils.py +6 -0
- pymodaq/utils/messenger.py +6 -0
- pymodaq/utils/parameter/__init__.py +10 -0
- pymodaq/utils/parameter/utils.py +6 -0
- pymodaq/utils/scanner/__init__.py +5 -0
- pymodaq/utils/scanner/scan_config.py +16 -0
- pymodaq/utils/scanner/scan_factory.py +259 -0
- pymodaq/utils/scanner/scan_selector.py +477 -0
- pymodaq/utils/scanner/scanner.py +324 -0
- pymodaq/utils/scanner/scanners/_1d_scanners.py +174 -0
- pymodaq/utils/scanner/scanners/_2d_scanners.py +299 -0
- pymodaq/utils/scanner/scanners/__init__.py +1 -0
- pymodaq/utils/scanner/scanners/sequential.py +224 -0
- pymodaq/utils/scanner/scanners/tabular.py +319 -0
- pymodaq/utils/scanner/utils.py +110 -0
- pymodaq/utils/svg/__init__.py +6 -0
- pymodaq/utils/svg/svg_renderer.py +20 -0
- pymodaq/utils/svg/svg_view.py +35 -0
- pymodaq/utils/svg/svg_viewer2D.py +50 -0
- pymodaq/utils/tcp_ip/__init__.py +6 -0
- pymodaq/utils/tcp_ip/mysocket.py +12 -0
- pymodaq/utils/tcp_ip/serializer.py +13 -0
- pymodaq/utils/tcp_ip/tcp_server_client.py +772 -0
- pymodaq-5.1.6.dist-info/METADATA +238 -0
- pymodaq-5.1.6.dist-info/RECORD +154 -0
- pymodaq-5.1.6.dist-info/WHEEL +4 -0
- pymodaq-5.1.6.dist-info/entry_points.txt +7 -0
- pymodaq-5.1.6.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,591 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
Created the 03/10/2022
|
|
4
|
+
|
|
5
|
+
@author: Sebastien Weber
|
|
6
|
+
"""
|
|
7
|
+
from random import randint
|
|
8
|
+
from typing import Optional, Type, Union
|
|
9
|
+
from easydict import EasyDict as edict
|
|
10
|
+
|
|
11
|
+
from qtpy.QtCore import Signal, QObject, Qt, Slot, QThread
|
|
12
|
+
|
|
13
|
+
from pymodaq.control_modules.thread_commands import ThreadStatus
|
|
14
|
+
from pymodaq_utils.utils import ThreadCommand, find_dict_in_list_from_key_val
|
|
15
|
+
from pymodaq_utils.config import Config
|
|
16
|
+
from pymodaq_utils.enums import BaseEnum
|
|
17
|
+
from pymodaq_utils.logger import get_base_logger, set_logger, get_module_name
|
|
18
|
+
|
|
19
|
+
from pymodaq_gui.parameter import Parameter, ioxml
|
|
20
|
+
from pymodaq_gui.parameter.utils import ParameterWithPath
|
|
21
|
+
from pymodaq_gui.managers.parameter_manager import ParameterManager
|
|
22
|
+
from pymodaq_gui.plotting.data_viewers import ViewersEnum
|
|
23
|
+
from pymodaq_gui.h5modules.saving import H5Saver
|
|
24
|
+
|
|
25
|
+
from pymodaq.utils.tcp_ip.tcp_server_client import TCPClient
|
|
26
|
+
from pymodaq.utils.exceptions import DetectorError
|
|
27
|
+
from pymodaq.utils.leco.pymodaq_listener import ActorListener, LECOClientCommands, LECOCommands
|
|
28
|
+
|
|
29
|
+
from pymodaq.utils.daq_utils import get_plugins
|
|
30
|
+
from pymodaq.utils.h5modules.module_saving import DetectorSaver, ActuatorSaver
|
|
31
|
+
from pymodaq.utils.config import Config as ControlModulesConfig
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class DAQTypesEnum(BaseEnum):
|
|
35
|
+
"""enum relating a given DAQType and a viewer type
|
|
36
|
+
See Also
|
|
37
|
+
--------
|
|
38
|
+
pymodaq.utils.plotting.data_viewers.viewer.ViewersEnum
|
|
39
|
+
"""
|
|
40
|
+
DAQ0D = 'Viewer0D'
|
|
41
|
+
DAQ1D = 'Viewer1D'
|
|
42
|
+
DAQ2D = 'Viewer2D'
|
|
43
|
+
DAQND = 'ViewerND'
|
|
44
|
+
|
|
45
|
+
def to_data_type(self):
|
|
46
|
+
return ViewersEnum[self.value].value
|
|
47
|
+
|
|
48
|
+
def to_viewer_type(self):
|
|
49
|
+
return self.value
|
|
50
|
+
|
|
51
|
+
def to_daq_type(self):
|
|
52
|
+
return self.name
|
|
53
|
+
|
|
54
|
+
def increase_dim(self, ndim: int):
|
|
55
|
+
dim = self.get_dim()
|
|
56
|
+
if dim != 'N':
|
|
57
|
+
dim_as_int = int(dim) + ndim
|
|
58
|
+
if dim_as_int > 2:
|
|
59
|
+
dim = 'N'
|
|
60
|
+
else:
|
|
61
|
+
dim = str(dim_as_int)
|
|
62
|
+
else:
|
|
63
|
+
dim = 'N'
|
|
64
|
+
return DAQTypesEnum(f'Viewer{dim}D')
|
|
65
|
+
|
|
66
|
+
def get_dim(self):
|
|
67
|
+
return self.value.split('Viewer')[1].split('D')[0]
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
DAQ_TYPES = DAQTypesEnum
|
|
71
|
+
|
|
72
|
+
DET_TYPES = {'DAQ0D': get_plugins('daq_0Dviewer'),
|
|
73
|
+
'DAQ1D': get_plugins('daq_1Dviewer'),
|
|
74
|
+
'DAQ2D': get_plugins('daq_2Dviewer'),
|
|
75
|
+
'DAQND': get_plugins('daq_NDviewer'),
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if len(DET_TYPES['DAQ0D']) == 0:
|
|
79
|
+
raise DetectorError('No installed Detector')
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
config_utils = Config()
|
|
83
|
+
config = ControlModulesConfig()
|
|
84
|
+
logger = set_logger(get_module_name(__file__))
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ViewerError(Exception):
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def get_viewer_plugins(daq_type, det_name):
|
|
92
|
+
parent_module = find_dict_in_list_from_key_val(DET_TYPES[daq_type], 'name', det_name)
|
|
93
|
+
match_name = daq_type.lower()
|
|
94
|
+
match_name = f'{match_name[0:3]}_{match_name[3:].upper()}viewer_'
|
|
95
|
+
obj = getattr(getattr(parent_module['module'], match_name + det_name),
|
|
96
|
+
f'{match_name[0:7].upper()}{match_name[7:]}{det_name}')
|
|
97
|
+
params = getattr(obj, 'params')
|
|
98
|
+
det_params = Parameter.create(name='Det Settings', type='group', children=params)
|
|
99
|
+
return det_params, obj
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
class ControlModule(QObject):
|
|
103
|
+
"""Abstract Base class common to both DAQ_Move and DAQ_Viewer control modules
|
|
104
|
+
|
|
105
|
+
Attributes
|
|
106
|
+
----------
|
|
107
|
+
init_signal : Signal[bool]
|
|
108
|
+
This signal is emitted when the chosen hardware is correctly initialized
|
|
109
|
+
command_hardware : Signal[ThreadCommand]
|
|
110
|
+
This signal is used to communicate with the instrument plugin within a separate thread
|
|
111
|
+
command_tcpip : Signal[ThreadCommand]
|
|
112
|
+
This signal is used to communicate through the TCP/IP Network
|
|
113
|
+
quit_signal : Signal[]
|
|
114
|
+
This signal is emitted when the user requested to stop the module
|
|
115
|
+
"""
|
|
116
|
+
init_signal = Signal(bool)
|
|
117
|
+
command_hardware = Signal(ThreadCommand)
|
|
118
|
+
_command_tcpip = Signal(ThreadCommand)
|
|
119
|
+
quit_signal = Signal()
|
|
120
|
+
_update_settings_signal = Signal(edict)
|
|
121
|
+
status_sig = Signal(str)
|
|
122
|
+
custom_sig = Signal(ThreadCommand)
|
|
123
|
+
ui = None
|
|
124
|
+
|
|
125
|
+
def __init__(self):
|
|
126
|
+
super().__init__()
|
|
127
|
+
self._title = ""
|
|
128
|
+
self.config = config
|
|
129
|
+
# the hardware controller instance set after initialization and to be used by other modules if they share the
|
|
130
|
+
# same controller
|
|
131
|
+
self.controller = None
|
|
132
|
+
self._initialized_state = False
|
|
133
|
+
self._send_to_tcpip = False
|
|
134
|
+
self._tcpclient_thread = None
|
|
135
|
+
self._hardware_thread = None
|
|
136
|
+
|
|
137
|
+
self.plugin_config: Optional[Config] = None
|
|
138
|
+
|
|
139
|
+
self._h5saver: Optional[H5Saver] = None
|
|
140
|
+
self._module_and_data_saver = None
|
|
141
|
+
|
|
142
|
+
def __repr__(self):
|
|
143
|
+
return f'{self.__class__.__name__}: {self.title}'
|
|
144
|
+
|
|
145
|
+
def create_new_file(self, new_file: bool):
|
|
146
|
+
if new_file:
|
|
147
|
+
self.close_file()
|
|
148
|
+
|
|
149
|
+
self.module_and_data_saver.h5saver = self.h5saver
|
|
150
|
+
return True
|
|
151
|
+
|
|
152
|
+
@property
|
|
153
|
+
def h5saver(self):
|
|
154
|
+
if self._h5saver is None:
|
|
155
|
+
self._h5saver = H5Saver(backend=config_utils('general', 'hdf5_backend'))
|
|
156
|
+
if self._h5saver.h5_file is None:
|
|
157
|
+
self._h5saver.init_file(update_h5=True)
|
|
158
|
+
if not self._h5saver.isopen():
|
|
159
|
+
self._h5saver.init_file(addhoc_file_path=self._h5saver.settings['current_h5_file'])
|
|
160
|
+
return self._h5saver
|
|
161
|
+
|
|
162
|
+
@h5saver.setter
|
|
163
|
+
def h5saver(self, h5saver_temp: H5Saver):
|
|
164
|
+
self._h5saver = h5saver_temp
|
|
165
|
+
|
|
166
|
+
def close_file(self):
|
|
167
|
+
self.h5saver.close_file()
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def module_and_data_saver(self):
|
|
171
|
+
if self._module_and_data_saver.h5saver is None or not self._module_and_data_saver.h5saver.isopen():
|
|
172
|
+
self._module_and_data_saver.h5saver = self.h5saver
|
|
173
|
+
return self._module_and_data_saver
|
|
174
|
+
|
|
175
|
+
@module_and_data_saver.setter
|
|
176
|
+
def module_and_data_saver(self, mod: Union[DetectorSaver, ActuatorSaver]):
|
|
177
|
+
self._module_and_data_saver = mod
|
|
178
|
+
self._module_and_data_saver.h5saver = self.h5saver
|
|
179
|
+
|
|
180
|
+
def custom_command(self, command: str, **kwargs):
|
|
181
|
+
self.command_hardware.emit(ThreadCommand(command, kwargs))
|
|
182
|
+
|
|
183
|
+
def thread_status(self, status: ThreadCommand, control_module_type='detector'):
|
|
184
|
+
"""Get back info (using the ThreadCommand object) from the hardware
|
|
185
|
+
|
|
186
|
+
And re-emit this ThreadCommand using the custom_sig signal if it should be used in a higher level module
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
Parameters
|
|
190
|
+
----------
|
|
191
|
+
status: ThreadCommand
|
|
192
|
+
The info returned from the hardware, the command (str) can be either:
|
|
193
|
+
* Update_Status: display messages and log info (deprecated)
|
|
194
|
+
* update_status: display info on the UI status bar
|
|
195
|
+
* close: close the current thread and delete corresponding attribute on cascade.
|
|
196
|
+
* update_settings: Update the "detector setting" node in the settings tree.
|
|
197
|
+
* update_main_settings: update the "main setting" node in the settings tree
|
|
198
|
+
* raise_timeout:
|
|
199
|
+
* show_splash: Display the splash screen with attribute as message
|
|
200
|
+
* close_splash
|
|
201
|
+
* show_config: display the plugin configuration
|
|
202
|
+
"""
|
|
203
|
+
|
|
204
|
+
if status.command == "Update_Status":
|
|
205
|
+
# legacy
|
|
206
|
+
if len(status.attribute) > 1:
|
|
207
|
+
self.update_status(status.attribute[0], log=status.attribute[1])
|
|
208
|
+
else:
|
|
209
|
+
self.update_status(status.attribute[0])
|
|
210
|
+
|
|
211
|
+
elif status.command == ThreadStatus.UPDATE_STATUS:
|
|
212
|
+
self.update_status(status.attribute)
|
|
213
|
+
|
|
214
|
+
elif status.command == ThreadStatus.CLOSE:
|
|
215
|
+
try:
|
|
216
|
+
self.update_status(status.attribute[0])
|
|
217
|
+
self._hardware_thread.quit()
|
|
218
|
+
terminated = self._hardware_thread.wait(5000)
|
|
219
|
+
if not terminated:
|
|
220
|
+
self._hardware_thread.terminate()
|
|
221
|
+
self._hardware_thread.wait()
|
|
222
|
+
self.update_status('thread is locked?!', 'log')
|
|
223
|
+
except Exception as e:
|
|
224
|
+
logger.exception(f'Wrong call to the "close" command: \n{str(e)}')
|
|
225
|
+
|
|
226
|
+
self._initialized_state = False
|
|
227
|
+
self.init_signal.emit(self._initialized_state)
|
|
228
|
+
|
|
229
|
+
elif status.command == ThreadStatus.UPDATE_MAIN_SETTINGS:
|
|
230
|
+
# this is a way for the plugins to update main settings of the ui (solely values, limits and options)
|
|
231
|
+
try:
|
|
232
|
+
if status.attribute[2] == 'value':
|
|
233
|
+
self.settings.child('main_settings', *status.attribute[0]).setValue(status.attribute[1])
|
|
234
|
+
elif status.attribute[2] == 'limits':
|
|
235
|
+
self.settings.child('main_settings', *status.attribute[0]).setLimits(status.attribute[1])
|
|
236
|
+
elif status.attribute[2] == 'options':
|
|
237
|
+
self.settings.child('main_settings', *status.attribute[0]).setOpts(**status.attribute[1])
|
|
238
|
+
except Exception as e:
|
|
239
|
+
logger.exception(f'Wrong call to the "update_main_settings" command: \n{str(e)}')
|
|
240
|
+
|
|
241
|
+
elif status.command == ThreadStatus.UPDATE_SETTINGS:
|
|
242
|
+
# using this the settings shown in the UI for the plugin reflects the real plugin settings
|
|
243
|
+
try:
|
|
244
|
+
self.settings.sigTreeStateChanged.disconnect(
|
|
245
|
+
self.parameter_tree_changed) # any changes on the detcetor settings will update accordingly the gui
|
|
246
|
+
except Exception as e:
|
|
247
|
+
logger.exception(str(e))
|
|
248
|
+
try:
|
|
249
|
+
if status.attribute[2] == 'value':
|
|
250
|
+
self.settings.child(f'{control_module_type}_settings',
|
|
251
|
+
*status.attribute[0]).setValue(status.attribute[1])
|
|
252
|
+
elif status.attribute[2] == 'limits':
|
|
253
|
+
self.settings.child(f'{control_module_type}_settings',
|
|
254
|
+
*status.attribute[0]).setLimits(status.attribute[1])
|
|
255
|
+
|
|
256
|
+
elif status.attribute[2] == 'options':
|
|
257
|
+
self.settings.child(f'{control_module_type}_settings',
|
|
258
|
+
*status.attribute[0]).setOpts(**status.attribute[1])
|
|
259
|
+
elif status.attribute[2] == 'childAdded':
|
|
260
|
+
child = Parameter.create(name='tmp')
|
|
261
|
+
child.restoreState(status.attribute[1][0])
|
|
262
|
+
self.settings.child(f'{control_module_type}_settings',
|
|
263
|
+
*status.attribute[0]).addChild(status.attribute[1][0])
|
|
264
|
+
|
|
265
|
+
except Exception as e:
|
|
266
|
+
logger.exception(f'Wrong call to the "update_settings" command: \n{str(e)}')
|
|
267
|
+
self.settings.sigTreeStateChanged.connect(self.parameter_tree_changed)
|
|
268
|
+
|
|
269
|
+
elif status.command == ThreadStatus.UPDATE_UI:
|
|
270
|
+
try:
|
|
271
|
+
if self.ui is not None:
|
|
272
|
+
if hasattr(self.ui, status.attribute):
|
|
273
|
+
getattr(self.ui, status.attribute)(*status.args,
|
|
274
|
+
**status.kwargs)
|
|
275
|
+
except Exception as e:
|
|
276
|
+
logger.info(f'Wrong call to the "update_ui" command: \n{str(e)}')
|
|
277
|
+
|
|
278
|
+
elif status.command == ThreadStatus.RAISE_TIMEOUT:
|
|
279
|
+
self.raise_timeout()
|
|
280
|
+
|
|
281
|
+
elif status.command == ThreadStatus.SHOW_SPLASH:
|
|
282
|
+
self.settings_tree.setEnabled(False)
|
|
283
|
+
self.splash_sc.show()
|
|
284
|
+
self.splash_sc.raise_()
|
|
285
|
+
self.splash_sc.showMessage(status.attribute, color=Qt.white)
|
|
286
|
+
|
|
287
|
+
elif status.command == ThreadStatus.CLOSE_SPLASH:
|
|
288
|
+
self.splash_sc.close()
|
|
289
|
+
self.settings_tree.setEnabled(True)
|
|
290
|
+
|
|
291
|
+
self.custom_sig.emit(status) # to be used if needed in custom application connected to this module
|
|
292
|
+
|
|
293
|
+
@property
|
|
294
|
+
def module_type(self):
|
|
295
|
+
"""str: Get the module type, either DAQ_Move or DAQ_viewer"""
|
|
296
|
+
return type(self).__name__
|
|
297
|
+
|
|
298
|
+
@property
|
|
299
|
+
def initialized_state(self):
|
|
300
|
+
"""bool: Check if the module is initialized"""
|
|
301
|
+
return self._initialized_state
|
|
302
|
+
|
|
303
|
+
@property
|
|
304
|
+
def title(self):
|
|
305
|
+
"""str: get the title of the module"""
|
|
306
|
+
return self._title
|
|
307
|
+
|
|
308
|
+
def grab(self):
|
|
309
|
+
"""Programmatic entry to grab data from detectors or current value from actuator"""
|
|
310
|
+
raise NotImplementedError
|
|
311
|
+
|
|
312
|
+
def stop_grab(self):
|
|
313
|
+
"""Programmatic entry to stop data grabbing from detectors or current value polling from actuator"""
|
|
314
|
+
raise NotImplementedError
|
|
315
|
+
|
|
316
|
+
def _add_data_to_saver(self, *args, **kwargs):
|
|
317
|
+
raise NotImplementedError
|
|
318
|
+
|
|
319
|
+
def append_data(self, *args, **kwargs):
|
|
320
|
+
raise NotImplementedError
|
|
321
|
+
|
|
322
|
+
def insert_data(self, *args, **kwargs):
|
|
323
|
+
raise NotImplementedError
|
|
324
|
+
|
|
325
|
+
def quit_fun(self):
|
|
326
|
+
"""Programmatic entry to quit the control module"""
|
|
327
|
+
raise NotImplementedError
|
|
328
|
+
|
|
329
|
+
def init_hardware(self, do_init=True):
|
|
330
|
+
"""Programmatic entry to initialize/deinitialize the control module
|
|
331
|
+
|
|
332
|
+
Parameters
|
|
333
|
+
----------
|
|
334
|
+
do_init : bool
|
|
335
|
+
if True initialize the selected hardware else deinitialize it
|
|
336
|
+
|
|
337
|
+
See Also
|
|
338
|
+
--------
|
|
339
|
+
:meth:`init_hardware_ui`
|
|
340
|
+
"""
|
|
341
|
+
raise NotImplementedError
|
|
342
|
+
|
|
343
|
+
def init_hardware_ui(self, do_init=True):
|
|
344
|
+
"""Programmatic entry to simulate a click on the user interface init button
|
|
345
|
+
|
|
346
|
+
Parameters
|
|
347
|
+
----------
|
|
348
|
+
do_init : bool
|
|
349
|
+
if True initialize the selected hardware else deinitialize it
|
|
350
|
+
|
|
351
|
+
Notes
|
|
352
|
+
-----
|
|
353
|
+
This method should be preferred to :meth:`init_hardware`
|
|
354
|
+
"""
|
|
355
|
+
if self.ui is not None:
|
|
356
|
+
self.ui.do_init(do_init)
|
|
357
|
+
|
|
358
|
+
def show_log(self):
|
|
359
|
+
"""Open the log file in the default text editor"""
|
|
360
|
+
import webbrowser
|
|
361
|
+
webbrowser.open(get_base_logger(logger).handlers[0].baseFilename)
|
|
362
|
+
|
|
363
|
+
def show_config(self, config: Config) -> Config:
|
|
364
|
+
""" Display in a tree the current configuration"""
|
|
365
|
+
if config is not None:
|
|
366
|
+
from pymodaq_gui.utils.widgets.tree_toml import TreeFromToml
|
|
367
|
+
config_tree = TreeFromToml(config)
|
|
368
|
+
config_tree.show_dialog()
|
|
369
|
+
|
|
370
|
+
return ControlModulesConfig()
|
|
371
|
+
|
|
372
|
+
def update_status(self, txt: str, log=True):
|
|
373
|
+
"""Display a message in the ui status bar and eventually log the message
|
|
374
|
+
|
|
375
|
+
Parameters
|
|
376
|
+
----------
|
|
377
|
+
txt : str
|
|
378
|
+
message to display
|
|
379
|
+
log : bool
|
|
380
|
+
if True, log the message in the logger
|
|
381
|
+
"""
|
|
382
|
+
if self.ui is not None:
|
|
383
|
+
self.ui.display_status(txt)
|
|
384
|
+
self.status_sig.emit(txt)
|
|
385
|
+
if log:
|
|
386
|
+
logger.info(txt)
|
|
387
|
+
|
|
388
|
+
def manage_ui_actions(self, action_name: str, attribute: str, value):
|
|
389
|
+
"""Method to manage actions for the UI (if any).
|
|
390
|
+
|
|
391
|
+
Will try to apply the given value to the given attribute of the corresponding action
|
|
392
|
+
|
|
393
|
+
Parameters
|
|
394
|
+
----------
|
|
395
|
+
action_name: str
|
|
396
|
+
attribute: method signature or attribute
|
|
397
|
+
value: object
|
|
398
|
+
actual type and value depend on the triggered attribute
|
|
399
|
+
|
|
400
|
+
Examples
|
|
401
|
+
--------
|
|
402
|
+
>>>manage_ui_actions('quit', 'setEnabled', False)
|
|
403
|
+
# will disable the quit action (button) on the UI
|
|
404
|
+
"""
|
|
405
|
+
if self.ui is not None:
|
|
406
|
+
if self.ui.has_action(action_name):
|
|
407
|
+
action = self.ui.get_action(action_name)
|
|
408
|
+
if hasattr(action, attribute):
|
|
409
|
+
attr = getattr(action, attribute)
|
|
410
|
+
if callable(attr):
|
|
411
|
+
attr(value)
|
|
412
|
+
else:
|
|
413
|
+
attr = value
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
class ParameterControlModule(ParameterManager, ControlModule):
|
|
417
|
+
"""Base class for a control module with parameters."""
|
|
418
|
+
|
|
419
|
+
_update_settings_signal = Signal(edict)
|
|
420
|
+
|
|
421
|
+
listener_class: Type[ActorListener] = ActorListener
|
|
422
|
+
|
|
423
|
+
def __init__(self, **kwargs):
|
|
424
|
+
ParameterManager.__init__(self, action_list=('save', 'update'))
|
|
425
|
+
ControlModule.__init__(self)
|
|
426
|
+
|
|
427
|
+
def value_changed(self, param: Parameter) -> Optional[Parameter]:
|
|
428
|
+
"""ParameterManager subclassed method. Process events from value changed by user in the UI Settings
|
|
429
|
+
|
|
430
|
+
Parameters
|
|
431
|
+
----------
|
|
432
|
+
param: Parameter
|
|
433
|
+
a given parameter whose value has been changed by user
|
|
434
|
+
"""
|
|
435
|
+
if param.name() == 'plugin_config':
|
|
436
|
+
self.show_config(self.plugin_config)
|
|
437
|
+
|
|
438
|
+
elif param.name() == 'connect_server':
|
|
439
|
+
if param.value():
|
|
440
|
+
self.connect_tcp_ip()
|
|
441
|
+
else:
|
|
442
|
+
self._command_tcpip.emit(ThreadCommand('quit', ))
|
|
443
|
+
|
|
444
|
+
elif param.name() == 'ip_address' or param.name == 'port':
|
|
445
|
+
self._command_tcpip.emit(
|
|
446
|
+
ThreadCommand('update_connection',
|
|
447
|
+
dict(ipaddress=self.settings['main_settings', 'tcpip', 'ip_address'],
|
|
448
|
+
port=self.settings['main_settings', 'tcpip', 'port'])))
|
|
449
|
+
|
|
450
|
+
elif param.name() == 'connect_leco_server':
|
|
451
|
+
self.connect_leco(param.value())
|
|
452
|
+
|
|
453
|
+
elif param.name() == "name":
|
|
454
|
+
name = param.value()
|
|
455
|
+
try:
|
|
456
|
+
self._leco_client.name = name
|
|
457
|
+
except AttributeError:
|
|
458
|
+
pass
|
|
459
|
+
|
|
460
|
+
else:
|
|
461
|
+
# not handled
|
|
462
|
+
return param
|
|
463
|
+
|
|
464
|
+
def _update_settings(self, param: Parameter):
|
|
465
|
+
# I do not understand what it does
|
|
466
|
+
path = self.settings.childPath(param)
|
|
467
|
+
if path is not None:
|
|
468
|
+
if 'main_settings' not in path:
|
|
469
|
+
self._update_settings_signal.emit(edict(path=path, param=param, change='value'))
|
|
470
|
+
if self.settings.child('main_settings', 'tcpip', 'tcp_connected').value():
|
|
471
|
+
self._command_tcpip.emit(ThreadCommand('send_info', dict(path=path, param=param)))
|
|
472
|
+
if self.settings.child('main_settings', 'leco', 'leco_connected').value():
|
|
473
|
+
self._command_tcpip.emit(
|
|
474
|
+
ThreadCommand(LECOCommands.SEND_INFO,
|
|
475
|
+
ParameterWithPath(param, path)))
|
|
476
|
+
|
|
477
|
+
def connect_tcp_ip(self, params_state=None, client_type: str = "GRABBER") -> None:
|
|
478
|
+
"""Init a TCPClient in a separated thread to communicate with a distant TCp/IP Server
|
|
479
|
+
|
|
480
|
+
Use the settings: ip_address and port to specify the connection
|
|
481
|
+
|
|
482
|
+
See Also
|
|
483
|
+
--------
|
|
484
|
+
TCPServer
|
|
485
|
+
"""
|
|
486
|
+
if self.settings.child('main_settings', 'tcpip', 'connect_server').value():
|
|
487
|
+
self._tcpclient_thread = QThread()
|
|
488
|
+
|
|
489
|
+
tcpclient = TCPClient(self.settings.child('main_settings', 'tcpip', 'ip_address').value(),
|
|
490
|
+
self.settings.child('main_settings', 'tcpip', 'port').value(),
|
|
491
|
+
params_state=params_state,
|
|
492
|
+
client_type=client_type)
|
|
493
|
+
tcpclient.moveToThread(self._tcpclient_thread)
|
|
494
|
+
self._tcpclient_thread.tcpclient = tcpclient
|
|
495
|
+
tcpclient.cmd_signal.connect(self.process_tcpip_cmds)
|
|
496
|
+
|
|
497
|
+
self._command_tcpip[ThreadCommand].connect(tcpclient.queue_command)
|
|
498
|
+
self._tcpclient_thread.started.connect(tcpclient.init_connection)
|
|
499
|
+
|
|
500
|
+
self._tcpclient_thread.start()
|
|
501
|
+
|
|
502
|
+
def get_leco_name(self) -> str:
|
|
503
|
+
name = self.settings["main_settings", "leco", "leco_name"]
|
|
504
|
+
if name == '':
|
|
505
|
+
# take the module name as alternative
|
|
506
|
+
name = self.settings["main_settings", "module_name"]
|
|
507
|
+
if name == '':
|
|
508
|
+
# a name is required, invent one
|
|
509
|
+
name = f"viewer_{randint(0, 10000)}"
|
|
510
|
+
name = self.settings.child("main_settings", "leco", "leco_name").setValue(name)
|
|
511
|
+
return name
|
|
512
|
+
|
|
513
|
+
def get_leco_host_port(self) -> tuple:
|
|
514
|
+
host = self.settings["main_settings", "leco", "host"]
|
|
515
|
+
port = self.settings["main_settings", "leco", "port"]
|
|
516
|
+
if host == '':
|
|
517
|
+
# take the localhost as default
|
|
518
|
+
host = 'localhost'
|
|
519
|
+
if port == '':
|
|
520
|
+
# take the default port as 12300
|
|
521
|
+
port = 12300
|
|
522
|
+
return (host, port)
|
|
523
|
+
|
|
524
|
+
def connect_leco(self, connect: bool) -> None:
|
|
525
|
+
if connect:
|
|
526
|
+
name = self.get_leco_name()
|
|
527
|
+
host, port = self.get_leco_host_port()
|
|
528
|
+
try:
|
|
529
|
+
self._leco_client.name = name
|
|
530
|
+
except AttributeError:
|
|
531
|
+
self._leco_client = self.listener_class(name=name, host=host, port=port)
|
|
532
|
+
self._leco_client.cmd_signal.connect(self.process_tcpip_cmds)
|
|
533
|
+
self._command_tcpip[ThreadCommand].connect(self._leco_client.queue_command)
|
|
534
|
+
self._leco_client.start_listen()
|
|
535
|
+
# self._leco_client.cmd_signal.emit(ThreadCommand(LECOCommands.SET_INFO, attribute=["detector_settings", ""]))
|
|
536
|
+
else:
|
|
537
|
+
self._command_tcpip.emit(ThreadCommand(LECOCommands.QUIT, ))
|
|
538
|
+
try:
|
|
539
|
+
self._command_tcpip[ThreadCommand].disconnect(self._leco_client.queue_command)
|
|
540
|
+
except TypeError:
|
|
541
|
+
pass # already disconnected
|
|
542
|
+
|
|
543
|
+
@Slot(ThreadCommand)
|
|
544
|
+
def process_tcpip_cmds(self, status: ThreadCommand) -> Optional[ThreadCommand]:
|
|
545
|
+
if status.command == 'connected':
|
|
546
|
+
self.settings.child('main_settings', 'tcpip', 'tcp_connected').setValue(True)
|
|
547
|
+
|
|
548
|
+
elif status.command == 'disconnected':
|
|
549
|
+
self.settings.child('main_settings', 'tcpip', 'tcp_connected').setValue(False)
|
|
550
|
+
|
|
551
|
+
elif status.command == LECOClientCommands.LECO_CONNECTED:
|
|
552
|
+
self.settings.child('main_settings', 'leco', 'leco_connected').setValue(True)
|
|
553
|
+
|
|
554
|
+
elif status.command == LECOClientCommands.LECO_DISCONNECTED:
|
|
555
|
+
self.settings.child('main_settings', 'leco', 'leco_connected').setValue(False)
|
|
556
|
+
|
|
557
|
+
elif status.command == 'Update_Status':
|
|
558
|
+
self.thread_status(status)
|
|
559
|
+
|
|
560
|
+
elif status.command == 'set_info':
|
|
561
|
+
""" The Director sent a parameter to be updated"""
|
|
562
|
+
path_in_settings = status.attribute.path
|
|
563
|
+
if 'move' in self.__class__.__name__.lower():
|
|
564
|
+
common_param = 'move_settings'
|
|
565
|
+
else:
|
|
566
|
+
common_param = 'detector_settings'
|
|
567
|
+
if common_param in path_in_settings:
|
|
568
|
+
param = self.settings.child(*path_in_settings)
|
|
569
|
+
elif 'settings_client' in path_in_settings:
|
|
570
|
+
param = self.settings.child(common_param, *path_in_settings[1:])
|
|
571
|
+
else:
|
|
572
|
+
param = self.settings.child(common_param, *path_in_settings)
|
|
573
|
+
|
|
574
|
+
param.setValue(status.attribute.parameter.value())
|
|
575
|
+
|
|
576
|
+
elif status.command == LECOCommands.GET_SETTINGS:
|
|
577
|
+
""" The Director requested the content of the actuator settings"""
|
|
578
|
+
if 'move' in self.__class__.__name__.lower():
|
|
579
|
+
common_param = 'move_settings'
|
|
580
|
+
else:
|
|
581
|
+
common_param = 'detector_settings'
|
|
582
|
+
self._command_tcpip.emit(
|
|
583
|
+
ThreadCommand(LECOCommands.SET_DIRECTOR_SETTINGS,
|
|
584
|
+
ioxml.parameter_to_xml_string(
|
|
585
|
+
self.settings.child(common_param))))
|
|
586
|
+
|
|
587
|
+
else:
|
|
588
|
+
# not handled
|
|
589
|
+
return status
|
|
590
|
+
|
|
591
|
+
|