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)),
78 dme_bus_power(fgGetNode("/systems/electrical/outputs/dme", true)),
89 SGPath path( globals->get_fg_root() );
91 term.append( "Navaids/range.term" );
93 low.append( "Navaids/range.low" );
95 high.append( "Navaids/range.high" );
97 term_tbl = new SGInterpTable( term.str() );
98 low_tbl = new SGInterpTable( low.str() );
99 high_tbl = new SGInterpTable( high.str() );
100 dme_last_time.stamp();
105 FGRadioStack::~FGRadioStack()
111 unbind(); // FIXME: should be called externally
120 FGRadioStack::init ()
122 navcom1.set_bind_index( 0 );
125 navcom2.set_bind_index( 1 );
141 update(0); // FIXME: use dt
147 // Search radio database once per second
148 global_events.Register( "fgRadioSearch()",
149 current_radiostack, &FGRadioStack::search,
154 FGRadioStack::bind ()
158 fgTie("/radios/dme/frequencies/selected-khz", this,
159 &FGRadioStack::get_dme_freq, &FGRadioStack::set_dme_freq);
162 fgTie("/radios/dme/in-range", this, &FGRadioStack::get_dme_inrange);
164 fgTie("/radios/dme/distance-nm", this, &FGRadioStack::get_dme_dist);
166 fgTie("/radios/dme/speed-kt", this, &FGRadioStack::get_dme_spd);
168 fgTie("/radios/dme/ete-min", this, &FGRadioStack::get_dme_ete);
170 fgTie("/radios/marker-beacon/inner", this,
171 &FGRadioStack::get_inner_blink);
173 fgTie("/radios/marker-beacon/middle", this,
174 &FGRadioStack::get_middle_blink);
176 fgTie("/radios/marker-beacon/outer", this,
177 &FGRadioStack::get_outer_blink);
179 navcom1.set_bind_index( 0 );
181 navcom2.set_bind_index( 1 );
188 FGRadioStack::unbind ()
190 fgUntie("/radios/dme/frequencies/selected-khz");
193 fgUntie("/radios/dme/in-range");
194 fgUntie("/radios/dme/distance-nm");
195 fgUntie("/radios/dme/speed-kt");
196 fgUntie("/radios/dme/ete-min");
198 fgUntie("/radios/marker-beacon/inner");
199 fgUntie("/radios/marker-beacon/middle");
200 fgUntie("/radios/marker-beacon/outer");
209 // Update the various nav values based on position and valid tuned in navs
211 FGRadioStack::update(double dt)
213 double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
214 double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
215 double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
219 Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
222 navcom1.update( dt );
223 navcom2.update( dt );
225 ////////////////////////////////////////////////////////////////////////
227 ////////////////////////////////////////////////////////////////////////
229 if ( dme_valid && dme_has_power() ) {
230 station = Point3D( dme_x, dme_y, dme_z );
231 dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
232 dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
233 if (dme_dist < dme_effective_range * SG_NM_TO_METER) {
235 } else if (dme_dist < 2 * dme_effective_range * SG_NM_TO_METER) {
236 dme_inrange = sg_random() <
237 (2 * dme_effective_range * SG_NM_TO_METER - dme_dist) /
238 (dme_effective_range * SG_NM_TO_METER);
243 SGTimeStamp current_time;
244 station = Point3D( dme_x, dme_y, dme_z );
245 dme_dist = aircraft.distance3D( station ) * SG_METER_TO_NM;
246 current_time.stamp();
247 long dMs = (current_time - dme_last_time) / 1000;
248 // Update every second
250 double dDist = dme_dist - dme_prev_dist;
251 dme_spd = fabs((dDist/dMs) * 3600000);
252 // FIXME: the panel should be able to
256 dme_ete = fabs((dme_dist/dme_spd) * 60.0);
257 // FIXME: the panel should be able to
261 dme_prev_dist = dme_dist;
262 dme_last_time.stamp();
273 // marker beacon blinking
274 bool light_on = ( outer_blink || middle_blink || inner_blink );
278 if ( light_on && (current - blink > 400000) ) {
281 } else if ( !light_on && (current - blink > 100000) ) {
286 if ( outer_marker ) {
287 outer_blink = light_on;
292 if ( middle_marker ) {
293 middle_blink = light_on;
295 middle_blink = false;
298 if ( inner_marker ) {
299 inner_blink = light_on;
304 // cout << outer_blink << " " << middle_blink << " " << inner_blink << endl;
307 xponder.update( dt );
311 // Update current nav/adf radio stations based on current postition
312 void FGRadioStack::search()
314 static FGMkrBeacon::fgMkrBeacType last_beacon = FGMkrBeacon::NOBEACON;
316 double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
317 double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
318 double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
320 // FIXME: the panel should handle this
321 // don't worry about overhead for now,
322 // since this is handled only periodically
323 dme_switch_pos = fgGetInt("/radios/dme/switch-position", 2);
324 if ( dme_switch_pos == 1 && dme_has_power() && navcom1.has_power() ) {
325 if ( dme_freq != navcom1.get_nav_freq() ) {
326 dme_freq = navcom1.get_nav_freq();
329 } else if ( dme_switch_pos == 3 && dme_has_power() && navcom2.has_power() ){
330 if ( dme_freq != navcom2.get_nav_freq() ) {
331 dme_freq = navcom2.get_nav_freq();
345 ////////////////////////////////////////////////////////////////////////
347 ////////////////////////////////////////////////////////////////////////
349 if ( current_ilslist->query( lon, lat, elev, dme_freq, &ils ) ) {
350 if (ils.get_has_dme()) {
352 dme_lon = ils.get_loclon();
353 dme_lat = ils.get_loclat();
354 dme_elev = ils.get_gselev();
355 dme_range = FG_ILS_DEFAULT_RANGE;
356 dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
357 dme_x = ils.get_dme_x();
358 dme_y = ils.get_dme_y();
359 dme_z = ils.get_dme_z();
361 } else if ( current_navlist->query( lon, lat, elev, dme_freq, &nav ) ) {
362 if (nav.get_has_dme()) {
364 dme_lon = nav.get_lon();
365 dme_lat = nav.get_lat();
366 dme_elev = nav.get_elev();
367 dme_range = nav.get_range();
368 dme_effective_range = kludgeRange(dme_elev, elev, dme_range);
379 ////////////////////////////////////////////////////////////////////////
381 ////////////////////////////////////////////////////////////////////////
383 FGMkrBeacon::fgMkrBeacType beacon_type
384 = current_beacons->query( lon * SGD_RADIANS_TO_DEGREES,
385 lat * SGD_RADIANS_TO_DEGREES, elev );
387 outer_marker = middle_marker = inner_marker = false;
389 if ( beacon_type == FGMkrBeacon::OUTER ) {
391 // cout << "OUTER MARKER" << endl;
392 #ifdef ENABLE_AUDIO_SUPPORT
393 if ( last_beacon != FGMkrBeacon::OUTER ) {
394 if ( ! globals->get_soundmgr()->exists( "outer-marker" ) ) {
395 FGSimpleSound *sound = beacon.get_outer();
396 sound->set_volume( 0.3 );
397 globals->get_soundmgr()->add( sound, "outer-marker" );
399 if ( !globals->get_soundmgr()->is_playing("outer-marker") ) {
400 globals->get_soundmgr()->play_looped( "outer-marker" );
404 } else if ( beacon_type == FGMkrBeacon::MIDDLE ) {
405 middle_marker = true;
406 // cout << "MIDDLE MARKER" << endl;
407 #ifdef ENABLE_AUDIO_SUPPORT
408 if ( last_beacon != FGMkrBeacon::MIDDLE ) {
409 if ( ! globals->get_soundmgr()->exists( "middle-marker" ) ) {
410 FGSimpleSound *sound = beacon.get_middle();
411 sound->set_volume( 0.3 );
412 globals->get_soundmgr()->add( sound, "middle-marker" );
414 if ( !globals->get_soundmgr()->is_playing("middle-marker") ) {
415 globals->get_soundmgr()->play_looped( "middle-marker" );
419 } else if ( beacon_type == FGMkrBeacon::INNER ) {
421 // cout << "INNER MARKER" << endl;
422 #ifdef ENABLE_AUDIO_SUPPORT
423 if ( last_beacon != FGMkrBeacon::INNER ) {
424 if ( ! globals->get_soundmgr()->exists( "inner-marker" ) ) {
425 FGSimpleSound *sound = beacon.get_inner();
426 sound->set_volume( 0.3 );
427 globals->get_soundmgr()->add( sound, "inner-marker" );
429 if ( !globals->get_soundmgr()->is_playing("inner-marker") ) {
430 globals->get_soundmgr()->play_looped( "inner-marker" );
435 // cout << "no marker" << endl;
436 #ifdef ENABLE_AUDIO_SUPPORT
437 globals->get_soundmgr()->stop( "outer-marker" );
438 globals->get_soundmgr()->stop( "middle-marker" );
439 globals->get_soundmgr()->stop( "inner-marker" );
442 last_beacon = beacon_type;