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,22 @@ def printImproperTfError(self):
287296 msg .exec_ ()
288297
289298 def createPidLayout (self ):
299+ self .gain_line_edit = {}
300+ self .gain_slider = {}
301+ self .parallel_gain_lbl = {}
302+ slider_props = {
303+ "P" : {"min" : 0.001 , "max" : 4.0 , "step" : 0.001 },
304+ "I" : {"min" : 0.0 , "max" : 20.0 , "step" : 0.1 },
305+ "D" : {"min" : 0.0 , "max" : 0.2 , "step" : 0.001 },
306+ "FF" : {"min" : 0.0 , "max" : 1.0 , "step" : 0.001 }
307+ }
308+
309+ def make_slider_callback (gain ):
310+ return lambda : self .updateGainFromSlider (gain )
311+
312+ def make_line_edit_callback (gain ):
313+ return lambda : self .updateGainFromLineEdit (gain )
314+
290315 layout_pid = QGridLayout ()
291316
292317 layout_options = QHBoxLayout ()
@@ -304,87 +329,62 @@ def createPidLayout(self):
304329 layout_pid .addWidget (QLabel ("Ideal/Standard\n Kp * [1 + Ki + Kd]" ), 0 , 2 )
305330 layout_pid .addWidget (QLabel ("Parallel\n Kp + Ki + Kd" ), 0 , 3 )
306331
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-
343- 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 )
332+ row = 1
333+ for gain in self .gains .keys ():
334+ if gain == "FF" :
335+ layout_pid .addWidget (QLabel ("{}" .format (gain )), row , 0 )
336+ else :
337+ layout_pid .addWidget (QLabel ("K{}" .format (gain .lower ())), row , 0 )
338+
339+ self .gain_slider [gain ] = DoubleSlider (Qt .Horizontal )
340+ self .gain_slider [gain ].setMinimum (slider_props [gain ]["min" ])
341+ self .gain_slider [gain ].setMaximum (slider_props [gain ]["max" ])
342+ self .gain_slider [gain ].setInterval (slider_props [gain ]["step" ])
343+ self .gain_slider [gain ].valueChanged .connect (make_slider_callback (gain ))
344+ layout_pid .addWidget (self .gain_slider [gain ], row , 1 )
345+
346+ self .gain_line_edit [gain ] = QLineEdit ("{:.3f}" .format (self .gains [gain ]))
347+ self .gain_line_edit [gain ].setSizePolicy (QSizePolicy .Minimum , QSizePolicy .Fixed )
348+ self .gain_line_edit [gain ].setMinimumWidth (0 )
349+ self .gain_line_edit [gain ].setMinimumSize (0 , 0 )
350+ self .gain_line_edit [gain ].setAlignment (Qt .AlignCenter )
351+ self .gain_line_edit [gain ].textChanged .connect (make_line_edit_callback (gain ))
352+ layout_pid .addWidget (self .gain_line_edit [gain ], row , 2 )
353+
354+ if gain == "P" or gain == "FF" :
355+ self .parallel_gain_lbl [gain ] = QLabel ("{:.4f}" .format (self .gains [gain ]))
356+ else :
357+ self .parallel_gain_lbl [gain ] = QLabel ("{:.4f}" .format (self .gains ["P" ] * self .gains [gain ]))
358+ self .parallel_gain_lbl [gain ].setAlignment (Qt .AlignCenter )
359+ layout_pid .addWidget (self .parallel_gain_lbl [gain ], row , 3 )
360+
361+ row += 1
354362
355363 return layout_pid
356364
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 ))
361-
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 ():
365+ def updateGainFromSlider (self , gain : str ):
366+ if self .gain_slider [gain ].hasFocus ():
367+ self .gains [gain ] = self .gain_slider [gain ].value ()
368+ self .gain_line_edit [gain ].setText ("{:.3f}" .format (self .gains [gain ]))
369+ self .updateGainLabels (gain )
370+ if self .gain_slider [gain ].isSliderDown ():
371+ self .updateClosedLoop ()
372+
373+ def updateGainFromLineEdit (self , gain : str ):
374+ if isNumber (self .gain_line_edit [gain ].text ()) and self .gain_line_edit [gain ].hasFocus ():
375+ self .gains [gain ] = float (self .gain_line_edit [gain ].text ())
376+ self .gain_slider [gain ].setValue (self .gains [gain ])
377+ self .updateGainLabels (gain )
373378 self .updateClosedLoop ()
374379
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 ():
380- self .updateClosedLoop ()
381-
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 ()
380+ def updateGainLabels (self , gain : str ):
381+ if gain == "FF" :
382+ self .parallel_gain_lbl [gain ].setText ("{:.4f}" .format (self .gains [gain ]))
383+ else :
384+ # Kp also modifies the Ki and Kd gains of the parallel form
385+ self .parallel_gain_lbl ["P" ].setText ("{:.4f}" .format (self .gains ["P" ]))
386+ self .parallel_gain_lbl ["I" ].setText ("{:.4f}" .format (self .gains ["P" ] * self .gains ["I" ]))
387+ self .parallel_gain_lbl ["D" ].setText ("{:.4f}" .format (self .gains ["P" ] * self .gains ["D" ]))
388388
389389 def createGmvcLayout (self ):
390390 layout_gmvc = QFormLayout ()
@@ -553,20 +553,20 @@ def computeController(self):
553553 sigma = self .rise_time # rise time
554554 delta = self .damping_index # damping property, set between 0 and 2 (1 for Butterworth)
555555 lbda = self .detune_coeff
556- (self .kc , self .ki , self .kd ) = computePidGmvc (self .num , self .den , self .dt , sigma , delta , lbda )
556+ (self .gains [ "P" ] , self .gains [ "I" ] , self .gains [ "D" ] ) = computePidGmvc (self .num , self .den , self .dt , sigma , delta , lbda )
557557 #TODO:find a better solution
558- self .ki /= 5.0
558+ self .gains [ "I" ] /= 5.0
559559 static_gain = sum (self .num ) / sum (self .den )
560- self .kff = max (1 / static_gain , 0.0 )
560+ self .gains [ "FF" ] = max (1 / static_gain , 0.0 )
561561
562562 self .updateKIDSliders ()
563563 self .updateClosedLoop ()
564564
565565 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 )
566+ for gain in self .gains . keys ():
567+ self .gain_line_edit [ gain ]. setText ( "{:.3f}" . format ( self .gains [ gain ]) )
568+ self .gain_slider [ gain ] .setValue (self .gains [ gain ] )
569+ self .updateGainLabels ( gain )
570570
571571 def updateClosedLoop (self ):
572572 if not self .is_system_identified :
@@ -575,10 +575,10 @@ def updateClosedLoop(self):
575575 num = self .num
576576 den = self .den
577577 dt = self .dt
578- kc = self .kc
579- ki = self .ki
580- kd = self .kd
581- kff = self .kff
578+ kc = self .gains [ "P" ]
579+ ki = self .gains [ "I" ]
580+ kd = self .gains [ "D" ]
581+ kff = self .gains [ "FF" ]
582582
583583 delays = ctrl .TransferFunction ([1 ], np .append ([1 ], np .zeros (self .sys_id_delays )), dt , inputs = 'r' , outputs = 'rd' )
584584 plant = ctrl .TransferFunction (num , den , dt , inputs = 'u' , outputs = 'plant_out' )
0 commit comments