@@ -57,7 +57,8 @@ class Window(QDialog):
5757 def __init__ (self , parent = None ):
5858 super (Window , self ).__init__ (parent )
5959
60- self ._plot_ref = None
60+ self .model_ref = None
61+ self .input_ref = None
6162 self .closed_loop_ref = None
6263 self .closed_loop_ax = None
6364 self .bode_plot_ref = None
@@ -124,6 +125,16 @@ def __init__(self, parent=None):
124125 layout_tf = self .createTfLayout ()
125126 left_menu .addLayout (layout_tf )
126127
128+
129+ trim_group = QFormLayout ()
130+ self .line_edit_trim = QDoubleSpinBox ()
131+ self .trim_airspeed = 20.0
132+ self .line_edit_trim .setValue (self .trim_airspeed )
133+ self .line_edit_trim .setRange (0.0 , 100.0 )
134+ self .line_edit_trim .textChanged .connect (self .onTrimChanged )
135+ trim_group .addRow (QLabel ("Trim airspeed" ), self .line_edit_trim )
136+ left_menu .addLayout (trim_group )
137+
127138 offset_group = QFormLayout ()
128139 self .line_edit_offset = QDoubleSpinBox ()
129140 self .line_edit_offset .setValue (0.0 )
@@ -151,7 +162,8 @@ def __init__(self, parent=None):
151162 self .setLayout (layout_v )
152163
153164 def reset (self ):
154- self ._plot_ref = None
165+ self .model_ref = None
166+ self .input_ref = None
155167 self .closed_loop_ref = None
156168 self .bode_plot_ref = None
157169 self .state_plot_refs = []
@@ -206,6 +218,16 @@ def updateCoeffTable(self):
206218 def onModelChanged (self ):
207219 self .btn_update_model .setEnabled (True )
208220
221+ def onTrimChanged (self ):
222+ try :
223+ self .trim_airspeed = float (self .line_edit_trim .text ())
224+ except ValueError :
225+ self .trim_airspeed = 0
226+ self .line_edit_trim .setValue (self .trim_airspeed )
227+
228+ self .btn_run_sys_id .setEnabled (True )
229+ self .plotInputOutput ()
230+
209231 def onOffsetChanged (self ):
210232 self .plotInputOutput ()
211233
@@ -543,17 +565,13 @@ def updateClosedLoop(self):
543565 kd = self .kd
544566 kff = self .kff
545567
546- airspeed = 40.0
547- airspeed_trim = 38.0
548- airspeed_scale = airspeed_trim / airspeed
549568 delays = ctrl .TransferFunction ([1 ], np .append ([1 ], np .zeros (self .sys_id_delays )), dt , inputs = 'r' , outputs = 'rd' )
550569 plant = ctrl .TransferFunction (num , den , dt , inputs = 'u' , outputs = 'plant_out' )
551570 sampler = ctrl .TransferFunction ([1 ], [1 , 0 ], dt , inputs = 'plant_out' , outputs = 'y' )
552571 sum_feedback = ctrl .summing_junction (inputs = ['rd' , '-y' ], output = 'e' )
553572
554573 # Default is standard PID
555574 feedforward = ctrl .TransferFunction ([kff ], [1 ], dt , inputs = 'rd' , outputs = 'ff_out' )
556- ff_scale = ctrl .TransferFunction ([airspeed_scale ], [1 ], inputs = 'ff_out' , outputs = 'ff_out_scaled' )
557575 i_control = ctrl .TransferFunction ([ki * dt , ki * dt ], [2 , - 2 ], dt , inputs = 'e' , outputs = 'i_out' ) # Integrator discretized using bilinear transform: s = 2(z-1)/(dt(z+1))
558576
559577 # Derivative with 1st order LPF (discretized using Euler method: s = (z-1)/dt)
@@ -565,8 +583,7 @@ def updateClosedLoop(self):
565583
566584 id_control = ctrl .summing_junction (inputs = ['e' , 'i_out' , 'd_out' ], output = 'id_out' )
567585 p_control = ctrl .TransferFunction ([kc ], [1 ], dt , inputs = 'id_out' , outputs = 'pid_out' )
568- pid_scale = ctrl .TransferFunction ([airspeed_scale ** 2 ], [1 ], inputs = 'pid_out' , outputs = 'pid_out_scaled' )
569- sum_control = ctrl .summing_junction (inputs = ['pid_out_scaled' , 'ff_out_scaled' ], output = 'u' )
586+ sum_control = ctrl .summing_junction (inputs = ['pid_out' , 'ff_out' ], output = 'u' )
570587
571588 remove_zero = False
572589 no_derivative_kick = True
@@ -579,7 +596,7 @@ def updateClosedLoop(self):
579596 # Derivative on feedback only to remove the "derivative kick"
580597 d_control = ctrl .TransferFunction (- derivative_num , derivative_den , dt , inputs = 'y' , outputs = 'd_out' )
581598
582- closed_loop = ctrl .interconnect ([delays , sampler , sum_feedback , feedforward , sum_control , p_control , i_control , d_control , id_control , pid_scale , ff_scale , plant ], inputs = 'r' , outputs = 'y' )
599+ closed_loop = ctrl .interconnect ([delays , sampler , sum_feedback , feedforward , sum_control , p_control , i_control , d_control , id_control , plant ], inputs = 'r' , outputs = 'y' )
583600
584601 t_out ,y_out = ctrl .step_response (closed_loop , T = np .arange (0 ,1 ,dt ))
585602 self .plotClosedLoop (t_out , y_out )
@@ -624,28 +641,35 @@ def plotBode(self, w, mag, w_cl, mag_cl):
624641 self .canvas .draw ()
625642
626643 def plotInputOutput (self , redraw = False ):
627- if self ._plot_ref is None or redraw :
644+ if len (self .true_airspeed ) == len (self .input ):
645+ scale = np .array (self .true_airspeed ) / self .trim_airspeed
646+ self .u = self .input * scale ** 2
647+
648+ if self .model_ref is None or redraw :
628649 # First time we have no plot reference, so do a normal plot.
629650 # .plot returns a list of line <reference>s, as we're
630651 # only getting one we can take the first element.
631652 self .figure .clear ()
632653 ax = self .figure .add_subplot (3 ,3 ,(1 ,3 ))
633- ax .plot (self .t , self .u , self .t , self .y )
654+ input_ref = ax .plot (self .t , self .u )
655+ self .input_ref = input_ref [0 ]
656+ ax .plot (self .t , self .y )
634657 plot_refs = ax .plot (0 , 0 )
635- self ._plot_ref = plot_refs [0 ]
658+ self .model_ref = plot_refs [0 ]
636659 ax .set_title ("Logged data" )
637660 ax .set_xlabel ("Time (s)" )
638661 ax .set_ylabel ("Amplitude" )
639662 ax .legend (["Input" , "Output" , "Model" ])
640663 else :
641664 # We have a reference, we can use it to update the data for that line.
642- self ._plot_ref .set_xdata (self .t_est )
665+ self .model_ref .set_xdata (self .t_est )
643666 try :
644667 offset = float (self .line_edit_offset .text ())
645668 except ValueError :
646669 offset = 0
647670
648- self ._plot_ref .set_ydata (self .y_est + offset )
671+ self .model_ref .set_ydata (self .y_est + offset )
672+ self .input_ref .set_ydata (self .u )
649673
650674 self .canvas .draw ()
651675
@@ -660,8 +684,10 @@ def loadLog(self):
660684 if select .exec_ ():
661685 self .reset ()
662686 self .t = select .t - select .t [0 ]
663- self .u = select .u
687+ self .input = select .u
688+ self .u = self .input
664689 self .y = select .y
690+ self .true_airspeed = select .v
665691 self .refreshInputOutputData ()
666692 self .runIdentification ()
667693 self .computeController ()
@@ -678,6 +704,8 @@ def resampleData(self, dt):
678704 self .t = np .arange (0 , self .t [- 1 ]+ self .dt , self .dt )
679705 self .u = resample (self .u , len (self .t ))
680706 self .y = resample (self .y , len (self .t ))
707+ self .true_airspeed = resample (self .true_airspeed , len (self .t ))
708+ self .input = resample (self .input , len (self .t ))
681709
682710class DoubleSlider (QSlider ):
683711
0 commit comments