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>
30 #include "xmlauto.hxx"
33 FGPIDController::FGPIDController( SGPropertyNode *node, bool old ):
34 proportional( false ),
60 for ( i = 0; i < node->nChildren(); ++i ) {
61 SGPropertyNode *child = node->getChild(i);
62 string cname = child->getName();
63 string cval = child->getStringValue();
64 if ( cname == "name" ) {
66 } else if ( cname == "enable" ) {
67 // cout << "parsing enable" << endl;
68 SGPropertyNode *prop = child->getChild( "prop" );
70 // cout << "prop = " << prop->getStringValue() << endl;
71 enable_prop = fgGetNode( prop->getStringValue(), true );
73 // cout << "no prop child" << endl;
75 SGPropertyNode *val = child->getChild( "value" );
77 enable_value = val->getStringValue();
79 } else if ( cname == "debug" ) {
80 debug = child->getBoolValue();
81 } else if ( cname == "input" ) {
82 SGPropertyNode *prop = child->getChild( "prop" );
84 input_prop = fgGetNode( prop->getStringValue(), true );
86 } else if ( cname == "reference" ) {
87 SGPropertyNode *prop = child->getChild( "prop" );
89 r_n_prop = fgGetNode( prop->getStringValue(), true );
91 prop = child->getChild( "value" );
93 r_n_value = prop->getDoubleValue();
96 } else if ( cname == "output" ) {
99 while ( (prop = child->getChild("prop", i)) != NULL ) {
100 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
101 output_list.push_back( tmp );
104 prop = child->getChild( "clamp" );
105 if ( prop != NULL ) {
110 tmp = prop->getChild( "min" );
112 u_min = tmp->getDoubleValue();
113 // cout << "min = " << u_min << endl;
116 tmp = prop->getChild( "max" );
118 u_max = tmp->getDoubleValue();
119 // cout << "max = " << u_max << endl;
122 } else if ( cname == "proportional" ) {
125 SGPropertyNode *prop;
127 prop = child->getChild( "pre" );
128 if ( prop != NULL ) {
129 prop = prop->getChild( "one-eighty" );
130 if ( prop != NULL && prop->getBoolValue() ) {
135 prop = child->getChild( "factor" );
136 if ( prop != NULL ) {
137 factor = prop->getDoubleValue();
140 prop = child->getChild( "offset" );
141 if ( prop != NULL ) {
142 SGPropertyNode *sub = prop->getChild( "prop" );
144 offset_prop = fgGetNode( sub->getStringValue(), true );
145 // cout << "offset prop = " << sub->getStringValue() << endl;
147 sub = prop->getChild( "value" );
149 offset_value = sub->getDoubleValue();
150 // cout << "offset value = " << offset_value << endl;
154 } else if ( cname == "integral" ) {
157 SGPropertyNode *prop;
158 prop = child->getChild( "gain" );
159 if ( prop != NULL ) {
160 gain = prop->getDoubleValue();
163 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
169 FGPIDController::FGPIDController( SGPropertyNode *node ):
170 proportional( false ),
196 for ( i = 0; i < node->nChildren(); ++i ) {
197 SGPropertyNode *child = node->getChild(i);
198 string cname = child->getName();
199 string cval = child->getStringValue();
200 if ( cname == "name" ) {
202 } else if ( cname == "debug" ) {
203 debug = child->getBoolValue();
204 } else if ( cname == "enable" ) {
205 // cout << "parsing enable" << endl;
206 SGPropertyNode *prop = child->getChild( "prop" );
207 if ( prop != NULL ) {
208 // cout << "prop = " << prop->getStringValue() << endl;
209 enable_prop = fgGetNode( prop->getStringValue(), true );
211 // cout << "no prop child" << endl;
213 SGPropertyNode *val = child->getChild( "value" );
215 enable_value = val->getStringValue();
217 } else if ( cname == "input" ) {
218 SGPropertyNode *prop = child->getChild( "prop" );
219 if ( prop != NULL ) {
220 input_prop = fgGetNode( prop->getStringValue(), true );
222 } else if ( cname == "reference" ) {
223 SGPropertyNode *prop = child->getChild( "prop" );
224 if ( prop != NULL ) {
225 r_n_prop = fgGetNode( prop->getStringValue(), true );
227 prop = child->getChild( "value" );
228 if ( prop != NULL ) {
229 r_n = prop->getDoubleValue();
232 } else if ( cname == "output" ) {
234 SGPropertyNode *prop;
235 while ( (prop = child->getChild("prop", i)) != NULL ) {
236 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
237 output_list.push_back( tmp );
240 } else if ( cname == "config" ) {
241 SGPropertyNode *prop;
243 prop = child->getChild( "Kp" );
244 if ( prop != NULL ) {
245 Kp = prop->getDoubleValue();
248 prop = child->getChild( "beta" );
249 if ( prop != NULL ) {
250 beta = prop->getDoubleValue();
253 prop = child->getChild( "alpha" );
254 if ( prop != NULL ) {
255 alpha = prop->getDoubleValue();
258 prop = child->getChild( "gamma" );
259 if ( prop != NULL ) {
260 gamma = prop->getDoubleValue();
263 prop = child->getChild( "Ti" );
264 if ( prop != NULL ) {
265 Ti = prop->getDoubleValue();
268 prop = child->getChild( "Td" );
269 if ( prop != NULL ) {
270 Td = prop->getDoubleValue();
273 prop = child->getChild( "u_min" );
274 if ( prop != NULL ) {
275 u_min = prop->getDoubleValue();
278 prop = child->getChild( "u_max" );
279 if ( prop != NULL ) {
280 u_max = prop->getDoubleValue();
283 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
284 if ( name.length() ) {
285 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
292 void FGPIDController::update_old( double dt ) {
293 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
295 // we have just been enabled, zero out int_sum
304 if ( debug ) cout << "Updating " << name << endl;
306 if ( input_prop != NULL ) {
307 input = input_prop->getDoubleValue();
311 if ( r_n_prop != NULL ) {
312 r_n = r_n_prop->getDoubleValue();
317 double error = r_n - input;
319 while ( error < -180.0 ) { error += 360.0; }
320 while ( error > 180.0 ) { error -= 360.0; }
322 if ( debug ) cout << "input = " << input
323 << " reference = " << r_n
324 << " error = " << error
327 double prop_comp = 0.0;
329 if ( offset_prop != NULL ) {
330 offset = offset_prop->getDoubleValue();
331 if ( debug ) cout << "offset = " << offset << endl;
333 offset = offset_value;
336 if ( proportional ) {
337 prop_comp = error * factor + offset;
341 int_sum += error * gain * dt;
346 if ( debug ) cout << "prop_comp = " << prop_comp
347 << " int_sum = " << int_sum << endl;
349 double output = prop_comp + int_sum;
352 if ( output < u_min ) {
355 if ( output > u_max ) {
359 if ( debug ) cout << "output = " << output << endl;
362 for ( i = 0; i < output_list.size(); ++i ) {
363 output_list[i]->setDoubleValue( output );
372 * Ok! Here is the PID controller algorithm that I would like to see
375 * delta_u_n = Kp * [ (ep_n - ep_n-1) + ((Ts/Ti)*e_n)
376 * + (Td/Ts)*(edf_n - 2*edf_n-1 + edf_n-2) ]
378 * u_n = u_n-1 + delta_u_n
382 * delta_u : The incremental output
383 * Kp : Proportional gain
384 * ep : Proportional error with reference weighing
387 * beta : Weighing factor
388 * r : Reference (setpoint)
389 * y : Process value, measured
392 * Ts : Sampling interval
393 * Ti : Integrator time
394 * Td : Derivator time
395 * edf : Derivate error with reference weighing and filtering
396 * edf_n = edf_n-1 / ((Ts/Tf) + 1) + ed_n * (Ts/Tf) / ((Ts/Tf) + 1)
399 * Tf = alpha * Td , where alpha usually is set to 0.1
400 * ed : Unfiltered derivate error with reference weighing
403 * gamma : Weighing factor
405 * u : absolute output
407 * Index n means the n'th value.
412 * y_n , r_n , beta=1 , gamma=0 , alpha=0.1 ,
413 * Kp , Ti , Td , Ts (is the sampling time available?)
420 void FGPIDController::update( double dt ) {
421 double ep_n; // proportional error with reference weighing
423 double ed_n; // derivative error
424 double edf_n; // derivative error filter
425 double Tf; // filter time
426 double delta_u_n; // incremental output
427 double u_n; // absolute output
428 double Ts = dt; // Sampling interval (sec)
431 // do nothing if time step is not positive (i.e. no time has
436 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
443 if ( debug ) cout << "Updating " << name << endl;
446 if ( input_prop != NULL ) {
447 y_n = input_prop->getDoubleValue();
451 if ( r_n_prop != NULL ) {
452 r_n = r_n_prop->getDoubleValue();
457 if ( debug ) cout << " input = " << y_n << " ref = " << r_n << endl;
459 // Calculates proportional error:
460 ep_n = beta * r_n - y_n;
461 if ( debug ) cout << " ep_n = " << ep_n;
462 if ( debug ) cout << " ep_n_1 = " << ep_n_1;
466 if ( debug ) cout << " e_n = " << e_n;
468 // Calculates derivate error:
469 ed_n = gamma * r_n - y_n;
470 if ( debug ) cout << " ed_n = " << ed_n;
472 // Calculates filter time:
474 if ( debug ) cout << " Tf = " << Tf;
476 // Filters the derivate error:
477 edf_n = edf_n_1 / (Ts/Tf + 1)
478 + ed_n * (Ts/Tf) / (Ts/Tf + 1);
479 if ( debug ) cout << " edf_n = " << edf_n;
481 // Calculates the incremental output:
482 delta_u_n = Kp * ( (ep_n - ep_n_1)
484 + ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
485 if ( debug ) cout << " delta_u_n = " << delta_u_n << endl;
487 // Integrator anti-windup logic:
488 if ( delta_u_n > (u_max - u_n_1) ) {
490 } else if ( delta_u_n < (u_min - u_n_1) ) {
494 // Calculates absolute output:
495 u_n = u_n_1 + delta_u_n;
496 if ( debug ) cout << " output = " << u_n << endl;
498 // Updates indexed values;
505 for ( i = 0; i < output_list.size(); ++i ) {
506 output_list[i]->setDoubleValue( u_n );
508 } else if ( !enabled ) {
512 // Updates indexed values;
521 FGXMLAutopilot::FGXMLAutopilot() {
525 FGXMLAutopilot::~FGXMLAutopilot() {
529 void FGXMLAutopilot::init() {
530 config_props = fgGetNode( "/autopilot/new-config", true );
532 SGPropertyNode *path_n = fgGetNode("/sim/systems/autopilot/path");
535 SGPath config( globals->get_fg_root() );
536 config.append( path_n->getStringValue() );
538 SG_LOG( SG_ALL, SG_INFO, "Reading autopilot configuration from "
541 readProperties( config.str(), config_props );
544 SG_LOG( SG_ALL, SG_ALERT,
545 "Detected an internal inconsistancy in the autopilot");
546 SG_LOG( SG_ALL, SG_ALERT,
547 " configuration. See earlier errors for" );
548 SG_LOG( SG_ALL, SG_ALERT,
552 } catch (const sg_exception& exc) {
553 SG_LOG( SG_ALL, SG_ALERT, "Failed to load autopilot configuration: "
558 SG_LOG( SG_ALL, SG_WARN,
559 "No autopilot configuration specified for this model!");
564 void FGXMLAutopilot::reinit() {
570 void FGXMLAutopilot::bind() {
573 void FGXMLAutopilot::unbind() {
576 bool FGXMLAutopilot::build() {
577 SGPropertyNode *node;
580 int count = config_props->nChildren();
581 for ( i = 0; i < count; ++i ) {
582 node = config_props->getChild(i);
583 string name = node->getName();
584 // cout << name << endl;
585 if ( name == "pid-controller" ) {
586 FGXMLAutoComponent *c = new FGPIDController( node );
587 components.push_back( c );
589 SG_LOG( SG_ALL, SG_ALERT, "Unknown top level section: "
600 * Update helper values
602 static void update_helper( double dt ) {
603 // Estimate speed in 5,10 seconds
604 static SGPropertyNode *vel = fgGetNode( "/velocities/airspeed-kt", true );
605 static SGPropertyNode *lookahead5
606 = fgGetNode( "/autopilot/internal/lookahead-5-sec-airspeed-kt", true );
607 static SGPropertyNode *lookahead10
608 = fgGetNode( "/autopilot/internal/lookahead-10-sec-airspeed-kt", true );
610 static double average = 0.0; // average/filtered prediction
611 static double v_last = 0.0; // last velocity
613 double v = vel->getDoubleValue();
616 a = (v - v_last) / dt;
619 average = (1.0 - dt) * average + dt * a;
624 lookahead5->setDoubleValue( v + average * 5.0 );
625 lookahead10->setDoubleValue( v + average * 10.0 );
629 // Calculate heading bug error normalized to +/- 180.0
630 static SGPropertyNode *bug
631 = fgGetNode( "/autopilot/settings/heading-bug-deg", true );
632 static SGPropertyNode *ind_hdg
633 = fgGetNode( "/instrumentation/heading-indicator/indicated-heading-deg",
635 static SGPropertyNode *bug_error
636 = fgGetNode( "/autopilot/internal/heading-bug-error-deg", true );
638 double diff = bug->getDoubleValue() - ind_hdg->getDoubleValue();
639 if ( diff < -180.0 ) { diff += 360.0; }
640 if ( diff > 180.0 ) { diff -= 360.0; }
641 bug_error->setDoubleValue( diff );
643 // Calculate true heading error normalized to +/- 180.0
644 static SGPropertyNode *target_true
645 = fgGetNode( "/autopilot/settings/true-heading-deg", true );
646 static SGPropertyNode *true_hdg
647 = fgGetNode( "/orientation/heading-deg", true );
648 static SGPropertyNode *true_error
649 = fgGetNode( "/autopilot/internal/true-heading-error-deg", true );
651 diff = target_true->getDoubleValue() - true_hdg->getDoubleValue();
652 if ( diff < -180.0 ) { diff += 360.0; }
653 if ( diff > 180.0 ) { diff -= 360.0; }
654 true_error->setDoubleValue( diff );
656 // Calculate nav1 target heading error normalized to +/- 180.0
657 static SGPropertyNode *target_nav1
658 = fgGetNode( "/radios/nav[0]/radials/target-auto-hdg-deg", true );
659 static SGPropertyNode *true_nav1
660 = fgGetNode( "/autopilot/internal/nav1-heading-error-deg", true );
662 diff = target_nav1->getDoubleValue() - true_hdg->getDoubleValue();
663 if ( diff < -180.0 ) { diff += 360.0; }
664 if ( diff > 180.0 ) { diff -= 360.0; }
665 true_nav1->setDoubleValue( diff );
670 * Update the list of autopilot components
673 void FGXMLAutopilot::update( double dt ) {
677 for ( i = 0; i < components.size(); ++i ) {
678 components[i]->update( dt );