Skip to content

Commit 106f705

Browse files
committed
Added editable text fields for PID gains
1 parent 706aade commit 106f705

File tree

1 file changed

+105
-94
lines changed

1 file changed

+105
-94
lines changed

autotune/autotune.py

Lines changed: 105 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,8 @@
4141
import control as ctrl
4242
import matplotlib.patches as mpatches
4343
import matplotlib.pyplot as plt
44-
from data_extractor import *
45-
from data_selection_window import DataSelectionWindow
4644
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
4745
from matplotlib.backends.backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
48-
from pid_design import computePidGmvc
4946
from PyQt5.QtCore import Qt
5047
from PyQt5.QtWidgets import (
5148
QApplication,
@@ -64,6 +61,7 @@
6461
QMessageBox,
6562
QPushButton,
6663
QRadioButton,
64+
QSizePolicy,
6765
QSlider,
6866
QSpinBox,
6967
QTableWidget,
@@ -73,9 +71,21 @@
7371
QWidget,
7472
)
7573
from scipy.signal import detrend
74+
75+
from data_extractor import *
76+
from data_selection_window import DataSelectionWindow
77+
from pid_design import computePidGmvc
7678
from system_identification import SystemIdentification
7779

7880

81+
def isNumber(value):
82+
try:
83+
float(value)
84+
return True
85+
except ValueError:
86+
return False
87+
88+
7989
class Window(QDialog):
8090
def __init__(self, parent=None):
8191
super(Window, self).__init__(parent)
@@ -93,10 +103,7 @@ def __init__(self, parent=None):
93103
self.rise_time = 0.13
94104
self.damping_index = 0.0
95105
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
106+
self.gains = {"P": 0.01, "I": 0.0, "D": 0.0, "FF": 0.0}
100107
self.figure = plt.figure(1)
101108
self.figure.subplots_adjust(hspace=0.5, wspace=1.0)
102109
self.num = []
@@ -315,6 +322,22 @@ def printImproperTfError(self):
315322
msg.exec_()
316323

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

320343
layout_options = QHBoxLayout()
@@ -332,87 +355,73 @@ def createPidLayout(self):
332355
layout_pid.addWidget(QLabel("Ideal/Standard\nKp * [1 + Ki + Kd]"), 0, 2)
333356
layout_pid.addWidget(QLabel("Parallel\nKp + Ki + Kd"), 0, 3)
334357

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)
358+
row = 1
359+
for gain in self.gains.keys():
360+
if gain == "FF":
361+
layout_pid.addWidget(QLabel("{}".format(gain)), row, 0)
362+
else:
363+
layout_pid.addWidget(QLabel("K{}".format(gain.lower())), row, 0)
364+
365+
self.gain_slider[gain] = DoubleSlider(Qt.Horizontal)
366+
self.gain_slider[gain].setMinimum(slider_props[gain]["min"])
367+
self.gain_slider[gain].setMaximum(slider_props[gain]["max"])
368+
self.gain_slider[gain].setInterval(slider_props[gain]["step"])
369+
self.gain_slider[gain].valueChanged.connect(make_slider_callback(gain))
370+
layout_pid.addWidget(self.gain_slider[gain], row, 1)
371+
372+
self.gain_line_edit[gain] = QLineEdit("{:.3f}".format(self.gains[gain]))
373+
self.gain_line_edit[gain].setSizePolicy(
374+
QSizePolicy.Minimum, QSizePolicy.Fixed
375+
)
376+
self.gain_line_edit[gain].setMinimumWidth(0)
377+
self.gain_line_edit[gain].setMinimumSize(0, 0)
378+
self.gain_line_edit[gain].setAlignment(Qt.AlignCenter)
379+
self.gain_line_edit[gain].textChanged.connect(make_line_edit_callback(gain))
380+
layout_pid.addWidget(self.gain_line_edit[gain], row, 2)
381+
382+
if gain == "P" or gain == "FF":
383+
self.parallel_gain_lbl[gain] = QLabel("{:.4f}".format(self.gains[gain]))
384+
else:
385+
self.parallel_gain_lbl[gain] = QLabel(
386+
"{:.4f}".format(self.gains["P"] * self.gains[gain])
387+
)
388+
self.parallel_gain_lbl[gain].setAlignment(Qt.AlignCenter)
389+
layout_pid.addWidget(self.parallel_gain_lbl[gain], row, 3)
390+
391+
row += 1
382392

383393
return layout_pid
384394

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():
395+
def updateGainFromSlider(self, gain: str):
396+
if self.gain_slider[gain].hasFocus():
397+
self.gains[gain] = self.gain_slider[gain].value()
398+
self.gain_line_edit[gain].setText("{:.3f}".format(self.gains[gain]))
399+
self.updateGainLabels(gain)
400+
if self.gain_slider[gain].isSliderDown():
401+
self.updateClosedLoop()
402+
403+
def updateGainFromLineEdit(self, gain: str):
404+
if (
405+
isNumber(self.gain_line_edit[gain].text())
406+
and self.gain_line_edit[gain].hasFocus()
407+
):
408+
self.gains[gain] = float(self.gain_line_edit[gain].text())
409+
self.gain_slider[gain].setValue(self.gains[gain])
410+
self.updateGainLabels(gain)
401411
self.updateClosedLoop()
402412

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()
413+
def updateGainLabels(self, gain: str):
414+
if gain == "FF":
415+
self.parallel_gain_lbl[gain].setText("{:.4f}".format(self.gains[gain]))
416+
else:
417+
# Kp also modifies the Ki and Kd gains of the parallel form
418+
self.parallel_gain_lbl["P"].setText("{:.4f}".format(self.gains["P"]))
419+
self.parallel_gain_lbl["I"].setText(
420+
"{:.4f}".format(self.gains["P"] * self.gains["I"])
421+
)
422+
self.parallel_gain_lbl["D"].setText(
423+
"{:.4f}".format(self.gains["P"] * self.gains["D"])
424+
)
416425

417426
def createGmvcLayout(self):
418427
layout_gmvc = QFormLayout()
@@ -509,6 +518,7 @@ def replayInputData(self):
509518
self.plotInputOutput()
510519

511520
def updateTfDisplay(self, a_coeffs, b_coeffs):
521+
512522
for i in range(self.sys_id_n_poles):
513523
self.t_coeffs.setItem(i, 0, QTableWidgetItem("{:.6f}".format(a_coeffs[i])))
514524

@@ -589,22 +599,22 @@ def computeController(self):
589599
self.damping_index
590600
) # damping property, set between 0 and 2 (1 for Butterworth)
591601
lbda = self.detune_coeff
592-
(self.kc, self.ki, self.kd) = computePidGmvc(
602+
(self.gains["P"], self.gains["I"], self.gains["D"]) = computePidGmvc(
593603
self.num, self.den, self.dt, sigma, delta, lbda
594604
)
595605
# TODO:find a better solution
596-
self.ki /= 5.0
606+
self.gains["I"] /= 5.0
597607
static_gain = sum(self.num) / sum(self.den)
598-
self.kff = max(1 / static_gain, 0.0)
608+
self.gains["FF"] = max(1 / static_gain, 0.0)
599609

600610
self.updateKIDSliders()
601611
self.updateClosedLoop()
602612

603613
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)
614+
for gain in self.gains.keys():
615+
self.gain_line_edit[gain].setText("{:.3f}".format(self.gains[gain]))
616+
self.gain_slider[gain].setValue(self.gains[gain])
617+
self.updateGainLabels(gain)
608618

609619
def updateClosedLoop(self):
610620
if not self.is_system_identified:
@@ -613,10 +623,10 @@ def updateClosedLoop(self):
613623
num = self.num
614624
den = self.den
615625
dt = self.dt
616-
kc = self.kc
617-
ki = self.ki
618-
kd = self.kd
619-
kff = self.kff
626+
kc = self.gains["P"]
627+
ki = self.gains["I"]
628+
kd = self.gains["D"]
629+
kff = self.gains["FF"]
620630

621631
delays = ctrl.TransferFunction(
622632
[1],
@@ -856,6 +866,7 @@ def resampleData(self, dt):
856866

857867

858868
class DoubleSlider(QSlider):
869+
859870
def __init__(self, *args, **kargs):
860871
super(DoubleSlider, self).__init__(*args, **kargs)
861872
self._min = 0

0 commit comments

Comments
 (0)