41
41
import control as ctrl
42
42
import matplotlib .patches as mpatches
43
43
import matplotlib .pyplot as plt
44
- from data_extractor import *
45
- from data_selection_window import DataSelectionWindow
46
44
from matplotlib .backends .backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
47
45
from matplotlib .backends .backend_qt5agg import NavigationToolbar2QT as NavigationToolbar
48
- from pid_design import computePidGmvc
49
46
from PyQt5 .QtCore import Qt
50
47
from PyQt5 .QtWidgets import (
51
48
QApplication ,
64
61
QMessageBox ,
65
62
QPushButton ,
66
63
QRadioButton ,
64
+ QSizePolicy ,
67
65
QSlider ,
68
66
QSpinBox ,
69
67
QTableWidget ,
73
71
QWidget ,
74
72
)
75
73
from scipy .signal import detrend
74
+
75
+ from data_extractor import *
76
+ from data_selection_window import DataSelectionWindow
77
+ from pid_design import computePidGmvc
76
78
from system_identification import SystemIdentification
77
79
78
80
81
+ def isNumber (value ):
82
+ try :
83
+ float (value )
84
+ return True
85
+ except ValueError :
86
+ return False
87
+
88
+
79
89
class Window (QDialog ):
80
90
def __init__ (self , parent = None ):
81
91
super (Window , self ).__init__ (parent )
@@ -93,10 +103,7 @@ def __init__(self, parent=None):
93
103
self .rise_time = 0.13
94
104
self .damping_index = 0.0
95
105
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 }
100
107
self .figure = plt .figure (1 )
101
108
self .figure .subplots_adjust (hspace = 0.5 , wspace = 1.0 )
102
109
self .num = []
@@ -315,6 +322,22 @@ def printImproperTfError(self):
315
322
msg .exec_ ()
316
323
317
324
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
+
318
341
layout_pid = QGridLayout ()
319
342
320
343
layout_options = QHBoxLayout ()
@@ -332,87 +355,73 @@ def createPidLayout(self):
332
355
layout_pid .addWidget (QLabel ("Ideal/Standard\n Kp * [1 + Ki + Kd]" ), 0 , 2 )
333
356
layout_pid .addWidget (QLabel ("Parallel\n Kp + Ki + Kd" ), 0 , 3 )
334
357
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
382
392
383
393
return layout_pid
384
394
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 )
401
411
self .updateClosedLoop ()
402
412
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
+ )
416
425
417
426
def createGmvcLayout (self ):
418
427
layout_gmvc = QFormLayout ()
@@ -509,6 +518,7 @@ def replayInputData(self):
509
518
self .plotInputOutput ()
510
519
511
520
def updateTfDisplay (self , a_coeffs , b_coeffs ):
521
+
512
522
for i in range (self .sys_id_n_poles ):
513
523
self .t_coeffs .setItem (i , 0 , QTableWidgetItem ("{:.6f}" .format (a_coeffs [i ])))
514
524
@@ -589,22 +599,22 @@ def computeController(self):
589
599
self .damping_index
590
600
) # damping property, set between 0 and 2 (1 for Butterworth)
591
601
lbda = self .detune_coeff
592
- (self .kc , self .ki , self .kd ) = computePidGmvc (
602
+ (self .gains [ "P" ] , self .gains [ "I" ] , self .gains [ "D" ] ) = computePidGmvc (
593
603
self .num , self .den , self .dt , sigma , delta , lbda
594
604
)
595
605
# TODO:find a better solution
596
- self .ki /= 5.0
606
+ self .gains [ "I" ] /= 5.0
597
607
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 )
599
609
600
610
self .updateKIDSliders ()
601
611
self .updateClosedLoop ()
602
612
603
613
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 )
608
618
609
619
def updateClosedLoop (self ):
610
620
if not self .is_system_identified :
@@ -613,10 +623,10 @@ def updateClosedLoop(self):
613
623
num = self .num
614
624
den = self .den
615
625
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" ]
620
630
621
631
delays = ctrl .TransferFunction (
622
632
[1 ],
@@ -856,6 +866,7 @@ def resampleData(self, dt):
856
866
857
867
858
868
class DoubleSlider (QSlider ):
869
+
859
870
def __init__ (self , * args , ** kargs ):
860
871
super (DoubleSlider , self ).__init__ (* args , ** kargs )
861
872
self ._min = 0
0 commit comments