1 // xmlauto.cxx - a more flexible, generic way to build autopilots
3 // Written by Curtis Olson, started January 2004.
5 // Copyright (C) 2004 Curtis L. Olson - curt@flightgear.org
7 // This program is free software; you can redistribute it and/or
8 // modify it under the terms of the GNU General Public License as
9 // published by the Free Software Foundation; either version 2 of the
10 // License, or (at your option) any later version.
12 // This program is distributed in the hope that it will be useful, but
13 // WITHOUT ANY WARRANTY; without even the implied warranty of
14 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 // General Public License for more details.
17 // You should have received a copy of the GNU General Public License
18 // along with this program; if not, write to the Free Software
19 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
24 #include <simgear/structure/exception.hxx>
25 #include <simgear/misc/sg_path.hxx>
27 #include <Main/fg_props.hxx>
28 #include <Main/globals.hxx>
29 #include <Main/util.hxx>
31 #include "xmlauto.hxx"
34 FGPIDController::FGPIDController( SGPropertyNode *node ):
54 for ( i = 0; i < node->nChildren(); ++i ) {
55 SGPropertyNode *child = node->getChild(i);
56 string cname = child->getName();
57 string cval = child->getStringValue();
58 if ( cname == "name" ) {
60 } else if ( cname == "debug" ) {
61 debug = child->getBoolValue();
62 } else if ( cname == "enable" ) {
63 // cout << "parsing enable" << endl;
64 SGPropertyNode *prop = child->getChild( "prop" );
66 // cout << "prop = " << prop->getStringValue() << endl;
67 enable_prop = fgGetNode( prop->getStringValue(), true );
69 // cout << "no prop child" << endl;
71 SGPropertyNode *val = child->getChild( "value" );
73 enable_value = val->getStringValue();
75 } else if ( cname == "input" ) {
76 SGPropertyNode *prop = child->getChild( "prop" );
78 input_prop = fgGetNode( prop->getStringValue(), true );
80 prop = child->getChild( "scale" );
82 y_scale = prop->getDoubleValue();
84 } else if ( cname == "reference" ) {
85 SGPropertyNode *prop = child->getChild( "prop" );
87 r_n_prop = fgGetNode( prop->getStringValue(), true );
89 prop = child->getChild( "value" );
91 r_n = prop->getDoubleValue();
94 prop = child->getChild( "scale" );
96 r_scale = prop->getDoubleValue();
98 } else if ( cname == "output" ) {
100 SGPropertyNode *prop;
101 while ( (prop = child->getChild("prop", i)) != NULL ) {
102 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
103 output_list.push_back( tmp );
106 } else if ( cname == "config" ) {
107 SGPropertyNode *prop;
109 prop = child->getChild( "Kp" );
110 if ( prop != NULL ) {
111 Kp = prop->getDoubleValue();
114 prop = child->getChild( "beta" );
115 if ( prop != NULL ) {
116 beta = prop->getDoubleValue();
119 prop = child->getChild( "alpha" );
120 if ( prop != NULL ) {
121 alpha = prop->getDoubleValue();
124 prop = child->getChild( "gamma" );
125 if ( prop != NULL ) {
126 gamma = prop->getDoubleValue();
129 prop = child->getChild( "Ti" );
130 if ( prop != NULL ) {
131 Ti = prop->getDoubleValue();
134 prop = child->getChild( "Td" );
135 if ( prop != NULL ) {
136 Td = prop->getDoubleValue();
139 prop = child->getChild( "u_min" );
140 if ( prop != NULL ) {
141 u_min = prop->getDoubleValue();
144 prop = child->getChild( "u_max" );
145 if ( prop != NULL ) {
146 u_max = prop->getDoubleValue();
149 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
150 if ( name.length() ) {
151 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
161 * Ok! Here is the PID controller algorithm that I would like to see
164 * delta_u_n = Kp * [ (ep_n - ep_n-1) + ((Ts/Ti)*e_n)
165 * + (Td/Ts)*(edf_n - 2*edf_n-1 + edf_n-2) ]
167 * u_n = u_n-1 + delta_u_n
171 * delta_u : The incremental output
172 * Kp : Proportional gain
173 * ep : Proportional error with reference weighing
176 * beta : Weighing factor
177 * r : Reference (setpoint)
178 * y : Process value, measured
181 * Ts : Sampling interval
182 * Ti : Integrator time
183 * Td : Derivator time
184 * edf : Derivate error with reference weighing and filtering
185 * edf_n = edf_n-1 / ((Ts/Tf) + 1) + ed_n * (Ts/Tf) / ((Ts/Tf) + 1)
188 * Tf = alpha * Td , where alpha usually is set to 0.1
189 * ed : Unfiltered derivate error with reference weighing
192 * gamma : Weighing factor
194 * u : absolute output
196 * Index n means the n'th value.
201 * y_n , r_n , beta=1 , gamma=0 , alpha=0.1 ,
202 * Kp , Ti , Td , Ts (is the sampling time available?)
209 void FGPIDController::update( double dt ) {
210 double ep_n; // proportional error with reference weighing
212 double ed_n; // derivative error
213 double edf_n; // derivative error filter
214 double Tf; // filter time
215 double delta_u_n = 0.0; // incremental output
216 double u_n = 0.0; // absolute output
217 double Ts = dt; // Sampling interval (sec)
220 // do nothing if time step is not positive (i.e. no time has
225 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
227 // first time being enabled, seed u_n with current
228 // property tree value
229 u_n = output_list[0]->getDoubleValue();
231 if ( u_n < u_min ) { u_n = u_min; }
232 if ( u_n > u_max ) { u_n = u_max; }
240 if ( enabled && Ts > 0.0) {
241 if ( debug ) cout << "Updating " << name << endl;
244 if ( input_prop != NULL ) {
245 y_n = input_prop->getDoubleValue() * y_scale;
249 if ( r_n_prop != NULL ) {
250 r_n = r_n_prop->getDoubleValue() * r_scale;
255 if ( debug ) cout << " input = " << y_n << " ref = " << r_n << endl;
257 // Calculates proportional error:
258 ep_n = beta * r_n - y_n;
259 if ( debug ) cout << " ep_n = " << ep_n;
260 if ( debug ) cout << " ep_n_1 = " << ep_n_1;
264 if ( debug ) cout << " e_n = " << e_n;
266 // Calculates derivate error:
267 ed_n = gamma * r_n - y_n;
268 if ( debug ) cout << " ed_n = " << ed_n;
271 // Calculates filter time:
273 if ( debug ) cout << " Tf = " << Tf;
275 // Filters the derivate error:
276 edf_n = edf_n_1 / (Ts/Tf + 1)
277 + ed_n * (Ts/Tf) / (Ts/Tf + 1);
278 if ( debug ) cout << " edf_n = " << edf_n;
283 // Calculates the incremental output:
285 delta_u_n = Kp * ( (ep_n - ep_n_1)
287 + ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
288 } else if ( Ti <= 0.0 ) {
289 delta_u_n = Kp * ( (ep_n - ep_n_1)
290 + ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
294 cout << " delta_u_n = " << delta_u_n << endl;
295 cout << "P:" << Kp * (ep_n - ep_n_1)
296 << " I:" << Kp * ((Ts/Ti) * e_n)
297 << " D:" << Kp * ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2))
301 // Integrator anti-windup logic:
302 if ( delta_u_n > (u_max - u_n_1) ) {
304 if ( debug ) cout << " max saturation " << endl;
305 } else if ( delta_u_n < (u_min - u_n_1) ) {
307 if ( debug ) cout << " min saturation " << endl;
310 // Calculates absolute output:
311 u_n = u_n_1 + delta_u_n;
312 if ( debug ) cout << " output = " << u_n << endl;
314 // Updates indexed values;
321 for ( i = 0; i < output_list.size(); ++i ) {
322 output_list[i]->setDoubleValue( u_n );
324 } else if ( !enabled ) {
327 // Updates indexed values;
336 FGPISimpleController::FGPISimpleController( SGPropertyNode *node ):
337 proportional( false ),
354 for ( i = 0; i < node->nChildren(); ++i ) {
355 SGPropertyNode *child = node->getChild(i);
356 string cname = child->getName();
357 string cval = child->getStringValue();
358 if ( cname == "name" ) {
360 } else if ( cname == "debug" ) {
361 debug = child->getBoolValue();
362 } else if ( cname == "enable" ) {
363 // cout << "parsing enable" << endl;
364 SGPropertyNode *prop = child->getChild( "prop" );
365 if ( prop != NULL ) {
366 // cout << "prop = " << prop->getStringValue() << endl;
367 enable_prop = fgGetNode( prop->getStringValue(), true );
369 // cout << "no prop child" << endl;
371 SGPropertyNode *val = child->getChild( "value" );
373 enable_value = val->getStringValue();
375 } else if ( cname == "input" ) {
376 SGPropertyNode *prop = child->getChild( "prop" );
377 if ( prop != NULL ) {
378 input_prop = fgGetNode( prop->getStringValue(), true );
380 prop = child->getChild( "scale" );
381 if ( prop != NULL ) {
382 y_scale = prop->getDoubleValue();
384 } else if ( cname == "reference" ) {
385 SGPropertyNode *prop = child->getChild( "prop" );
386 if ( prop != NULL ) {
387 r_n_prop = fgGetNode( prop->getStringValue(), true );
389 prop = child->getChild( "value" );
390 if ( prop != NULL ) {
391 r_n = prop->getDoubleValue();
394 prop = child->getChild( "scale" );
395 if ( prop != NULL ) {
396 r_scale = prop->getDoubleValue();
398 } else if ( cname == "output" ) {
400 SGPropertyNode *prop;
401 while ( (prop = child->getChild("prop", i)) != NULL ) {
402 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
403 output_list.push_back( tmp );
406 } else if ( cname == "config" ) {
407 SGPropertyNode *prop;
409 prop = child->getChild( "Kp" );
410 if ( prop != NULL ) {
411 Kp = prop->getDoubleValue();
415 prop = child->getChild( "Ki" );
416 if ( prop != NULL ) {
417 Ki = prop->getDoubleValue();
421 prop = child->getChild( "u_min" );
422 if ( prop != NULL ) {
423 u_min = prop->getDoubleValue();
427 prop = child->getChild( "u_max" );
428 if ( prop != NULL ) {
429 u_max = prop->getDoubleValue();
433 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
434 if ( name.length() ) {
435 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
442 void FGPISimpleController::update( double dt ) {
443 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
445 // we have just been enabled, zero out int_sum
454 if ( debug ) cout << "Updating " << name << endl;
456 if ( input_prop != NULL ) {
457 input = input_prop->getDoubleValue() * y_scale;
461 if ( r_n_prop != NULL ) {
462 r_n = r_n_prop->getDoubleValue() * r_scale;
467 double error = r_n - input;
468 if ( debug ) cout << "input = " << input
469 << " reference = " << r_n
470 << " error = " << error
473 double prop_comp = 0.0;
475 if ( offset_prop != NULL ) {
476 offset = offset_prop->getDoubleValue();
477 if ( debug ) cout << "offset = " << offset << endl;
479 offset = offset_value;
482 if ( proportional ) {
483 prop_comp = error * Kp + offset;
487 int_sum += error * Ki * dt;
492 if ( debug ) cout << "prop_comp = " << prop_comp
493 << " int_sum = " << int_sum << endl;
495 double output = prop_comp + int_sum;
498 if ( output < u_min ) {
501 if ( output > u_max ) {
505 if ( debug ) cout << "output = " << output << endl;
508 for ( i = 0; i < output_list.size(); ++i ) {
509 output_list[i]->setDoubleValue( output );
515 FGPredictor::FGPredictor ( SGPropertyNode *node ):
516 last_value ( 999999999.9 ),
524 for ( i = 0; i < node->nChildren(); ++i ) {
525 SGPropertyNode *child = node->getChild(i);
526 string cname = child->getName();
527 string cval = child->getStringValue();
528 if ( cname == "name" ) {
530 } else if ( cname == "debug" ) {
531 debug = child->getBoolValue();
532 } else if ( cname == "input" ) {
533 input_prop = fgGetNode( child->getStringValue(), true );
534 } else if ( cname == "seconds" ) {
535 seconds = child->getDoubleValue();
536 } else if ( cname == "filter-gain" ) {
537 filter_gain = child->getDoubleValue();
538 } else if ( cname == "output" ) {
539 SGPropertyNode *tmp = fgGetNode( child->getStringValue(), true );
540 output_list.push_back( tmp );
545 void FGPredictor::update( double dt ) {
547 Simple moving average filter converts input value to predicted value "seconds".
549 Smoothing as described by Curt Olson:
550 gain would be valid in the range of 0 - 1.0
551 1.0 would mean no filtering.
552 0.0 would mean no input.
553 0.5 would mean (1 part past value + 1 part current value) / 2
554 0.1 would mean (9 parts past value + 1 part current value) / 10
555 0.25 would mean (3 parts past value + 1 part current value) / 4
559 if ( input_prop != NULL ) {
560 ivalue = input_prop->getDoubleValue();
561 // no sense if there isn't an input :-)
569 // first time initialize average
570 if (last_value >= 999999999.0) {
575 double current = (ivalue - last_value)/dt; // calculate current error change (per second)
577 average = (1.0 - dt) * average + current * dt;
582 // calculate output with filter gain adjustment
583 double output = ivalue + (1.0 - filter_gain) * (average * seconds) + filter_gain * (current * seconds);
586 for ( i = 0; i < output_list.size(); ++i ) {
587 output_list[i]->setDoubleValue( output );
595 FGXMLAutopilot::FGXMLAutopilot() {
599 FGXMLAutopilot::~FGXMLAutopilot() {
603 void FGXMLAutopilot::init() {
604 config_props = fgGetNode( "/autopilot/new-config", true );
606 SGPropertyNode *path_n = fgGetNode("/sim/systems/autopilot/path");
609 SGPath config( globals->get_fg_root() );
610 config.append( path_n->getStringValue() );
612 SG_LOG( SG_ALL, SG_INFO, "Reading autopilot configuration from "
615 readProperties( config.str(), config_props );
618 SG_LOG( SG_ALL, SG_ALERT,
619 "Detected an internal inconsistancy in the autopilot");
620 SG_LOG( SG_ALL, SG_ALERT,
621 " configuration. See earlier errors for" );
622 SG_LOG( SG_ALL, SG_ALERT,
626 } catch (const sg_exception& exc) {
627 SG_LOG( SG_ALL, SG_ALERT, "Failed to load autopilot configuration: "
632 SG_LOG( SG_ALL, SG_WARN,
633 "No autopilot configuration specified for this model!");
638 void FGXMLAutopilot::reinit() {
645 void FGXMLAutopilot::bind() {
648 void FGXMLAutopilot::unbind() {
651 bool FGXMLAutopilot::build() {
652 SGPropertyNode *node;
655 int count = config_props->nChildren();
656 for ( i = 0; i < count; ++i ) {
657 node = config_props->getChild(i);
658 string name = node->getName();
659 // cout << name << endl;
660 if ( name == "pid-controller" ) {
661 FGXMLAutoComponent *c = new FGPIDController( node );
662 components.push_back( c );
663 } else if ( name == "pi-simple-controller" ) {
664 FGXMLAutoComponent *c = new FGPISimpleController( node );
665 components.push_back( c );
666 } else if ( name == "predict-simple" ) {
667 FGXMLAutoComponent *c = new FGPredictor( node );
668 components.push_back( c );
670 SG_LOG( SG_ALL, SG_ALERT, "Unknown top level section: "
681 * Update helper values
683 static void update_helper( double dt ) {
684 // Estimate speed in 5,10 seconds
685 static SGPropertyNode *vel = fgGetNode( "/velocities/airspeed-kt", true );
686 static SGPropertyNode *lookahead5
687 = fgGetNode( "/autopilot/internal/lookahead-5-sec-airspeed-kt", true );
688 static SGPropertyNode *lookahead10
689 = fgGetNode( "/autopilot/internal/lookahead-10-sec-airspeed-kt", true );
691 static double average = 0.0; // average/filtered prediction
692 static double v_last = 0.0; // last velocity
694 double v = vel->getDoubleValue();
697 a = (v - v_last) / dt;
700 average = (1.0 - dt) * average + dt * a;
705 lookahead5->setDoubleValue( v + average * 5.0 );
706 lookahead10->setDoubleValue( v + average * 10.0 );
710 // Calculate heading bug error normalized to +/- 180.0 (based on
711 // DG indicated heading)
712 static SGPropertyNode *bug
713 = fgGetNode( "/autopilot/settings/heading-bug-deg", true );
714 static SGPropertyNode *ind_hdg
715 = fgGetNode( "/instrumentation/heading-indicator/indicated-heading-deg",
717 static SGPropertyNode *ind_bug_error
718 = fgGetNode( "/autopilot/internal/heading-bug-error-deg", true );
720 double diff = bug->getDoubleValue() - ind_hdg->getDoubleValue();
721 if ( diff < -180.0 ) { diff += 360.0; }
722 if ( diff > 180.0 ) { diff -= 360.0; }
723 ind_bug_error->setDoubleValue( diff );
725 // Calculate heading bug error normalized to +/- 180.0 (based on
726 // actual/nodrift magnetic-heading, i.e. a DG slaved to magnetic
728 static SGPropertyNode *mag_hdg
729 = fgGetNode( "/orientation/heading-magnetic-deg", true );
730 static SGPropertyNode *fdm_bug_error
731 = fgGetNode( "/autopilot/internal/fdm-heading-bug-error-deg", true );
733 diff = bug->getDoubleValue() - mag_hdg->getDoubleValue();
734 if ( diff < -180.0 ) { diff += 360.0; }
735 if ( diff > 180.0 ) { diff -= 360.0; }
736 fdm_bug_error->setDoubleValue( diff );
738 // Calculate true heading error normalized to +/- 180.0
739 static SGPropertyNode *target_true
740 = fgGetNode( "/autopilot/settings/true-heading-deg", true );
741 static SGPropertyNode *true_hdg
742 = fgGetNode( "/orientation/heading-deg", true );
743 static SGPropertyNode *true_track
744 = fgGetNode( "/instrumentation/gps/indicated-track-true-deg", true );
745 static SGPropertyNode *true_error
746 = fgGetNode( "/autopilot/internal/true-heading-error-deg", true );
748 diff = target_true->getDoubleValue() - true_hdg->getDoubleValue();
749 if ( diff < -180.0 ) { diff += 360.0; }
750 if ( diff > 180.0 ) { diff -= 360.0; }
751 true_error->setDoubleValue( diff );
753 // Calculate nav1 target heading error normalized to +/- 180.0
754 static SGPropertyNode *target_nav1
755 = fgGetNode( "/radios/nav[0]/radials/target-auto-hdg-deg", true );
756 static SGPropertyNode *true_nav1
757 = fgGetNode( "/autopilot/internal/nav1-heading-error-deg", true );
758 static SGPropertyNode *true_track_nav1
759 = fgGetNode( "/autopilot/internal/nav1-track-error-deg", true );
761 diff = target_nav1->getDoubleValue() - true_hdg->getDoubleValue();
762 if ( diff < -180.0 ) { diff += 360.0; }
763 if ( diff > 180.0 ) { diff -= 360.0; }
764 true_nav1->setDoubleValue( diff );
766 diff = target_nav1->getDoubleValue() - true_track->getDoubleValue();
767 if ( diff < -180.0 ) { diff += 360.0; }
768 if ( diff > 180.0 ) { diff -= 360.0; }
769 true_track_nav1->setDoubleValue( diff );
771 // Calculate vertical speed in fpm
772 static SGPropertyNode *vs_fps
773 = fgGetNode( "/velocities/vertical-speed-fps", true );
774 static SGPropertyNode *vs_fpm
775 = fgGetNode( "/autopilot/internal/vert-speed-fpm", true );
777 vs_fpm->setDoubleValue( vs_fps->getDoubleValue() * 60.0 );
780 // Calculate static port pressure rate in [inhg/s].
781 // Used to determine vertical speed.
782 static SGPropertyNode *static_pressure
783 = fgGetNode( "/systems/static[0]/pressure-inhg", true );
784 static SGPropertyNode *pressure_rate
785 = fgGetNode( "/autopilot/internal/pressure-rate", true );
786 static SGPropertyNode *filter_time
787 = fgGetNode( "/autopilot/internal/ft", true );
789 static double last_static_pressure = 0.0;
790 static double last_pressure_rate = 0.0;
792 double Tf = filter_time->getDoubleValue();
794 if ( dt > 0.0 && Tf > 0.0) {
795 double current_static_pressure = static_pressure->getDoubleValue();
797 double current_pressure_rate =
798 ( current_static_pressure - last_static_pressure ) / dt;
803 current_pressure_rate =
804 (1.0/(W + 1.0))*last_pressure_rate +
805 ((W)/(W + 1.0))*current_pressure_rate;
807 pressure_rate->setDoubleValue(current_pressure_rate);
809 last_static_pressure = current_static_pressure;
810 last_pressure_rate = current_pressure_rate;
817 * Update the list of autopilot components
820 void FGXMLAutopilot::update( double dt ) {
824 for ( i = 0; i < components.size(); ++i ) {
825 components[i]->update( dt );