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>
32 #include <simgear/props/props_io.hxx>
34 #include <Main/fg_props.hxx>
35 #include <Main/globals.hxx>
36 #include <Main/util.hxx>
38 #include "xmlauto.hxx"
43 FGPeriodicalValue::FGPeriodicalValue( SGPropertyNode_ptr root )
45 SGPropertyNode_ptr minNode = root->getChild( "min" );
46 SGPropertyNode_ptr maxNode = root->getChild( "max" );
47 if( minNode == NULL || maxNode == NULL ) {
48 SG_LOG(SG_AUTOPILOT, SG_ALERT, "periodical defined, but no <min> and/or <max> tag. Period ignored." );
50 minPeriod = new FGXMLAutoInput( minNode );
51 maxPeriod = new FGXMLAutoInput( maxNode );
55 double FGPeriodicalValue::normalize( double value )
57 if( !(minPeriod && maxPeriod )) return value;
59 double p1 = minPeriod->get_value();
60 double p2 = maxPeriod->get_value();
62 double min = std::min<double>(p1,p2);
63 double max = std::max<double>(p1,p2);
64 double phase = fabs(max - min);
66 if( phase > SGLimitsd::min() ) {
67 while( value < min ) value += phase;
68 while( value >= max ) value -= phase;
70 value = min; // phase is zero
76 FGXMLAutoInput::FGXMLAutoInput( SGPropertyNode_ptr node, double value, double offset, double scale) :
81 parse( node, value, offset, scale );
85 void FGXMLAutoInput::parse( SGPropertyNode_ptr node, double aValue, double aOffset, double aScale )
100 if( (n = node->getChild("condition")) != NULL ) {
101 _condition = sgReadCondition(fgGetNode("/"), n);
104 if( (n = node->getChild( "scale" )) != NULL ) {
105 scale = new FGXMLAutoInput( n, aScale );
108 if( (n = node->getChild( "offset" )) != NULL ) {
109 offset = new FGXMLAutoInput( n, aOffset );
112 if( (n = node->getChild( "max" )) != NULL ) {
113 max = new FGXMLAutoInput( n );
116 if( (n = node->getChild( "min" )) != NULL ) {
117 min = new FGXMLAutoInput( n );
120 if( (n = node->getChild( "abs" )) != NULL ) {
121 abs = n->getBoolValue();
124 if( (n = node->getChild( "period" )) != NULL ) {
125 periodical = new FGPeriodicalValue( n );
128 SGPropertyNode *valueNode = node->getChild( "value" );
129 if ( valueNode != NULL ) {
130 value = valueNode->getDoubleValue();
133 n = node->getChild( "property" );
134 // if no <property> element, check for <prop> element for backwards
137 n = node->getChild( "prop" );
140 property = fgGetNode( n->getStringValue(), true );
141 if ( valueNode != NULL ) {
142 // initialize property with given value
143 // if both <prop> and <value> exist
144 double s = get_scale();
146 property->setDoubleValue( (value - get_offset())/s );
148 property->setDoubleValue( 0 ); // if scale is zero, value*scale is zero
152 if ( n == NULL && valueNode == NULL ) {
153 // no <value> element and no <prop> element, use text node
154 const char * textnode = node->getStringValue();
156 // try to convert to a double value. If the textnode does not start with a number
157 // endp will point to the beginning of the string. We assume this should be
159 value = strtod( textnode, &endp );
160 if( endp == textnode ) {
161 property = fgGetNode( textnode, true );
166 void FGXMLAutoInput::set_value( double aValue )
168 double s = get_scale();
170 property->setDoubleValue( (aValue - get_offset())/s );
172 property->setDoubleValue( 0 ); // if scale is zero, value*scale is zero
175 double FGXMLAutoInput::get_value()
177 if( property != NULL )
178 value = property->getDoubleValue();
181 value *= scale->get_value();
184 value += offset->get_value();
187 double m = min->get_value();
193 double m = max->get_value();
199 value = periodical->normalize( value );
202 return abs ? fabs(value) : value;
205 FGXMLAutoComponent::FGXMLAutoComponent() :
208 enable_value( NULL ),
209 passive_mode( fgGetNode("/autopilot/locks/passive-mode", true) ),
210 honor_passive( false ),
212 feedback_if_disabled( false ),
218 FGXMLAutoComponent::~FGXMLAutoComponent()
223 void FGXMLAutoComponent::parseNode(SGPropertyNode* aNode)
225 SGPropertyNode *prop;
226 for (int i = 0; i < aNode->nChildren(); ++i ) {
227 SGPropertyNode *child = aNode->getChild(i);
228 string cname(child->getName());
230 if (parseNodeHook(cname, child)) {
231 // derived class handled it, fine
232 } else if ( cname == "name" ) {
233 name = child->getStringValue();
234 } else if ( cname == "feedback-if-disabled" ) {
235 feedback_if_disabled = child->getBoolValue();
236 } else if ( cname == "debug" ) {
237 debug = child->getBoolValue();
238 } else if ( cname == "enable" ) {
239 if( (prop = child->getChild("condition")) != NULL ) {
240 _condition = sgReadCondition(fgGetNode("/"), prop);
242 if ( (prop = child->getChild( "prop" )) != NULL ) {
243 enable_prop = fgGetNode( prop->getStringValue(), true );
246 if ( (prop = child->getChild( "value" )) != NULL ) {
248 enable_value = new string(prop->getStringValue());
251 if ( (prop = child->getChild( "honor-passive" )) != NULL ) {
252 honor_passive = prop->getBoolValue();
254 } else if ( cname == "input" ) {
255 valueInput.push_back( new FGXMLAutoInput( child ) );
256 } else if ( cname == "reference" ) {
257 referenceInput.push_back( new FGXMLAutoInput( child ) );
258 } else if ( cname == "output" ) {
259 // grab all <prop> and <property> childs
261 // backwards compatibility: allow <prop> elements
262 for( int i = 0; (prop = child->getChild("prop", i)) != NULL; i++ ) {
263 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
264 output_list.push_back( tmp );
267 for( int i = 0; (prop = child->getChild("property", i)) != NULL; i++ ) {
268 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
269 output_list.push_back( tmp );
273 // no <prop> elements, text node of <output> is property name
275 output_list.push_back( fgGetNode(child->getStringValue(), true ) );
276 } else if ( cname == "config" ) {
278 } else if ( cname == "min" ) {
279 uminInput.push_back( new FGXMLAutoInput( child ) );
280 } else if ( cname == "u_min" ) {
281 uminInput.push_back( new FGXMLAutoInput( child ) );
282 } else if ( cname == "max" ) {
283 umaxInput.push_back( new FGXMLAutoInput( child ) );
284 } else if ( cname == "u_max" ) {
285 umaxInput.push_back( new FGXMLAutoInput( child ) );
286 } else if ( cname == "period" ) {
287 periodical = new FGPeriodicalValue( child );
289 SG_LOG(SG_AUTOPILOT, SG_ALERT, "malformed autopilot definition - unrecognized node:"
290 << cname << " in section " << name);
291 throw sg_io_exception("XMLAuto: unrecognized component node:" + cname, "Section=" + name);
293 } // of top-level iteration
296 void FGXMLAutoComponent::parseConfig(SGPropertyNode* aConfig)
298 for (int i = 0; i < aConfig->nChildren(); ++i ) {
299 SGPropertyNode *child = aConfig->getChild(i);
300 string cname(child->getName());
302 if (parseConfigHook(cname, child)) {
303 // derived class handled it, fine
304 } else if ( cname == "min" ) {
305 uminInput.push_back( new FGXMLAutoInput( child ) );
306 } else if ( cname == "u_min" ) {
307 uminInput.push_back( new FGXMLAutoInput( child ) );
308 } else if ( cname == "max" ) {
309 umaxInput.push_back( new FGXMLAutoInput( child ) );
310 } else if ( cname == "u_max" ) {
311 umaxInput.push_back( new FGXMLAutoInput( child ) );
313 SG_LOG(SG_AUTOPILOT, SG_ALERT, "malformed autopilot definition - unrecognized config node:"
314 << cname << " in section " << name);
315 throw sg_io_exception("XMLAuto: unrecognized config node:" + cname, "Section=" + name);
317 } // of config iteration
320 bool FGXMLAutoComponent::parseNodeHook(const string& aName, SGPropertyNode* aNode)
325 bool FGXMLAutoComponent::parseConfigHook(const string& aName, SGPropertyNode* aNode)
330 bool FGXMLAutoComponent::isPropertyEnabled()
333 return _condition->test();
337 return *enable_value == enable_prop->getStringValue();
339 return enable_prop->getBoolValue();
345 void FGXMLAutoComponent::do_feedback_if_disabled()
347 if( output_list.size() > 0 ) {
348 FGXMLAutoInput * input = valueInput.get_active();
350 input->set_value( output_list[0]->getDoubleValue() );
354 double FGXMLAutoComponent::clamp( double value )
356 //If this is a periodical value, normalize it into our domain
359 value = periodical->normalize( value );
361 // clamp, if either min or max is defined
362 if( uminInput.size() + umaxInput.size() > 0 ) {
363 double d = umaxInput.get_value( 0.0 );
364 if( value > d ) value = d;
365 d = uminInput.get_value( 0.0 );
366 if( value < d ) value = d;
371 FGPIDController::FGPIDController( SGPropertyNode *node ):
372 FGXMLAutoComponent(),
386 bool FGPIDController::parseConfigHook(const string& aName, SGPropertyNode* aNode)
389 desiredTs = aNode->getDoubleValue();
390 } else if (aName == "Kp") {
391 Kp.push_back( new FGXMLAutoInput(aNode) );
392 } else if (aName == "Ti") {
393 Ti.push_back( new FGXMLAutoInput(aNode) );
394 } else if (aName == "Td") {
395 Td.push_back( new FGXMLAutoInput(aNode) );
396 } else if (aName == "beta") {
397 beta = aNode->getDoubleValue();
398 } else if (aName == "alpha") {
399 alpha = aNode->getDoubleValue();
400 } else if (aName == "gamma") {
401 gamma = aNode->getDoubleValue();
403 // unhandled by us, let the base class try it
413 * Ok! Here is the PID controller algorithm that I would like to see
416 * delta_u_n = Kp * [ (ep_n - ep_n-1) + ((Ts/Ti)*e_n)
417 * + (Td/Ts)*(edf_n - 2*edf_n-1 + edf_n-2) ]
419 * u_n = u_n-1 + delta_u_n
423 * delta_u : The incremental output
424 * Kp : Proportional gain
425 * ep : Proportional error with reference weighing
428 * beta : Weighing factor
429 * r : Reference (setpoint)
430 * y : Process value, measured
433 * Ts : Sampling interval
434 * Ti : Integrator time
435 * Td : Derivator time
436 * edf : Derivate error with reference weighing and filtering
437 * edf_n = edf_n-1 / ((Ts/Tf) + 1) + ed_n * (Ts/Tf) / ((Ts/Tf) + 1)
440 * Tf = alpha * Td , where alpha usually is set to 0.1
441 * ed : Unfiltered derivate error with reference weighing
444 * gamma : Weighing factor
446 * u : absolute output
448 * Index n means the n'th value.
453 * y_n , r_n , beta=1 , gamma=0 , alpha=0.1 ,
454 * Kp , Ti , Td , Ts (is the sampling time available?)
461 void FGPIDController::update( double dt ) {
464 double delta_u_n = 0.0; // incremental output
465 double u_n = 0.0; // absolute output
466 double Ts; // sampling interval (sec)
468 double u_min = uminInput.get_value();
469 double u_max = umaxInput.get_value();
472 if ( elapsedTime <= desiredTs ) {
473 // do nothing if time step is not positive (i.e. no time has
480 if ( isPropertyEnabled() ) {
482 // first time being enabled, seed u_n with current
483 // property tree value
484 u_n = get_output_value();
493 if ( enabled && Ts > 0.0) {
494 if ( debug ) cout << "Updating " << get_name()
495 << " Ts " << Ts << endl;
497 double y_n = valueInput.get_value();
498 double r_n = referenceInput.get_value();
500 if ( debug ) cout << " input = " << y_n << " ref = " << r_n << endl;
502 // Calculates proportional error:
503 double ep_n = beta * r_n - y_n;
504 if ( debug ) cout << " ep_n = " << ep_n;
505 if ( debug ) cout << " ep_n_1 = " << ep_n_1;
509 if ( debug ) cout << " e_n = " << e_n;
511 double td = Td.get_value();
512 if ( td > 0.0 ) { // do we need to calcluate derivative error?
514 // Calculates derivate error:
515 double ed_n = gamma * r_n - y_n;
516 if ( debug ) cout << " ed_n = " << ed_n;
518 // Calculates filter time:
519 double Tf = alpha * td;
520 if ( debug ) cout << " Tf = " << Tf;
522 // Filters the derivate error:
523 edf_n = edf_n_1 / (Ts/Tf + 1)
524 + ed_n * (Ts/Tf) / (Ts/Tf + 1);
525 if ( debug ) cout << " edf_n = " << edf_n;
527 edf_n_2 = edf_n_1 = edf_n = 0.0;
530 // Calculates the incremental output:
531 double ti = Ti.get_value();
533 delta_u_n = Kp.get_value() * ( (ep_n - ep_n_1)
535 + ((td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
538 cout << " delta_u_n = " << delta_u_n << endl;
539 cout << "P:" << Kp.get_value() * (ep_n - ep_n_1)
540 << " I:" << Kp.get_value() * ((Ts/ti) * e_n)
541 << " D:" << Kp.get_value() * ((td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2))
546 // Integrator anti-windup logic:
547 if ( delta_u_n > (u_max - u_n_1) ) {
548 delta_u_n = u_max - u_n_1;
549 if ( debug ) cout << " max saturation " << endl;
550 } else if ( delta_u_n < (u_min - u_n_1) ) {
551 delta_u_n = u_min - u_n_1;
552 if ( debug ) cout << " min saturation " << endl;
555 // Calculates absolute output:
556 u_n = u_n_1 + delta_u_n;
557 if ( debug ) cout << " output = " << u_n << endl;
559 // Updates indexed values;
565 set_output_value( u_n );
566 } else if ( !enabled ) {
568 edf_n_2 = edf_n_1 = edf_n = 0.0;
573 FGPISimpleController::FGPISimpleController( SGPropertyNode *node ):
574 FGXMLAutoComponent(),
580 bool FGPISimpleController::parseConfigHook(const string& aName, SGPropertyNode* aNode)
583 Kp.push_back( new FGXMLAutoInput(aNode) );
584 } else if (aName == "Ki") {
585 Ki.push_back( new FGXMLAutoInput(aNode) );
587 // unhandled by us, let the base class try it
594 void FGPISimpleController::update( double dt ) {
596 if ( isPropertyEnabled() ) {
598 // we have just been enabled, zero out int_sum
608 if ( debug ) cout << "Updating " << get_name() << endl;
609 double y_n = valueInput.get_value();
610 double r_n = referenceInput.get_value();
612 double error = r_n - y_n;
613 if ( debug ) cout << "input = " << y_n
614 << " reference = " << r_n
615 << " error = " << error
618 double prop_comp = clamp(error * Kp.get_value());
619 int_sum += error * Ki.get_value() * dt;
622 double output = prop_comp + int_sum;
623 double clamped_output = clamp( output );
624 if( output != clamped_output ) // anti-windup
625 int_sum = clamped_output - prop_comp;
627 if ( debug ) cout << "prop_comp = " << prop_comp
628 << " int_sum = " << int_sum << endl;
630 set_output_value( clamped_output );
631 if ( debug ) cout << "output = " << clamped_output << endl;
636 FGPredictor::FGPredictor ( SGPropertyNode *node ):
637 FGXMLAutoComponent(),
643 bool FGPredictor::parseNodeHook(const string& aName, SGPropertyNode* aNode)
645 if (aName == "seconds") {
646 seconds.push_back( new FGXMLAutoInput( aNode, 0 ) );
647 } else if (aName == "filter-gain") {
648 filter_gain.push_back( new FGXMLAutoInput( aNode, 0 ) );
656 void FGPredictor::update( double dt ) {
658 Simple moving average filter converts input value to predicted value "seconds".
660 Smoothing as described by Curt Olson:
661 gain would be valid in the range of 0 - 1.0
662 1.0 would mean no filtering.
663 0.0 would mean no input.
664 0.5 would mean (1 part past value + 1 part current value) / 2
665 0.1 would mean (9 parts past value + 1 part current value) / 10
666 0.25 would mean (3 parts past value + 1 part current value) / 4
670 double ivalue = valueInput.get_value();
672 if ( isPropertyEnabled() ) {
674 // first time being enabled
686 double current = (ivalue - last_value)/dt; // calculate current error change (per second)
687 average = dt < 1.0 ? ((1.0 - dt) * average + current * dt) : current;
689 // calculate output with filter gain adjustment
690 double output = ivalue +
691 (1.0 - filter_gain.get_value()) * (average * seconds.get_value()) +
692 filter_gain.get_value() * (current * seconds.get_value());
693 output = clamp( output );
694 set_output_value( output );
701 FGDigitalFilter::FGDigitalFilter(SGPropertyNode *node):
702 FGXMLAutoComponent(),
707 output.resize(2, 0.0);
708 input.resize(samplesInput.get_value() + 1, 0.0);
712 bool FGDigitalFilter::parseNodeHook(const string& aName, SGPropertyNode* aNode)
714 if (aName == "type" ) {
715 string val(aNode->getStringValue());
716 if ( val == "exponential" ) {
717 filterType = exponential;
718 } else if (val == "double-exponential") {
719 filterType = doubleExponential;
720 } else if (val == "moving-average") {
721 filterType = movingAverage;
722 } else if (val == "noise-spike") {
723 filterType = noiseSpike;
724 } else if (val == "gain") {
726 } else if (val == "reciprocal") {
727 filterType = reciprocal;
728 } else if (val == "differential") {
729 filterType = differential;
730 // use a constant of two samples for current and previous input value
731 samplesInput.push_back( new FGXMLAutoInput(NULL, 2.0 ) );
733 } else if (aName == "filter-time" ) {
734 TfInput.push_back( new FGXMLAutoInput( aNode, 1.0 ) );
735 if( filterType == none ) filterType = exponential;
736 } else if (aName == "samples" ) {
737 samplesInput.push_back( new FGXMLAutoInput( aNode, 1 ) );
738 if( filterType == none ) filterType = movingAverage;
739 } else if (aName == "max-rate-of-change" ) {
740 rateOfChangeInput.push_back( new FGXMLAutoInput( aNode, 1 ) );
741 if( filterType == none ) filterType = noiseSpike;
742 } else if (aName == "gain" ) {
743 gainInput.push_back( new FGXMLAutoInput( aNode, 1 ) );
744 if( filterType == none ) filterType = gain;
746 return false; // not handled by us, let the base class try
752 void FGDigitalFilter::update(double dt)
754 if ( isPropertyEnabled() ) {
756 input.push_front(valueInput.get_value()-referenceInput.get_value());
757 input.resize(samplesInput.get_value() + 1, 0.0);
760 // first time being enabled, initialize output to the
761 // value of the output property to avoid bumping.
762 output.push_front(get_output_value());
771 if ( !enabled || dt < SGLimitsd::min() )
777 * Output[n] = alpha*Input[n] + (1-alpha)*Output[n-1]
780 if( debug ) cout << "Updating " << get_name()
781 << " dt " << dt << endl;
783 if (filterType == exponential)
785 double alpha = 1 / ((TfInput.get_value()/dt) + 1);
786 output.push_front(alpha * input[0] +
787 (1 - alpha) * output[0]);
789 else if (filterType == doubleExponential)
791 double alpha = 1 / ((TfInput.get_value()/dt) + 1);
792 output.push_front(alpha * alpha * input[0] +
793 2 * (1 - alpha) * output[0] -
794 (1 - alpha) * (1 - alpha) * output[1]);
796 else if (filterType == movingAverage)
798 output.push_front(output[0] +
799 (input[0] - input.back()) / samplesInput.get_value());
801 else if (filterType == noiseSpike)
803 double maxChange = rateOfChangeInput.get_value() * dt;
805 if ((output[0] - input[0]) > maxChange)
807 output.push_front(output[0] - maxChange);
809 else if ((output[0] - input[0]) < -maxChange)
811 output.push_front(output[0] + maxChange);
813 else if (fabs(input[0] - output[0]) <= maxChange)
815 output.push_front(input[0]);
818 else if (filterType == gain)
820 output[0] = gainInput.get_value() * input[0];
822 else if (filterType == reciprocal)
824 if (input[0] != 0.0) {
825 output[0] = gainInput.get_value() / input[0];
828 else if (filterType == differential)
830 if( dt > SGLimitsd::min() ) {
831 output[0] = (input[0]-input[1]) * TfInput.get_value() / dt;
835 output[0] = clamp(output[0]) ;
836 set_output_value( output[0] );
842 cout << "input:" << input[0]
843 << "\toutput:" << output[0] << endl;
847 FGXMLAutoLogic::FGXMLAutoLogic(SGPropertyNode * node ) :
848 FGXMLAutoComponent(),
854 bool FGXMLAutoLogic::parseNodeHook(const std::string& aName, SGPropertyNode* aNode)
856 if (aName == "input") {
857 input = sgReadCondition( fgGetNode("/"), aNode );
858 } else if (aName == "inverted") {
859 inverted = aNode->getBoolValue();
867 void FGXMLAutoLogic::update(double dt)
869 if ( isPropertyEnabled() ) {
871 // we have just been enabled
879 if ( !enabled || dt < SGLimitsd::min() )
882 if( input == NULL ) {
883 if ( debug ) cout << "No input for " << get_name() << endl;
887 bool i = input->test();
889 if ( debug ) cout << "Updating " << get_name() << ": " << (inverted ? !i : i) << endl;
891 set_output_value( i );
895 FGXMLAutopilotGroup::FGXMLAutopilotGroup() :
897 #ifdef XMLAUTO_USEHELPER
898 ,average(0.0), // average/filtered prediction
899 v_last(0.0), // last velocity
900 last_static_pressure(0.0),
901 vel(fgGetNode( "/velocities/airspeed-kt", true )),
902 // Estimate speed in 5,10 seconds
903 lookahead5(fgGetNode( "/autopilot/internal/lookahead-5-sec-airspeed-kt", true )),
904 lookahead10(fgGetNode( "/autopilot/internal/lookahead-10-sec-airspeed-kt", true )),
905 bug(fgGetNode( "/autopilot/settings/heading-bug-deg", true )),
906 mag_hdg(fgGetNode( "/orientation/heading-magnetic-deg", true )),
907 bug_error(fgGetNode( "/autopilot/internal/heading-bug-error-deg", true )),
908 fdm_bug_error(fgGetNode( "/autopilot/internal/fdm-heading-bug-error-deg", true )),
909 target_true(fgGetNode( "/autopilot/settings/true-heading-deg", true )),
910 true_hdg(fgGetNode( "/orientation/heading-deg", true )),
911 true_error(fgGetNode( "/autopilot/internal/true-heading-error-deg", true )),
912 target_nav1(fgGetNode( "/instrumentation/nav[0]/radials/target-auto-hdg-deg", true )),
913 true_nav1(fgGetNode( "/autopilot/internal/nav1-heading-error-deg", true )),
914 true_track_nav1(fgGetNode( "/autopilot/internal/nav1-track-error-deg", true )),
915 nav1_course_error(fgGetNode( "/autopilot/internal/nav1-course-error", true )),
916 nav1_selected_course(fgGetNode( "/instrumentation/nav[0]/radials/selected-deg", true )),
917 vs_fps(fgGetNode( "/velocities/vertical-speed-fps", true )),
918 vs_fpm(fgGetNode( "/autopilot/internal/vert-speed-fpm", true )),
919 static_pressure(fgGetNode( "/systems/static[0]/pressure-inhg", true )),
920 pressure_rate(fgGetNode( "/autopilot/internal/pressure-rate", true )),
921 track(fgGetNode( "/orientation/track-deg", true ))
926 void FGXMLAutopilotGroup::update( double dt )
928 // update all configured autopilots
929 SGSubsystemGroup::update( dt );
930 #ifdef XMLAUTO_USEHELPER
931 // update helper values
932 double v = vel->getDoubleValue();
935 a = (v - v_last) / dt;
938 average = (1.0 - dt) * average + dt * a;
943 lookahead5->setDoubleValue( v + average * 5.0 );
944 lookahead10->setDoubleValue( v + average * 10.0 );
948 // Calculate heading bug error normalized to +/- 180.0
949 double diff = bug->getDoubleValue() - mag_hdg->getDoubleValue();
950 SG_NORMALIZE_RANGE(diff, -180.0, 180.0);
951 bug_error->setDoubleValue( diff );
953 fdm_bug_error->setDoubleValue( diff );
955 // Calculate true heading error normalized to +/- 180.0
956 diff = target_true->getDoubleValue() - true_hdg->getDoubleValue();
957 SG_NORMALIZE_RANGE(diff, -180.0, 180.0);
958 true_error->setDoubleValue( diff );
960 // Calculate nav1 target heading error normalized to +/- 180.0
961 diff = target_nav1->getDoubleValue() - true_hdg->getDoubleValue();
962 SG_NORMALIZE_RANGE(diff, -180.0, 180.0);
963 true_nav1->setDoubleValue( diff );
965 // Calculate true groundtrack
966 diff = target_nav1->getDoubleValue() - track->getDoubleValue();
967 SG_NORMALIZE_RANGE(diff, -180.0, 180.0);
968 true_track_nav1->setDoubleValue( diff );
970 // Calculate nav1 selected course error normalized to +/- 180.0
971 diff = nav1_selected_course->getDoubleValue() - mag_hdg->getDoubleValue();
972 SG_NORMALIZE_RANGE( diff, -180.0, 180.0 );
973 nav1_course_error->setDoubleValue( diff );
975 // Calculate vertical speed in fpm
976 vs_fpm->setDoubleValue( vs_fps->getDoubleValue() * 60.0 );
979 // Calculate static port pressure rate in [inhg/s].
980 // Used to determine vertical speed.
982 double current_static_pressure = static_pressure->getDoubleValue();
983 double current_pressure_rate =
984 ( current_static_pressure - last_static_pressure ) / dt;
986 pressure_rate->setDoubleValue(current_pressure_rate);
987 last_static_pressure = current_static_pressure;
992 void FGXMLAutopilotGroup::reinit()
994 for( vector<string>::size_type i = 0; i < _autopilotNames.size(); i++ ) {
995 FGXMLAutopilot * ap = (FGXMLAutopilot*)get_subsystem( _autopilotNames[i] );
996 if( ap == NULL ) continue; // ?
997 remove_subsystem( _autopilotNames[i] );
1000 _autopilotNames.clear();
1004 void FGXMLAutopilotGroup::init()
1006 vector<SGPropertyNode_ptr> autopilotNodes = fgGetNode( "/sim/systems", true )->getChildren("autopilot");
1007 if( autopilotNodes.size() == 0 ) {
1008 SG_LOG( SG_ALL, SG_WARN, "No autopilot configuration specified for this model!");
1012 for( vector<SGPropertyNode_ptr>::size_type i = 0; i < autopilotNodes.size(); i++ ) {
1013 SGPropertyNode_ptr pathNode = autopilotNodes[i]->getNode( "path" );
1014 if( pathNode == NULL ) {
1015 SG_LOG( SG_ALL, SG_WARN, "No autopilot configuration file specified for this autopilot!");
1020 SGPropertyNode_ptr nameNode = autopilotNodes[i]->getNode( "name" );
1021 if( nameNode != NULL ) {
1022 apName = nameNode->getStringValue();
1024 std::ostringstream buf;
1025 buf << "unnamed_autopilot_" << i;
1029 if( get_subsystem( apName.c_str() ) != NULL ) {
1030 SG_LOG( SG_ALL, SG_ALERT, "Duplicate autopilot configuration name " << apName << " ignored" );
1034 SGPath config( globals->get_fg_root() );
1035 config.append( pathNode->getStringValue() );
1037 SG_LOG( SG_ALL, SG_INFO, "Reading autopilot configuration from " << config.str() );
1039 FGXMLAutopilot * ap = new FGXMLAutopilot;
1041 SGPropertyNode_ptr root = new SGPropertyNode();
1042 readProperties( config.str(), root );
1045 if ( ! ap->build( root ) ) {
1046 SG_LOG( SG_ALL, SG_ALERT,
1047 "Detected an internal inconsistency in the autopilot configuration." << endl << " See earlier errors for details." );
1051 } catch (const sg_exception& e) {
1052 SG_LOG( SG_AUTOPILOT, SG_ALERT, "Failed to load autopilot configuration: "
1053 << config.str() << ":" << e.getMessage() );
1058 SG_LOG( SG_AUTOPILOT, SG_INFO, "adding autopilot subsystem " << apName );
1059 set_subsystem( apName, ap );
1060 _autopilotNames.push_back( apName );
1063 SGSubsystemGroup::init();
1066 FGXMLAutopilot::FGXMLAutopilot() {
1070 FGXMLAutopilot::~FGXMLAutopilot() {
1074 /* read all /sim/systems/autopilot[n]/path properties, try to read the file specified therein
1075 * and configure/add the digital filters specified in that file
1077 void FGXMLAutopilot::init()
1082 void FGXMLAutopilot::reinit() {
1088 void FGXMLAutopilot::bind() {
1091 void FGXMLAutopilot::unbind() {
1094 bool FGXMLAutopilot::build( SGPropertyNode_ptr config_props ) {
1095 SGPropertyNode *node;
1098 int count = config_props->nChildren();
1099 for ( i = 0; i < count; ++i ) {
1100 node = config_props->getChild(i);
1101 string name = node->getName();
1102 // cout << name << endl;
1103 SG_LOG( SG_AUTOPILOT, SG_BULK, "adding autopilot component " << name );
1104 if ( name == "pid-controller" ) {
1105 components.push_back( new FGPIDController( node ) );
1106 } else if ( name == "pi-simple-controller" ) {
1107 components.push_back( new FGPISimpleController( node ) );
1108 } else if ( name == "predict-simple" ) {
1109 components.push_back( new FGPredictor( node ) );
1110 } else if ( name == "filter" ) {
1111 components.push_back( new FGDigitalFilter( node ) );
1112 } else if ( name == "logic" ) {
1113 components.push_back( new FGXMLAutoLogic( node ) );
1115 SG_LOG( SG_AUTOPILOT, SG_WARN, "Unknown top level autopilot section: " << name );
1124 * Update the list of autopilot components
1127 void FGXMLAutopilot::update( double dt )
1130 for ( i = 0; i < components.size(); ++i ) {
1131 components[i]->update( dt );