//
// 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$
lon_node(fgGetNode("/position/longitude-deg", true)),
lat_node(fgGetNode("/position/latitude-deg", true)),
alt_node(fgGetNode("/position/altitude-ft", true)),
+ is_valid_node(NULL),
power_btn_node(NULL),
freq_node(NULL),
alt_freq_node(NULL),
- fmt_freq_node(NULL),
- fmt_alt_freq_node(NULL),
sel_radial_node(NULL),
vol_btn_node(NULL),
ident_btn_node(NULL),
audio_btn_node(NULL),
+ backcourse_node(NULL),
nav_serviceable_node(NULL),
cdi_serviceable_node(NULL),
gs_serviceable_node(NULL),
tofrom_serviceable_node(NULL),
+ fmt_freq_node(NULL),
+ fmt_alt_freq_node(NULL),
heading_node(NULL),
radial_node(NULL),
recip_radial_node(NULL),
last_x(0.0),
last_loc_dist(0.0),
last_xtrack_error(0.0),
- name("nav"),
- num(0),
+ _name(node->getStringValue("name", "nav")),
+ _num(node->getIntValue("number", 0)),
_time_before_search_sec(-1.0)
{
SGPath path( globals->get_fg_root() );
term_tbl = new SGInterpTable( term.str() );
low_tbl = new SGInterpTable( low.str() );
high_tbl = new SGInterpTable( high.str() );
-
- 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 == "number" ) {
- num = child->getIntValue();
- } else {
- SG_LOG( SG_INSTR, SG_WARN,
- "Error in nav radio config logic" );
- if ( name.length() ) {
- SG_LOG( SG_INSTR, SG_WARN, "Section = " << name );
- }
- }
- }
-
}
morse.init();
string branch;
- branch = "/instrumentation/" + name;
+ branch = "/instrumentation/" + _name;
- SGPropertyNode *node = fgGetNode(branch.c_str(), num, true );
+ SGPropertyNode *node = fgGetNode(branch.c_str(), _num, true );
bus_power_node =
- fgGetNode(("/systems/electrical/outputs/" + name).c_str(), true);
+ fgGetNode(("/systems/electrical/outputs/" + _name).c_str(), true);
// inputs
+ is_valid_node = node->getChild("data-is-valid", 0, true);
power_btn_node = node->getChild("power-btn", 0, true);
power_btn_node->setBoolValue( true );
vol_btn_node = node->getChild("volume", 0, true);
ident_btn_node->setBoolValue( true );
audio_btn_node = node->getChild("audio-btn", 0, true);
audio_btn_node->setBoolValue( true );
+ backcourse_node = node->getChild("back-course-btn", 0, true);
+ backcourse_node->setBoolValue( false );
nav_serviceable_node = node->getChild("serviceable", 0, true);
cdi_serviceable_node = (node->getChild("cdi", 0, true))
->getChild("serviceable", 0, true);
gps_from_flag_node = fgGetNode("/instrumentation/gps/from-flag", true);
std::ostringstream temp;
- temp << name << "nav-ident" << num;
+ temp << _name << "nav-ident" << _num;
nav_fx_name = temp.str();
- temp << name << "dme-ident" << num;
+ temp << _name << "dme-ident" << _num;
dme_fx_name = temp.str();
}
{
std::ostringstream temp;
string branch;
- temp << num;
- branch = "/instrumentation/" + name + "[" + temp.str() + "]";
+ temp << _num;
+ branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
}
{
std::ostringstream temp;
string branch;
- temp << num;
- branch = "/instrumentation/" + name + "[" + temp.str() + "]";
+ temp << _num;
+ branch = "/instrumentation/" + _name + "[" + temp.str() + "]";
}
void
FGNavRadio::update(double dt)
{
+ // Do a nav station search only once a second to reduce
+ // unnecessary work. (Also, make sure to do this before caching
+ // any values!)
+ _time_before_search_sec -= dt;
+ if ( _time_before_search_sec < 0 ) {
+ search();
+ }
+
// cache a few strategic values locally for speed
- double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
- double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
- double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
+ SGGeod pos = SGGeod::fromDegFt(lon_node->getDoubleValue(),
+ lat_node->getDoubleValue(),
+ alt_node->getDoubleValue());
bool power_btn = power_btn_node->getBoolValue();
bool nav_serviceable = nav_serviceable_node->getBoolValue();
bool cdi_serviceable = cdi_serviceable_node->getBoolValue();
bool is_loc = loc_node->getBoolValue();
double loc_dist = loc_dist_node->getDoubleValue();
- Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
- Point3D station;
double az1, az2, s;
// Create "formatted" versions of the nav frequencies for
sprintf( tmp, "%.2f", alt_freq_node->getDoubleValue() );
fmt_alt_freq_node->setStringValue(tmp);
- // Do a nav station search only once a second to reduce
- // unnecessary work.
- _time_before_search_sec -= dt;
- if ( _time_before_search_sec < 0 ) {
- search();
- }
-
// cout << "is_valid = " << is_valid
// << " power_btn = " << power_btn
// << " bus_power = " << bus_power_node->getDoubleValue()
if ( is_valid && power_btn && (bus_power_node->getDoubleValue() > 1.0)
&& nav_serviceable )
{
- station = Point3D( nav_x, nav_y, nav_z );
- loc_dist = aircraft.distance3D( station );
+ SGVec3d aircraft = SGVec3d::fromGeod(pos);
+ loc_dist = dist(aircraft, nav_xyz);
loc_dist_node->setDoubleValue( loc_dist );
- // cout << "station = " << station << " dist = " << loc_dist << endl;
+ // cout << "dt = " << dt << " dist = " << loc_dist << endl;
if ( has_gs ) {
// find closest distance to the gs base line
- sgdVec3 p;
- sgdSetVec3( p, aircraft.x(), aircraft.y(), aircraft.z() );
- sgdVec3 p0;
- sgdSetVec3( p0, gs_x, gs_y, gs_z );
- double dist = sgdClosestPointToLineDistSquared( p, p0,
- gs_base_vec );
+ SGVec3d p = aircraft;
+ double dist = sgdClosestPointToLineDistSquared(p.sg(), gs_xyz.sg(),
+ gs_base_vec.sg());
gs_dist_node->setDoubleValue( sqrt( dist ) );
// cout << "gs_dist = " << gs_dist_node->getDoubleValue()
// << endl;
- Point3D tmp( gs_x, gs_y, gs_z );
- // cout << " (" << aircraft.distance3D( tmp ) << ")" << endl;
-
// wgs84 heading to glide slope (to determine sign of distance)
- geo_inverse_wgs_84( elev,
- lat * SGD_RADIANS_TO_DEGREES,
- lon * SGD_RADIANS_TO_DEGREES,
- gs_lat, gs_lon,
+ geo_inverse_wgs_84( pos, SGGeod::fromDeg(gs_lon, gs_lat),
&az1, &az2, &s );
double r = az1 - target_radial;
while ( r > 180.0 ) { r -= 360.0;}
// compute forward and reverse wgs84 headings to localizer
//////////////////////////////////////////////////////////
double hdg;
- geo_inverse_wgs_84( elev,
- lat * SGD_RADIANS_TO_DEGREES,
- lon * SGD_RADIANS_TO_DEGREES,
- loc_lat, loc_lon,
+ geo_inverse_wgs_84( pos, SGGeod::fromDeg(loc_lon, loc_lat),
&hdg, &az2, &s );
// cout << "az1 = " << az1 << " magvar = " << nav_magvar << endl;
heading_node->setDoubleValue( hdg );
// cout << " heading = " << heading_node->getDoubleValue()
// << " dist = " << nav_dist << endl;
+ //////////////////////////////////////////////////////////
+ // compute the target/selected radial in "true" heading
+ //////////////////////////////////////////////////////////
+ double trtrue = 0.0;
+ if ( is_loc ) {
+ // ILS localizers radials are already "true" in our
+ // database
+ trtrue = target_radial;
+ } else {
+ // VOR radials need to have that vor's offset added in
+ trtrue = target_radial + twist;
+ }
+
+ while ( trtrue < 0.0 ) { trtrue += 360.0; }
+ while ( trtrue > 360.0 ) { trtrue -= 360.0; }
+ target_radial_true_node->setDoubleValue( trtrue );
+
//////////////////////////////////////////////////////////
// adjust reception range for altitude
+ // FIXME: make sure we are using the navdata range now that
+ // it is valid in the data file
//////////////////////////////////////////////////////////
if ( is_loc ) {
double offset = radial - target_radial;
while ( offset > 180.0 ) { offset -= 360.0; }
// cout << "ils offset = " << offset << endl;
effective_range
- = adjustILSRange( nav_elev, elev, offset,
+ = adjustILSRange( nav_elev, pos.getElevationM(), offset,
loc_dist * SG_METER_TO_NM );
} else {
- effective_range = adjustNavRange( nav_elev, elev, range );
+ effective_range
+ = adjustNavRange( nav_elev, pos.getElevationM(), range );
}
// cout << "nav range = " << effective_range
// << " (" << range << ")" << endl;
//////////////////////////////////////////////////////////
// compute to/from flag status
//////////////////////////////////////////////////////////
- double value = false;
+ bool value = false;
double offset = fabs(radial - target_radial);
if ( tofrom_serviceable ) {
if ( nav_slaved_to_gps_node->getBoolValue() ) {
// of ( -10 , 10 )
//////////////////////////////////////////////////////////
double r = 0.0;
+ bool loc_backside = false; // an in-code flag indicating that we are
+ // on a localizer backcourse.
if ( cdi_serviceable ) {
if ( nav_slaved_to_gps_node->getBoolValue() ) {
r = gps_cdi_deflection_node->getDoubleValue();
// We want +- 5 dots deflection for the gps, so clamp
// to -12.5/12.5
- if ( r < -12.5 ) { r = -12.5; }
- if ( r > 12.5 ) { r = 12.5; }
+ SG_CLAMP_RANGE( r, -12.5, 12.5 );
} else if ( inrange ) {
r = radial - target_radial;
// cout << "Target radial = " << target_radial
while ( r < -180.0 ) { r += 360.0;}
if ( fabs(r) > 90.0 ) {
r = ( r<0.0 ? -r-180.0 : -r+180.0 );
+ } else {
+ if ( is_loc ) {
+ loc_backside = true;
+ }
}
- // According to Robin Peel, the ILS is 4x more
- // sensitive than a vor
r = -r; // reverse, since radial is outbound
- if ( is_loc ) { r *= 4.0; }
- if ( r < -10.0 ) { r = -10.0; }
- if ( r > 10.0 ) { r = 10.0; }
+ if ( is_loc ) {
+ // According to Robin Peel, the ILS is 4x more
+ // sensitive than a vor
+ r *= 4.0;
+ }
+ SG_CLAMP_RANGE( r, -10.0, 10.0 );
}
}
cdi_deflection_node->setDoubleValue( r );
//////////////////////////////////////////////////////////
double hdg_error = 0.0;
if ( inrange && cdi_serviceable ) {
- double ddist = last_loc_dist - loc_dist;
- double dxtrack = last_xtrack_error - xtrack_error;
- double a = atan2( dxtrack, ddist ) * SGD_RADIANS_TO_DEGREES;
- SGPropertyNode *maghead
- = fgGetNode("/orientation/heading-magnetic-deg", true);
- cout << "heading = " << maghead->getDoubleValue()
- << " selrad = " << sel_radial_node->getDoubleValue()
- << " artr = " << a
- << endl;
- double est_hdg = sel_radial_node->getDoubleValue() + a;
- if ( est_hdg < 0.0 ) { est_hdg += 360.0; }
- if ( est_hdg >= 360.0 ) { est_hdg -= 360.0; }
- hdg_error = est_hdg - maghead->getDoubleValue();
+ double vn = fgGetDouble( "/velocities/speed-north-fps" );
+ double ve = fgGetDouble( "/velocities/speed-east-fps" );
+ double gnd_trk_true = atan2( ve, vn ) * SGD_RADIANS_TO_DEGREES;
+ if ( gnd_trk_true < 0.0 ) { gnd_trk_true += 360.0; }
+
+ SGPropertyNode *true_hdg
+ = fgGetNode("/orientation/heading-deg", true);
+ hdg_error = gnd_trk_true - true_hdg->getDoubleValue();
+
+ // cout << "ground track = " << gnd_trk_true
+ // << " orientation = " << true_hdg->getDoubleValue() << endl;
}
cdi_xtrack_hdg_err_node->setDoubleValue( hdg_error );
//////////////////////////////////////////////////////////
// compute the amount of glide slope needle deflection
// (.i.e. the number of degrees we are off the glide slope * 5.0
+ //
+ // CLO - 13 Mar 2006: The glide slope needle should peg at
+ // +/-0.7 degrees off the ideal glideslope. I'm not sure why
+ // we compute the factor the way we do (5*gs_error), but we
+ // need to compensate for our 'odd' number in the glideslope
+ // needle animation. This means that the needle should peg
+ // when this values is +/-3.5.
//////////////////////////////////////////////////////////
r = 0.0;
if ( has_gs && gs_serviceable_node->getBoolValue() ) {
// a nav/ils radial.
//////////////////////////////////////////////////////////
- // determine the target radial in "true" heading
- double trtrue = 0.0;
- if ( is_loc ) {
- // ILS localizers radials are already "true" in our
- // database
- trtrue = target_radial;
- } else {
- // VOR radials need to have that vor's offset added in
- trtrue = target_radial + twist;
- }
-
- while ( trtrue < 0.0 ) { trtrue += 360.0; }
- while ( trtrue > 360.0 ) { trtrue -= 360.0; }
- target_radial_true_node->setDoubleValue( trtrue );
-
- // FIXME: this smells odd, there must be a better (or more
- // linear) solution
+ // Now that we have cross track heading adjustment built in,
+ // we shouldn't need to overdrive the heading angle within 8km
+ // of the station.
//
- // determine the heading adjustment needed.
- // over 8km scale by 3.0
- // (3 is chosen because max deflection is 10
- // and 30 is clamped angle to radial)
- // under 8km scale by 10.0
- // because the overstated error helps drive it to the radial in a
- // moderate cross wind.
- double adjustment = 0.0;
- if ( loc_dist > 8000 ) {
- adjustment = cdi_deflection_node->getDoubleValue() * 3.0;
- } else {
- adjustment = cdi_deflection_node->getDoubleValue() * 10.0;
- }
+ // The cdi deflection should be +/-10 for a full range of deflection
+ // so multiplying this by 3 gives us +/- 30 degrees heading
+ // compensation.
+ double adjustment = cdi_deflection_node->getDoubleValue() * 3.0;
SG_CLAMP_RANGE( adjustment, -30.0, 30.0 );
-
+
// determine the target heading to fly to intercept the
// tgt_radial = target radial (true) + cdi offset adjustmest -
// xtrack heading error adjustment
- double nta_hdg = trtrue + adjustment /* - hdg_error */;
+ double nta_hdg;
+ if ( is_loc && backcourse_node->getBoolValue() ) {
+ // tuned to a localizer and backcourse mode activated
+ trtrue += 180.0; // reverse the target localizer heading
+ while ( trtrue > 360.0 ) { trtrue -= 360.0; }
+ nta_hdg = trtrue - adjustment - hdg_error;
+ } else {
+ nta_hdg = trtrue + adjustment - hdg_error;
+ }
+
while ( nta_hdg < 0.0 ) { nta_hdg += 360.0; }
- while ( nta_hdg > 360.0 ) { nta_hdg -= 360.0; }
+ while ( nta_hdg >= 360.0 ) { nta_hdg -= 360.0; }
target_auto_hdg_node->setDoubleValue( nta_hdg );
last_xtrack_error = xtrack_error;
while ( target_radial > 360.0 ) { target_radial -= 360.0; }
loc_lon = loc->get_lon();
loc_lat = loc->get_lat();
- nav_x = loc->get_x();
- nav_y = loc->get_y();
- nav_z = loc->get_z();
+ nav_xyz = loc->get_cart();
last_nav_id = nav_id;
last_nav_vor = false;
loc_node->setBoolValue( true );
nav_elev = gs->get_elev_ft();
int tmp = (int)(gs->get_multiuse() / 1000.0);
target_gs = (double)tmp / 100.0;
- gs_x = gs->get_x();
- gs_y = gs->get_y();
- gs_z = gs->get_z();
+ gs_xyz = gs->get_cart();
// derive GS baseline (perpendicular to the runay
// along the ground)
double tlon, tlat, taz;
geo_direct_wgs_84 ( 0.0, gs_lat, gs_lon,
- target_radial + 90,
+ target_radial + 90,
100.0, &tlat, &tlon, &taz );
// cout << "target_radial = " << target_radial << endl;
// cout << "nav_loc = " << loc_node->getBoolValue() << endl;
// cout << gs_lon << "," << gs_lat << " "
// << tlon << "," << tlat << " (" << nav_elev << ")"
// << endl;
- Point3D p1 = sgGeodToCart( Point3D(tlon*SGD_DEGREES_TO_RADIANS,
- tlat*SGD_DEGREES_TO_RADIANS,
- nav_elev*SG_FEET_TO_METER)
- );
- // cout << gs_x << "," << gs_y << "," << gs_z
- // << endl;
+ SGGeod tpos = SGGeod::fromDegFt(tlon, tlat, nav_elev);
+ SGVec3d p1 = SGVec3d::fromGeod(tpos);
+
+ // cout << gs_xyz << endl;
// cout << p1 << endl;
- sgdSetVec3( gs_base_vec,
- p1.x()-gs_x, p1.y()-gs_y, p1.z()-gs_z );
- // cout << gs_base_vec[0] << "," << gs_base_vec[1] << ","
- // << gs_base_vec[2] << endl;
+ gs_base_vec = p1 - gs_xyz;
+ // cout << gs_base_vec << endl;
} else {
has_gs_node->setBoolValue( false );
nav_elev = loc->get_elev_ft();
effective_range = adjustNavRange(nav_elev, elev, range);
target_gs = 0.0;
target_radial = sel_radial_node->getDoubleValue();
- nav_x = nav->get_x();
- nav_y = nav->get_y();
- nav_z = nav->get_z();
+ nav_xyz = nav->get_cart();
if ( globals->get_soundmgr()->exists( nav_fx_name ) ) {
globals->get_soundmgr()->remove( nav_fx_name );
}
- SGSoundSample *sound;
- sound = morse.make_ident( trans_ident, LO_FREQUENCY );
- sound->set_volume( 0.3 );
- if ( globals->get_soundmgr()->add( sound, nav_fx_name ) ) {
- // cout << "Added nav-vor-ident sound" << endl;
- } else {
- SG_LOG(SG_COCKPIT, SG_WARN, "Failed to add v1-vor-ident sound");
- }
-
- if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
- globals->get_soundmgr()->remove( dme_fx_name );
- }
- sound = morse.make_ident( trans_ident, HI_FREQUENCY );
- sound->set_volume( 0.3 );
- globals->get_soundmgr()->add( sound, dme_fx_name );
-
- int offset = (int)(sg_random() * 30.0);
- play_count = offset / 4;
- last_time = globals->get_time_params()->get_cur_time() - offset;
- // cout << "offset = " << offset << " play_count = "
- // << play_count << " last_time = "
- // << last_time << " current time = "
- // << globals->get_time_params()->get_cur_time() << endl;
+ try {
+ SGSoundSample *sound;
+ sound = morse.make_ident( trans_ident, LO_FREQUENCY );
+ sound->set_volume( 0.3 );
+ if ( globals->get_soundmgr()->add( sound, nav_fx_name ) ) {
+ // cout << "Added nav-vor-ident sound" << endl;
+ } else {
+ SG_LOG(SG_COCKPIT, SG_WARN, "Failed to add v1-vor-ident sound");
+ }
- // cout << "Found a vor station in range" << endl;
- // cout << " id = " << nav->get_ident() << endl;
+ if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
+ globals->get_soundmgr()->remove( dme_fx_name );
+ }
+ sound = morse.make_ident( trans_ident, HI_FREQUENCY );
+ sound->set_volume( 0.3 );
+ globals->get_soundmgr()->add( sound, dme_fx_name );
+
+ int offset = (int)(sg_random() * 30.0);
+ play_count = offset / 4;
+ last_time = globals->get_time_params()->get_cur_time() - offset;
+ // cout << "offset = " << offset << " play_count = "
+ // << play_count << " last_time = "
+ // << last_time << " current time = "
+ // << globals->get_time_params()->get_cur_time() << endl;
+
+ // cout << "Found a vor station in range" << endl;
+ // cout << " id = " << nav->get_ident() << endl;
+ } catch ( sg_io_exception &e ) {
+ SG_LOG(SG_GENERAL, SG_ALERT, e.getFormattedMessage());
+ }
}
} else {
is_valid = false;
target_radial = 0;
trans_ident = "";
last_nav_id = "";
- if ( ! globals->get_soundmgr()->remove( nav_fx_name ) ) {
- SG_LOG(SG_COCKPIT, SG_WARN, "Failed to remove nav-vor-ident sound");
- }
+ globals->get_soundmgr()->remove( nav_fx_name );
globals->get_soundmgr()->remove( dme_fx_name );
- // cout << "not picking up vor1. :-(" << endl;
}
+ is_valid_node->setBoolValue( is_valid );
+
char tmpid[5];
strncpy( tmpid, nav_id.c_str(), 5 );
id_c1_node->setIntValue( (int)tmpid[0] );