6464 QMessageBox ,
6565 QPushButton ,
6666 QRadioButton ,
67+ QSizePolicy ,
6768 QSlider ,
6869 QSpinBox ,
6970 QTableWidget ,
7677from 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+
7988class 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\n Kp * [1 + Ki + Kd]" ), 0 , 2 )
333355 layout_pid .addWidget (QLabel ("Parallel\n Kp + 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
858867class DoubleSlider (QSlider ):
868+
859869 def __init__ (self , * args , ** kargs ):
860870 super (DoubleSlider , self ).__init__ (* args , ** kargs )
861871 self ._min = 0
0 commit comments