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,1016 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from functools import partial # needed for the button to sync setpoint with currpoint
|
|
3
|
+
from typing import Dict, List, TYPE_CHECKING
|
|
4
|
+
from collections import deque
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from qtpy import QtGui, QtWidgets
|
|
8
|
+
from qtpy.QtCore import QObject, Slot, QThread, Signal
|
|
9
|
+
|
|
10
|
+
from simple_pid import PID
|
|
11
|
+
|
|
12
|
+
from pymodaq_utils.logger import set_logger, get_module_name
|
|
13
|
+
from pymodaq_utils.utils import ThreadCommand, find_dict_in_list_from_key_val
|
|
14
|
+
from pymodaq.utils.exceptions import DetectorError, ActuatorError, PIDError
|
|
15
|
+
|
|
16
|
+
from pymodaq_gui.parameter import utils as putils
|
|
17
|
+
from pymodaq_gui.parameter import Parameter, ParameterTree
|
|
18
|
+
from pymodaq_gui.plotting.data_viewers.viewer0D import Viewer0D
|
|
19
|
+
from pymodaq_gui.utils.widgets import QLED, LabelWithFont, SpinBox
|
|
20
|
+
from pymodaq_gui.utils.dock import DockArea, Dock
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
from pymodaq_data.data import DataToExport, DataCalculated, DataRaw
|
|
24
|
+
from pymodaq_utils.config import Config
|
|
25
|
+
|
|
26
|
+
from pymodaq.utils.managers.modules_manager import ModulesManager
|
|
27
|
+
from pymodaq.extensions.pid.utils import get_models
|
|
28
|
+
from pymodaq.utils.data import DataActuator, DataToActuators
|
|
29
|
+
from pymodaq.extensions.pid.actuator_controller import PIDController
|
|
30
|
+
from pymodaq.extensions.pid.utils import PIDModelGeneric
|
|
31
|
+
|
|
32
|
+
from pymodaq.extensions.utils import CustomExt
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from pymodaq.control_modules.daq_move import DAQ_Move
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
config = Config()
|
|
39
|
+
logger = set_logger(get_module_name(__file__))
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class DAQ_PID(CustomExt):
|
|
43
|
+
""" """
|
|
44
|
+
|
|
45
|
+
command_pid = Signal(ThreadCommand)
|
|
46
|
+
curr_points_signal = Signal(dict)
|
|
47
|
+
setpoints_signal = Signal(dict)
|
|
48
|
+
emit_curr_points_sig = Signal()
|
|
49
|
+
|
|
50
|
+
models = get_models()
|
|
51
|
+
|
|
52
|
+
params = [
|
|
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
|
+
},
|
|
84
|
+
# here only to be compatible with DAQ_Scan, the model could update it
|
|
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
|
+
},
|
|
232
|
+
]
|
|
233
|
+
|
|
234
|
+
def __init__(self, dockarea, dashboard):
|
|
235
|
+
super().__init__(dockarea, dashboard)
|
|
236
|
+
|
|
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"
|
|
244
|
+
|
|
245
|
+
self.initialized_state = False
|
|
246
|
+
self.model_class: PIDModelGeneric = None
|
|
247
|
+
self._curr_points = dict([])
|
|
248
|
+
self._setpoints = dict([])
|
|
249
|
+
self.queue_points = dict([])
|
|
250
|
+
self.build_weight_array()
|
|
251
|
+
|
|
252
|
+
self.dock_area = dockarea
|
|
253
|
+
self.check_moving = False
|
|
254
|
+
self.setup_ui()
|
|
255
|
+
|
|
256
|
+
self.enable_controls_pid(False)
|
|
257
|
+
|
|
258
|
+
self.enable_controls_pid_run(False)
|
|
259
|
+
|
|
260
|
+
self.emit_curr_points_sig.connect(self.emit_curr_points)
|
|
261
|
+
|
|
262
|
+
def ini_PID(self):
|
|
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
|
|
291
|
+
pid_runner.pid_output_signal.connect(self.process_output)
|
|
292
|
+
pid_runner.time_elapsed_signal.connect(self.display_time_elapsed)
|
|
293
|
+
pid_runner.status_sig.connect(self.thread_status)
|
|
294
|
+
self.command_pid.connect(pid_runner.queue_command)
|
|
295
|
+
|
|
296
|
+
pid_runner.moveToThread(self.runner_thread)
|
|
297
|
+
|
|
298
|
+
self.runner_thread.start()
|
|
299
|
+
self.get_action("pid_led").set_as_true()
|
|
300
|
+
self.enable_controls_pid_run(True)
|
|
301
|
+
|
|
302
|
+
else:
|
|
303
|
+
if hasattr(self, "runner_thread"):
|
|
304
|
+
self.exit_runner_thread()
|
|
305
|
+
self.get_action("pid_led").set_as_false()
|
|
306
|
+
self.enable_controls_pid_run(False)
|
|
307
|
+
|
|
308
|
+
self.initialized_state = True
|
|
309
|
+
|
|
310
|
+
def process_output(self, data: DataToExport):
|
|
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
|
+
]
|
|
328
|
+
self.output_viewer.show_data(outputs)
|
|
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)
|
|
348
|
+
|
|
349
|
+
def enable_controls_pid(self, enable=False):
|
|
350
|
+
self.set_action_enabled("ini_pid", enable)
|
|
351
|
+
# self.setpoint_sb.setOpts(enabled=enable)
|
|
352
|
+
|
|
353
|
+
def enable_controls_pid_run(self, enable=False):
|
|
354
|
+
self.set_action_enabled("run", enable)
|
|
355
|
+
self.set_action_enabled("pause", enable)
|
|
356
|
+
|
|
357
|
+
def setup_menu(self, menubar: QtWidgets.QMenuBar = None):
|
|
358
|
+
"""
|
|
359
|
+
to be subclassed
|
|
360
|
+
create menu for actions contained into the self.actions_manager, for instance:
|
|
361
|
+
|
|
362
|
+
For instance:
|
|
363
|
+
|
|
364
|
+
file_menu = self.menubar.addMenu('File')
|
|
365
|
+
self.actions_manager.affect_to('load', file_menu)
|
|
366
|
+
self.actions_manager.affect_to('save', file_menu)
|
|
367
|
+
|
|
368
|
+
file_menu.addSeparator()
|
|
369
|
+
self.actions_manager.affect_to('quit', file_menu)
|
|
370
|
+
"""
|
|
371
|
+
pass
|
|
372
|
+
|
|
373
|
+
def value_changed(self, param):
|
|
374
|
+
"""to be subclassed for actions to perform when one of the param's value in self.settings is changed
|
|
375
|
+
|
|
376
|
+
For instance:
|
|
377
|
+
if param.name() == 'do_something':
|
|
378
|
+
if param.value():
|
|
379
|
+
print('Do something')
|
|
380
|
+
self.settings.child('main_settings', 'something_done').setValue(False)
|
|
381
|
+
|
|
382
|
+
Parameters
|
|
383
|
+
----------
|
|
384
|
+
param: (Parameter) the parameter whose value just changed
|
|
385
|
+
"""
|
|
386
|
+
if param.name() == "model_class":
|
|
387
|
+
self.get_set_model_params(param.value())
|
|
388
|
+
|
|
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
|
+
)
|
|
393
|
+
|
|
394
|
+
elif param.name() == "sample_time":
|
|
395
|
+
self.command_pid.emit(
|
|
396
|
+
ThreadCommand("update_options", dict(sample_time=param.value()))
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
elif param.name() in putils.iter_children(
|
|
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
|
+
)
|
|
406
|
+
|
|
407
|
+
elif param.name() in putils.iter_children(
|
|
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
|
+
)
|
|
416
|
+
|
|
417
|
+
elif param.name() in putils.iter_children(
|
|
418
|
+
self.settings.child("models", "model_params"), []
|
|
419
|
+
):
|
|
420
|
+
if self.model_class is not None:
|
|
421
|
+
self.model_class.update_settings(param)
|
|
422
|
+
|
|
423
|
+
elif param.name() == "detector_modules":
|
|
424
|
+
self.model_class.update_detector_names()
|
|
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
|
+
|
|
439
|
+
def connect_things(self):
|
|
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")
|
|
451
|
+
|
|
452
|
+
def setup_actions(self):
|
|
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")
|
|
498
|
+
|
|
499
|
+
def setup_docks(self):
|
|
500
|
+
logger.debug("settings the extension docks")
|
|
501
|
+
self.dock_pid = Dock("PID controller", self.dock_area)
|
|
502
|
+
self.dock_area.addDock(self.dock_pid)
|
|
503
|
+
|
|
504
|
+
widget = QtWidgets.QWidget()
|
|
505
|
+
widget_toolbar = QtWidgets.QWidget()
|
|
506
|
+
verlayout = QtWidgets.QVBoxLayout()
|
|
507
|
+
widget.setLayout(verlayout)
|
|
508
|
+
self.toolbar_layout = QtWidgets.QGridLayout()
|
|
509
|
+
widget_toolbar.setLayout(self.toolbar_layout)
|
|
510
|
+
|
|
511
|
+
logger.debug("settings the extension docks done")
|
|
512
|
+
|
|
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:")
|
|
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)
|
|
521
|
+
|
|
522
|
+
verlayout.addWidget(widget_toolbar)
|
|
523
|
+
verlayout.addWidget(self.settings_tree)
|
|
524
|
+
|
|
525
|
+
self.dock_output = Dock("PID output")
|
|
526
|
+
widget_output = QtWidgets.QWidget()
|
|
527
|
+
self.output_viewer = Viewer0D(widget_output)
|
|
528
|
+
self.dock_output.addWidget(widget_output)
|
|
529
|
+
self.dock_area.addDock(self.dock_output, "right", self.dock_pid)
|
|
530
|
+
|
|
531
|
+
self.dock_input = Dock("PID input")
|
|
532
|
+
widget_input = QtWidgets.QWidget()
|
|
533
|
+
self.input_viewer = Viewer0D(widget_input)
|
|
534
|
+
self.dock_input.addWidget(widget_input)
|
|
535
|
+
self.dock_area.addDock(self.dock_input, "bottom", self.dock_output)
|
|
536
|
+
|
|
537
|
+
if len(self.models) != 0:
|
|
538
|
+
self.get_set_model_params(self.models[0]["name"])
|
|
539
|
+
|
|
540
|
+
self.dock_pid.addWidget(widget)
|
|
541
|
+
|
|
542
|
+
def create_setp_actuators(self):
|
|
543
|
+
# Now that we have the module manager, load PID if it is checked in managers
|
|
544
|
+
try:
|
|
545
|
+
for setp in self.model_class.setpoints_names:
|
|
546
|
+
self.dashboard.add_move_from_extension(setp, "PID", PIDController(self, setp))
|
|
547
|
+
self.set_action_enabled("create_setp_actuators", False)
|
|
548
|
+
|
|
549
|
+
except Exception as e:
|
|
550
|
+
raise PIDError(
|
|
551
|
+
"Could not load the PID extension and create setpoints actuators"
|
|
552
|
+
f"{str(e)}"
|
|
553
|
+
)
|
|
554
|
+
|
|
555
|
+
def get_set_model_params(self, model_name):
|
|
556
|
+
self.settings.child("models", "model_params").clearChildren()
|
|
557
|
+
models = get_models()
|
|
558
|
+
if len(models) > 0:
|
|
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)
|
|
564
|
+
|
|
565
|
+
def run_PID(self):
|
|
566
|
+
if self.is_action_checked("run"):
|
|
567
|
+
self.get_action("run").set_icon("stop")
|
|
568
|
+
self.command_pid.emit(ThreadCommand("start_PID", []))
|
|
569
|
+
QtWidgets.QApplication.processEvents()
|
|
570
|
+
|
|
571
|
+
QtWidgets.QApplication.processEvents()
|
|
572
|
+
|
|
573
|
+
self.command_pid.emit(
|
|
574
|
+
ThreadCommand("run_PID", [np.zeros_like(self.model_class.curr_output)])
|
|
575
|
+
)
|
|
576
|
+
else:
|
|
577
|
+
self.get_action("run").set_icon("run2")
|
|
578
|
+
self.command_pid.emit(ThreadCommand("stop_PID"))
|
|
579
|
+
|
|
580
|
+
QtWidgets.QApplication.processEvents()
|
|
581
|
+
|
|
582
|
+
def pause_PID(self):
|
|
583
|
+
for setp in self.setpoints_sb:
|
|
584
|
+
setp.setEnabled(not self.is_action_checked("pause"))
|
|
585
|
+
self.command_pid.emit(
|
|
586
|
+
ThreadCommand("pause_PID", [self.is_action_checked("pause")])
|
|
587
|
+
)
|
|
588
|
+
|
|
589
|
+
def stop_moves(self, overshoot):
|
|
590
|
+
"""
|
|
591
|
+
Foreach module of the move module object list, stop motion.
|
|
592
|
+
|
|
593
|
+
See Also
|
|
594
|
+
--------
|
|
595
|
+
stop_scan, DAQ_Move_main.daq_move.stop_Motion
|
|
596
|
+
"""
|
|
597
|
+
self.overshoot = overshoot
|
|
598
|
+
for mod in self.modules_manager.actuators:
|
|
599
|
+
mod.stop_Motion()
|
|
600
|
+
|
|
601
|
+
def set_model(self):
|
|
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)
|
|
606
|
+
self.set_setpoints_buttons()
|
|
607
|
+
self.update_queues(refresh=True)
|
|
608
|
+
self.model_class.ini_model()
|
|
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()
|
|
628
|
+
|
|
629
|
+
def ini_model(self):
|
|
630
|
+
try:
|
|
631
|
+
if self.model_class is None:
|
|
632
|
+
self.set_model()
|
|
633
|
+
|
|
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
|
+
)
|
|
640
|
+
|
|
641
|
+
self.enable_controls_pid(True)
|
|
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)
|
|
645
|
+
|
|
646
|
+
except Exception as e:
|
|
647
|
+
logger.exception(str(e))
|
|
648
|
+
|
|
649
|
+
@property
|
|
650
|
+
def setpoints(self):
|
|
651
|
+
return [sp.value() for sp in self.setpoints_sb]
|
|
652
|
+
|
|
653
|
+
@setpoints.setter
|
|
654
|
+
def setpoints(self, values):
|
|
655
|
+
for ind, sp in enumerate(self.setpoints_sb):
|
|
656
|
+
sp.setValue(values[ind])
|
|
657
|
+
self.update_queues(refresh=True) # Refresh queues when setpoints are updated
|
|
658
|
+
|
|
659
|
+
def setpoints_external(self, values_dict: Dict[str, DataActuator]):
|
|
660
|
+
for key in values_dict:
|
|
661
|
+
index = self.model_class.setpoints_names.index(key)
|
|
662
|
+
self.setpoints_sb[index].setValue(values_dict[key].value())
|
|
663
|
+
self.queue_points[key].clear() # Refresh queue when setpoint is updated
|
|
664
|
+
|
|
665
|
+
@property
|
|
666
|
+
def curr_points(self):
|
|
667
|
+
return [sp.value() for sp in self.currpoints_sb]
|
|
668
|
+
|
|
669
|
+
@curr_points.setter
|
|
670
|
+
def curr_points(self, values):
|
|
671
|
+
for ind, sp in enumerate(self.currpoints_sb):
|
|
672
|
+
sp.setValue(values[ind])
|
|
673
|
+
|
|
674
|
+
def emit_curr_points(self):
|
|
675
|
+
if self.model_class is not None:
|
|
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])
|
|
688
|
+
|
|
689
|
+
def set_setpoints_buttons(self):
|
|
690
|
+
self.setpoints_sb = []
|
|
691
|
+
self.currpoints_sb = []
|
|
692
|
+
self.stabpoints_sb = []
|
|
693
|
+
# self.meanpoints_sb = []
|
|
694
|
+
self.syncvalue_pb = []
|
|
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
|
+
)
|
|
711
|
+
|
|
712
|
+
self.setpoints_sb[-1].valueChanged.connect(self.update_runner_setpoints)
|
|
713
|
+
|
|
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)
|
|
728
|
+
self.setpoints_signal.connect(self.setpoints_external)
|
|
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
|
+
|
|
742
|
+
def currpoint_as_setpoint(self, i=0):
|
|
743
|
+
"""
|
|
744
|
+
Function used by the sync buttons. The button i will attribute the value of the i-th currpoint to the i-th setpoint.
|
|
745
|
+
"""
|
|
746
|
+
self.setpoints_sb[i].setValue(self.curr_points[i])
|
|
747
|
+
self.update_runner_setpoints()
|
|
748
|
+
|
|
749
|
+
def quit_fun(self):
|
|
750
|
+
""" """
|
|
751
|
+
try:
|
|
752
|
+
try:
|
|
753
|
+
self.exit_runner_thread()
|
|
754
|
+
except Exception as e:
|
|
755
|
+
print(e)
|
|
756
|
+
|
|
757
|
+
areas = self.dock_area.tempAreas[:]
|
|
758
|
+
for area in areas:
|
|
759
|
+
area.win.close()
|
|
760
|
+
QtWidgets.QApplication.processEvents()
|
|
761
|
+
QThread.msleep(1000)
|
|
762
|
+
QtWidgets.QApplication.processEvents()
|
|
763
|
+
|
|
764
|
+
self.dock_area.parent().close()
|
|
765
|
+
self.dashboard.remove_modules([setp for setp in self.model_class.setpoints_names])
|
|
766
|
+
|
|
767
|
+
except Exception as e:
|
|
768
|
+
print(e)
|
|
769
|
+
|
|
770
|
+
def update_runner_setpoints(self):
|
|
771
|
+
self.command_pid.emit(ThreadCommand("update_setpoints", self.setpoints))
|
|
772
|
+
|
|
773
|
+
@Slot(list)
|
|
774
|
+
def thread_status(
|
|
775
|
+
self, status
|
|
776
|
+
): # general function to get datas/infos from all threads back to the main
|
|
777
|
+
""" """
|
|
778
|
+
pass
|
|
779
|
+
|
|
780
|
+
|
|
781
|
+
class PIDRunner(QObject):
|
|
782
|
+
status_sig = Signal(list)
|
|
783
|
+
pid_output_signal = Signal(DataToExport)
|
|
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
|
+
):
|
|
793
|
+
"""
|
|
794
|
+
Init the PID instance with params as initial conditions
|
|
795
|
+
|
|
796
|
+
Parameters
|
|
797
|
+
----------
|
|
798
|
+
params: (dict) Kp=1.0, Ki=0.0, Kd=0.0,setpoints=[0], sample_time=0.01, output_limits=(None, None),
|
|
799
|
+
auto_mode=True,
|
|
800
|
+
proportional_on_measurement=False)
|
|
801
|
+
"""
|
|
802
|
+
super().__init__()
|
|
803
|
+
self.model_class = model_class
|
|
804
|
+
self.modules_manager = modules_manager
|
|
805
|
+
Nsetpoints = model_class.Nsetpoints
|
|
806
|
+
self.current_time = 0
|
|
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"]
|
|
832
|
+
else:
|
|
833
|
+
self.sample_time = 0.010 # in secs
|
|
834
|
+
|
|
835
|
+
self.pids = [
|
|
836
|
+
PID(setpoint=setpoints[0], **params) for ind in range(Nsetpoints)
|
|
837
|
+
] # #PID(object):
|
|
838
|
+
for pid in self.pids:
|
|
839
|
+
pid.set_auto_mode(False)
|
|
840
|
+
self.refreshing_ouput_time = 200
|
|
841
|
+
self.running = True
|
|
842
|
+
self.timer = self.startTimer(self.refreshing_ouput_time)
|
|
843
|
+
|
|
844
|
+
self.paused = True
|
|
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
|
+
|
|
851
|
+
# self.timeout_timer = QtCore.QTimer()
|
|
852
|
+
# self.timeout_timer.setInterval(10000)
|
|
853
|
+
# self.timeout_scan_flag = False
|
|
854
|
+
# self.timeout_timer.timeout.connect(self.timeout)
|
|
855
|
+
#
|
|
856
|
+
def timerEvent(self, event):
|
|
857
|
+
outputs_dwa = self.outputs_to_actuators.merge_as_dwa("Data0D", name="outputs")
|
|
858
|
+
outputs_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)
|
|
861
|
+
dte.append(inputs_dwa)
|
|
862
|
+
self.pid_output_signal.emit(dte)
|
|
863
|
+
self.time_elapsed_signal.emit(self.time_elapsed)
|
|
864
|
+
self.clear_queues = True
|
|
865
|
+
|
|
866
|
+
@Slot(ThreadCommand)
|
|
867
|
+
def queue_command(self, command: ThreadCommand):
|
|
868
|
+
""" """
|
|
869
|
+
if command.command == "start_PID":
|
|
870
|
+
self.start_PID(*command.attribute)
|
|
871
|
+
|
|
872
|
+
elif command.command == "run_PID":
|
|
873
|
+
self.run_PID(*command.attribute)
|
|
874
|
+
|
|
875
|
+
elif command.command == "pause_PID":
|
|
876
|
+
self.pause_PID(*command.attribute)
|
|
877
|
+
|
|
878
|
+
elif command.command == "stop_PID":
|
|
879
|
+
self.stop_PID()
|
|
880
|
+
|
|
881
|
+
elif command.command == "update_options":
|
|
882
|
+
self.set_option(**command.attribute)
|
|
883
|
+
|
|
884
|
+
elif command.command == "update_setpoints":
|
|
885
|
+
self.update_setpoints(command.attribute)
|
|
886
|
+
|
|
887
|
+
elif command.command == "input":
|
|
888
|
+
self.update_input(*command.attribute)
|
|
889
|
+
|
|
890
|
+
elif command.command == "update_timer":
|
|
891
|
+
if command.attribute[0] == "refresh_plot_time":
|
|
892
|
+
self.killTimer(self.timer)
|
|
893
|
+
self.refreshing_ouput_time = command.attribute[1]
|
|
894
|
+
self.refresh_queues()
|
|
895
|
+
self.timer = self.startTimer(self.refreshing_ouput_time)
|
|
896
|
+
|
|
897
|
+
elif command.attribute[0] == "timeout":
|
|
898
|
+
self.timeout_timer.setInterval(command.attribute[1])
|
|
899
|
+
|
|
900
|
+
def update_input(self, measurements: DataToExport):
|
|
901
|
+
self.inputs_from_dets = self.model_class.convert_input(measurements)
|
|
902
|
+
|
|
903
|
+
def start_PID(self, sync_detectors=True, sync_acts=False):
|
|
904
|
+
"""Start the pid controller loop
|
|
905
|
+
|
|
906
|
+
Parameters
|
|
907
|
+
----------
|
|
908
|
+
sync_detectors: (bool) if True will make sure all selected detectors (if any) all got their data before calling
|
|
909
|
+
the model
|
|
910
|
+
sync_acts: (bool) if True will make sure all selected actuators (if any) all reached their target position
|
|
911
|
+
before calling the model
|
|
912
|
+
"""
|
|
913
|
+
self.running = True
|
|
914
|
+
try:
|
|
915
|
+
if sync_detectors:
|
|
916
|
+
self.modules_manager.connect_detectors()
|
|
917
|
+
if sync_acts:
|
|
918
|
+
self.modules_manager.connect_actuators()
|
|
919
|
+
|
|
920
|
+
self.current_time = time.perf_counter()
|
|
921
|
+
logger.info("PID loop starting")
|
|
922
|
+
while self.running:
|
|
923
|
+
# # GRAB DATA FIRST AND WAIT ALL DETECTORS RETURNED
|
|
924
|
+
self.det_done_datas: DataToExport = self.modules_manager.grab_datas()
|
|
925
|
+
|
|
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))
|
|
936
|
+
# # EXECUTE THE PID
|
|
937
|
+
self.outputs = []
|
|
938
|
+
for ind, pid in enumerate(self.pids):
|
|
939
|
+
self.outputs.append(pid(float(self.inputs_from_dets[ind][0][0])))
|
|
940
|
+
|
|
941
|
+
self.current_time = time.perf_counter() # Update current time
|
|
942
|
+
|
|
943
|
+
# # APPLY THE PID OUTPUT TO THE ACTUATORS
|
|
944
|
+
self.outputs_to_actuators: DataToActuators = (
|
|
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])
|
|
952
|
+
|
|
953
|
+
if not self.paused:
|
|
954
|
+
self.modules_manager.move_actuators(
|
|
955
|
+
self.outputs_to_actuators,
|
|
956
|
+
self.outputs_to_actuators.mode,
|
|
957
|
+
polling=False,
|
|
958
|
+
)
|
|
959
|
+
QtWidgets.QApplication.processEvents()
|
|
960
|
+
|
|
961
|
+
logger.info("PID loop exiting")
|
|
962
|
+
self.modules_manager.connect_actuators(False)
|
|
963
|
+
self.modules_manager.connect_detectors(False)
|
|
964
|
+
|
|
965
|
+
except Exception as e:
|
|
966
|
+
logger.exception(str(e))
|
|
967
|
+
|
|
968
|
+
def update_setpoints(self, setpoints):
|
|
969
|
+
for ind, pid in enumerate(self.pids):
|
|
970
|
+
pid.setpoint = setpoints[ind]
|
|
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
|
+
|
|
976
|
+
def set_option(self, **option):
|
|
977
|
+
for pid in self.pids:
|
|
978
|
+
for key in option:
|
|
979
|
+
if hasattr(pid, key):
|
|
980
|
+
if key == "sample_time":
|
|
981
|
+
self.sample_time = option[key] / 1000
|
|
982
|
+
setattr(pid, key, self.sample_time)
|
|
983
|
+
self.refresh_queues()
|
|
984
|
+
else:
|
|
985
|
+
setattr(pid, key, option[key])
|
|
986
|
+
|
|
987
|
+
def run_PID(self, last_values):
|
|
988
|
+
logger.info("Stabilization started")
|
|
989
|
+
for ind, pid in enumerate(self.pids):
|
|
990
|
+
pid.set_auto_mode(True, last_values[ind])
|
|
991
|
+
|
|
992
|
+
def pause_PID(self, pause_state):
|
|
993
|
+
for ind, pid in enumerate(self.pids):
|
|
994
|
+
if pause_state:
|
|
995
|
+
pid.set_auto_mode(False)
|
|
996
|
+
logger.info("Stabilization paused")
|
|
997
|
+
else:
|
|
998
|
+
pid.set_auto_mode(True, self.outputs[ind])
|
|
999
|
+
logger.info("Stabilization restarted from pause")
|
|
1000
|
+
self.paused = pause_state
|
|
1001
|
+
|
|
1002
|
+
def stop_PID(self):
|
|
1003
|
+
self.running = False
|
|
1004
|
+
logger.info("PID loop exiting")
|
|
1005
|
+
|
|
1006
|
+
|
|
1007
|
+
if __name__ == "__main__":
|
|
1008
|
+
from pymodaq_gui.utils.utils import mkQApp
|
|
1009
|
+
from pymodaq.utils.gui_utils.loader_utils import load_dashboard_with_preset
|
|
1010
|
+
|
|
1011
|
+
app = mkQApp("DAQ_PID")
|
|
1012
|
+
preset_file_name = config("presets", f"default_preset_for_pid")
|
|
1013
|
+
|
|
1014
|
+
dashboard, extension, win = load_dashboard_with_preset(preset_file_name, "DAQ_PID")
|
|
1015
|
+
|
|
1016
|
+
app.exec()
|