pymodaq 5.0.18__py3-none-any.whl → 5.1.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.
Potentially problematic release.
This version of pymodaq might be problematic. Click here for more details.
- pymodaq/__init__.py +23 -11
- pymodaq/control_modules/__init__.py +1 -0
- pymodaq/control_modules/daq_move.py +451 -246
- 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.py → daq_move_ui/ui_base.py} +168 -210
- 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 +113 -101
- pymodaq/control_modules/daq_viewer_ui.py +41 -31
- pymodaq/control_modules/mocks.py +2 -2
- pymodaq/control_modules/move_utility_classes.py +113 -41
- pymodaq/control_modules/thread_commands.py +137 -0
- pymodaq/control_modules/ui_utils.py +72 -0
- pymodaq/control_modules/utils.py +107 -63
- pymodaq/control_modules/viewer_utility_classes.py +13 -17
- pymodaq/dashboard.py +1294 -625
- pymodaq/examples/qt_less_standalone_module.py +48 -11
- pymodaq/extensions/__init__.py +8 -3
- 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 +1 -1
- 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 +71 -297
- pymodaq/extensions/daq_logger/daq_logger.py +7 -12
- pymodaq/extensions/daq_logger/h5logging.py +1 -1
- pymodaq/extensions/daq_scan.py +30 -55
- 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 +3 -34
- 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/actuator_controller.py +3 -2
- pymodaq/extensions/pid/daq_move_PID.py +107 -30
- pymodaq/extensions/pid/pid_controller.py +613 -287
- pymodaq/extensions/pid/utils.py +8 -5
- pymodaq/extensions/utils.py +17 -2
- pymodaq/resources/config_template.toml +57 -0
- pymodaq/resources/preset_default.xml +1 -1
- pymodaq/utils/config.py +10 -4
- pymodaq/utils/daq_utils.py +14 -0
- pymodaq/utils/data.py +1 -0
- pymodaq/utils/gui_utils/loader_utils.py +25 -15
- pymodaq/utils/h5modules/module_saving.py +134 -22
- pymodaq/utils/leco/daq_move_LECODirector.py +123 -84
- pymodaq/utils/leco/daq_xDviewer_LECODirector.py +84 -97
- pymodaq/utils/leco/director_utils.py +32 -16
- pymodaq/utils/leco/leco_director.py +104 -27
- pymodaq/utils/leco/pymodaq_listener.py +186 -97
- pymodaq/utils/leco/rpc_method_definitions.py +43 -0
- pymodaq/utils/leco/utils.py +25 -25
- pymodaq/utils/managers/batchscan_manager.py +12 -11
- pymodaq/utils/managers/modules_manager.py +74 -33
- pymodaq/utils/managers/overshoot_manager.py +11 -10
- pymodaq/utils/managers/preset_manager.py +100 -64
- pymodaq/utils/managers/preset_manager_utils.py +163 -107
- pymodaq/utils/managers/remote_manager.py +21 -16
- pymodaq/utils/scanner/scan_factory.py +12 -3
- pymodaq/utils/scanner/scan_selector.py +1 -3
- pymodaq/utils/scanner/scanner.py +35 -6
- pymodaq/utils/scanner/scanners/_1d_scanners.py +15 -46
- pymodaq/utils/scanner/scanners/_2d_scanners.py +21 -68
- pymodaq/utils/scanner/scanners/sequential.py +50 -31
- pymodaq/utils/scanner/scanners/tabular.py +45 -28
- {pymodaq-5.0.18.dist-info → pymodaq-5.1.0.dist-info}/METADATA +7 -6
- pymodaq-5.1.0.dist-info/RECORD +154 -0
- {pymodaq-5.0.18.dist-info → pymodaq-5.1.0.dist-info}/entry_points.txt +0 -2
- pymodaq/extensions/bayesian/bayesian_optimisation.py +0 -690
- pymodaq/utils/leco/desktop.ini +0 -2
- pymodaq-5.0.18.dist-info/RECORD +0 -121
- {pymodaq-5.0.18.dist-info → pymodaq-5.1.0.dist-info}/WHEEL +0 -0
- {pymodaq-5.0.18.dist-info → pymodaq-5.1.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import time
|
|
2
2
|
from functools import partial # needed for the button to sync setpoint with currpoint
|
|
3
3
|
from typing import Dict, List, TYPE_CHECKING
|
|
4
|
-
|
|
4
|
+
from collections import deque
|
|
5
5
|
import numpy as np
|
|
6
6
|
|
|
7
7
|
from qtpy import QtGui, QtWidgets
|
|
@@ -27,6 +27,8 @@ from pymodaq.utils.managers.modules_manager import ModulesManager
|
|
|
27
27
|
from pymodaq.extensions.pid.utils import get_models
|
|
28
28
|
from pymodaq.utils.data import DataActuator, DataToActuators
|
|
29
29
|
from pymodaq.extensions.pid.actuator_controller import PIDController
|
|
30
|
+
from pymodaq.extensions.pid.utils import PIDModelGeneric
|
|
31
|
+
|
|
30
32
|
from pymodaq.extensions.utils import CustomExt
|
|
31
33
|
|
|
32
34
|
if TYPE_CHECKING:
|
|
@@ -37,18 +39,9 @@ config = Config()
|
|
|
37
39
|
logger = set_logger(get_module_name(__file__))
|
|
38
40
|
|
|
39
41
|
|
|
40
|
-
def convert_output_limits(lim_min=-10., min_status=False, lim_max=10., max_status=False):
|
|
41
|
-
output = [None, None]
|
|
42
|
-
if min_status:
|
|
43
|
-
output[0] = lim_min
|
|
44
|
-
if max_status:
|
|
45
|
-
output[1] = lim_max
|
|
46
|
-
return output
|
|
47
|
-
|
|
48
|
-
|
|
49
42
|
class DAQ_PID(CustomExt):
|
|
50
|
-
"""
|
|
51
|
-
|
|
43
|
+
""" """
|
|
44
|
+
|
|
52
45
|
command_pid = Signal(ThreadCommand)
|
|
53
46
|
curr_points_signal = Signal(dict)
|
|
54
47
|
setpoints_signal = Signal(dict)
|
|
@@ -57,55 +50,204 @@ class DAQ_PID(CustomExt):
|
|
|
57
50
|
models = get_models()
|
|
58
51
|
|
|
59
52
|
params = [
|
|
60
|
-
{
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
53
|
+
{
|
|
54
|
+
"title": "Models",
|
|
55
|
+
"name": "models",
|
|
56
|
+
"type": "group",
|
|
57
|
+
"expanded": True,
|
|
58
|
+
"visible": True,
|
|
59
|
+
"children": [
|
|
60
|
+
{
|
|
61
|
+
"title": "Models class:",
|
|
62
|
+
"name": "model_class",
|
|
63
|
+
"type": "list",
|
|
64
|
+
"limits": [d["name"] for d in models],
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"title": "Model params:",
|
|
68
|
+
"name": "model_params",
|
|
69
|
+
"type": "group",
|
|
70
|
+
"children": [],
|
|
71
|
+
},
|
|
72
|
+
],
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
"title": "Move settings:",
|
|
76
|
+
"name": "move_settings",
|
|
77
|
+
"expanded": True,
|
|
78
|
+
"type": "group",
|
|
79
|
+
"visible": False,
|
|
80
|
+
"children": [
|
|
81
|
+
{"title": "Units:", "name": "units", "type": "str", "value": ""}
|
|
82
|
+
],
|
|
83
|
+
},
|
|
68
84
|
# here only to be compatible with DAQ_Scan, the model could update it
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
{
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
{
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
85
|
+
{
|
|
86
|
+
"title": "Main Settings:",
|
|
87
|
+
"name": "main_settings",
|
|
88
|
+
"expanded": True,
|
|
89
|
+
"type": "group",
|
|
90
|
+
"children": [
|
|
91
|
+
{
|
|
92
|
+
"title": "Acquisition Timeout (ms):",
|
|
93
|
+
"name": "timeout",
|
|
94
|
+
"type": "int",
|
|
95
|
+
"value": 10000,
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"title": "epsilon",
|
|
99
|
+
"name": "epsilon",
|
|
100
|
+
"type": "float",
|
|
101
|
+
"value": 0.01,
|
|
102
|
+
"tooltip": "Precision at which move is considered as done",
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"title": "Queue length",
|
|
106
|
+
"name": "queue_length",
|
|
107
|
+
"type": "int",
|
|
108
|
+
"value": 25,
|
|
109
|
+
"tooltip": "Length of queue to calculate stability",
|
|
110
|
+
},
|
|
111
|
+
{
|
|
112
|
+
"title": "Weighting type",
|
|
113
|
+
"name": "queue_weighting",
|
|
114
|
+
"type": "int",
|
|
115
|
+
"value": 0,
|
|
116
|
+
"tooltip": """Type of weighting when calculating the stability. The weight goes as q**N where q is the qth element in the queue and N is the wanted order.
|
|
117
|
+
""",
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"title": "Refresh queue",
|
|
121
|
+
"name": "refresh_queue",
|
|
122
|
+
"type": "bool_push",
|
|
123
|
+
"value": False,
|
|
124
|
+
"tooltip": "Refresh queues to zero length",
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
"title": "Refresh plot",
|
|
128
|
+
"name": "refresh_plot",
|
|
129
|
+
"type": "bool_push",
|
|
130
|
+
"value": False,
|
|
131
|
+
"tooltip": "Refresh both plots to zero length",
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"title": "PID settings:",
|
|
135
|
+
"name": "pid_settings",
|
|
136
|
+
"type": "group",
|
|
137
|
+
"children": [
|
|
138
|
+
{
|
|
139
|
+
"title": "Time per loop (ms):",
|
|
140
|
+
"name": "effective_sample_time",
|
|
141
|
+
"type": "int",
|
|
142
|
+
"value": 0,
|
|
143
|
+
"readonly": True,
|
|
144
|
+
"tooltip": "Time elapsed in the PID thread for each loop, ",
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"title": "Sample time (ms):",
|
|
148
|
+
"name": "sample_time",
|
|
149
|
+
"type": "int",
|
|
150
|
+
"value": 10,
|
|
151
|
+
"tooltip": "The minimum desired time between two PID calculations, ",
|
|
152
|
+
},
|
|
153
|
+
{
|
|
154
|
+
"title": "Refresh plot time (ms):",
|
|
155
|
+
"name": "refresh_plot_time",
|
|
156
|
+
"type": "int",
|
|
157
|
+
"value": 200,
|
|
158
|
+
"tooltip": "Update display every x ms, ",
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"title": "Output limits:",
|
|
162
|
+
"name": "output_limits",
|
|
163
|
+
"expanded": True,
|
|
164
|
+
"type": "group",
|
|
165
|
+
"tooltip": "Limits on the output of the PID controller, ",
|
|
166
|
+
"children": [
|
|
167
|
+
{
|
|
168
|
+
"title": "Output limit (min):",
|
|
169
|
+
"name": "output_limit_min_enabled",
|
|
170
|
+
"type": "bool",
|
|
171
|
+
"value": False,
|
|
172
|
+
},
|
|
173
|
+
{
|
|
174
|
+
"title": "Output limit (min):",
|
|
175
|
+
"name": "output_limit_min",
|
|
176
|
+
"type": "float",
|
|
177
|
+
"value": 0,
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"title": "Output limit (max):",
|
|
181
|
+
"name": "output_limit_max_enabled",
|
|
182
|
+
"type": "bool",
|
|
183
|
+
"value": False,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
"title": "Output limit (max:",
|
|
187
|
+
"name": "output_limit_max",
|
|
188
|
+
"type": "float",
|
|
189
|
+
"value": 100,
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
"title": "Prop. on measurement:",
|
|
195
|
+
"name": "proportional_on_measurement",
|
|
196
|
+
"type": "bool",
|
|
197
|
+
"value": False,
|
|
198
|
+
"tooltip": "If True, the PID will stabilize the value instead of reducing the error (see pid.py).",
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
"title": "PID constants:",
|
|
202
|
+
"name": "pid_constants",
|
|
203
|
+
"type": "group",
|
|
204
|
+
"children": [
|
|
205
|
+
{
|
|
206
|
+
"title": "Kp:",
|
|
207
|
+
"name": "kp",
|
|
208
|
+
"type": "float",
|
|
209
|
+
"value": 0.1,
|
|
210
|
+
"min": 0,
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"title": "Ki:",
|
|
214
|
+
"name": "ki",
|
|
215
|
+
"type": "float",
|
|
216
|
+
"value": 0.01,
|
|
217
|
+
"min": 0,
|
|
218
|
+
},
|
|
219
|
+
{
|
|
220
|
+
"title": "Kd:",
|
|
221
|
+
"name": "kd",
|
|
222
|
+
"type": "float",
|
|
223
|
+
"value": 0.001,
|
|
224
|
+
"min": 0,
|
|
225
|
+
},
|
|
226
|
+
],
|
|
227
|
+
},
|
|
228
|
+
],
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
97
232
|
]
|
|
98
233
|
|
|
99
234
|
def __init__(self, dockarea, dashboard):
|
|
100
235
|
super().__init__(dockarea, dashboard)
|
|
101
236
|
|
|
102
|
-
self.settings = Parameter.create(
|
|
103
|
-
|
|
237
|
+
self.settings = Parameter.create(
|
|
238
|
+
title="PID settings",
|
|
239
|
+
name="pid_settings",
|
|
240
|
+
type="group",
|
|
241
|
+
children=self.params,
|
|
242
|
+
)
|
|
243
|
+
self.title = "PyMoDAQ PID"
|
|
104
244
|
|
|
105
245
|
self.initialized_state = False
|
|
106
|
-
self.model_class = None
|
|
246
|
+
self.model_class: PIDModelGeneric = None
|
|
107
247
|
self._curr_points = dict([])
|
|
108
248
|
self._setpoints = dict([])
|
|
249
|
+
self.queue_points = dict([])
|
|
250
|
+
self.build_weight_array()
|
|
109
251
|
|
|
110
252
|
self.dock_area = dockarea
|
|
111
253
|
self.check_moving = False
|
|
@@ -116,70 +258,104 @@ class DAQ_PID(CustomExt):
|
|
|
116
258
|
self.enable_controls_pid_run(False)
|
|
117
259
|
|
|
118
260
|
self.emit_curr_points_sig.connect(self.emit_curr_points)
|
|
119
|
-
|
|
261
|
+
|
|
120
262
|
def ini_PID(self):
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
263
|
+
if self.is_action_checked("ini_pid"):
|
|
264
|
+
output_limits = self.get_output_limits()
|
|
265
|
+
self.update_queues(refresh=True)
|
|
266
|
+
self.runner_thread = QThread()
|
|
267
|
+
pid_runner = PIDRunner(
|
|
268
|
+
self.model_class,
|
|
269
|
+
self.modules_manager,
|
|
270
|
+
setpoints=self.setpoints,
|
|
271
|
+
params=dict(
|
|
272
|
+
Kp=self.settings[
|
|
273
|
+
"main_settings", "pid_settings", "pid_constants", "kp"
|
|
274
|
+
],
|
|
275
|
+
Ki=self.settings[
|
|
276
|
+
"main_settings", "pid_settings", "pid_constants", "ki"
|
|
277
|
+
],
|
|
278
|
+
Kd=self.settings[
|
|
279
|
+
"main_settings", "pid_settings", "pid_constants", "kd"
|
|
280
|
+
],
|
|
281
|
+
sample_time=self.settings[
|
|
282
|
+
"main_settings", "pid_settings", "sample_time"
|
|
283
|
+
]
|
|
284
|
+
/ 1000,
|
|
285
|
+
output_limits=output_limits,
|
|
286
|
+
auto_mode=False,
|
|
287
|
+
),
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
self.runner_thread.pid_runner = pid_runner
|
|
144
291
|
pid_runner.pid_output_signal.connect(self.process_output)
|
|
292
|
+
pid_runner.time_elapsed_signal.connect(self.display_time_elapsed)
|
|
145
293
|
pid_runner.status_sig.connect(self.thread_status)
|
|
146
294
|
self.command_pid.connect(pid_runner.queue_command)
|
|
147
295
|
|
|
148
|
-
pid_runner.moveToThread(self.
|
|
296
|
+
pid_runner.moveToThread(self.runner_thread)
|
|
149
297
|
|
|
150
|
-
self.
|
|
151
|
-
self.get_action(
|
|
298
|
+
self.runner_thread.start()
|
|
299
|
+
self.get_action("pid_led").set_as_true()
|
|
152
300
|
self.enable_controls_pid_run(True)
|
|
153
301
|
|
|
154
302
|
else:
|
|
155
|
-
if hasattr(self,
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
self.PIDThread.quit()
|
|
159
|
-
except Exception:
|
|
160
|
-
pass
|
|
161
|
-
self.get_action('pid_led').set_as_false()
|
|
303
|
+
if hasattr(self, "runner_thread"):
|
|
304
|
+
self.exit_runner_thread()
|
|
305
|
+
self.get_action("pid_led").set_as_false()
|
|
162
306
|
self.enable_controls_pid_run(False)
|
|
163
307
|
|
|
164
308
|
self.initialized_state = True
|
|
165
309
|
|
|
166
310
|
def process_output(self, data: DataToExport):
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
self.curr_points = [float(
|
|
311
|
+
outputs: DataRaw = data.get_data_from_name("outputs")
|
|
312
|
+
inputs: DataRaw = data.get_data_from_name("inputs")
|
|
313
|
+
self.curr_points = [float(_input[-1]) for _input in inputs.isig[-1]]
|
|
314
|
+
inputs_plot = DataRaw("inputs",data=[np.array([_curr_point,]) for _curr_point in self.curr_points],labels=self.model_class.setpoints_names)
|
|
315
|
+
for _input, _setpoint_name in zip(inputs, self.model_class.setpoints_names):
|
|
316
|
+
self.queue_points[_setpoint_name].extend(_input)
|
|
317
|
+
|
|
318
|
+
# Would that be useful to add too?
|
|
319
|
+
# self.mean_points = [
|
|
320
|
+
# np.mean(np.array(queue) - setpoint)
|
|
321
|
+
# for queue, setpoint in zip(self.queue_points.values(), self.setpoints)
|
|
322
|
+
# ]
|
|
323
|
+
# queues_length = len(list(self.queue_points.values())[0])
|
|
324
|
+
self.stab_points = [
|
|
325
|
+
np.sqrt(np.cov(_queue, aweights=self.queue_weight[:len(_queue)], ddof=0))
|
|
326
|
+
for _queue in self.queue_points.values()
|
|
327
|
+
]
|
|
170
328
|
self.output_viewer.show_data(outputs)
|
|
171
|
-
self.input_viewer.show_data(
|
|
329
|
+
self.input_viewer.show_data(inputs_plot)
|
|
330
|
+
def build_weight_array(self):
|
|
331
|
+
# Build a weighting array of the form np.arange(1+queue_length)**(queue_weighting)
|
|
332
|
+
self.queue_weight = np.arange(1+
|
|
333
|
+
self.settings.child("main_settings","queue_length").value()
|
|
334
|
+
)**self.settings.child("main_settings","queue_weighting").value()
|
|
335
|
+
|
|
336
|
+
def get_output_limits(self):
|
|
337
|
+
output_limits = [None, None]
|
|
338
|
+
output_parameters = self.settings.child("main_settings", "pid_settings", "output_limits")
|
|
339
|
+
if output_parameters["output_limit_min_enabled"]:
|
|
340
|
+
output_limits[0] = output_parameters[ "output_limit_min"]
|
|
341
|
+
if output_parameters["output_limit_max_enabled"]:
|
|
342
|
+
output_limits[1] = output_parameters[ "output_limit_max"]
|
|
343
|
+
return output_limits
|
|
344
|
+
|
|
345
|
+
def display_time_elapsed(self, time_elapsed: float):
|
|
346
|
+
"""Display the time elapsed in the PID thread"""
|
|
347
|
+
self.settings.child("main_settings", "pid_settings", "effective_sample_time").setValue(time_elapsed*1000)
|
|
172
348
|
|
|
173
349
|
def enable_controls_pid(self, enable=False):
|
|
174
|
-
self.set_action_enabled(
|
|
350
|
+
self.set_action_enabled("ini_pid", enable)
|
|
175
351
|
# self.setpoint_sb.setOpts(enabled=enable)
|
|
176
352
|
|
|
177
353
|
def enable_controls_pid_run(self, enable=False):
|
|
178
|
-
self.set_action_enabled(
|
|
179
|
-
self.set_action_enabled(
|
|
354
|
+
self.set_action_enabled("run", enable)
|
|
355
|
+
self.set_action_enabled("pause", enable)
|
|
180
356
|
|
|
181
357
|
def setup_menu(self, menubar: QtWidgets.QMenuBar = None):
|
|
182
|
-
|
|
358
|
+
"""
|
|
183
359
|
to be subclassed
|
|
184
360
|
create menu for actions contained into the self.actions_manager, for instance:
|
|
185
361
|
|
|
@@ -191,11 +367,11 @@ class DAQ_PID(CustomExt):
|
|
|
191
367
|
|
|
192
368
|
file_menu.addSeparator()
|
|
193
369
|
self.actions_manager.affect_to('quit', file_menu)
|
|
194
|
-
|
|
370
|
+
"""
|
|
195
371
|
pass
|
|
196
372
|
|
|
197
373
|
def value_changed(self, param):
|
|
198
|
-
|
|
374
|
+
"""to be subclassed for actions to perform when one of the param's value in self.settings is changed
|
|
199
375
|
|
|
200
376
|
For instance:
|
|
201
377
|
if param.name() == 'do_something':
|
|
@@ -206,81 +382,123 @@ class DAQ_PID(CustomExt):
|
|
|
206
382
|
Parameters
|
|
207
383
|
----------
|
|
208
384
|
param: (Parameter) the parameter whose value just changed
|
|
209
|
-
|
|
210
|
-
if param.name() ==
|
|
385
|
+
"""
|
|
386
|
+
if param.name() == "model_class":
|
|
211
387
|
self.get_set_model_params(param.value())
|
|
212
388
|
|
|
213
|
-
elif param.name() ==
|
|
214
|
-
self.command_pid.emit(
|
|
389
|
+
elif param.name() == "refresh_plot_time" or param.name() == "timeout":
|
|
390
|
+
self.command_pid.emit(
|
|
391
|
+
ThreadCommand("update_timer", [param.name(), param.value()])
|
|
392
|
+
)
|
|
215
393
|
|
|
216
|
-
elif param.name() ==
|
|
217
|
-
self.command_pid.emit(
|
|
394
|
+
elif param.name() == "sample_time":
|
|
395
|
+
self.command_pid.emit(
|
|
396
|
+
ThreadCommand("update_options", dict(sample_time=param.value()))
|
|
397
|
+
)
|
|
218
398
|
|
|
219
399
|
elif param.name() in putils.iter_children(
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
output_limits =
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
'output_limit_min_enabled'],
|
|
227
|
-
self.settings['main_settings', 'pid_controls', 'output_limits',
|
|
228
|
-
'output_limit_max'],
|
|
229
|
-
self.settings['main_settings', 'pid_controls', 'output_limits',
|
|
230
|
-
'output_limit_max_enabled'])
|
|
231
|
-
|
|
232
|
-
self.command_pid.emit(ThreadCommand('update_options', dict(output_limits=output_limits)))
|
|
400
|
+
self.settings.child("main_settings", "pid_settings", "output_limits"), []
|
|
401
|
+
):
|
|
402
|
+
output_limits = self.get_output_limits()
|
|
403
|
+
self.command_pid.emit(
|
|
404
|
+
ThreadCommand("update_options", dict(output_limits=output_limits))
|
|
405
|
+
)
|
|
233
406
|
|
|
234
407
|
elif param.name() in putils.iter_children(
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
self.
|
|
408
|
+
self.settings.child("main_settings", "pid_settings", "pid_constants"), []
|
|
409
|
+
):
|
|
410
|
+
Kp = self.settings["main_settings", "pid_settings", "pid_constants", "kp"]
|
|
411
|
+
Ki = self.settings["main_settings", "pid_settings", "pid_constants", "ki"]
|
|
412
|
+
Kd = self.settings["main_settings", "pid_settings", "pid_constants", "kd"]
|
|
413
|
+
self.command_pid.emit(
|
|
414
|
+
ThreadCommand("update_options", dict(tunings=(Kp, Ki, Kd)))
|
|
415
|
+
)
|
|
240
416
|
|
|
241
|
-
elif param.name() in putils.iter_children(
|
|
417
|
+
elif param.name() in putils.iter_children(
|
|
418
|
+
self.settings.child("models", "model_params"), []
|
|
419
|
+
):
|
|
242
420
|
if self.model_class is not None:
|
|
243
421
|
self.model_class.update_settings(param)
|
|
244
422
|
|
|
245
|
-
elif param.name() ==
|
|
423
|
+
elif param.name() == "detector_modules":
|
|
246
424
|
self.model_class.update_detector_names()
|
|
247
|
-
|
|
425
|
+
elif param.name() == "queue_length":
|
|
426
|
+
self.update_queues()
|
|
427
|
+
elif param.name() == "queue_weighting":
|
|
428
|
+
self.build_weight_array()
|
|
429
|
+
elif param.name() == "refresh_queue":
|
|
430
|
+
if param.value():
|
|
431
|
+
self.update_queues(refresh=True)
|
|
432
|
+
self.settings.child("main_settings", param.name()).setValue(False)
|
|
433
|
+
elif param.name() == "refresh_plot":
|
|
434
|
+
if param.value():
|
|
435
|
+
self.input_viewer.view.data_displayer.clear_data()
|
|
436
|
+
self.output_viewer.view.data_displayer.clear_data()
|
|
437
|
+
self.settings.child('main_settings', param.name()).setValue(False)
|
|
438
|
+
|
|
248
439
|
def connect_things(self):
|
|
249
|
-
logger.debug(
|
|
250
|
-
self.connect_action(
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
self.connect_action(
|
|
255
|
-
self.connect_action(
|
|
256
|
-
|
|
440
|
+
logger.debug("connecting actions and other")
|
|
441
|
+
self.connect_action(
|
|
442
|
+
"quit",
|
|
443
|
+
self.quit_fun,
|
|
444
|
+
)
|
|
445
|
+
self.connect_action("ini_model", self.ini_model)
|
|
446
|
+
self.connect_action("create_setp_actuators", self.create_setp_actuators)
|
|
447
|
+
self.connect_action("ini_pid", self.ini_PID)
|
|
448
|
+
self.connect_action("run", self.run_PID)
|
|
449
|
+
self.connect_action("pause", self.pause_PID)
|
|
450
|
+
logger.debug("connecting done")
|
|
257
451
|
|
|
258
452
|
def setup_actions(self):
|
|
259
|
-
logger.debug(
|
|
260
|
-
self.add_action(
|
|
261
|
-
self.add_widget(
|
|
262
|
-
self.add_action(
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
self.add_action(
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
self.
|
|
279
|
-
|
|
453
|
+
logger.debug("setting actions")
|
|
454
|
+
self.add_action("quit", "Quit", "close2", "Quit program")
|
|
455
|
+
self.add_widget("model_label", QtWidgets.QLabel, "Init Model:")
|
|
456
|
+
self.add_action(
|
|
457
|
+
"ini_model",
|
|
458
|
+
"Init Model",
|
|
459
|
+
"ini",
|
|
460
|
+
tip="Initialize the selected model: algo/data conversion",
|
|
461
|
+
)
|
|
462
|
+
self.add_widget("model_led", QLED, toolbar=self.toolbar)
|
|
463
|
+
|
|
464
|
+
self.add_action(
|
|
465
|
+
"create_setp_actuators",
|
|
466
|
+
"Create SetPoint Actuators",
|
|
467
|
+
"Add_Step",
|
|
468
|
+
tip="Create a DAQ_Move Control Module for each SetPoint allowing to"
|
|
469
|
+
"control them from the DashBoard, therefore within other extensions",
|
|
470
|
+
)
|
|
471
|
+
|
|
472
|
+
self.add_widget("model_label", QtWidgets.QLabel, "Init PID Runner:")
|
|
473
|
+
self.add_action(
|
|
474
|
+
"ini_pid",
|
|
475
|
+
"Init the PID loop",
|
|
476
|
+
"ini",
|
|
477
|
+
tip="Init the PID thread",
|
|
478
|
+
checkable=True,
|
|
479
|
+
)
|
|
480
|
+
self.add_widget("pid_led", QLED, toolbar=self.toolbar)
|
|
481
|
+
self.add_action(
|
|
482
|
+
"run",
|
|
483
|
+
"Run The PID loop",
|
|
484
|
+
"run2",
|
|
485
|
+
tip="run or stop the pid loop",
|
|
486
|
+
checkable=True,
|
|
487
|
+
)
|
|
488
|
+
self.add_action(
|
|
489
|
+
"pause",
|
|
490
|
+
"Pause the PID loop",
|
|
491
|
+
"pause",
|
|
492
|
+
tip="Pause the PID loop",
|
|
493
|
+
checkable=True,
|
|
494
|
+
)
|
|
495
|
+
self.set_action_checked("pause", True)
|
|
496
|
+
self.set_action_enabled("create_setp_actuators", False)
|
|
497
|
+
logger.debug("actions set")
|
|
280
498
|
|
|
281
499
|
def setup_docks(self):
|
|
282
|
-
logger.debug(
|
|
283
|
-
self.dock_pid = Dock(
|
|
500
|
+
logger.debug("settings the extension docks")
|
|
501
|
+
self.dock_pid = Dock("PID controller", self.dock_area)
|
|
284
502
|
self.dock_area.addDock(self.dock_pid)
|
|
285
503
|
|
|
286
504
|
widget = QtWidgets.QWidget()
|
|
@@ -290,28 +508,34 @@ class DAQ_PID(CustomExt):
|
|
|
290
508
|
self.toolbar_layout = QtWidgets.QGridLayout()
|
|
291
509
|
widget_toolbar.setLayout(self.toolbar_layout)
|
|
292
510
|
|
|
293
|
-
logger.debug(
|
|
511
|
+
logger.debug("settings the extension docks done")
|
|
294
512
|
|
|
295
|
-
labmaj = QtWidgets.QLabel(
|
|
513
|
+
labmaj = QtWidgets.QLabel("Setpoints:")
|
|
514
|
+
self.toolbar_layout.addWidget(labmaj, 3, 0, 1, 2)
|
|
515
|
+
labmaj = QtWidgets.QLabel("Value:")
|
|
516
|
+
self.toolbar_layout.addWidget(labmaj, 4, 0, 1, 2)
|
|
517
|
+
labmaj = QtWidgets.QLabel("Stability:")
|
|
296
518
|
self.toolbar_layout.addWidget(labmaj, 5, 0, 1, 2)
|
|
519
|
+
labmaj = QtWidgets.QLabel("Sync Value:")
|
|
520
|
+
self.toolbar_layout.addWidget(labmaj, 6, 0, 1, 2)
|
|
297
521
|
|
|
298
522
|
verlayout.addWidget(widget_toolbar)
|
|
299
523
|
verlayout.addWidget(self.settings_tree)
|
|
300
524
|
|
|
301
|
-
self.dock_output = Dock(
|
|
525
|
+
self.dock_output = Dock("PID output")
|
|
302
526
|
widget_output = QtWidgets.QWidget()
|
|
303
527
|
self.output_viewer = Viewer0D(widget_output)
|
|
304
528
|
self.dock_output.addWidget(widget_output)
|
|
305
|
-
self.dock_area.addDock(self.dock_output,
|
|
529
|
+
self.dock_area.addDock(self.dock_output, "right", self.dock_pid)
|
|
306
530
|
|
|
307
|
-
self.dock_input = Dock(
|
|
531
|
+
self.dock_input = Dock("PID input")
|
|
308
532
|
widget_input = QtWidgets.QWidget()
|
|
309
533
|
self.input_viewer = Viewer0D(widget_input)
|
|
310
534
|
self.dock_input.addWidget(widget_input)
|
|
311
|
-
self.dock_area.addDock(self.dock_input,
|
|
535
|
+
self.dock_area.addDock(self.dock_input, "bottom", self.dock_output)
|
|
312
536
|
|
|
313
537
|
if len(self.models) != 0:
|
|
314
|
-
self.get_set_model_params(self.models[0][
|
|
538
|
+
self.get_set_model_params(self.models[0]["name"])
|
|
315
539
|
|
|
316
540
|
self.dock_pid.addWidget(widget)
|
|
317
541
|
|
|
@@ -319,72 +543,105 @@ class DAQ_PID(CustomExt):
|
|
|
319
543
|
# Now that we have the module manager, load PID if it is checked in managers
|
|
320
544
|
try:
|
|
321
545
|
for setp in self.model_class.setpoints_names:
|
|
322
|
-
self.dashboard.add_move_from_extension(setp,
|
|
323
|
-
self.set_action_enabled(
|
|
546
|
+
self.dashboard.add_move_from_extension(setp, "PID", PIDController(self, setp))
|
|
547
|
+
self.set_action_enabled("create_setp_actuators", False)
|
|
324
548
|
|
|
325
549
|
except Exception as e:
|
|
326
|
-
raise PIDError(
|
|
327
|
-
|
|
550
|
+
raise PIDError(
|
|
551
|
+
"Could not load the PID extension and create setpoints actuators"
|
|
552
|
+
f"{str(e)}"
|
|
553
|
+
)
|
|
328
554
|
|
|
329
555
|
def get_set_model_params(self, model_name):
|
|
330
|
-
self.settings.child(
|
|
556
|
+
self.settings.child("models", "model_params").clearChildren()
|
|
331
557
|
models = get_models()
|
|
332
558
|
if len(models) > 0:
|
|
333
|
-
model_class = find_dict_in_list_from_key_val(models,
|
|
334
|
-
|
|
335
|
-
|
|
559
|
+
model_class = find_dict_in_list_from_key_val(models, "name", model_name)[
|
|
560
|
+
"class"
|
|
561
|
+
]
|
|
562
|
+
params = getattr(model_class, "params")
|
|
563
|
+
self.settings.child("models", "model_params").addChildren(params)
|
|
336
564
|
|
|
337
565
|
def run_PID(self):
|
|
338
|
-
if self.is_action_checked(
|
|
339
|
-
self.get_action(
|
|
340
|
-
self.command_pid.emit(ThreadCommand(
|
|
566
|
+
if self.is_action_checked("run"):
|
|
567
|
+
self.get_action("run").set_icon("stop")
|
|
568
|
+
self.command_pid.emit(ThreadCommand("start_PID", []))
|
|
341
569
|
QtWidgets.QApplication.processEvents()
|
|
342
570
|
|
|
343
571
|
QtWidgets.QApplication.processEvents()
|
|
344
572
|
|
|
345
|
-
self.command_pid.emit(
|
|
573
|
+
self.command_pid.emit(
|
|
574
|
+
ThreadCommand("run_PID", [np.zeros_like(self.model_class.curr_output)])
|
|
575
|
+
)
|
|
346
576
|
else:
|
|
347
|
-
self.get_action(
|
|
348
|
-
self.command_pid.emit(ThreadCommand(
|
|
577
|
+
self.get_action("run").set_icon("run2")
|
|
578
|
+
self.command_pid.emit(ThreadCommand("stop_PID"))
|
|
349
579
|
|
|
350
580
|
QtWidgets.QApplication.processEvents()
|
|
351
581
|
|
|
352
582
|
def pause_PID(self):
|
|
353
583
|
for setp in self.setpoints_sb:
|
|
354
|
-
setp.setEnabled(not self.is_action_checked(
|
|
355
|
-
self.command_pid.emit(
|
|
584
|
+
setp.setEnabled(not self.is_action_checked("pause"))
|
|
585
|
+
self.command_pid.emit(
|
|
586
|
+
ThreadCommand("pause_PID", [self.is_action_checked("pause")])
|
|
587
|
+
)
|
|
356
588
|
|
|
357
589
|
def stop_moves(self, overshoot):
|
|
358
590
|
"""
|
|
359
|
-
|
|
591
|
+
Foreach module of the move module object list, stop motion.
|
|
360
592
|
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
593
|
+
See Also
|
|
594
|
+
--------
|
|
595
|
+
stop_scan, DAQ_Move_main.daq_move.stop_Motion
|
|
364
596
|
"""
|
|
365
597
|
self.overshoot = overshoot
|
|
366
598
|
for mod in self.modules_manager.actuators:
|
|
367
599
|
mod.stop_Motion()
|
|
368
600
|
|
|
369
601
|
def set_model(self):
|
|
370
|
-
model_name = self.settings[
|
|
371
|
-
self.model_class = find_dict_in_list_from_key_val(
|
|
602
|
+
model_name = self.settings["models", "model_class"]
|
|
603
|
+
self.model_class: PIDModelGeneric = find_dict_in_list_from_key_val(
|
|
604
|
+
self.models, "name", model_name
|
|
605
|
+
)["class"](self)
|
|
372
606
|
self.set_setpoints_buttons()
|
|
607
|
+
self.update_queues(refresh=True)
|
|
373
608
|
self.model_class.ini_model()
|
|
374
|
-
self.settings.child(
|
|
609
|
+
self.settings.child("main_settings", "epsilon").setValue(
|
|
610
|
+
self.model_class.epsilon
|
|
611
|
+
)
|
|
612
|
+
|
|
613
|
+
def init_queues(self):
|
|
614
|
+
self.queue_points = {
|
|
615
|
+
setpoint_name: deque(maxlen=self.settings.child("main_settings", "queue_length").value())
|
|
616
|
+
for setpoint_name in self.model_class.setpoints_names
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
def update_queues(self, refresh=False):
|
|
620
|
+
if refresh:
|
|
621
|
+
self.init_queues()
|
|
622
|
+
else:
|
|
623
|
+
self.queue_points = {
|
|
624
|
+
setpoint_name: deque(queue, maxlen=self.settings.child("main_settings", "queue_length").value())
|
|
625
|
+
for queue, setpoint_name in zip(self.queue_points.values(), self.model_class.setpoints_names)
|
|
626
|
+
}
|
|
627
|
+
self.build_weight_array()
|
|
375
628
|
|
|
376
629
|
def ini_model(self):
|
|
377
630
|
try:
|
|
378
631
|
if self.model_class is None:
|
|
379
632
|
self.set_model()
|
|
380
633
|
|
|
381
|
-
self.modules_manager.selected_actuators_name =
|
|
382
|
-
|
|
634
|
+
self.modules_manager.selected_actuators_name = (
|
|
635
|
+
self.model_class.actuators_name
|
|
636
|
+
)
|
|
637
|
+
self.modules_manager.selected_detectors_name = (
|
|
638
|
+
self.model_class.detectors_name
|
|
639
|
+
)
|
|
383
640
|
|
|
384
641
|
self.enable_controls_pid(True)
|
|
385
|
-
self.get_action(
|
|
386
|
-
self.set_action_enabled(
|
|
387
|
-
self.set_action_enabled(
|
|
642
|
+
self.get_action("model_led").set_as_true()
|
|
643
|
+
self.set_action_enabled("ini_model", False)
|
|
644
|
+
self.set_action_enabled("create_setp_actuators", True)
|
|
388
645
|
|
|
389
646
|
except Exception as e:
|
|
390
647
|
logger.exception(str(e))
|
|
@@ -397,11 +654,13 @@ class DAQ_PID(CustomExt):
|
|
|
397
654
|
def setpoints(self, values):
|
|
398
655
|
for ind, sp in enumerate(self.setpoints_sb):
|
|
399
656
|
sp.setValue(values[ind])
|
|
657
|
+
self.update_queues(refresh=True) # Refresh queues when setpoints are updated
|
|
400
658
|
|
|
401
659
|
def setpoints_external(self, values_dict: Dict[str, DataActuator]):
|
|
402
660
|
for key in values_dict:
|
|
403
661
|
index = self.model_class.setpoints_names.index(key)
|
|
404
662
|
self.setpoints_sb[index].setValue(values_dict[key].value())
|
|
663
|
+
self.queue_points[key].clear() # Refresh queue when setpoint is updated
|
|
405
664
|
|
|
406
665
|
@property
|
|
407
666
|
def curr_points(self):
|
|
@@ -414,55 +673,84 @@ class DAQ_PID(CustomExt):
|
|
|
414
673
|
|
|
415
674
|
def emit_curr_points(self):
|
|
416
675
|
if self.model_class is not None:
|
|
417
|
-
self.curr_points_signal.emit(
|
|
676
|
+
self.curr_points_signal.emit(
|
|
677
|
+
dict(zip(self.model_class.setpoints_names, self.curr_points))
|
|
678
|
+
)
|
|
679
|
+
|
|
680
|
+
@property
|
|
681
|
+
def stab_points(self):
|
|
682
|
+
return [sp.value() for sp in self.stabpoints_sb]
|
|
683
|
+
|
|
684
|
+
@stab_points.setter
|
|
685
|
+
def stab_points(self, values):
|
|
686
|
+
for ind, sp in enumerate(self.stabpoints_sb):
|
|
687
|
+
sp.setValue(values[ind])
|
|
418
688
|
|
|
419
689
|
def set_setpoints_buttons(self):
|
|
420
690
|
self.setpoints_sb = []
|
|
421
691
|
self.currpoints_sb = []
|
|
692
|
+
self.stabpoints_sb = []
|
|
693
|
+
# self.meanpoints_sb = []
|
|
422
694
|
self.syncvalue_pb = []
|
|
423
695
|
for ind_set in range(self.model_class.Nsetpoints):
|
|
696
|
+
label = LabelWithFont(
|
|
697
|
+
self.model_class.setpoints_names[ind_set],
|
|
698
|
+
font_name="Tahoma",
|
|
699
|
+
font_size=14,
|
|
700
|
+
isbold=True,
|
|
701
|
+
isitalic=True,
|
|
702
|
+
)
|
|
703
|
+
col_ind = 2 + ind_set
|
|
704
|
+
|
|
705
|
+
self.toolbar_layout.addWidget(label, 2, col_ind, 1, 1)
|
|
706
|
+
|
|
707
|
+
self.setpoints_sb.append(self.make_spinbox(no_button=False))
|
|
708
|
+
self.toolbar_layout.addWidget(
|
|
709
|
+
self.setpoints_sb[-1], 3, col_ind, 1, 1
|
|
710
|
+
)
|
|
424
711
|
|
|
425
|
-
label = LabelWithFont(self.model_class.setpoints_names[ind_set],
|
|
426
|
-
font_name="Tahoma", font_size=14, isbold=True, isitalic=True)
|
|
427
|
-
|
|
428
|
-
self.setpoints_sb.append(SpinBox())
|
|
429
|
-
self.setpoints_sb[-1].setMinimumHeight(40)
|
|
430
|
-
font = self.setpoints_sb[-1].font()
|
|
431
|
-
font.setPointSizeF(20)
|
|
432
|
-
self.setpoints_sb[-1].setFont(font)
|
|
433
|
-
self.setpoints_sb[-1].setDecimals(6)
|
|
434
|
-
self.toolbar_layout.addWidget(label, 2, 2 + ind_set, 1, 1)
|
|
435
|
-
self.toolbar_layout.addWidget(self.setpoints_sb[-1], 3, 2 + ind_set, 1, 1)
|
|
436
712
|
self.setpoints_sb[-1].valueChanged.connect(self.update_runner_setpoints)
|
|
437
713
|
|
|
438
|
-
self.currpoints_sb.append(
|
|
439
|
-
self.
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
self.
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
self.syncvalue_pb.
|
|
449
|
-
|
|
450
|
-
|
|
714
|
+
self.currpoints_sb.append(self.make_spinbox())
|
|
715
|
+
self.toolbar_layout.addWidget(
|
|
716
|
+
self.currpoints_sb[-1], 4, col_ind, 1, 1
|
|
717
|
+
)
|
|
718
|
+
|
|
719
|
+
self.stabpoints_sb.append(self.make_spinbox())
|
|
720
|
+
self.toolbar_layout.addWidget(self.stabpoints_sb[-1], 5, col_ind, 1, 1)
|
|
721
|
+
self.syncvalue_pb.append(
|
|
722
|
+
QtWidgets.QPushButton("Synchro {}".format(ind_set))
|
|
723
|
+
)
|
|
724
|
+
self.syncvalue_pb[ind_set].clicked.connect(
|
|
725
|
+
partial(self.currpoint_as_setpoint, ind_set)
|
|
726
|
+
)
|
|
727
|
+
self.toolbar_layout.addWidget(self.syncvalue_pb[-1], 6, col_ind, 1, 1)
|
|
451
728
|
self.setpoints_signal.connect(self.setpoints_external)
|
|
452
729
|
|
|
730
|
+
def make_spinbox(self, no_button=True):
|
|
731
|
+
""" """
|
|
732
|
+
spinbox = SpinBox()
|
|
733
|
+
spinbox.setMinimumHeight(40)
|
|
734
|
+
font = spinbox.font()
|
|
735
|
+
font.setPointSizeF(20)
|
|
736
|
+
spinbox.setFont(font)
|
|
737
|
+
spinbox.setDecimals(6)
|
|
738
|
+
if no_button:
|
|
739
|
+
spinbox.setButtonSymbols(QtWidgets.QAbstractSpinBox.NoButtons)
|
|
740
|
+
return spinbox
|
|
741
|
+
|
|
453
742
|
def currpoint_as_setpoint(self, i=0):
|
|
454
|
-
|
|
743
|
+
"""
|
|
455
744
|
Function used by the sync buttons. The button i will attribute the value of the i-th currpoint to the i-th setpoint.
|
|
456
|
-
|
|
745
|
+
"""
|
|
457
746
|
self.setpoints_sb[i].setValue(self.curr_points[i])
|
|
458
|
-
self.update_runner_setpoints
|
|
747
|
+
self.update_runner_setpoints()
|
|
459
748
|
|
|
460
749
|
def quit_fun(self):
|
|
461
|
-
"""
|
|
462
|
-
"""
|
|
750
|
+
""" """
|
|
463
751
|
try:
|
|
464
752
|
try:
|
|
465
|
-
self.
|
|
753
|
+
self.exit_runner_thread()
|
|
466
754
|
except Exception as e:
|
|
467
755
|
print(e)
|
|
468
756
|
|
|
@@ -473,27 +761,35 @@ class DAQ_PID(CustomExt):
|
|
|
473
761
|
QThread.msleep(1000)
|
|
474
762
|
QtWidgets.QApplication.processEvents()
|
|
475
763
|
|
|
476
|
-
self.dock_area.parent().close()
|
|
764
|
+
self.dock_area.parent().close()
|
|
765
|
+
self.dashboard.remove_modules([setp for setp in self.model_class.setpoints_names])
|
|
477
766
|
|
|
478
767
|
except Exception as e:
|
|
479
768
|
print(e)
|
|
480
769
|
|
|
481
770
|
def update_runner_setpoints(self):
|
|
482
|
-
self.command_pid.emit(ThreadCommand(
|
|
771
|
+
self.command_pid.emit(ThreadCommand("update_setpoints", self.setpoints))
|
|
483
772
|
|
|
484
773
|
@Slot(list)
|
|
485
|
-
def thread_status(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
"""
|
|
774
|
+
def thread_status(
|
|
775
|
+
self, status
|
|
776
|
+
): # general function to get datas/infos from all threads back to the main
|
|
777
|
+
""" """
|
|
489
778
|
pass
|
|
490
779
|
|
|
491
780
|
|
|
492
781
|
class PIDRunner(QObject):
|
|
493
782
|
status_sig = Signal(list)
|
|
494
783
|
pid_output_signal = Signal(DataToExport)
|
|
495
|
-
|
|
496
|
-
|
|
784
|
+
time_elapsed_signal = Signal(float)
|
|
785
|
+
|
|
786
|
+
def __init__(
|
|
787
|
+
self,
|
|
788
|
+
model_class: PIDModelGeneric,
|
|
789
|
+
modules_manager: ModulesManager,
|
|
790
|
+
setpoints=[],
|
|
791
|
+
params=dict([]),
|
|
792
|
+
):
|
|
497
793
|
"""
|
|
498
794
|
Init the PID instance with params as initial conditions
|
|
499
795
|
|
|
@@ -508,22 +804,37 @@ class PIDRunner(QObject):
|
|
|
508
804
|
self.modules_manager = modules_manager
|
|
509
805
|
Nsetpoints = model_class.Nsetpoints
|
|
510
806
|
self.current_time = 0
|
|
511
|
-
self.
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
807
|
+
self.time_elapsed = 0 # Time elapsed in the running loop
|
|
808
|
+
self.inputs_from_dets = DataToExport(
|
|
809
|
+
"inputs",
|
|
810
|
+
data=[
|
|
811
|
+
DataCalculated(
|
|
812
|
+
self.model_class.setpoints_names[ind],
|
|
813
|
+
data=[np.array([setpoints[ind]])],
|
|
814
|
+
)
|
|
815
|
+
for ind in range(Nsetpoints)
|
|
816
|
+
],
|
|
817
|
+
)
|
|
818
|
+
self.outputs = [0.0 for _ in range(Nsetpoints)]
|
|
819
|
+
self.outputs_to_actuators = DataToActuators(
|
|
820
|
+
"pid",
|
|
821
|
+
mode="rel",
|
|
822
|
+
data=[
|
|
823
|
+
DataActuator(
|
|
824
|
+
self.model_class.actuators_name[ind], data=self.outputs[ind]
|
|
825
|
+
)
|
|
826
|
+
for ind in range(Nsetpoints)
|
|
827
|
+
],
|
|
828
|
+
)
|
|
829
|
+
|
|
830
|
+
if "sample_time" in params:
|
|
831
|
+
self.sample_time = params["sample_time"]
|
|
523
832
|
else:
|
|
524
833
|
self.sample_time = 0.010 # in secs
|
|
525
834
|
|
|
526
|
-
self.pids = [
|
|
835
|
+
self.pids = [
|
|
836
|
+
PID(setpoint=setpoints[0], **params) for ind in range(Nsetpoints)
|
|
837
|
+
] # #PID(object):
|
|
527
838
|
for pid in self.pids:
|
|
528
839
|
pid.set_auto_mode(False)
|
|
529
840
|
self.refreshing_ouput_time = 200
|
|
@@ -532,25 +843,29 @@ class PIDRunner(QObject):
|
|
|
532
843
|
|
|
533
844
|
self.paused = True
|
|
534
845
|
|
|
846
|
+
self.refresh_queues() # Initialize queues with the desired length
|
|
847
|
+
|
|
848
|
+
[queue_input.append(output) for queue_input, output in zip(self.queue_inputs, self.outputs)] # Prefill queues with initial output
|
|
849
|
+
self.clear_queues = True # Clear queues on first iteration
|
|
850
|
+
|
|
535
851
|
# self.timeout_timer = QtCore.QTimer()
|
|
536
852
|
# self.timeout_timer.setInterval(10000)
|
|
537
853
|
# self.timeout_scan_flag = False
|
|
538
854
|
# self.timeout_timer.timeout.connect(self.timeout)
|
|
539
855
|
#
|
|
540
856
|
def timerEvent(self, event):
|
|
541
|
-
outputs_dwa = self.outputs_to_actuators.merge_as_dwa(
|
|
857
|
+
outputs_dwa = self.outputs_to_actuators.merge_as_dwa("Data0D", name="outputs")
|
|
542
858
|
outputs_dwa.labels = self.modules_manager.selected_actuators_name
|
|
543
|
-
dte = DataToExport(
|
|
544
|
-
|
|
545
|
-
inputs_dwa = self.inputs_from_dets.merge_as_dwa('Data0D', name='inputs')
|
|
546
|
-
inputs_dwa.labels = self.modules_manager.selected_actuators_name
|
|
859
|
+
dte = DataToExport("toplot", data=[outputs_dwa])
|
|
860
|
+
inputs_dwa = DataRaw("inputs", data=[np.array(queue_input) for queue_input in self.queue_inputs], labels=self.modules_manager.selected_actuators_name)
|
|
547
861
|
dte.append(inputs_dwa)
|
|
548
|
-
self.pid_output_signal.emit(dte)
|
|
862
|
+
self.pid_output_signal.emit(dte)
|
|
863
|
+
self.time_elapsed_signal.emit(self.time_elapsed)
|
|
864
|
+
self.clear_queues = True
|
|
549
865
|
|
|
550
866
|
@Slot(ThreadCommand)
|
|
551
867
|
def queue_command(self, command: ThreadCommand):
|
|
552
|
-
"""
|
|
553
|
-
"""
|
|
868
|
+
""" """
|
|
554
869
|
if command.command == "start_PID":
|
|
555
870
|
self.start_PID(*command.attribute)
|
|
556
871
|
|
|
@@ -563,21 +878,23 @@ class PIDRunner(QObject):
|
|
|
563
878
|
elif command.command == "stop_PID":
|
|
564
879
|
self.stop_PID()
|
|
565
880
|
|
|
566
|
-
elif command.command ==
|
|
881
|
+
elif command.command == "update_options":
|
|
567
882
|
self.set_option(**command.attribute)
|
|
568
883
|
|
|
569
|
-
elif command.command ==
|
|
884
|
+
elif command.command == "update_setpoints":
|
|
570
885
|
self.update_setpoints(command.attribute)
|
|
571
886
|
|
|
572
|
-
elif command.command ==
|
|
887
|
+
elif command.command == "input":
|
|
573
888
|
self.update_input(*command.attribute)
|
|
574
889
|
|
|
575
|
-
elif command.command ==
|
|
576
|
-
if command.attribute[0] ==
|
|
890
|
+
elif command.command == "update_timer":
|
|
891
|
+
if command.attribute[0] == "refresh_plot_time":
|
|
577
892
|
self.killTimer(self.timer)
|
|
578
893
|
self.refreshing_ouput_time = command.attribute[1]
|
|
894
|
+
self.refresh_queues()
|
|
579
895
|
self.timer = self.startTimer(self.refreshing_ouput_time)
|
|
580
|
-
|
|
896
|
+
|
|
897
|
+
elif command.attribute[0] == "timeout":
|
|
581
898
|
self.timeout_timer.setInterval(command.attribute[1])
|
|
582
899
|
|
|
583
900
|
def update_input(self, measurements: DataToExport):
|
|
@@ -601,38 +918,47 @@ class PIDRunner(QObject):
|
|
|
601
918
|
self.modules_manager.connect_actuators()
|
|
602
919
|
|
|
603
920
|
self.current_time = time.perf_counter()
|
|
604
|
-
logger.info(
|
|
921
|
+
logger.info("PID loop starting")
|
|
605
922
|
while self.running:
|
|
606
|
-
# print('input: {}'.format(self.input))
|
|
607
923
|
# # GRAB DATA FIRST AND WAIT ALL DETECTORS RETURNED
|
|
608
|
-
|
|
609
924
|
self.det_done_datas: DataToExport = self.modules_manager.grab_datas()
|
|
610
925
|
|
|
611
|
-
self.inputs_from_dets: DataToExport = self.model_class.convert_input(
|
|
612
|
-
|
|
926
|
+
self.inputs_from_dets: DataToExport = self.model_class.convert_input(
|
|
927
|
+
self.det_done_datas
|
|
928
|
+
)
|
|
929
|
+
# # CHECK TIME ELAPSED FROM LAST LOOP
|
|
930
|
+
self.time_elapsed = (
|
|
931
|
+
time.perf_counter() - self.current_time
|
|
932
|
+
) # Time elapsed since last loop
|
|
933
|
+
sleep_time = self.sample_time - self.time_elapsed
|
|
934
|
+
if sleep_time > 0:
|
|
935
|
+
QThread.msleep(int(sleep_time * 1000))
|
|
613
936
|
# # EXECUTE THE PID
|
|
614
937
|
self.outputs = []
|
|
615
938
|
for ind, pid in enumerate(self.pids):
|
|
616
939
|
self.outputs.append(pid(float(self.inputs_from_dets[ind][0][0])))
|
|
617
940
|
|
|
618
|
-
|
|
619
|
-
if self.outputs is None:
|
|
620
|
-
self.outputs = [pid.setpoint for pid in self.pids]
|
|
941
|
+
self.current_time = time.perf_counter() # Update current time
|
|
621
942
|
|
|
622
|
-
|
|
943
|
+
# # APPLY THE PID OUTPUT TO THE ACTUATORS
|
|
623
944
|
self.outputs_to_actuators: DataToActuators = (
|
|
624
|
-
self.model_class.convert_output(self.outputs, dt
|
|
945
|
+
self.model_class.convert_output(self.outputs, dt=None)
|
|
946
|
+
)
|
|
947
|
+
if self.clear_queues:
|
|
948
|
+
[queue_input.clear() for queue_input in self.queue_inputs]
|
|
949
|
+
self.clear_queues = False
|
|
950
|
+
for data_input, queue_input in zip(self.inputs_from_dets.data, self.queue_inputs):
|
|
951
|
+
queue_input.append(data_input[0][0])
|
|
625
952
|
|
|
626
953
|
if not self.paused:
|
|
627
|
-
self.modules_manager.move_actuators(
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
954
|
+
self.modules_manager.move_actuators(
|
|
955
|
+
self.outputs_to_actuators,
|
|
956
|
+
self.outputs_to_actuators.mode,
|
|
957
|
+
polling=False,
|
|
958
|
+
)
|
|
632
959
|
QtWidgets.QApplication.processEvents()
|
|
633
|
-
QThread.msleep(int(self.sample_time * 1000))
|
|
634
960
|
|
|
635
|
-
logger.info(
|
|
961
|
+
logger.info("PID loop exiting")
|
|
636
962
|
self.modules_manager.connect_actuators(False)
|
|
637
963
|
self.modules_manager.connect_detectors(False)
|
|
638
964
|
|
|
@@ -643,17 +969,23 @@ class PIDRunner(QObject):
|
|
|
643
969
|
for ind, pid in enumerate(self.pids):
|
|
644
970
|
pid.setpoint = setpoints[ind]
|
|
645
971
|
|
|
972
|
+
def refresh_queues(self):
|
|
973
|
+
self.queue_length = 5 * int(self.refreshing_ouput_time * 1e-3 / self.sample_time) # Queue length is approximately output_time/sampling_time (*5 for safety)
|
|
974
|
+
self.queue_inputs = [deque(maxlen=self.queue_length) for ind in range(len(self.outputs))]
|
|
975
|
+
|
|
646
976
|
def set_option(self, **option):
|
|
647
977
|
for pid in self.pids:
|
|
648
978
|
for key in option:
|
|
649
979
|
if hasattr(pid, key):
|
|
650
|
-
if key ==
|
|
651
|
-
|
|
980
|
+
if key == "sample_time":
|
|
981
|
+
self.sample_time = option[key] / 1000
|
|
982
|
+
setattr(pid, key, self.sample_time)
|
|
983
|
+
self.refresh_queues()
|
|
652
984
|
else:
|
|
653
985
|
setattr(pid, key, option[key])
|
|
654
986
|
|
|
655
987
|
def run_PID(self, last_values):
|
|
656
|
-
logger.info(
|
|
988
|
+
logger.info("Stabilization started")
|
|
657
989
|
for ind, pid in enumerate(self.pids):
|
|
658
990
|
pid.set_auto_mode(True, last_values[ind])
|
|
659
991
|
|
|
@@ -661,30 +993,24 @@ class PIDRunner(QObject):
|
|
|
661
993
|
for ind, pid in enumerate(self.pids):
|
|
662
994
|
if pause_state:
|
|
663
995
|
pid.set_auto_mode(False)
|
|
664
|
-
logger.info(
|
|
996
|
+
logger.info("Stabilization paused")
|
|
665
997
|
else:
|
|
666
998
|
pid.set_auto_mode(True, self.outputs[ind])
|
|
667
|
-
logger.info(
|
|
999
|
+
logger.info("Stabilization restarted from pause")
|
|
668
1000
|
self.paused = pause_state
|
|
669
1001
|
|
|
670
1002
|
def stop_PID(self):
|
|
671
1003
|
self.running = False
|
|
672
|
-
logger.info(
|
|
1004
|
+
logger.info("PID loop exiting")
|
|
673
1005
|
|
|
674
1006
|
|
|
675
|
-
if __name__ ==
|
|
1007
|
+
if __name__ == "__main__":
|
|
676
1008
|
from pymodaq_gui.utils.utils import mkQApp
|
|
677
1009
|
from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
|
|
678
1010
|
|
|
679
|
-
app = mkQApp(
|
|
680
|
-
preset_file_name = config(
|
|
1011
|
+
app = mkQApp("DAQ_PID")
|
|
1012
|
+
preset_file_name = config("presets", f"default_preset_for_pid")
|
|
681
1013
|
|
|
682
|
-
dashboard, extension, win = load_dashboard_with_preset(preset_file_name,
|
|
1014
|
+
dashboard, extension, win = load_dashboard_with_preset(preset_file_name, "DAQ_PID")
|
|
683
1015
|
|
|
684
1016
|
app.exec()
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|