Completed
Pull Request — master (#380)
by
unknown
03:38
created

PIDLogic.loop()   B

Complexity

Conditions 5

Size

Total Lines 24

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 5
dl 0
loc 24
rs 8.1671
c 3
b 0
f 0
1
# -*- coding: utf-8 -*-
2
3
"""
4
A module for controlling processes via PID regulation.
5
6
Qudi is free software: you can redistribute it and/or modify
7
it under the terms of the GNU General Public License as published by
8
the Free Software Foundation, either version 3 of the License, or
9
(at your option) any later version.
10
11
Qudi is distributed in the hope that it will be useful,
12
but WITHOUT ANY WARRANTY; without even the implied warranty of
13
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
GNU General Public License for more details.
15
16
You should have received a copy of the GNU General Public License
17
along with Qudi. If not, see <http://www.gnu.org/licenses/>.
18
19
Copyright (c) the Qudi Developers. See the COPYRIGHT.txt file at the
20
top-level directory of this distribution and at <https://github.com/Ulm-IQO/qudi/>
21
"""
22
23
import numpy as np
24
25
from core.module import Connector, ConfigOption, StatusVar
26
from core.util.mutex import Mutex
27
from logic.generic_logic import GenericLogic
28
from qtpy import QtCore
29
30
31
class PIDLogic(GenericLogic):
32
    """
33
    Control a process via software PID.
34
    """
35
    _modclass = 'pidlogic'
36
    _modtype = 'logic'
37
38
    ## declare connectors
39
    controller = Connector(interface='PIDControllerInterface')
40
    savelogic = Connector(interface='SaveLogic')
41
    _features = ConfigOption('features', ['PID_CONTROLLER'])
42
    # can include 'PID_CONTROLLER', 'SETPOINT_CONTROLLER', 'SETPOINT', 'PROCESS_VARIABLE' or 'PROCESS_CONTROL'
43
    # depending on what is available
44
45
    # status vars
46
    bufferLength = StatusVar('bufferlength', 1000)
47
    # TODO: Maybe the logic should keep everything (this should not be correlated with the GUI)
48
    
49
    _timestep = StatusVar(default=100)
50
    _loop_enabled = False
51
52
    # signals
53
    sigUpdateDisplay = QtCore.Signal()
54
55
    def __init__(self, config, **kwargs):
56
        super().__init__(config=config, **kwargs)
57
58
        # number of lines in the matrix plot
59
        self.NumberOfSecondsLog = 100  # This breaks logic/GUI compartmentalisation
60
        self.threadlock = Mutex()
61
62
    def on_activate(self):
63
        """ Initialisation performed during activation of the module.
64
        """
65
        self._controller = self.controller()
66
        self._save_logic = self.savelogic()
67
68
        self._has_controller = set(['PID_CONTROLLER', 'SETPOINT_CONTROLLER']).intersection(set(self._features))
69
        self._has_setpoint = bool(set(['PID_CONTROLLER', 'SETPOINT_CONTROLLER', 'SETPOINT']).intersection(set(self._features)))
70
        self._has_process_value = bool(set(['PID_CONTROLLER', 'SETPOINT_CONTROLLER', 'PROCESS_VARIABLE']).intersection(set(self._features)))
71
        self._has_control_value = bool(set(['PID_CONTROLLER', 'PROCESS_VARIABLE']).intersection(set(self._features)))
72
        self._has_pid = 'PID_CONTROLLER' in self._features
73
74
75
        self.history = np.zeros([3, self.bufferLength])
76
        self.savingState = False
77
78
        self.timer = QtCore.QTimer()
79
        self.timer.setSingleShot(True)
80
        self.timer.setInterval(self._timestep)
81
        self.timer.timeout.connect(self.loop)
82
83
        self.start_loop()
84
85
    def on_deactivate(self):
86
        """ Perform required deactivation. """
87
        pass
88
89
    def getBufferLength(self):  #TODO: This breaks logic/GUI compartmentalisation (and naming conventions)
90
        """ Get the current data buffer length.
91
        """
92
        return self.bufferLength
93
94
    def setBufferLength(self, newBufferLength): #TODO: This breaks logic/GUI compartmentalisation (and naming conventions)
95
        """ Change buffer length to new value.
96
97
            @param int newBufferLength: new buffer length
98
        """
99
        self.bufferLength = newBufferLength
100
        self.history = np.zeros([3, self.bufferLength])
101
102
    def start_loop(self):
103
        """ Start the data acquisition loop.
104
        """
105
        self._loop_enabled = True
106
        self.timer.start(self._timestep)
107
108
    def stop_loop(self):
109
        """ Stop the data acquisition loop.
110
        """
111
        self._loop_enabled = False
112
113
    def loop(self):
114
        """ Execute step in the data acquisition loop: save one of each control and process values
115
        """
116
        self.history = np.roll(self.history, -1, axis=1)  # TODO : What is the efficiency of "roll storing" method on large array ?
117
118
        #TODO: only store activated info, hybrid for now
119
        if self._has_process_value:
120
            self.history[0, -1] = self._controller.get_process_value()
121
        else:
122
            self.history[0, -1] = 0
123
124
        if self._has_control_value:
125
            self.history[1, -1] = self._controller.get_control_value()
126
        else:
127
            self.history[1, -1] = 0
128
129
        if self._has_setpoint:
130
            self.history[2, -1] = self._controller.get_setpoint()
131
        else:
132
            self.history[2, -1] = 0
133
134
        self.sigUpdateDisplay.emit()
135
        if self._loop_enabled:
136
            self.timer.start(self._timestep)
137
138
    # TODO: to make the GUI happy for now, this could vary so this need to be redesigned
139
    def get_timestep(self):
140
        return self._timestep
141
142
    def get_features(self):
143
        return self._features
144
145
    def get_saving_state(self):
146
        """ Return whether we are saving data
147
148
            @return bool: whether we are saving data right now
149
        """
150
        return self.savingState
151
152
    def start_saving(self):
153
        """ Start saving data.
154
155
            Function does nothing right now.
156
        """
157
        pass
158
159
    def save_sata(self):
160
        """ Stop saving data and write data to file.
161
162
            Function does nothing right now.
163
        """
164
        pass
165
166
    # Beginning of features dependent methods :
167
168
    def get_kp(self):
169
        """ Return the proportional constant.
170
171
            @return float: proportional constant of PID controller
172
        """
173
        if self._has_pid:
174
            return self._controller.get_kp()
175
        else:
176
            return 0
177
178
    def set_kp(self, kp):
179
        """ Set the proportional constant of the PID controller.
180
181
            @prarm float kp: proportional constant of PID controller
182
        """
183
        if self._has_pid:
184
            return self._controller.set_kp(kp)
185
        else:
186
            return 0
187
188
    def get_ki(self):
189
        """ Get the integration constant of the PID controller
190
191
            @return float: integration constant of the PID controller
192
        """
193
        if self._has_pid:
194
            return self._controller.get_ki()
195
        else:
196
            return 0
197
198
    def set_ki(self, ki):
199
        """ Set the integration constant of the PID controller.
200
201
            @param float ki: integration constant of the PID controller
202
        """
203
        if self._has_pid:
204
            return self._controller.set_ki(ki)
205
        else:
206
            return 0
207
208
    def get_kd(self):
209
        """ Get the derivative constant of the PID controller
210
211
            @return float: the derivative constant of the PID controller
212
        """
213
        if self._has_pid:
214
            return self._controller.get_kd()
215
        else:
216
            return 0
217
218
    def set_kd(self, kd):
219
        """ Set the derivative constant of the PID controller
220
221
            @param float kd: the derivative constant of the PID controller
222
        """
223
        if self._has_pid:
224
            return self._controller.set_kd(kd)
225
        else:
226
            return 0
227
228
    def get_setpoint(self):
229
        """ Get the current setpoint of the controller.
230
231
            @return float: current set point of the controller
232
        """
233
        if self._has_setpoint:
234
            return self.history[2, -1]
235
        else:
236
            return 0
237
238
    def set_setpoint(self, setpoint):
239
        """ Set the current setpoint of the PID controller.
240
241
            @param float setpoint: new set point of the controller
242
        """
243
        if self._has_setpoint:
244
            return self._controller.set_setpoint(setpoint)
245
        else:
246
            return 0
247
248
    def get_enabled(self):
249
        """ See if the PID controller is controlling a process.
250
251
            @return bool: whether the PID controller is preparing to or conreolling a process
252
        """
253
        if self._has_controller:
254
            return self._controller.get_enabled()
255
        else:
256
            return 0
257
258
    def set_enabled(self, enabled):
259
        """ Set the state of the PID controller.
260
261
            @param bool enabled: desired state of PID controller
262
        """
263
        if self._has_controller:
264
            return self._controller.set_enabled(enabled)
265
        else:
266
            return 0
267
268
    def get_control_limits(self):
269
        """ Get the minimum and maximum value of the control actuator.
270
271
            @return list(float): (minimum, maximum) values of the control actuator
272
        """
273
        if self._has_control_value:
274
            return self._controller.get_control_limits()
275
        else:
276
            return 0
277
278
    def set_control_limits(self, limits):  # TODO: Should this be ok ?
279
        """ Set the minimum and maximum value of the control actuator.
280
281
            @param list(float) limits: (minimum, maximum) values of the control actuator
282
283
            This function does nothing, control limits are handled by the control module
284
        """
285
        if self._has_pid:
286
            return self._controller.set_control_limits(limits)
287
        else:
288
            return 0
289
290
    def get_pv(self): # TODO : change name ??? It's confusion with PID variables
291
        """ Get current process input value.
292
293
            @return float: current process input value
294
        """
295
        if self._has_process_value:
296
            return self.history[0, -1]
297
        else:
298
            return 0
299
300
    def get_cv(self):
301
        """ Get current control output value.
302
303
            @return float: control output value
304
        """
305
        if self._has_process_value:
306
            return self.history[1, -1]
307
        else:
308
            return 0
309
310
    # TODO : What is that exactly ?
311
    def get_extra(self):
312
        if self._has_pid:
313
            return self._controller.get_extra()
314
        else:
315
            return []
316
317
    # TODO: How does manual fit into all this ?
318
    def get_manual_value(self):
319
        """ Return the control value for manual mode.
320
321
            @return float: control value for manual mode
322
        """
323
324
        if self._has_pid:
325
            return self._controller.get_manual_value()
326
        else:
327
            return 0
328
329
    def set_manual_value(self, manualvalue):
330
        """ Set the control value for manual mode.
331
332
            @param float manualvalue: control value for manual mode of controller
333
        """
334
        if self._has_pid:
335
            return self._controller.set_manual_value(manualvalue)
336
        else:
337
            return 0