3737"""
3838
3939import 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
4141from PyQt5 .QtCore import Qt
4242
4343from matplotlib .backends .backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
5353
5454from 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+
5663class 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\n Kp * [1 + Ki + Kd]" ), 0 , 2 )
305331 layout_pid .addWidget (QLabel ("Parallel\n Kp + 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