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 ):
51 for ( i = 0; i < node->nChildren(); ++i ) {
52 SGPropertyNode *child = node->getChild(i);
53 string cname = child->getName();
54 string cval = child->getStringValue();
55 if ( cname == "name" ) {
57 } else if ( cname == "debug" ) {
58 debug = child->getBoolValue();
59 } else if ( cname == "enable" ) {
60 // cout << "parsing enable" << endl;
61 SGPropertyNode *prop = child->getChild( "prop" );
63 // cout << "prop = " << prop->getStringValue() << endl;
64 enable_prop = fgGetNode( prop->getStringValue(), true );
66 // cout << "no prop child" << endl;
68 SGPropertyNode *val = child->getChild( "value" );
70 enable_value = val->getStringValue();
72 } else if ( cname == "input" ) {
73 SGPropertyNode *prop = child->getChild( "prop" );
75 input_prop = fgGetNode( prop->getStringValue(), true );
77 } else if ( cname == "reference" ) {
78 SGPropertyNode *prop = child->getChild( "prop" );
80 r_n_prop = fgGetNode( prop->getStringValue(), true );
82 prop = child->getChild( "value" );
84 r_n = prop->getDoubleValue();
87 } else if ( cname == "output" ) {
90 while ( (prop = child->getChild("prop", i)) != NULL ) {
91 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
92 output_list.push_back( tmp );
95 } else if ( cname == "config" ) {
98 prop = child->getChild( "Kp" );
100 Kp = prop->getDoubleValue();
103 prop = child->getChild( "beta" );
104 if ( prop != NULL ) {
105 beta = prop->getDoubleValue();
108 prop = child->getChild( "alpha" );
109 if ( prop != NULL ) {
110 alpha = prop->getDoubleValue();
113 prop = child->getChild( "gamma" );
114 if ( prop != NULL ) {
115 gamma = prop->getDoubleValue();
118 prop = child->getChild( "Ti" );
119 if ( prop != NULL ) {
120 Ti = prop->getDoubleValue();
123 prop = child->getChild( "Td" );
124 if ( prop != NULL ) {
125 Td = prop->getDoubleValue();
128 prop = child->getChild( "u_min" );
129 if ( prop != NULL ) {
130 u_min = prop->getDoubleValue();
133 prop = child->getChild( "u_max" );
134 if ( prop != NULL ) {
135 u_max = prop->getDoubleValue();
138 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
139 if ( name.length() ) {
140 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
150 * Ok! Here is the PID controller algorithm that I would like to see
153 * delta_u_n = Kp * [ (ep_n - ep_n-1) + ((Ts/Ti)*e_n)
154 * + (Td/Ts)*(edf_n - 2*edf_n-1 + edf_n-2) ]
156 * u_n = u_n-1 + delta_u_n
160 * delta_u : The incremental output
161 * Kp : Proportional gain
162 * ep : Proportional error with reference weighing
165 * beta : Weighing factor
166 * r : Reference (setpoint)
167 * y : Process value, measured
170 * Ts : Sampling interval
171 * Ti : Integrator time
172 * Td : Derivator time
173 * edf : Derivate error with reference weighing and filtering
174 * edf_n = edf_n-1 / ((Ts/Tf) + 1) + ed_n * (Ts/Tf) / ((Ts/Tf) + 1)
177 * Tf = alpha * Td , where alpha usually is set to 0.1
178 * ed : Unfiltered derivate error with reference weighing
181 * gamma : Weighing factor
183 * u : absolute output
185 * Index n means the n'th value.
190 * y_n , r_n , beta=1 , gamma=0 , alpha=0.1 ,
191 * Kp , Ti , Td , Ts (is the sampling time available?)
198 void FGPIDController::update( double dt ) {
199 double ep_n; // proportional error with reference weighing
201 double ed_n; // derivative error
202 double edf_n; // derivative error filter
203 double Tf; // filter time
204 double delta_u_n = 0.0; // incremental output
205 double u_n = 0.0; // absolute output
206 double Ts = dt; // Sampling interval (sec)
209 // do nothing if time step is not positive (i.e. no time has
214 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
216 // first time being enabled, seed u_n with current
217 // property tree value
218 u_n = output_list[0]->getDoubleValue();
220 if ( u_n < u_min ) { u_n = u_min; }
221 if ( u_n > u_max ) { u_n = u_max; }
229 if ( enabled && Ts > 0.0) {
230 if ( debug ) cout << "Updating " << name << endl;
233 if ( input_prop != NULL ) {
234 y_n = input_prop->getDoubleValue();
238 if ( r_n_prop != NULL ) {
239 r_n = r_n_prop->getDoubleValue();
244 if ( debug ) cout << " input = " << y_n << " ref = " << r_n << endl;
246 // Calculates proportional error:
247 ep_n = beta * r_n - y_n;
248 if ( debug ) cout << " ep_n = " << ep_n;
249 if ( debug ) cout << " ep_n_1 = " << ep_n_1;
253 if ( debug ) cout << " e_n = " << e_n;
255 // Calculates derivate error:
256 ed_n = gamma * r_n - y_n;
257 if ( debug ) cout << " ed_n = " << ed_n;
260 // Calculates filter time:
262 if ( debug ) cout << " Tf = " << Tf;
264 // Filters the derivate error:
265 edf_n = edf_n_1 / (Ts/Tf + 1)
266 + ed_n * (Ts/Tf) / (Ts/Tf + 1);
267 if ( debug ) cout << " edf_n = " << edf_n;
272 // Calculates the incremental output:
274 delta_u_n = Kp * ( (ep_n - ep_n_1)
276 + ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
277 } else if ( Ti <= 0.0 ) {
278 delta_u_n = Kp * ( (ep_n - ep_n_1)
279 + ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
283 cout << " delta_u_n = " << delta_u_n << endl;
284 cout << "P:" << Kp * (ep_n - ep_n_1)
285 << " I:" << Kp * ((Ts/Ti) * e_n)
286 << " D:" << Kp * ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2))
290 // Integrator anti-windup logic:
291 if ( delta_u_n > (u_max - u_n_1) ) {
293 if ( debug ) cout << " max saturation " << endl;
294 } else if ( delta_u_n < (u_min - u_n_1) ) {
296 if ( debug ) cout << " min saturation " << endl;
299 // Calculates absolute output:
300 u_n = u_n_1 + delta_u_n;
301 if ( debug ) cout << " output = " << u_n << endl;
303 // Updates indexed values;
310 for ( i = 0; i < output_list.size(); ++i ) {
311 output_list[i]->setDoubleValue( u_n );
313 } else if ( !enabled ) {
316 // Updates indexed values;
325 FGPISimpleController::FGPISimpleController( SGPropertyNode *node ):
326 proportional( false ),
341 for ( i = 0; i < node->nChildren(); ++i ) {
342 SGPropertyNode *child = node->getChild(i);
343 string cname = child->getName();
344 string cval = child->getStringValue();
345 if ( cname == "name" ) {
347 } else if ( cname == "debug" ) {
348 debug = child->getBoolValue();
349 } else if ( cname == "enable" ) {
350 // cout << "parsing enable" << endl;
351 SGPropertyNode *prop = child->getChild( "prop" );
352 if ( prop != NULL ) {
353 // cout << "prop = " << prop->getStringValue() << endl;
354 enable_prop = fgGetNode( prop->getStringValue(), true );
356 // cout << "no prop child" << endl;
358 SGPropertyNode *val = child->getChild( "value" );
360 enable_value = val->getStringValue();
362 } else if ( cname == "input" ) {
363 SGPropertyNode *prop = child->getChild( "prop" );
364 if ( prop != NULL ) {
365 input_prop = fgGetNode( prop->getStringValue(), true );
367 } else if ( cname == "reference" ) {
368 SGPropertyNode *prop = child->getChild( "prop" );
369 if ( prop != NULL ) {
370 r_n_prop = fgGetNode( prop->getStringValue(), true );
372 prop = child->getChild( "value" );
373 if ( prop != NULL ) {
374 r_n = prop->getDoubleValue();
377 } else if ( cname == "output" ) {
379 SGPropertyNode *prop;
380 while ( (prop = child->getChild("prop", i)) != NULL ) {
381 SGPropertyNode *tmp = fgGetNode( prop->getStringValue(), true );
382 output_list.push_back( tmp );
385 } else if ( cname == "config" ) {
386 SGPropertyNode *prop;
388 prop = child->getChild( "Kp" );
389 if ( prop != NULL ) {
390 Kp = prop->getDoubleValue();
394 prop = child->getChild( "Ki" );
395 if ( prop != NULL ) {
396 Ki = prop->getDoubleValue();
400 prop = child->getChild( "u_min" );
401 if ( prop != NULL ) {
402 u_min = prop->getDoubleValue();
406 prop = child->getChild( "u_max" );
407 if ( prop != NULL ) {
408 u_max = prop->getDoubleValue();
412 SG_LOG( SG_AUTOPILOT, SG_WARN, "Error in autopilot config logic" );
413 if ( name.length() ) {
414 SG_LOG( SG_AUTOPILOT, SG_WARN, "Section = " << name );
421 void FGPISimpleController::update( double dt ) {
422 if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
424 // we have just been enabled, zero out int_sum
433 if ( debug ) cout << "Updating " << name << endl;
435 if ( input_prop != NULL ) {
436 input = input_prop->getDoubleValue();
440 if ( r_n_prop != NULL ) {
441 r_n = r_n_prop->getDoubleValue();
446 double error = r_n - input;
447 if ( debug ) cout << "input = " << input
448 << " reference = " << r_n
449 << " error = " << error
452 double prop_comp = 0.0;
454 if ( offset_prop != NULL ) {
455 offset = offset_prop->getDoubleValue();
456 if ( debug ) cout << "offset = " << offset << endl;
458 offset = offset_value;
461 if ( proportional ) {
462 prop_comp = error * Kp + offset;
466 int_sum += error * Ki * dt;
471 if ( debug ) cout << "prop_comp = " << prop_comp
472 << " int_sum = " << int_sum << endl;
474 double output = prop_comp + int_sum;
477 if ( output < u_min ) {
480 if ( output > u_max ) {
484 if ( debug ) cout << "output = " << output << endl;
487 for ( i = 0; i < output_list.size(); ++i ) {
488 output_list[i]->setDoubleValue( output );
494 FGXMLAutopilot::FGXMLAutopilot() {
498 FGXMLAutopilot::~FGXMLAutopilot() {
502 void FGXMLAutopilot::init() {
503 config_props = fgGetNode( "/autopilot/new-config", true );
505 SGPropertyNode *path_n = fgGetNode("/sim/systems/autopilot/path");
508 SGPath config( globals->get_fg_root() );
509 config.append( path_n->getStringValue() );
511 SG_LOG( SG_ALL, SG_INFO, "Reading autopilot configuration from "
514 readProperties( config.str(), config_props );
517 SG_LOG( SG_ALL, SG_ALERT,
518 "Detected an internal inconsistancy in the autopilot");
519 SG_LOG( SG_ALL, SG_ALERT,
520 " configuration. See earlier errors for" );
521 SG_LOG( SG_ALL, SG_ALERT,
525 } catch (const sg_exception& exc) {
526 SG_LOG( SG_ALL, SG_ALERT, "Failed to load autopilot configuration: "
531 SG_LOG( SG_ALL, SG_WARN,
532 "No autopilot configuration specified for this model!");
537 void FGXMLAutopilot::reinit() {
544 void FGXMLAutopilot::bind() {
547 void FGXMLAutopilot::unbind() {
550 bool FGXMLAutopilot::build() {
551 SGPropertyNode *node;
554 int count = config_props->nChildren();
555 for ( i = 0; i < count; ++i ) {
556 node = config_props->getChild(i);
557 string name = node->getName();
558 // cout << name << endl;
559 if ( name == "pid-controller" ) {
560 FGXMLAutoComponent *c = new FGPIDController( node );
561 components.push_back( c );
562 } else if ( name == "pi-simple-controller" ) {
563 FGXMLAutoComponent *c = new FGPISimpleController( node );
564 components.push_back( c );
566 SG_LOG( SG_ALL, SG_ALERT, "Unknown top level section: "
577 * Update helper values
579 static void update_helper( double dt ) {
580 // Estimate speed in 5,10 seconds
581 static SGPropertyNode *vel = fgGetNode( "/velocities/airspeed-kt", true );
582 static SGPropertyNode *lookahead5
583 = fgGetNode( "/autopilot/internal/lookahead-5-sec-airspeed-kt", true );
584 static SGPropertyNode *lookahead10
585 = fgGetNode( "/autopilot/internal/lookahead-10-sec-airspeed-kt", true );
587 static double average = 0.0; // average/filtered prediction
588 static double v_last = 0.0; // last velocity
590 double v = vel->getDoubleValue();
593 a = (v - v_last) / dt;
596 average = (1.0 - dt) * average + dt * a;
601 lookahead5->setDoubleValue( v + average * 5.0 );
602 lookahead10->setDoubleValue( v + average * 10.0 );
606 // Calculate heading bug error normalized to +/- 180.0 (based on
607 // DG indicated heading)
608 static SGPropertyNode *bug
609 = fgGetNode( "/autopilot/settings/heading-bug-deg", true );
610 static SGPropertyNode *ind_hdg
611 = fgGetNode( "/instrumentation/heading-indicator/indicated-heading-deg",
613 static SGPropertyNode *ind_bug_error
614 = fgGetNode( "/autopilot/internal/heading-bug-error-deg", true );
616 double diff = bug->getDoubleValue() - ind_hdg->getDoubleValue();
617 if ( diff < -180.0 ) { diff += 360.0; }
618 if ( diff > 180.0 ) { diff -= 360.0; }
619 ind_bug_error->setDoubleValue( diff );
621 // Calculate heading bug error normalized to +/- 180.0 (based on
622 // actual/nodrift magnetic-heading, i.e. a DG slaved to magnetic
624 static SGPropertyNode *mag_hdg
625 = fgGetNode( "/orientation/heading-magnetic-deg", true );
626 static SGPropertyNode *fdm_bug_error
627 = fgGetNode( "/autopilot/internal/fdm-heading-bug-error-deg", true );
629 diff = bug->getDoubleValue() - mag_hdg->getDoubleValue();
630 if ( diff < -180.0 ) { diff += 360.0; }
631 if ( diff > 180.0 ) { diff -= 360.0; }
632 fdm_bug_error->setDoubleValue( diff );
634 // Calculate true heading error normalized to +/- 180.0
635 static SGPropertyNode *target_true
636 = fgGetNode( "/autopilot/settings/true-heading-deg", true );
637 static SGPropertyNode *true_hdg
638 = fgGetNode( "/orientation/heading-deg", true );
639 static SGPropertyNode *true_error
640 = fgGetNode( "/autopilot/internal/true-heading-error-deg", true );
642 diff = target_true->getDoubleValue() - true_hdg->getDoubleValue();
643 if ( diff < -180.0 ) { diff += 360.0; }
644 if ( diff > 180.0 ) { diff -= 360.0; }
645 true_error->setDoubleValue( diff );
647 // Calculate nav1 target heading error normalized to +/- 180.0
648 static SGPropertyNode *target_nav1
649 = fgGetNode( "/radios/nav[0]/radials/target-auto-hdg-deg", true );
650 static SGPropertyNode *true_nav1
651 = fgGetNode( "/autopilot/internal/nav1-heading-error-deg", true );
653 diff = target_nav1->getDoubleValue() - true_hdg->getDoubleValue();
654 if ( diff < -180.0 ) { diff += 360.0; }
655 if ( diff > 180.0 ) { diff -= 360.0; }
656 true_nav1->setDoubleValue( diff );
658 // Calculate vertical speed in fpm
659 static SGPropertyNode *vs_fps
660 = fgGetNode( "/velocities/vertical-speed-fps", true );
661 static SGPropertyNode *vs_fpm
662 = fgGetNode( "/autopilot/internal/vert-speed-fpm", true );
664 vs_fpm->setDoubleValue( vs_fps->getDoubleValue() * 60.0 );
669 * Update the list of autopilot components
672 void FGXMLAutopilot::update( double dt ) {
676 for ( i = 0; i < components.size(); ++i ) {
677 components[i]->update( dt );