//
// Written by Curtis Olson, started January 2004.
//
-// Copyright (C) 2004 Curtis L. Olson - curt@flightgear.org
+// Copyright (C) 2004 Curtis L. Olson - http://www.flightgear.org/~curt
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
-// Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
//
// $Id$
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
#include <simgear/structure/exception.hxx>
#include <simgear/misc/sg_path.hxx>
+#include <simgear/sg_inlines.h>
#include <Main/fg_props.hxx>
#include <Main/globals.hxx>
+#include <Main/util.hxx>
#include "xmlauto.hxx"
r_n( 0.0 ),
y_scale( 1.0 ),
r_scale( 1.0 ),
+ y_offset( 0.0 ),
+ r_offset( 0.0 ),
Kp( 0.0 ),
alpha( 0.1 ),
beta( 1.0 ),
ep_n_1( 0.0 ),
edf_n_1( 0.0 ),
edf_n_2( 0.0 ),
- u_n_1( 0.0 )
+ u_n_1( 0.0 ),
+ desiredTs( 0.0 )
{
int i;
for ( i = 0; i < node->nChildren(); ++i ) {
if ( val != NULL ) {
enable_value = val->getStringValue();
}
+ SGPropertyNode *pass = child->getChild( "honor-passive" );
+ if ( pass != NULL ) {
+ honor_passive = pass->getBoolValue();
+ }
} else if ( cname == "input" ) {
SGPropertyNode *prop = child->getChild( "prop" );
if ( prop != NULL ) {
if ( prop != NULL ) {
y_scale = prop->getDoubleValue();
}
+ prop = child->getChild( "offset" );
+ if ( prop != NULL ) {
+ y_offset = prop->getDoubleValue();
+ }
} else if ( cname == "reference" ) {
SGPropertyNode *prop = child->getChild( "prop" );
if ( prop != NULL ) {
if ( prop != NULL ) {
r_scale = prop->getDoubleValue();
}
+ prop = child->getChild( "offset" );
+ if ( prop != NULL ) {
+ r_offset = prop->getDoubleValue();
+ }
} else if ( cname == "output" ) {
int i = 0;
SGPropertyNode *prop;
} else if ( cname == "config" ) {
SGPropertyNode *prop;
+ prop = child->getChild( "Ts" );
+ if ( prop != NULL ) {
+ desiredTs = prop->getDoubleValue();
+ }
+
prop = child->getChild( "Kp" );
if ( prop != NULL ) {
Kp = prop->getDoubleValue();
double Tf; // filter time
double delta_u_n = 0.0; // incremental output
double u_n = 0.0; // absolute output
- double Ts = dt; // Sampling interval (sec)
-
- if ( Ts <= 0.0 ) {
+ double Ts; // sampling interval (sec)
+
+ elapsedTime += dt;
+ if ( elapsedTime <= desiredTs ) {
// do nothing if time step is not positive (i.e. no time has
// elapsed)
return;
}
+ Ts = elapsedTime;
+ elapsedTime = 0.0;
if (enable_prop != NULL && enable_prop->getStringValue() == enable_value) {
if ( !enabled ) {
}
if ( enabled && Ts > 0.0) {
- if ( debug ) cout << "Updating " << name << endl;
+ if ( debug ) cout << "Updating " << name
+ << " Ts " << Ts << endl;
double y_n = 0.0;
if ( input_prop != NULL ) {
- y_n = input_prop->getDoubleValue() * y_scale;
+ y_n = input_prop->getDoubleValue() * y_scale + y_offset;
}
double r_n = 0.0;
if ( r_n_prop != NULL ) {
- r_n = r_n_prop->getDoubleValue() * r_scale;
+ r_n = r_n_prop->getDoubleValue() * r_scale + r_offset;
} else {
r_n = r_n_value;
}
delta_u_n = Kp * ( (ep_n - ep_n_1)
+ ((Ts/Ti) * e_n)
+ ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
- } else if ( Ti <= 0.0 ) {
- delta_u_n = Kp * ( (ep_n - ep_n_1)
- + ((Td/Ts) * (edf_n - 2*edf_n_1 + edf_n_2)) );
}
if ( debug ) {
// Integrator anti-windup logic:
if ( delta_u_n > (u_max - u_n_1) ) {
- delta_u_n = 0;
+ delta_u_n = u_max - u_n_1;
if ( debug ) cout << " max saturation " << endl;
} else if ( delta_u_n < (u_min - u_n_1) ) {
- delta_u_n = 0;
+ delta_u_n = u_min - u_n_1;
if ( debug ) cout << " min saturation " << endl;
}
edf_n_2 = edf_n_1;
edf_n_1 = edf_n;
- unsigned int i;
- for ( i = 0; i < output_list.size(); ++i ) {
- output_list[i]->setDoubleValue( u_n );
+ // passive_ignore == true means that we go through all the
+ // motions, but drive the outputs. This is analogous to
+ // running the autopilot with the "servos" off. This is
+ // helpful for things like flight directors which position
+ // their vbars from the autopilot computations.
+ if ( passive_mode->getBoolValue() && honor_passive ) {
+ // skip output step
+ } else {
+ unsigned int i;
+ for ( i = 0; i < output_list.size(); ++i ) {
+ output_list[i]->setDoubleValue( u_n );
+ }
}
} else if ( !enabled ) {
ep_n = 0.0;
}
+FGDigitalFilter::FGDigitalFilter(SGPropertyNode *node)
+{
+ samples = 1;
+
+ int i;
+ for ( i = 0; i < node->nChildren(); ++i ) {
+ SGPropertyNode *child = node->getChild(i);
+ string cname = child->getName();
+ string cval = child->getStringValue();
+ if ( cname == "name" ) {
+ name = cval;
+ } else if ( cname == "debug" ) {
+ debug = child->getBoolValue();
+ } else if ( cname == "type" ) {
+ if ( cval == "exponential" ) {
+ filterType = exponential;
+ } else if (cval == "double-exponential") {
+ filterType = doubleExponential;
+ } else if (cval == "moving-average") {
+ filterType = movingAverage;
+ } else if (cval == "noise-spike") {
+ filterType = noiseSpike;
+ }
+ } else if ( cname == "input" ) {
+ input_prop = fgGetNode( child->getStringValue(), true );
+ } else if ( cname == "filter-time" ) {
+ Tf = child->getDoubleValue();
+ } else if ( cname == "samples" ) {
+ samples = child->getIntValue();
+ } else if ( cname == "max-rate-of-change" ) {
+ rateOfChange = child->getDoubleValue();
+ } else if ( cname == "output" ) {
+ SGPropertyNode *tmp = fgGetNode( child->getStringValue(), true );
+ output_list.push_back( tmp );
+ }
+ }
+
+ output.resize(2, 0.0);
+ input.resize(samples + 1, 0.0);
+}
+
+void FGDigitalFilter::update(double dt)
+{
+ if ( input_prop != NULL ) {
+ input.push_front(input_prop->getDoubleValue());
+ input.resize(samples + 1, 0.0);
+ // no sense if there isn't an input :-)
+ enabled = true;
+ } else {
+ enabled = false;
+ }
+
+ if ( enabled && dt > 0.0 ) {
+ /*
+ * Exponential filter
+ *
+ * Output[n] = alpha*Input[n] + (1-alpha)*Output[n-1]
+ *
+ */
+
+ if (filterType == exponential)
+ {
+ double alpha = 1 / ((Tf/dt) + 1);
+ output.push_front(alpha * input[0] +
+ (1 - alpha) * output[0]);
+ unsigned int i;
+ for ( i = 0; i < output_list.size(); ++i ) {
+ output_list[i]->setDoubleValue( output[0] );
+ }
+ output.resize(1);
+ }
+ else if (filterType == doubleExponential)
+ {
+ double alpha = 1 / ((Tf/dt) + 1);
+ output.push_front(alpha * alpha * input[0] +
+ 2 * (1 - alpha) * output[0] -
+ (1 - alpha) * (1 - alpha) * output[1]);
+ unsigned int i;
+ for ( i = 0; i < output_list.size(); ++i ) {
+ output_list[i]->setDoubleValue( output[0] );
+ }
+ output.resize(2);
+ }
+ else if (filterType == movingAverage)
+ {
+ output.push_front(output[0] +
+ (input[0] - input.back()) / samples);
+ unsigned int i;
+ for ( i = 0; i < output_list.size(); ++i ) {
+ output_list[i]->setDoubleValue( output[0] );
+ }
+ output.resize(1);
+ }
+ else if (filterType == noiseSpike)
+ {
+ double maxChange = rateOfChange * dt;
+
+ if ((output[0] - input[0]) > maxChange)
+ {
+ output.push_front(output[0] - maxChange);
+ }
+ else if ((output[0] - input[0]) < -maxChange)
+ {
+ output.push_front(output[0] + maxChange);
+ }
+ else if (fabs(input[0] - output[0]) <= maxChange)
+ {
+ output.push_front(input[0]);
+ }
+
+ unsigned int i;
+ for ( i = 0; i < output_list.size(); ++i ) {
+ output_list[i]->setDoubleValue( output[0] );
+ }
+ output.resize(1);
+ }
+ if (debug)
+ {
+ cout << "input:" << input[0]
+ << "\toutput:" << output[0] << endl;
+ }
+ }
+}
+
+
FGXMLAutopilot::FGXMLAutopilot() {
}
if ( ! build() ) {
SG_LOG( SG_ALL, SG_ALERT,
- "Detected an internal inconsistancy in the autopilot");
+ "Detected an internal inconsistency in the autopilot");
SG_LOG( SG_ALL, SG_ALERT,
" configuration. See earlier errors for" );
SG_LOG( SG_ALL, SG_ALERT,
} else if ( name == "predict-simple" ) {
FGXMLAutoComponent *c = new FGPredictor( node );
components.push_back( c );
+ } else if ( name == "filter" ) {
+ FGXMLAutoComponent *c = new FGDigitalFilter( node );
+ components.push_back( c );
} else {
SG_LOG( SG_ALL, SG_ALERT, "Unknown top level section: "
<< name );
// Calculate nav1 target heading error normalized to +/- 180.0
static SGPropertyNode *target_nav1
- = fgGetNode( "/radios/nav[0]/radials/target-auto-hdg-deg", true );
+ = fgGetNode( "/instrumentation/nav[0]/radials/target-auto-hdg-deg", true );
static SGPropertyNode *true_nav1
= fgGetNode( "/autopilot/internal/nav1-heading-error-deg", true );
static SGPropertyNode *true_track_nav1
if ( diff > 180.0 ) { diff -= 360.0; }
true_track_nav1->setDoubleValue( diff );
+ // Calculate nav1 selected course error normalized to +/- 180.0
+ // (based on DG indicated heading)
+ static SGPropertyNode *nav1_course_error
+ = fgGetNode( "/autopilot/internal/nav1-course-error", true );
+ static SGPropertyNode *nav1_selected_course
+ = fgGetNode( "/instrumentation/nav[0]/radials/selected-deg", true );
+
+ diff = nav1_selected_course->getDoubleValue() - ind_hdg->getDoubleValue();
+// if ( diff < -180.0 ) { diff += 360.0; }
+// if ( diff > 180.0 ) { diff -= 360.0; }
+ SG_NORMALIZE_RANGE( diff, -180.0, 180.0 );
+ nav1_course_error->setDoubleValue( diff );
+
// Calculate vertical speed in fpm
static SGPropertyNode *vs_fps
= fgGetNode( "/velocities/vertical-speed-fps", true );
static SGPropertyNode *vs_fpm
= fgGetNode( "/autopilot/internal/vert-speed-fpm", true );
- vs_fpm->setDoubleValue( vs_fps->getDoubleValue() * 60.0 );
+ vs_fpm->setDoubleValue( vs_fps->getDoubleValue() * 60.0 );
+
+
+ // Calculate static port pressure rate in [inhg/s].
+ // Used to determine vertical speed.
+ static SGPropertyNode *static_pressure
+ = fgGetNode( "/systems/static[0]/pressure-inhg", true );
+ static SGPropertyNode *pressure_rate
+ = fgGetNode( "/autopilot/internal/pressure-rate", true );
+
+ static double last_static_pressure = 0.0;
+
+ if ( dt > 0.0 ) {
+ double current_static_pressure = static_pressure->getDoubleValue();
+
+ double current_pressure_rate =
+ ( current_static_pressure - last_static_pressure ) / dt;
+
+ pressure_rate->setDoubleValue(current_pressure_rate);
+
+ last_static_pressure = current_static_pressure;
+ }
+
}