1 // radiostack.cxx -- class to manage an instance of the radio stack
3 // Written by Curtis Olson, started April 2000.
5 // Copyright (C) 2000 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.
28 #include <stdio.h> // snprintf
30 #include <simgear/compiler.h>
31 #include <simgear/math/sg_random.h>
33 #include <Aircraft/aircraft.hxx>
34 #include <Navaids/ilslist.hxx>
35 #include <Navaids/mkrbeacons.hxx>
36 #include <Navaids/navlist.hxx>
37 #include <Time/FGEventMgr.hxx>
39 #include "radiostack.hxx"
45 FGRadioStack *current_radiostack;
49 * Boy, this is ugly! Make the VOR range vary by altitude difference.
51 static double kludgeRange ( double stationElev, double aircraftElev,
54 // Assume that the nominal range (usually
55 // 50nm) applies at a 5,000 ft difference.
57 double factor = ((aircraftElev*SG_METER_TO_FEET) - stationElev) / 5000.0;
58 double range = fabs(nominalRange * factor);
60 // Clamp the range to keep it sane; for
61 // now, never less than 25% or more than
62 // 500% of nominal range.
63 if (range < nominalRange/4.0) {
64 range = nominalRange/4.0;
65 } else if (range > nominalRange*5.0) {
66 range = nominalRange*5.0;
74 FGRadioStack::FGRadioStack() :
75 lon_node(fgGetNode("/position/longitude-deg", true)),
76 lat_node(fgGetNode("/position/latitude-deg", true)),
77 alt_node(fgGetNode("/position/altitude-ft", true)),
88 SGPath path( globals->get_fg_root() );
90 term.append( "Navaids/range.term" );
92 low.append( "Navaids/range.low" );
94 high.append( "Navaids/range.high" );
96 term_tbl = new SGInterpTable( term.str() );
97 low_tbl = new SGInterpTable( low.str() );
98 high_tbl = new SGInterpTable( high.str() );
99 dme_last_time.stamp();
104 FGRadioStack::~FGRadioStack()
110 unbind(); // FIXME: should be called externally
119 FGRadioStack::init ()
121 navcom1.set_bind_index( 0 );
124 navcom2.set_bind_index( 1 );
140 update(0); // FIXME: use dt
146 // Search radio database once per second
147 global_events.Register( "fgRadioSearch()",
148 current_radiostack, &FGRadioStack::search,
153 FGRadioStack::bind ()
157 fgTie("/radios/dme/frequencies/selected-khz", this,
158 &FGRadioStack::get_dme_freq, &FGRadioStack::set_dme_freq);
161 fgTie("/radios/dme/in-range", this, &FGRadioStack::get_dme_inrange);
163 fgTie("/radios/dme/distance-nm", this, &FGRadioStack::get_dme_dist);
165 fgTie("/radios/dme/speed-kt", this, &FGRadioStack::get_dme_spd);
167 fgTie("/radios/dme/ete-min", this, &FGRadioStack::get_dme_ete);
169 fgTie("/radios/marker-beacon/inner", this,
170 &FGRadioStack::get_inner_blink);
172 fgTie("/radios/marker-beacon/middle", this,
173 &FGRadioStack::get_middle_blink);
175 fgTie("/radios/marker-beacon/outer", this,
176 &FGRadioStack::get_outer_blink);
178 navcom1.set_bind_index( 0 );
180 navcom2.set_bind_index( 1 );
187 FGRadioStack::unbind ()
189 fgUntie("/radios/dme/frequencies/selected-khz");
192 fgUntie("/radios/dme/in-range");
193 fgUntie("/radios/dme/distance-nm");
194 fgUntie("/radios/dme/speed-kt");
195 fgUntie("/radios/dme/ete-min");
197 fgUntie("/radios/marker-beacon/inner");
198 fgUntie("/radios/marker-beacon/middle");
199 fgUntie("/radios/marker-beacon/outer");
208 // model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
209 double FGRadioStack::adjustNavRange( double stationElev, double aircraftElev,
210 double nominalRange )
212 // extend out actual usable range to be 1.3x the published safe range
213 const double usability_factor = 1.3;
215 // assumptions we model the standard service volume, plus
216 // ... rather than specifying a cylinder, we model a cone that
217 // contains the cylinder. Then we put an upside down cone on top
218 // to model diminishing returns at too-high altitudes.
220 // altitude difference
221 double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
222 // cout << "aircraft elev = " << aircraftElev * SG_METER_TO_FEET
223 // << " station elev = " << stationElev << endl;
225 if ( nominalRange < 25.0 + SG_EPSILON ) {
226 // Standard Terminal Service Volume
227 return term_tbl->interpolate( alt ) * usability_factor;
228 } else if ( nominalRange < 50.0 + SG_EPSILON ) {
229 // Standard Low Altitude Service Volume
230 // table is based on range of 40, scale to actual range
231 return low_tbl->interpolate( alt ) * nominalRange / 40.0
234 // Standard High Altitude Service Volume
235 // table is based on range of 130, scale to actual range
236 return high_tbl->interpolate( alt ) * nominalRange / 130.0
242 // model standard ILS service volumes as per AIM 1-1-9
243 double FGRadioStack::adjustILSRange( double stationElev, double aircraftElev,
244 double offsetDegrees, double distance )
246 // assumptions we model the standard service volume, plus
248 // altitude difference
249 // double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
250 double offset = fabs( offsetDegrees );
253 return FG_ILS_DEFAULT_RANGE;
254 } else if ( offset < 35 ) {
255 return 10 + (35 - offset) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
256 } else if ( offset < 45 ) {
257 return (45 - offset);
258 } else if ( offset > 170 ) {
259 return FG_ILS_DEFAULT_RANGE;
260 } else if ( offset > 145 ) {
261 return 10 + (offset - 145) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
262 } else if ( offset > 135 ) {
263 return (offset - 135);
270 // Update the various nav values based on position and valid tuned in navs
272 FGRadioStack::update(double dt)
274 double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
275 double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
276 double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
280 Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
283 navcom1.update( dt );
284 navcom2.update( dt );
286 ////////////////////////////////////////////////////////////////////////
288 ////////////////////////////////////////////////////////////////////////
291 station = Point3D( dme_x, dme_y, dme_z );
292 dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
293 dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
294 if (dme_dist < dme_effective_range * SG_NM_TO_METER) {
296 } else if (dme_dist < 2 * dme_effective_range * SG_NM_TO_METER) {
297 dme_inrange = sg_random() <
298 (2 * dme_effective_range * SG_NM_TO_METER - dme_dist) /
299 (dme_effective_range * SG_NM_TO_METER);
304 SGTimeStamp current_time;
305 station = Point3D( dme_x, dme_y, dme_z );
306 dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
307 current_time.stamp();
308 long dMs = (current_time - dme_last_time) / 1000;
309 // Update every second
311 double dDist = dme_dist - dme_prev_dist;
312 dme_spd = fabs((dDist/dMs) * 3600000);
313 // FIXME: the panel should be able to
317 dme_ete = fabs((dme_dist/dme_spd) * 60.0);
318 // FIXME: the panel should be able to
322 dme_prev_dist = dme_dist;
323 dme_last_time.stamp();
332 // marker beacon blinking
333 bool light_on = ( outer_blink || middle_blink || inner_blink );
337 if ( light_on && (current - blink > 400000) ) {
340 } else if ( !light_on && (current - blink > 100000) ) {
345 if ( outer_marker ) {
346 outer_blink = light_on;
351 if ( middle_marker ) {
352 middle_blink = light_on;
354 middle_blink = false;
357 if ( inner_marker ) {
358 inner_blink = light_on;
363 // cout << outer_blink << " " << middle_blink << " " << inner_blink << endl;
366 xponder.update( dt );
370 // Update current nav/adf radio stations based on current postition
371 void FGRadioStack::search()
373 static FGMkrBeacon::fgMkrBeacType last_beacon = FGMkrBeacon::NOBEACON;
375 double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
376 double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
377 double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
379 // FIXME: the panel should handle this
380 // don't worry about overhead for now,
381 // since this is handled only periodically
382 int dme_switch_pos = fgGetInt("/radios/dme/switch-position");
383 if (dme_switch_pos == 0) {
386 } else if (dme_switch_pos == 1) {
387 if (dme_freq != navcom1.get_nav_freq()) {
388 dme_freq = navcom1.get_nav_freq();
391 } else if (dme_switch_pos == 3) {
392 if (dme_freq != navcom2.get_nav_freq()) {
393 dme_freq = navcom2.get_nav_freq();
404 ////////////////////////////////////////////////////////////////////////
406 ////////////////////////////////////////////////////////////////////////
408 if ( current_ilslist->query( lon, lat, elev, dme_freq, &ils ) ) {
409 if (ils.get_has_dme()) {
411 dme_lon = ils.get_loclon();
412 dme_lat = ils.get_loclat();
413 dme_elev = ils.get_gselev();
414 dme_range = FG_ILS_DEFAULT_RANGE;
415 dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
416 dme_x = ils.get_dme_x();
417 dme_y = ils.get_dme_y();
418 dme_z = ils.get_dme_z();
420 } else if ( current_navlist->query( lon, lat, elev, dme_freq, &nav ) ) {
421 if (nav.get_has_dme()) {
423 dme_lon = nav.get_lon();
424 dme_lat = nav.get_lat();
425 dme_elev = nav.get_elev();
426 dme_range = nav.get_range();
427 dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
438 ////////////////////////////////////////////////////////////////////////
440 ////////////////////////////////////////////////////////////////////////
442 FGMkrBeacon::fgMkrBeacType beacon_type
443 = current_beacons->query( lon * SGD_RADIANS_TO_DEGREES,
444 lat * SGD_RADIANS_TO_DEGREES, elev );
446 outer_marker = middle_marker = inner_marker = false;
448 if ( beacon_type == FGMkrBeacon::OUTER ) {
450 // cout << "OUTER MARKER" << endl;
451 #ifdef ENABLE_AUDIO_SUPPORT
452 if ( last_beacon != FGMkrBeacon::OUTER ) {
453 if ( ! globals->get_soundmgr()->exists( "outer-marker" ) ) {
454 FGSimpleSound *sound = beacon.get_outer();
455 sound->set_volume( 0.3 );
456 globals->get_soundmgr()->add( sound, "outer-marker" );
458 if ( !globals->get_soundmgr()->is_playing("outer-marker") ) {
459 globals->get_soundmgr()->play_looped( "outer-marker" );
463 } else if ( beacon_type == FGMkrBeacon::MIDDLE ) {
464 middle_marker = true;
465 // cout << "MIDDLE MARKER" << endl;
466 #ifdef ENABLE_AUDIO_SUPPORT
467 if ( last_beacon != FGMkrBeacon::MIDDLE ) {
468 if ( ! globals->get_soundmgr()->exists( "middle-marker" ) ) {
469 FGSimpleSound *sound = beacon.get_middle();
470 sound->set_volume( 0.3 );
471 globals->get_soundmgr()->add( sound, "middle-marker" );
473 if ( !globals->get_soundmgr()->is_playing("middle-marker") ) {
474 globals->get_soundmgr()->play_looped( "middle-marker" );
478 } else if ( beacon_type == FGMkrBeacon::INNER ) {
480 // cout << "INNER MARKER" << endl;
481 #ifdef ENABLE_AUDIO_SUPPORT
482 if ( last_beacon != FGMkrBeacon::INNER ) {
483 if ( ! globals->get_soundmgr()->exists( "inner-marker" ) ) {
484 FGSimpleSound *sound = beacon.get_inner();
485 sound->set_volume( 0.3 );
486 globals->get_soundmgr()->add( sound, "inner-marker" );
488 if ( !globals->get_soundmgr()->is_playing("inner-marker") ) {
489 globals->get_soundmgr()->play_looped( "inner-marker" );
494 // cout << "no marker" << endl;
495 #ifdef ENABLE_AUDIO_SUPPORT
496 globals->get_soundmgr()->stop( "outer-marker" );
497 globals->get_soundmgr()->stop( "middle-marker" );
498 globals->get_soundmgr()->stop( "inner-marker" );
501 last_beacon = beacon_type;