Skip to content

Commit a3e7629

Browse files
committed
Added editable text fields for PID gains
1 parent 3bdec39 commit a3e7629

1 file changed

Lines changed: 136 additions & 91 deletions

File tree

autotune/autotune.py

Lines changed: 136 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"""
3838

3939
import sys
40-
from PyQt5.QtWidgets import QDialog, QApplication, QLabel, QRadioButton, QSlider, QPushButton, QVBoxLayout, QHBoxLayout, QFormLayout, QFileDialog, QLineEdit, QSpinBox, QDoubleSpinBox, QMessageBox, QCheckBox, QTableWidget, QTableWidgetItem, QHeaderView, QGroupBox, QComboBox, QTabWidget, QWidget, QGridLayout
40+
from PyQt5.QtWidgets import QDialog, QApplication, QLabel, QRadioButton, QSlider, QPushButton, QVBoxLayout, QHBoxLayout, QFormLayout, QFileDialog, QLineEdit, QSpinBox, QDoubleSpinBox, QMessageBox, QCheckBox, QTableWidget, QTableWidgetItem, QHeaderView, QGroupBox, QComboBox, QTabWidget, QWidget, QGridLayout, QSizePolicy
4141
from PyQt5.QtCore import Qt
4242

4343
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
@@ -53,6 +53,13 @@
5353

5454
from data_selection_window import DataSelectionWindow
5555

56+
def isNumber(value):
57+
try:
58+
float(value)
59+
return True
60+
except ValueError:
61+
return False
62+
5663
class Window(QDialog):
5764
def __init__(self, parent=None):
5865
super(Window, self).__init__(parent)
@@ -70,10 +77,12 @@ def __init__(self, parent=None):
7077
self.rise_time = 0.13
7178
self.damping_index = 0.0
7279
self.detune_coeff = 0.5
73-
self.kc = 0.01
74-
self.ki = 0.0
75-
self.kd = 0.0
76-
self.kff = 0.0
80+
self.gains = {
81+
"P": 0.01,
82+
"I": 0.0,
83+
"D": 0.0,
84+
"FF": 0.0
85+
}
7786
self.figure = plt.figure(1)
7887
self.figure.subplots_adjust(hspace=0.5, wspace=1.0)
7988
self.num = []
@@ -287,6 +296,23 @@ def printImproperTfError(self):
287296
msg.exec_()
288297

289298
def createPidLayout(self):
299+
self.gain_line_edit = {
300+
"P": None,
301+
"I": None,
302+
"D": None,
303+
"FF": None
304+
}
305+
self.gain_slider = {
306+
"P": None,
307+
"I": None,
308+
"D": None,
309+
"FF": None
310+
}
311+
self.parallel_gain_lbl = {
312+
"I": None,
313+
"D": None,
314+
}
315+
290316
layout_pid = QGridLayout()
291317

292318
layout_options = QHBoxLayout()
@@ -304,87 +330,106 @@ def createPidLayout(self):
304330
layout_pid.addWidget(QLabel("Ideal/Standard\nKp * [1 + Ki + Kd]"), 0, 2)
305331
layout_pid.addWidget(QLabel("Parallel\nKp + Ki + Kd"), 0, 3)
306332

307-
layout_pid.addWidget(QLabel("K"), 1, 0)
308-
self.slider_k = DoubleSlider(Qt.Horizontal)
309-
self.slider_k.setMinimum(0.001)
310-
self.slider_k.setMaximum(4.0)
311-
self.slider_k.setInterval(0.001)
312-
self.slider_k.valueChanged.connect(self.updateLabelK)
313-
layout_pid.addWidget(self.slider_k, 1, 1)
314-
self.lbl_k_standard = QLabel("{:.3f}".format(self.kc))
315-
layout_pid.addWidget(self.lbl_k_standard, 1, 2)
316-
self.lbl_k_parallel = QLabel("{:.3f}".format(self.kc))
317-
layout_pid.addWidget(self.lbl_k_parallel, 1, 3)
318-
319-
layout_pid.addWidget(QLabel("I"), 2, 0)
320-
self.slider_i = DoubleSlider(Qt.Horizontal)
321-
self.slider_i.setMinimum(0.0)
322-
self.slider_i.setMaximum(20.0)
323-
self.slider_i.setInterval(0.1)
324-
self.slider_i.valueChanged.connect(self.updateLabelI)
325-
layout_pid.addWidget(self.slider_i, 2, 1)
326-
self.lbl_i_standard = QLabel("{:.2f}".format(self.ki))
327-
layout_pid.addWidget(self.lbl_i_standard, 2, 2)
328-
self.lbl_i_parallel = QLabel("{:.2f}".format(self.kc * self.ki))
329-
layout_pid.addWidget(self.lbl_i_parallel, 2, 3)
330-
331-
layout_pid.addWidget(QLabel("D"), 3, 0)
332-
self.slider_d = DoubleSlider(Qt.Horizontal)
333-
self.slider_d.setMinimum(0.0)
334-
self.slider_d.setMaximum(0.2)
335-
self.slider_d.setInterval(0.001)
336-
self.slider_d.valueChanged.connect(self.updateLabelD)
337-
layout_pid.addWidget(self.slider_d, 3, 1)
338-
self.lbl_d_standard = QLabel("{:.3f}".format(self.kd))
339-
layout_pid.addWidget(self.lbl_d_standard, 3, 2)
340-
self.lbl_d_parallel = QLabel("{:.4f}".format(self.kc * self.kd))
341-
layout_pid.addWidget(self.lbl_d_parallel, 3, 3)
342-
333+
## Proportional gain
334+
layout_pid.addWidget(QLabel("Kp"), 1, 0)
335+
336+
self.gain_slider["P"] = DoubleSlider(Qt.Horizontal)
337+
self.gain_slider["P"].setMinimum(0.001)
338+
self.gain_slider["P"].setMaximum(4.0)
339+
self.gain_slider["P"].setInterval(0.001)
340+
self.gain_slider["P"].valueChanged.connect(lambda : self.updateGainFromSlider("P"))
341+
layout_pid.addWidget(self.gain_slider["P"], 1, 1)
342+
343+
self.gain_line_edit["P"] = QLineEdit("{:.3f}".format(self.gains["P"]))
344+
self.gain_line_edit["P"].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
345+
self.gain_line_edit["P"].setMinimumWidth(0)
346+
self.gain_line_edit["P"].setMinimumSize(0, 0)
347+
self.gain_line_edit["P"].setAlignment(Qt.AlignCenter)
348+
self.gain_line_edit["P"].textChanged.connect(lambda : self.updateGainFromLineEdit("P"))
349+
layout_pid.addWidget(self.gain_line_edit["P"], 1, 2, 1, 2)
350+
351+
## Integral gain
352+
layout_pid.addWidget(QLabel("Ki"), 2, 0)
353+
354+
self.gain_slider["I"] = DoubleSlider(Qt.Horizontal)
355+
self.gain_slider["I"].setMinimum(0.0)
356+
self.gain_slider["I"].setMaximum(20.0)
357+
self.gain_slider["I"].setInterval(0.1)
358+
self.gain_slider["I"].valueChanged.connect(lambda : self.updateGainFromSlider("I"))
359+
layout_pid.addWidget(self.gain_slider["I"], 2, 1)
360+
361+
self.gain_line_edit["I"] = QLineEdit("{:.3f}".format(self.gains["I"]))
362+
self.gain_line_edit["I"].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
363+
self.gain_line_edit["I"].setMinimumWidth(0)
364+
self.gain_line_edit["I"].setMinimumSize(0, 0)
365+
self.gain_line_edit["I"].setAlignment(Qt.AlignCenter)
366+
self.gain_line_edit["I"].textChanged.connect(lambda : self.updateGainFromLineEdit("I"))
367+
layout_pid.addWidget(self.gain_line_edit["I"], 2, 2)
368+
self.parallel_gain_lbl["I"] = QLabel("{:.4f}".format(self.gains["P"] * self.gains["I"]))
369+
self.parallel_gain_lbl["I"].setAlignment(Qt.AlignCenter)
370+
layout_pid.addWidget(self.parallel_gain_lbl["I"], 2, 3)
371+
372+
## Derivative gain
373+
layout_pid.addWidget(QLabel("Kd"), 3, 0)
374+
375+
self.gain_slider["D"] = DoubleSlider(Qt.Horizontal)
376+
self.gain_slider["D"].setMinimum(0.0)
377+
self.gain_slider["D"].setMaximum(0.2)
378+
self.gain_slider["D"].setInterval(0.001)
379+
self.gain_slider["D"].valueChanged.connect(lambda : self.updateGainFromSlider("D"))
380+
layout_pid.addWidget(self.gain_slider["D"], 3, 1)
381+
382+
self.gain_line_edit["D"] = QLineEdit("{:.3f}".format(self.gains["D"]))
383+
self.gain_line_edit["D"].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
384+
self.gain_line_edit["D"].setMinimumWidth(0)
385+
self.gain_line_edit["D"].setMinimumSize(0, 0)
386+
self.gain_line_edit["D"].setAlignment(Qt.AlignCenter)
387+
self.gain_line_edit["D"].textChanged.connect(lambda : self.updateGainFromLineEdit("D"))
388+
layout_pid.addWidget(self.gain_line_edit["D"], 3, 2)
389+
self.parallel_gain_lbl["D"] = QLabel("{:.4f}".format(self.gains["P"] * self.gains["D"]))
390+
self.parallel_gain_lbl["D"].setAlignment(Qt.AlignCenter)
391+
layout_pid.addWidget(self.parallel_gain_lbl["D"], 3, 3)
392+
393+
## Feedforward gain
343394
layout_pid.addWidget(QLabel("FF"), 4, 0)
344-
self.slider_ff = DoubleSlider(Qt.Horizontal)
345-
self.slider_ff.setMinimum(0.0)
346-
self.slider_ff.setMaximum(5.0)
347-
self.slider_ff.setInterval(0.01)
348-
self.slider_ff.valueChanged.connect(self.updateLabelFF)
349-
layout_pid.addWidget(self.slider_ff, 4, 1)
350-
self.lbl_ff_standard = QLabel("{:.3f}".format(self.kff))
351-
layout_pid.addWidget(self.lbl_ff_standard, 4, 2)
352-
self.lbl_ff_parallel = QLabel("{:.3f}".format(self.kff))
353-
layout_pid.addWidget(self.lbl_ff_parallel, 4, 3)
354-
355-
return layout_pid
356395

357-
def updateLabelK(self):
358-
self.kc = self.slider_k.value()
359-
self.lbl_k_standard.setText("{:.3f}".format(self.kc))
360-
self.lbl_k_parallel.setText("{:.3f}".format(self.kc))
396+
self.gain_slider["FF"] = DoubleSlider(Qt.Horizontal)
397+
self.gain_slider["FF"].setMinimum(0.0)
398+
self.gain_slider["FF"].setMaximum(5.0)
399+
self.gain_slider["FF"].setInterval(0.01)
400+
self.gain_slider["FF"].valueChanged.connect(lambda : self.updateGainFromSlider("FF"))
401+
layout_pid.addWidget(self.gain_slider["FF"], 4, 1)
402+
403+
self.gain_line_edit["FF"] = QLineEdit("{:.3f}".format(self.gains["FF"]))
404+
self.gain_line_edit["FF"].setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
405+
self.gain_line_edit["FF"].setMinimumWidth(0)
406+
self.gain_line_edit["FF"].setMinimumSize(0, 0)
407+
self.gain_line_edit["FF"].setAlignment(Qt.AlignCenter)
408+
self.gain_line_edit["FF"].textChanged.connect(lambda : self.updateGainFromLineEdit("FF"))
409+
layout_pid.addWidget(self.gain_line_edit["FF"], 4, 2, 1, 2)
361410

362-
# Kc also modifies the Ki and Kd gains of the parallel form
363-
self.lbl_i_parallel.setText("{:.2f}".format(self.kc * self.ki))
364-
self.lbl_d_parallel.setText("{:.4f}".format(self.kc * self.kd))
365-
if self.slider_k.isSliderDown():
366-
self.updateClosedLoop()
367-
368-
def updateLabelI(self):
369-
self.ki = self.slider_i.value()
370-
self.lbl_i_standard.setText("{:.2f}".format(self.ki))
371-
self.lbl_i_parallel.setText("{:.2f}".format(self.kc * self.ki))
372-
if self.slider_i.isSliderDown():
373-
self.updateClosedLoop()
411+
return layout_pid
374412

375-
def updateLabelD(self):
376-
self.kd = self.slider_d.value()
377-
self.lbl_d_standard.setText("{:.3f}".format(self.kd))
378-
self.lbl_d_parallel.setText("{:.4f}".format(self.kc * self.kd))
379-
if self.slider_d.isSliderDown():
413+
def updateGainFromSlider(self, gain : str):
414+
if self.gain_slider[gain].hasFocus():
415+
self.gains[gain] = self.gain_slider[gain].value()
416+
self.gain_line_edit[gain].setText("{:.3f}".format(self.gains[gain]))
417+
self.updateGainLabels(gain)
418+
if self.gain_slider[gain].isSliderDown():
419+
self.updateClosedLoop()
420+
421+
def updateGainFromLineEdit(self, gain : str):
422+
if isNumber(self.gain_line_edit[gain].text()) and self.gain_line_edit[gain].hasFocus():
423+
self.gains[gain] = float(self.gain_line_edit[gain].text())
424+
self.gain_slider[gain].setValue(self.gains[gain])
425+
self.updateGainLabels(gain)
380426
self.updateClosedLoop()
381427

382-
def updateLabelFF(self):
383-
self.kff = self.slider_ff.value()
384-
self.lbl_ff_standard.setText("{:.3f}".format(self.kff))
385-
self.lbl_ff_parallel.setText("{:.3f}".format(self.kff))
386-
if self.slider_ff.isSliderDown():
387-
self.updateClosedLoop()
428+
def updateGainLabels(self, gain : str):
429+
if gain != "FF":
430+
# Kp also modifies the Ki and Kd gains of the parallel form
431+
self.parallel_gain_lbl["I"].setText("{:.4f}".format(self.gains["P"] * self.gains["I"]))
432+
self.parallel_gain_lbl["D"].setText("{:.4f}".format(self.gains["P"] * self.gains["D"]))
388433

389434
def createGmvcLayout(self):
390435
layout_gmvc = QFormLayout()
@@ -553,20 +598,20 @@ def computeController(self):
553598
sigma = self.rise_time # rise time
554599
delta = self.damping_index # damping property, set between 0 and 2 (1 for Butterworth)
555600
lbda = self.detune_coeff
556-
(self.kc, self.ki, self.kd) = computePidGmvc(self.num, self.den, self.dt, sigma, delta, lbda)
601+
(self.gains["P"], self.gains["I"], self.gains["D"]) = computePidGmvc(self.num, self.den, self.dt, sigma, delta, lbda)
557602
#TODO:find a better solution
558-
self.ki /= 5.0
603+
self.gains["I"] /= 5.0
559604
static_gain = sum(self.num) / sum(self.den)
560-
self.kff = max(1 / static_gain, 0.0)
605+
self.gains["FF"] = max(1 / static_gain, 0.0)
561606

562607
self.updateKIDSliders()
563608
self.updateClosedLoop()
564609

565610
def updateKIDSliders(self):
566-
self.slider_k.setValue(self.kc)
567-
self.slider_i.setValue(self.ki)
568-
self.slider_d.setValue(self.kd)
569-
self.slider_ff.setValue(self.kff)
611+
for gain in self.gains.keys():
612+
self.gain_line_edit[gain].setText("{:.3f}".format(self.gains[gain]))
613+
self.gain_slider[gain].setValue(self.gains[gain])
614+
self.updateGainLabels(gain)
570615

571616
def updateClosedLoop(self):
572617
if not self.is_system_identified:
@@ -575,10 +620,10 @@ def updateClosedLoop(self):
575620
num = self.num
576621
den = self.den
577622
dt = self.dt
578-
kc = self.kc
579-
ki = self.ki
580-
kd = self.kd
581-
kff = self.kff
623+
kc = self.gains["P"]
624+
ki = self.gains["I"]
625+
kd = self.gains["D"]
626+
kff = self.gains["FF"]
582627

583628
delays = ctrl.TransferFunction([1], np.append([1], np.zeros(self.sys_id_delays)), dt, inputs='r', outputs='rd')
584629
plant = ctrl.TransferFunction(num, den, dt, inputs='u', outputs='plant_out')

0 commit comments

Comments
 (0)