Skip to content

Commit 7b96392

Browse files
authored
Added editable text fields for PID gains (#40)
1 parent 9e70325 commit 7b96392

1 file changed

Lines changed: 101 additions & 91 deletions

File tree

autotune/autotune.py

Lines changed: 101 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
QMessageBox,
6565
QPushButton,
6666
QRadioButton,
67+
QSizePolicy,
6768
QSlider,
6869
QSpinBox,
6970
QTableWidget,
@@ -76,6 +77,14 @@
7677
from system_identification import SystemIdentification
7778

7879

80+
def isNumber(value):
81+
try:
82+
float(value)
83+
return True
84+
except ValueError:
85+
return False
86+
87+
7988
class Window(QDialog):
8089
def __init__(self, parent=None):
8190
super(Window, self).__init__(parent)
@@ -93,10 +102,7 @@ def __init__(self, parent=None):
93102
self.rise_time = 0.13
94103
self.damping_index = 0.0
95104
self.detune_coeff = 0.5
96-
self.kc = 0.01
97-
self.ki = 0.0
98-
self.kd = 0.0
99-
self.kff = 0.0
105+
self.gains = {"P": 0.01, "I": 0.0, "D": 0.0, "FF": 0.0}
100106
self.figure = plt.figure(1)
101107
self.figure.subplots_adjust(hspace=0.5, wspace=1.0)
102108
self.num = []
@@ -315,6 +321,22 @@ def printImproperTfError(self):
315321
msg.exec_()
316322

317323
def createPidLayout(self):
324+
self.gain_line_edit = {}
325+
self.gain_slider = {}
326+
self.parallel_gain_lbl = {}
327+
slider_props = {
328+
"P": {"min": 0.001, "max": 4.0, "step": 0.001},
329+
"I": {"min": 0.0, "max": 20.0, "step": 0.1},
330+
"D": {"min": 0.0, "max": 0.2, "step": 0.001},
331+
"FF": {"min": 0.0, "max": 1.0, "step": 0.001},
332+
}
333+
334+
def make_slider_callback(gain):
335+
return lambda: self.updateGainFromSlider(gain)
336+
337+
def make_line_edit_callback(gain):
338+
return lambda: self.updateGainFromLineEdit(gain)
339+
318340
layout_pid = QGridLayout()
319341

320342
layout_options = QHBoxLayout()
@@ -332,87 +354,73 @@ def createPidLayout(self):
332354
layout_pid.addWidget(QLabel("Ideal/Standard\nKp * [1 + Ki + Kd]"), 0, 2)
333355
layout_pid.addWidget(QLabel("Parallel\nKp + Ki + Kd"), 0, 3)
334356

335-
layout_pid.addWidget(QLabel("K"), 1, 0)
336-
self.slider_k = DoubleSlider(Qt.Horizontal)
337-
self.slider_k.setMinimum(0.001)
338-
self.slider_k.setMaximum(4.0)
339-
self.slider_k.setInterval(0.001)
340-
self.slider_k.valueChanged.connect(self.updateLabelK)
341-
layout_pid.addWidget(self.slider_k, 1, 1)
342-
self.lbl_k_standard = QLabel("{:.3f}".format(self.kc))
343-
layout_pid.addWidget(self.lbl_k_standard, 1, 2)
344-
self.lbl_k_parallel = QLabel("{:.3f}".format(self.kc))
345-
layout_pid.addWidget(self.lbl_k_parallel, 1, 3)
346-
347-
layout_pid.addWidget(QLabel("I"), 2, 0)
348-
self.slider_i = DoubleSlider(Qt.Horizontal)
349-
self.slider_i.setMinimum(0.0)
350-
self.slider_i.setMaximum(20.0)
351-
self.slider_i.setInterval(0.1)
352-
self.slider_i.valueChanged.connect(self.updateLabelI)
353-
layout_pid.addWidget(self.slider_i, 2, 1)
354-
self.lbl_i_standard = QLabel("{:.2f}".format(self.ki))
355-
layout_pid.addWidget(self.lbl_i_standard, 2, 2)
356-
self.lbl_i_parallel = QLabel("{:.2f}".format(self.kc * self.ki))
357-
layout_pid.addWidget(self.lbl_i_parallel, 2, 3)
358-
359-
layout_pid.addWidget(QLabel("D"), 3, 0)
360-
self.slider_d = DoubleSlider(Qt.Horizontal)
361-
self.slider_d.setMinimum(0.0)
362-
self.slider_d.setMaximum(0.2)
363-
self.slider_d.setInterval(0.001)
364-
self.slider_d.valueChanged.connect(self.updateLabelD)
365-
layout_pid.addWidget(self.slider_d, 3, 1)
366-
self.lbl_d_standard = QLabel("{:.3f}".format(self.kd))
367-
layout_pid.addWidget(self.lbl_d_standard, 3, 2)
368-
self.lbl_d_parallel = QLabel("{:.4f}".format(self.kc * self.kd))
369-
layout_pid.addWidget(self.lbl_d_parallel, 3, 3)
370-
371-
layout_pid.addWidget(QLabel("FF"), 4, 0)
372-
self.slider_ff = DoubleSlider(Qt.Horizontal)
373-
self.slider_ff.setMinimum(0.0)
374-
self.slider_ff.setMaximum(5.0)
375-
self.slider_ff.setInterval(0.01)
376-
self.slider_ff.valueChanged.connect(self.updateLabelFF)
377-
layout_pid.addWidget(self.slider_ff, 4, 1)
378-
self.lbl_ff_standard = QLabel("{:.3f}".format(self.kff))
379-
layout_pid.addWidget(self.lbl_ff_standard, 4, 2)
380-
self.lbl_ff_parallel = QLabel("{:.3f}".format(self.kff))
381-
layout_pid.addWidget(self.lbl_ff_parallel, 4, 3)
357+
row = 1
358+
for gain in self.gains.keys():
359+
if gain == "FF":
360+
layout_pid.addWidget(QLabel("{}".format(gain)), row, 0)
361+
else:
362+
layout_pid.addWidget(QLabel("K{}".format(gain.lower())), row, 0)
363+
364+
self.gain_slider[gain] = DoubleSlider(Qt.Horizontal)
365+
self.gain_slider[gain].setMinimum(slider_props[gain]["min"])
366+
self.gain_slider[gain].setMaximum(slider_props[gain]["max"])
367+
self.gain_slider[gain].setInterval(slider_props[gain]["step"])
368+
self.gain_slider[gain].valueChanged.connect(make_slider_callback(gain))
369+
layout_pid.addWidget(self.gain_slider[gain], row, 1)
370+
371+
self.gain_line_edit[gain] = QLineEdit("{:.3f}".format(self.gains[gain]))
372+
self.gain_line_edit[gain].setSizePolicy(
373+
QSizePolicy.Minimum, QSizePolicy.Fixed
374+
)
375+
self.gain_line_edit[gain].setMinimumWidth(0)
376+
self.gain_line_edit[gain].setMinimumSize(0, 0)
377+
self.gain_line_edit[gain].setAlignment(Qt.AlignCenter)
378+
self.gain_line_edit[gain].textChanged.connect(make_line_edit_callback(gain))
379+
layout_pid.addWidget(self.gain_line_edit[gain], row, 2)
380+
381+
if gain == "P" or gain == "FF":
382+
self.parallel_gain_lbl[gain] = QLabel("{:.4f}".format(self.gains[gain]))
383+
else:
384+
self.parallel_gain_lbl[gain] = QLabel(
385+
"{:.4f}".format(self.gains["P"] * self.gains[gain])
386+
)
387+
self.parallel_gain_lbl[gain].setAlignment(Qt.AlignCenter)
388+
layout_pid.addWidget(self.parallel_gain_lbl[gain], row, 3)
389+
390+
row += 1
382391

383392
return layout_pid
384393

385-
def updateLabelK(self):
386-
self.kc = self.slider_k.value()
387-
self.lbl_k_standard.setText("{:.3f}".format(self.kc))
388-
self.lbl_k_parallel.setText("{:.3f}".format(self.kc))
389-
390-
# Kc also modifies the Ki and Kd gains of the parallel form
391-
self.lbl_i_parallel.setText("{:.2f}".format(self.kc * self.ki))
392-
self.lbl_d_parallel.setText("{:.4f}".format(self.kc * self.kd))
393-
if self.slider_k.isSliderDown():
394-
self.updateClosedLoop()
395-
396-
def updateLabelI(self):
397-
self.ki = self.slider_i.value()
398-
self.lbl_i_standard.setText("{:.2f}".format(self.ki))
399-
self.lbl_i_parallel.setText("{:.2f}".format(self.kc * self.ki))
400-
if self.slider_i.isSliderDown():
394+
def updateGainFromSlider(self, gain: str):
395+
if self.gain_slider[gain].hasFocus():
396+
self.gains[gain] = self.gain_slider[gain].value()
397+
self.gain_line_edit[gain].setText("{:.3f}".format(self.gains[gain]))
398+
self.updateGainLabels(gain)
399+
if self.gain_slider[gain].isSliderDown():
400+
self.updateClosedLoop()
401+
402+
def updateGainFromLineEdit(self, gain: str):
403+
if (
404+
isNumber(self.gain_line_edit[gain].text())
405+
and self.gain_line_edit[gain].hasFocus()
406+
):
407+
self.gains[gain] = float(self.gain_line_edit[gain].text())
408+
self.gain_slider[gain].setValue(self.gains[gain])
409+
self.updateGainLabels(gain)
401410
self.updateClosedLoop()
402411

403-
def updateLabelD(self):
404-
self.kd = self.slider_d.value()
405-
self.lbl_d_standard.setText("{:.3f}".format(self.kd))
406-
self.lbl_d_parallel.setText("{:.4f}".format(self.kc * self.kd))
407-
if self.slider_d.isSliderDown():
408-
self.updateClosedLoop()
409-
410-
def updateLabelFF(self):
411-
self.kff = self.slider_ff.value()
412-
self.lbl_ff_standard.setText("{:.3f}".format(self.kff))
413-
self.lbl_ff_parallel.setText("{:.3f}".format(self.kff))
414-
if self.slider_ff.isSliderDown():
415-
self.updateClosedLoop()
412+
def updateGainLabels(self, gain: str):
413+
if gain == "FF":
414+
self.parallel_gain_lbl[gain].setText("{:.4f}".format(self.gains[gain]))
415+
else:
416+
# Kp also modifies the Ki and Kd gains of the parallel form
417+
self.parallel_gain_lbl["P"].setText("{:.4f}".format(self.gains["P"]))
418+
self.parallel_gain_lbl["I"].setText(
419+
"{:.4f}".format(self.gains["P"] * self.gains["I"])
420+
)
421+
self.parallel_gain_lbl["D"].setText(
422+
"{:.4f}".format(self.gains["P"] * self.gains["D"])
423+
)
416424

417425
def createGmvcLayout(self):
418426
layout_gmvc = QFormLayout()
@@ -509,6 +517,7 @@ def replayInputData(self):
509517
self.plotInputOutput()
510518

511519
def updateTfDisplay(self, a_coeffs, b_coeffs):
520+
512521
for i in range(self.sys_id_n_poles):
513522
self.t_coeffs.setItem(i, 0, QTableWidgetItem("{:.6f}".format(a_coeffs[i])))
514523

@@ -589,22 +598,22 @@ def computeController(self):
589598
self.damping_index
590599
) # damping property, set between 0 and 2 (1 for Butterworth)
591600
lbda = self.detune_coeff
592-
(self.kc, self.ki, self.kd) = computePidGmvc(
601+
(self.gains["P"], self.gains["I"], self.gains["D"]) = computePidGmvc(
593602
self.num, self.den, self.dt, sigma, delta, lbda
594603
)
595604
# TODO:find a better solution
596-
self.ki /= 5.0
605+
self.gains["I"] /= 5.0
597606
static_gain = sum(self.num) / sum(self.den)
598-
self.kff = max(1 / static_gain, 0.0)
607+
self.gains["FF"] = max(1 / static_gain, 0.0)
599608

600609
self.updateKIDSliders()
601610
self.updateClosedLoop()
602611

603612
def updateKIDSliders(self):
604-
self.slider_k.setValue(self.kc)
605-
self.slider_i.setValue(self.ki)
606-
self.slider_d.setValue(self.kd)
607-
self.slider_ff.setValue(self.kff)
613+
for gain in self.gains.keys():
614+
self.gain_line_edit[gain].setText("{:.3f}".format(self.gains[gain]))
615+
self.gain_slider[gain].setValue(self.gains[gain])
616+
self.updateGainLabels(gain)
608617

609618
def updateClosedLoop(self):
610619
if not self.is_system_identified:
@@ -613,10 +622,10 @@ def updateClosedLoop(self):
613622
num = self.num
614623
den = self.den
615624
dt = self.dt
616-
kc = self.kc
617-
ki = self.ki
618-
kd = self.kd
619-
kff = self.kff
625+
kc = self.gains["P"]
626+
ki = self.gains["I"]
627+
kd = self.gains["D"]
628+
kff = self.gains["FF"]
620629

621630
delays = ctrl.TransferFunction(
622631
[1],
@@ -856,6 +865,7 @@ def resampleData(self, dt):
856865

857866

858867
class DoubleSlider(QSlider):
868+
859869
def __init__(self, *args, **kargs):
860870
super(DoubleSlider, self).__init__(*args, **kargs)
861871
self._min = 0

0 commit comments

Comments
 (0)