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 - http://www.flightgear.org/~curt
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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
29 #include <simgear/structure/exception.hxx>
30 #include <simgear/misc/sg_path.hxx>
31 #include <simgear/sg_inlines.h>
33 #include <Main/fg_props.hxx>
34 #include <Main/globals.hxx>
35 #include <Main/util.hxx>
37 #include "xmlauto.hxx"
42 FGPIDController::FGPIDController( SGPropertyNode *node ):
66 for ( i = 0; i < node->nChildren(); ++i ) {
67 SGPropertyNode *child = node->getChild(i);
68 string cname = child->getName();
69 string cval = child->getStringValue();
70 if ( cname == "name" ) {
72 } else if ( cname == "debug" ) {
73 debug = child->getBoolValue();
74 } else if ( cname == "enable" ) {
75 // cout << "parsing enable" << endl;
76 SGPropertyNode *prop = child->getChild( "prop" );
78 // cout << "prop = " << prop->getStringValue() << endl;
79 enable_prop = fgGetNode( prop->getStringValue(), true );
81 // cout << "no prop child" << endl;
83 SGPropertyNode *val = child->getChild( "value" );
85 enable_value = val->getStringValue();
87 SGPropertyNode *pass = child->getChild( "honor-passive" );
89 honor_passive = pass->getBoolValue();
91 } else if ( cname == "input" ) {
92 SGPropertyNode *prop = child->getChild( "prop" );
94 input_prop = fgGetNode( prop->getStringValue(), true );
96 prop = child->getChild( "scale" );
98 y_scale = prop->getDoubleValue();
100 prop = child->getChild( "offset" );
101 if ( prop != NULL ) {
102 y_offset = prop->getDoubleValue();
104 } else if ( cname == "reference" ) {
105 SGPropertyNode *prop = child->getChild( "prop" );
106 if ( prop != NULL ) {
107 r_n_prop = fgGetNode( prop->getStringValue(), true );
109 prop = child->getChild( "value" );
110 if ( prop != NULL ) {
111 r_n_value = prop->getDoubleValue();
114 prop = child->getChild( "scale" );
115 if ( prop != NULL ) {
116 r_scale = prop->getDoubleValue();
118 prop = child->getChild( "offset" );
119 if ( prop != NULL ) {
120 r_offset = prop->getDoubleValue();
122 } else if ( cname == "output" ) {
124 SGPropertyNode *prop;
125 while ( (prop = child->getChild("prop", i)) != NULL ) {
126 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
127 output_list.push_back( tmp );
130 } else if ( cname == "config" ) {
131 SGPropertyNode *config;
133 config = child->getChild( "Ts" );
134 if ( config != NULL ) {
135 desiredTs = config->getDoubleValue();
138 config = child->getChild( "Kp" );
139 if ( config != NULL ) {
140 SGPropertyNode *val = config->getChild( "value" );
142 Kp = val->getDoubleValue();
145 SGPropertyNode *prop = config->getChild( "prop" );
146 if ( prop != NULL ) {
147 Kp_prop = fgGetNode( prop->getStringValue(), true );
149 Kp_prop->setDoubleValue(Kp);
153 // output deprecated usage warning
154 if (val == NULL && prop == NULL) {
155 Kp = config->getDoubleValue();
156 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated Kp config. Please use <prop> and/or <value> tags." );
157 if ( name.length() ) {
158 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
163 config = child->getChild( "beta" );
164 if ( config != NULL ) {
165 beta = config->getDoubleValue();
168 config = child->getChild( "alpha" );
169 if ( config != NULL ) {
170 alpha = config->getDoubleValue();
173 config = child->getChild( "gamma" );
174 if ( config != NULL ) {
175 gamma = config->getDoubleValue();
178 config = child->getChild( "Ti" );
179 if ( config != NULL ) {
180 SGPropertyNode *val = config->getChild( "value" );
182 Ti = val->getDoubleValue();
185 SGPropertyNode *prop = config->getChild( "prop" );
186 if ( prop != NULL ) {
187 Ti_prop = fgGetNode( prop->getStringValue(), true );
189 Ti_prop->setDoubleValue(Kp);
193 // output deprecated usage warning
194 if (val == NULL && prop == NULL) {
195 Ti = config->getDoubleValue();
196 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated Ti config. Please use <prop> and/or <value> tags." );
197 if ( name.length() ) {
198 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
203 config = child->getChild( "Td" );
204 if ( config != NULL ) {
205 SGPropertyNode *val = config->getChild( "value" );
207 Td = val->getDoubleValue();
210 SGPropertyNode *prop = config->getChild( "prop" );
211 if ( prop != NULL ) {
212 Td_prop = fgGetNode( prop->getStringValue(), true );
214 Td_prop->setDoubleValue(Kp);
218 // output deprecated usage warning
219 if (val == NULL && prop == NULL) {
220 Td = config->getDoubleValue();
221 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated Td config. Please use <prop> and/or <value> tags." );
222 if ( name.length() ) {
223 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
228 config = child->getChild( "u_min" );
229 if ( config != NULL ) {
230 SGPropertyNode *val = config->getChild( "value" );
232 u_min = val->getDoubleValue();
235 SGPropertyNode *prop = config->getChild( "prop" );
236 if ( prop != NULL ) {
237 umin_prop = fgGetNode( prop->getStringValue(), true );
239 umin_prop->setDoubleValue(u_min);
243 // output deprecated usage warning
244 if (val == NULL && prop == NULL) {
245 u_min = config->getDoubleValue();
246 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated u_min config. Please use <prop> and/or <value> tags." );
247 if ( name.length() ) {
248 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
253 config = child->getChild( "u_max" );
254 if ( config != NULL ) {
255 SGPropertyNode *val = config->getChild( "value" );
257 u_max = val->getDoubleValue();
260 SGPropertyNode *prop = config->getChild( "prop" );
261 if ( prop != NULL ) {
262 umax_prop = fgGetNode( prop->getStringValue(), true );
264 umax_prop->setDoubleValue(u_max);
268 // output deprecated usage warning
269 if (val == NULL && prop == NULL) {
270 u_max = config->getDoubleValue();
271 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated u_max config. Please use <prop> and/or <value> tags." );
272 if ( name.length() ) {
273 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
278 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
279 if ( name.length() ) {
280 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
290 * Ok! Here is the PID controller algorithm that I would like to see
293 * delta_u_n = Kp * [ (ep_n - ep_n-1) + ((Ts/Ti)*e_n)
294 * + (Td/Ts)*(edf_n - 2*edf_n-1 + edf_n-2) ]
296 * u_n = u_n-1 + delta_u_n
300 * delta_u : The incremental output
301 * Kp : Proportional gain
302 * ep : Proportional error with reference weighing
305 * beta : Weighing factor
306 * r : Reference (setpoint)
307 * y : Process value, measured
310 * Ts : Sampling interval
311 * Ti : Integrator time
312 * Td : Derivator time
313 * edf : Derivate error with reference weighing and filtering
314 * edf_n = edf_n-1 / ((Ts/Tf) + 1) + ed_n * (Ts/Tf) / ((Ts/Tf) + 1)
317 * Tf = alpha * Td , where alpha usually is set to 0.1
318 * ed : Unfiltered derivate error with reference weighing
321 * gamma : Weighing factor
323 * u : absolute output
325 * Index n means the n'th value.
330 * y_n , r_n , beta=1 , gamma=0 , alpha=0.1 ,
331 * Kp , Ti , Td , Ts (is the sampling time available?)
338 void FGPIDController::update( double dt ) {
339 double ep_n; // proportional error with reference weighing
341 double ed_n; // derivative error
342 double edf_n; // derivative error filter
343 double Tf; // filter time
344 double delta_u_n = 0.0; // incremental output
345 double u_n = 0.0; // absolute output
346 double Ts; // sampling interval (sec)
347 if (umin_prop != NULL)u_min = umin_prop->getDoubleValue();
348 if (umax_prop != NULL)u_max = umax_prop->getDoubleValue();
349 if (Ti_prop != NULL)Ti = Ti_prop->getDoubleValue();
350 if (Td_prop != NULL)Td = Td_prop->getDoubleValue();
353 if ( elapsedTime <= desiredTs ) {
354 // do nothing if time step is not positive (i.e. no time has
361 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
363 // first time being enabled, seed u_n with current
364 // property tree value
365 u_n = output_list[0]->getDoubleValue();
367 if ( u_n < u_min ) { u_n = u_min; }
368 if ( u_n > u_max ) { u_n = u_max; }
376 if ( enabled && Ts > 0.0) {
377 if ( debug ) cout << "Updating " << name
378 << " Ts " << Ts << endl;
381 if ( input_prop != NULL ) {
382 y_n = input_prop->getDoubleValue() * y_scale + y_offset;
386 if ( r_n_prop != NULL ) {
387 r_n = r_n_prop->getDoubleValue() * r_scale + r_offset;
392 if ( debug ) cout << " input = " << y_n << " ref = " << r_n << endl;
394 // Calculates proportional error:
395 ep_n = beta * r_n - y_n;
396 if ( debug ) cout << " ep_n = " << ep_n;
397 if ( debug ) cout << " ep_n_1 = " << ep_n_1;
401 if ( debug ) cout << " e_n = " << e_n;
403 // Calculates derivate error:
404 ed_n = gamma * r_n - y_n;
405 if ( debug ) cout << " ed_n = " << ed_n;
408 // Calculates filter time:
410 if ( debug ) cout << " Tf = " << Tf;
412 // Filters the derivate error:
413 edf_n = edf_n_1 / (Ts/Tf + 1)
414 + ed_n * (Ts/Tf) / (Ts/Tf + 1);
415 if ( debug ) cout << " edf_n = " << edf_n;
420 // Calculates the incremental output:
422 if (Kp_prop != NULL) {
423 Kp = Kp_prop->getDoubleValue();
425 delta_u_n = Kp * ( (ep_n - ep_n_1)
427 + ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
431 cout << " delta_u_n = " << delta_u_n << endl;
432 cout << "P:" << Kp * (ep_n - ep_n_1)
433 << " I:" << Kp * ((Ts/Ti) * e_n)
434 << " D:" << Kp * ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2))
438 // Integrator anti-windup logic:
439 if ( delta_u_n > (u_max - u_n_1) ) {
440 delta_u_n = u_max - u_n_1;
441 if ( debug ) cout << " max saturation " << endl;
442 } else if ( delta_u_n < (u_min - u_n_1) ) {
443 delta_u_n = u_min - u_n_1;
444 if ( debug ) cout << " min saturation " << endl;
447 // Calculates absolute output:
448 u_n = u_n_1 + delta_u_n;
449 if ( debug ) cout << " output = " << u_n << endl;
451 // Updates indexed values;
457 // passive_ignore == true means that we go through all the
458 // motions, but drive the outputs. This is analogous to
459 // running the autopilot with the "servos" off. This is
460 // helpful for things like flight directors which position
461 // their vbars from the autopilot computations.
462 if ( passive_mode->getBoolValue() && honor_passive ) {
466 for ( i = 0; i < output_list.size(); ++i ) {
467 output_list[i]->setDoubleValue( u_n );
470 } else if ( !enabled ) {
473 // Updates indexed values;
482 FGPISimpleController::FGPISimpleController( SGPropertyNode *node ):
483 proportional( false ),
500 for ( i = 0; i < node->nChildren(); ++i ) {
501 SGPropertyNode *child = node->getChild(i);
502 string cname = child->getName();
503 string cval = child->getStringValue();
504 if ( cname == "name" ) {
506 } else if ( cname == "debug" ) {
507 debug = child->getBoolValue();
508 } else if ( cname == "enable" ) {
509 // cout << "parsing enable" << endl;
510 SGPropertyNode *prop = child->getChild( "prop" );
511 if ( prop != NULL ) {
512 // cout << "prop = " << prop->getStringValue() << endl;
513 enable_prop = fgGetNode( prop->getStringValue(), true );
515 // cout << "no prop child" << endl;
517 SGPropertyNode *val = child->getChild( "value" );
519 enable_value = val->getStringValue();
521 } else if ( cname == "input" ) {
522 SGPropertyNode *prop = child->getChild( "prop" );
523 if ( prop != NULL ) {
524 input_prop = fgGetNode( prop->getStringValue(), true );
526 prop = child->getChild( "scale" );
527 if ( prop != NULL ) {
528 y_scale = prop->getDoubleValue();
530 } else if ( cname == "reference" ) {
531 SGPropertyNode *prop = child->getChild( "prop" );
532 if ( prop != NULL ) {
533 r_n_prop = fgGetNode( prop->getStringValue(), true );
535 prop = child->getChild( "value" );
536 if ( prop != NULL ) {
537 r_n_value = prop->getDoubleValue();
540 prop = child->getChild( "scale" );
541 if ( prop != NULL ) {
542 r_scale = prop->getDoubleValue();
544 } else if ( cname == "output" ) {
546 SGPropertyNode *prop;
547 while ( (prop = child->getChild("prop", i)) != NULL ) {
548 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
549 output_list.push_back( tmp );
552 } else if ( cname == "config" ) {
553 SGPropertyNode *prop;
555 prop = child->getChild( "Kp" );
556 if ( prop != NULL ) {
557 SGPropertyNode *val = prop->getChild( "value" );
559 Kp = val->getDoubleValue();
562 SGPropertyNode *prop1 = prop->getChild( "prop" );
563 if ( prop1 != NULL ) {
564 Kp_prop = fgGetNode( prop1->getStringValue(), true );
566 Kp_prop->setDoubleValue(Kp);
570 // output deprecated usage warning
571 if (val == NULL && prop1 == NULL) {
572 Kp = prop->getDoubleValue();
573 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated Kp config. Please use <prop> and/or <value> tags." );
578 prop = child->getChild( "Ki" );
579 if ( prop != NULL ) {
580 Ki = prop->getDoubleValue();
584 prop = child->getChild( "u_min" );
585 if ( prop != NULL ) {
586 SGPropertyNode *val = prop->getChild( "value" );
588 u_min = val->getDoubleValue();
591 SGPropertyNode *prop1 = prop->getChild( "prop" );
592 if ( prop1 != NULL ) {
593 umin_prop = fgGetNode( prop1->getStringValue(), true );
595 umin_prop->setDoubleValue(u_min);
599 // output deprecated usage warning
600 if (val == NULL && prop1 == NULL) {
601 u_min = prop->getDoubleValue();
602 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated u_min config. Please use <prop> and/or <value> tags." );
607 prop = child->getChild( "u_max" );
608 if ( prop != NULL ) {
609 SGPropertyNode *val = prop->getChild( "value" );
611 u_max = val->getDoubleValue();
614 SGPropertyNode *prop1 = prop->getChild( "prop" );
615 if ( prop1 != NULL ) {
616 umax_prop = fgGetNode( prop1->getStringValue(), true );
618 umax_prop->setDoubleValue(u_max);
622 // output deprecated usage warning
623 if (val == NULL && prop1 == NULL) {
624 u_max = prop->getDoubleValue();
625 SG_LOG( SG_AUTOPILOT, SG_WARN, "Deprecated u_max config. Please use <prop> and/or <value> tags." );
630 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
631 if ( name.length() ) {
632 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
639 void FGPISimpleController::update( double dt ) {
640 if (umin_prop != NULL)u_min = umin_prop->getDoubleValue();
641 if (umax_prop != NULL)u_max = umax_prop->getDoubleValue();
642 if (Kp_prop != NULL)Kp = Kp_prop->getDoubleValue();
644 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
646 // we have just been enabled, zero out int_sum
655 if ( debug ) cout << "Updating " << name << endl;
657 if ( input_prop != NULL ) {
658 input = input_prop->getDoubleValue() * y_scale;
662 if ( r_n_prop != NULL ) {
663 r_n = r_n_prop->getDoubleValue() * r_scale;
668 double error = r_n - input;
669 if ( debug ) cout << "input = " << input
670 << " reference = " << r_n
671 << " error = " << error
674 double prop_comp = 0.0;
676 if ( offset_prop != NULL ) {
677 offset = offset_prop->getDoubleValue();
678 if ( debug ) cout << "offset = " << offset << endl;
680 offset = offset_value;
683 if ( proportional ) {
684 prop_comp = error * Kp + offset;
688 int_sum += error * Ki * dt;
693 if ( debug ) cout << "prop_comp = " << prop_comp
694 << " int_sum = " << int_sum << endl;
696 double output = prop_comp + int_sum;
699 if ( output < u_min ) {
702 if ( output > u_max ) {
706 if ( debug ) cout << "output = " << output << endl;
709 for ( i = 0; i < output_list.size(); ++i ) {
710 output_list[i]->setDoubleValue( output );
716 FGPredictor::FGPredictor ( SGPropertyNode *node ):
717 last_value ( 999999999.9 ),
725 for ( i = 0; i < node->nChildren(); ++i ) {
726 SGPropertyNode *child = node->getChild(i);
727 string cname = child->getName();
728 string cval = child->getStringValue();
729 if ( cname == "name" ) {
731 } else if ( cname == "debug" ) {
732 debug = child->getBoolValue();
733 } else if ( cname == "input" ) {
734 input_prop = fgGetNode( child->getStringValue(), true );
735 } else if ( cname == "seconds" ) {
736 seconds = child->getDoubleValue();
737 } else if ( cname == "filter-gain" ) {
738 filter_gain = child->getDoubleValue();
739 } else if ( cname == "output" ) {
740 SGPropertyNode *tmp = fgGetNode( child->getStringValue(), true );
741 output_list.push_back( tmp );
746 void FGPredictor::update( double dt ) {
748 Simple moving average filter converts input value to predicted value "seconds".
750 Smoothing as described by Curt Olson:
751 gain would be valid in the range of 0 - 1.0
752 1.0 would mean no filtering.
753 0.0 would mean no input.
754 0.5 would mean (1 part past value + 1 part current value) / 2
755 0.1 would mean (9 parts past value + 1 part current value) / 10
756 0.25 would mean (3 parts past value + 1 part current value) / 4
760 if ( input_prop != NULL ) {
761 ivalue = input_prop->getDoubleValue();
762 // no sense if there isn't an input :-)
770 // first time initialize average
771 if (last_value >= 999999999.0) {
776 double current = (ivalue - last_value)/dt; // calculate current error change (per second)
778 average = (1.0 - dt) * average + current * dt;
783 // calculate output with filter gain adjustment
784 double output = ivalue + (1.0 - filter_gain) * (average * seconds) + filter_gain * (current * seconds);
787 for ( i = 0; i < output_list.size(); ++i ) {
788 output_list[i]->setDoubleValue( output );
796 FGDigitalFilter::FGDigitalFilter(SGPropertyNode *node):
802 output_min_clamp( -std::numeric_limits<double>::max() ),
803 output_max_clamp( std::numeric_limits<double>::max() )
806 for ( i = 0; i < node->nChildren(); ++i ) {
807 SGPropertyNode *child = node->getChild(i);
808 string cname = child->getName();
809 string cval = child->getStringValue();
810 if ( cname == "name" ) {
812 } else if ( cname == "debug" ) {
813 debug = child->getBoolValue();
814 } else if ( cname == "enable" ) {
815 SGPropertyNode *prop = child->getChild( "prop" );
816 if ( prop != NULL ) {
817 enable_prop = fgGetNode( prop->getStringValue(), true );
819 SGPropertyNode *val = child->getChild( "value" );
821 enable_value = val->getStringValue();
823 SGPropertyNode *pass = child->getChild( "honor-passive" );
824 if ( pass != NULL ) {
825 honor_passive = pass->getBoolValue();
827 } else if ( cname == "type" ) {
828 if ( cval == "exponential" ) {
829 filterType = exponential;
830 } else if (cval == "double-exponential") {
831 filterType = doubleExponential;
832 } else if (cval == "moving-average") {
833 filterType = movingAverage;
834 } else if (cval == "noise-spike") {
835 filterType = noiseSpike;
836 } else if (cval == "gain") {
838 } else if (cval == "reciprocal") {
839 filterType = reciprocal;
841 } else if ( cname == "input" ) {
842 input_prop = fgGetNode( child->getStringValue(), true );
843 } else if ( cname == "filter-time" ) {
844 Tf = child->getDoubleValue();
845 } else if ( cname == "samples" ) {
846 samples = child->getIntValue();
847 } else if ( cname == "max-rate-of-change" ) {
848 rateOfChange = child->getDoubleValue();
849 } else if ( cname == "gain" ) {
850 SGPropertyNode *val = child->getChild( "value" );
852 gainFactor = val->getDoubleValue();
854 SGPropertyNode *prop = child->getChild( "prop" );
855 if ( prop != NULL ) {
856 gain_prop = fgGetNode( prop->getStringValue(), true );
857 gain_prop->setDoubleValue(gainFactor);
859 } else if ( cname == "u_min" ) {
860 output_min_clamp = child->getDoubleValue();
861 } else if ( cname == "u_max" ) {
862 output_max_clamp = child->getDoubleValue();
863 } else if ( cname == "output" ) {
864 SGPropertyNode *tmp = fgGetNode( child->getStringValue(), true );
865 output_list.push_back( tmp );
869 output.resize(2, 0.0);
870 input.resize(samples + 1, 0.0);
873 void FGDigitalFilter::update(double dt)
875 if ( (input_prop != NULL &&
876 enable_prop != NULL &&
877 enable_prop->getStringValue() == enable_value) ||
878 (enable_prop == NULL &&
879 input_prop != NULL) ) {
881 input.push_front(input_prop->getDoubleValue());
882 input.resize(samples + 1, 0.0);
885 // first time being enabled, initialize output to the
886 // value of the output property to avoid bumping.
887 output.push_front(output_list[0]->getDoubleValue());
895 if ( enabled && dt > 0.0 ) {
899 * Output[n] = alpha*Input[n] + (1-alpha)*Output[n-1]
903 if (filterType == exponential)
905 double alpha = 1 / ((Tf/dt) + 1);
906 output.push_front(alpha * input[0] +
907 (1 - alpha) * output[0]);
909 else if (filterType == doubleExponential)
911 double alpha = 1 / ((Tf/dt) + 1);
912 output.push_front(alpha * alpha * input[0] +
913 2 * (1 - alpha) * output[0] -
914 (1 - alpha) * (1 - alpha) * output[1]);
916 else if (filterType == movingAverage)
918 output.push_front(output[0] +
919 (input[0] - input.back()) / samples);
921 else if (filterType == noiseSpike)
923 double maxChange = rateOfChange * dt;
925 if ((output[0] - input[0]) > maxChange)
927 output.push_front(output[0] - maxChange);
929 else if ((output[0] - input[0]) < -maxChange)
931 output.push_front(output[0] + maxChange);
933 else if (fabs(input[0] - output[0]) <= maxChange)
935 output.push_front(input[0]);
938 else if (filterType == gain)
940 if (gain_prop != NULL) {
941 gainFactor = gain_prop->getDoubleValue();
943 output[0] = gainFactor * input[0];
945 else if (filterType == reciprocal)
947 if (gain_prop != NULL) {
948 gainFactor = gain_prop->getDoubleValue();
950 if (input[0] != 0.0) {
951 output[0] = gainFactor / input[0];
955 if (output[0] < output_min_clamp) {
956 output[0] = output_min_clamp;
958 else if (output[0] > output_max_clamp) {
959 output[0] = output_max_clamp;
963 for ( i = 0; i < output_list.size(); ++i ) {
964 output_list[i]->setDoubleValue( output[0] );
970 cout << "input:" << input[0]
971 << "\toutput:" << output[0] << endl;
977 FGXMLAutopilot::FGXMLAutopilot() {
981 FGXMLAutopilot::~FGXMLAutopilot() {
985 void FGXMLAutopilot::init() {
986 config_props = fgGetNode( "/autopilot/new-config", true );
988 SGPropertyNode *path_n = fgGetNode("/sim/systems/autopilot/path");
991 SGPath config( globals->get_fg_root() );
992 config.append( path_n->getStringValue() );
994 SG_LOG( SG_ALL, SG_INFO, "Reading autopilot configuration from "
997 readProperties( config.str(), config_props );
1000 SG_LOG( SG_ALL, SG_ALERT,
1001 "Detected an internal inconsistency in the autopilot");
1002 SG_LOG( SG_ALL, SG_ALERT,
1003 " configuration. See earlier errors for" );
1004 SG_LOG( SG_ALL, SG_ALERT,
1008 } catch (const sg_exception& exc) {
1009 SG_LOG( SG_ALL, SG_ALERT, "Failed to load autopilot configuration: "
1014 SG_LOG( SG_ALL, SG_WARN,
1015 "No autopilot configuration specified for this model!");
1020 void FGXMLAutopilot::reinit() {
1026 void FGXMLAutopilot::bind() {
1029 void FGXMLAutopilot::unbind() {
1032 bool FGXMLAutopilot::build() {
1033 SGPropertyNode *node;
1036 int count = config_props->nChildren();
1037 for ( i = 0; i < count; ++i ) {
1038 node = config_props->getChild(i);
1039 string name = node->getName();
1040 // cout << name << endl;
1041 if ( name == "pid-controller" ) {
1042 components.push_back( new FGPIDController( node ) );
1043 } else if ( name == "pi-simple-controller" ) {
1044 components.push_back( new FGPISimpleController( node ) );
1045 } else if ( name == "predict-simple" ) {
1046 components.push_back( new FGPredictor( node ) );
1047 } else if ( name == "filter" ) {
1048 components.push_back( new FGDigitalFilter( node ) );
1050 SG_LOG( SG_ALL, SG_ALERT, "Unknown top level section: "
1061 * Update helper values
1063 static void update_helper( double dt ) {
1064 // Estimate speed in 5,10 seconds
1065 static SGPropertyNode *vel = fgGetNode( "/velocities/airspeed-kt", true );
1066 static SGPropertyNode *lookahead5
1067 = fgGetNode( "/autopilot/internal/lookahead-5-sec-airspeed-kt", true );
1068 static SGPropertyNode *lookahead10
1069 = fgGetNode( "/autopilot/internal/lookahead-10-sec-airspeed-kt", true );
1071 static double average = 0.0; // average/filtered prediction
1072 static double v_last = 0.0; // last velocity
1074 double v = vel->getDoubleValue();
1077 a = (v - v_last) / dt;
1080 average = (1.0 - dt) * average + dt * a;
1085 lookahead5->setDoubleValue( v + average * 5.0 );
1086 lookahead10->setDoubleValue( v + average * 10.0 );
1090 // Calculate heading bug error normalized to +/- 180.0 (based on
1091 // DG indicated heading)
1092 static SGPropertyNode *bug
1093 = fgGetNode( "/autopilot/settings/heading-bug-deg", true );
1094 static SGPropertyNode *ind_hdg
1095 = fgGetNode( "/instrumentation/heading-indicator/indicated-heading-deg",
1097 static SGPropertyNode *ind_bug_error
1098 = fgGetNode( "/autopilot/internal/heading-bug-error-deg", true );
1100 double diff = bug->getDoubleValue() - ind_hdg->getDoubleValue();
1101 if ( diff < -180.0 ) { diff += 360.0; }
1102 if ( diff > 180.0 ) { diff -= 360.0; }
1103 ind_bug_error->setDoubleValue( diff );
1105 // Calculate heading bug error normalized to +/- 180.0 (based on
1106 // actual/nodrift magnetic-heading, i.e. a DG slaved to magnetic
1108 static SGPropertyNode *mag_hdg
1109 = fgGetNode( "/orientation/heading-magnetic-deg", true );
1110 static SGPropertyNode *fdm_bug_error
1111 = fgGetNode( "/autopilot/internal/fdm-heading-bug-error-deg", true );
1113 diff = bug->getDoubleValue() - mag_hdg->getDoubleValue();
1114 if ( diff < -180.0 ) { diff += 360.0; }
1115 if ( diff > 180.0 ) { diff -= 360.0; }
1116 fdm_bug_error->setDoubleValue( diff );
1118 // Calculate true heading error normalized to +/- 180.0
1119 static SGPropertyNode *target_true
1120 = fgGetNode( "/autopilot/settings/true-heading-deg", true );
1121 static SGPropertyNode *true_hdg
1122 = fgGetNode( "/orientation/heading-deg", true );
1123 static SGPropertyNode *true_track
1124 = fgGetNode( "/instrumentation/gps/indicated-track-true-deg", true );
1125 static SGPropertyNode *true_error
1126 = fgGetNode( "/autopilot/internal/true-heading-error-deg", true );
1128 diff = target_true->getDoubleValue() - true_hdg->getDoubleValue();
1129 if ( diff < -180.0 ) { diff += 360.0; }
1130 if ( diff > 180.0 ) { diff -= 360.0; }
1131 true_error->setDoubleValue( diff );
1133 // Calculate nav1 target heading error normalized to +/- 180.0
1134 static SGPropertyNode *target_nav1
1135 = fgGetNode( "/instrumentation/nav[0]/radials/target-auto-hdg-deg", true );
1136 static SGPropertyNode *true_nav1
1137 = fgGetNode( "/autopilot/internal/nav1-heading-error-deg", true );
1138 static SGPropertyNode *true_track_nav1
1139 = fgGetNode( "/autopilot/internal/nav1-track-error-deg", true );
1141 diff = target_nav1->getDoubleValue() - true_hdg->getDoubleValue();
1142 if ( diff < -180.0 ) { diff += 360.0; }
1143 if ( diff > 180.0 ) { diff -= 360.0; }
1144 true_nav1->setDoubleValue( diff );
1146 diff = target_nav1->getDoubleValue() - true_track->getDoubleValue();
1147 if ( diff < -180.0 ) { diff += 360.0; }
1148 if ( diff > 180.0 ) { diff -= 360.0; }
1149 true_track_nav1->setDoubleValue( diff );
1151 // Calculate nav1 selected course error normalized to +/- 180.0
1152 // (based on DG indicated heading)
1153 static SGPropertyNode *nav1_course_error
1154 = fgGetNode( "/autopilot/internal/nav1-course-error", true );
1155 static SGPropertyNode *nav1_selected_course
1156 = fgGetNode( "/instrumentation/nav[0]/radials/selected-deg", true );
1158 diff = nav1_selected_course->getDoubleValue() - ind_hdg->getDoubleValue();
1159 // if ( diff < -180.0 ) { diff += 360.0; }
1160 // if ( diff > 180.0 ) { diff -= 360.0; }
1161 SG_NORMALIZE_RANGE( diff, -180.0, 180.0 );
1162 nav1_course_error->setDoubleValue( diff );
1164 // Calculate vertical speed in fpm
1165 static SGPropertyNode *vs_fps
1166 = fgGetNode( "/velocities/vertical-speed-fps", true );
1167 static SGPropertyNode *vs_fpm
1168 = fgGetNode( "/autopilot/internal/vert-speed-fpm", true );
1170 vs_fpm->setDoubleValue( vs_fps->getDoubleValue() * 60.0 );
1173 // Calculate static port pressure rate in [inhg/s].
1174 // Used to determine vertical speed.
1175 static SGPropertyNode *static_pressure
1176 = fgGetNode( "/systems/static[0]/pressure-inhg", true );
1177 static SGPropertyNode *pressure_rate
1178 = fgGetNode( "/autopilot/internal/pressure-rate", true );
1180 static double last_static_pressure = 0.0;
1183 double current_static_pressure = static_pressure->getDoubleValue();
1185 double current_pressure_rate =
1186 ( current_static_pressure - last_static_pressure ) / dt;
1188 pressure_rate->setDoubleValue(current_pressure_rate);
1190 last_static_pressure = current_static_pressure;
1197 * Update the list of autopilot components
1200 void FGXMLAutopilot::update( double dt ) {
1201 update_helper( dt );
1204 for ( i = 0; i < components.size(); ++i ) {
1205 components[i]->update( dt );