]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/radiostack.cxx
More property node optimizations.
[flightgear.git] / src / Cockpit / radiostack.cxx
1 // radiostack.cxx -- class to manage an instance of the radio stack
2 //
3 // Written by Curtis Olson, started April 2000.
4 //
5 // Copyright (C) 2000  Curtis L. Olson - curt@flightgear.org
6 //
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.
11 //
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.
16 //
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.
20 //
21 // $Id$
22
23
24 #ifdef HAVE_CONFIG_H
25 #  include <config.h>
26 #endif
27
28 #include <simgear/compiler.h>
29 #include <simgear/math/sg_random.h>
30
31 #include <Aircraft/aircraft.hxx>
32 #include <Navaids/ilslist.hxx>
33 #include <Navaids/mkrbeacons.hxx>
34 #include <Navaids/navlist.hxx>
35 #include <Time/event.hxx>
36
37 #include "radiostack.hxx"
38
39 static int nav1_play_count = 0;
40 static int nav2_play_count = 0;
41 static int adf_play_count = 0;
42 static time_t nav1_last_time = 0;
43 static time_t nav2_last_time = 0;
44 static time_t adf_last_time = 0;
45
46
47 FGRadioStack *current_radiostack;
48
49
50 /**
51  * Boy, this is ugly!  Make the VOR range vary by altitude difference.
52  */
53 static double kludgeRange ( double stationElev, double aircraftElev,
54                             double nominalRange)
55 {
56                                 // Assume that the nominal range (usually
57                                 // 50nm) applies at a 5,000 ft difference.
58                                 // Just a wild guess!
59   double factor = ((aircraftElev*SG_METER_TO_FEET) - stationElev) / 5000.0;
60   double range = fabs(nominalRange * factor);
61
62                                 // Clamp the range to keep it sane; for
63                                 // now, never less than 25% or more than
64                                 // 500% of nominal range.
65   if (range < nominalRange/4.0) {
66     range = nominalRange/4.0;
67   } else if (range > nominalRange*5.0) {
68     range = nominalRange*5.0;
69   }
70
71   return range;
72 }
73
74
75 // periodic radio station search wrapper
76 static void fgRadioSearch( void ) {
77     current_radiostack->search();
78 }
79
80 // Constructor
81 FGRadioStack::FGRadioStack() {
82     nav1_radial = 0.0;
83     nav1_dme_dist = 0.0;
84     nav2_radial = 0.0;
85     nav2_dme_dist = 0.0;
86     need_update = true;
87     lon_node = fgGetNode("/position/longitude");
88     lat_node = fgGetNode("/position/latitude");
89     alt_node = fgGetNode("/position/altitude");
90 }
91
92
93 // Destructor
94 FGRadioStack::~FGRadioStack() 
95 {
96     unbind();                   // FIXME: should be called externally
97 }
98
99
100 void
101 FGRadioStack::init ()
102 {
103     morse.init();
104     beacon.init();
105     blink.stamp();
106
107     search();
108     update();
109
110     SGPath path( globals->get_fg_root() );
111     SGPath term = path;
112     term.append( "Navaids/range.term" );
113     SGPath low = path;
114     low.append( "Navaids/range.low" );
115     SGPath high = path;
116     high.append( "Navaids/range.high" );
117
118     term_tbl = new SGInterpTable( term.str() );
119     low_tbl = new SGInterpTable( low.str() );
120     high_tbl = new SGInterpTable( high.str() );
121
122     // Search radio database once per second
123     global_events.Register( "fgRadioSearch()", fgRadioSearch,
124                             fgEVENT::FG_EVENT_READY, 1000);
125 }
126
127 void
128 FGRadioStack::bind ()
129 {
130                                 // User inputs
131     fgTie("/radios/nav1/frequencies/selected", this,
132           &FGRadioStack::get_nav1_freq, &FGRadioStack::set_nav1_freq);
133     fgTie("/radios/nav1/frequencies/standby", this,
134           &FGRadioStack::get_nav1_alt_freq, &FGRadioStack::set_nav1_alt_freq);
135     fgTie("/radios/nav1/radials/selected", this,
136           &FGRadioStack::get_nav1_sel_radial,
137           &FGRadioStack::set_nav1_sel_radial);
138     fgTie("/radios/nav1/volume", this,
139           &FGRadioStack::get_nav1_vol_btn,
140           &FGRadioStack::set_nav1_vol_btn);
141     fgTie("/radios/nav1/ident", this,
142           &FGRadioStack::get_nav1_ident_btn,
143           &FGRadioStack::set_nav1_ident_btn);
144
145                                 // Radio outputs
146     fgTie("/radios/nav1/radials/actual", this, &FGRadioStack::get_nav1_radial);
147     fgTie("/radios/nav1/to-flag", this, &FGRadioStack::get_nav1_to_flag);
148     fgTie("/radios/nav1/from-flag", this, &FGRadioStack::get_nav1_from_flag);
149     fgTie("/radios/nav1/in-range", this, &FGRadioStack::get_nav1_inrange);
150     fgTie("/radios/nav1/dme/distance", this, &FGRadioStack::get_nav1_dme_dist);
151     fgTie("/radios/nav1/dme/in-range", this,
152           &FGRadioStack::get_nav1_dme_inrange);
153     fgTie("/radios/nav1/heading-needle-deflection", this,
154           &FGRadioStack::get_nav1_heading_needle_deflection);
155     fgTie("/radios/nav1/gs-needle-deflection", this,
156           &FGRadioStack::get_nav1_gs_needle_deflection);
157
158                                 // User inputs
159     fgTie("/radios/nav2/frequencies/selected", this,
160           &FGRadioStack::get_nav2_freq, &FGRadioStack::set_nav2_freq);
161     fgTie("/radios/nav2/frequencies/standby", this,
162           &FGRadioStack::get_nav2_alt_freq, &FGRadioStack::set_nav2_alt_freq);
163     fgTie("/radios/nav2/radials/selected", this,
164           &FGRadioStack::get_nav2_sel_radial,
165           &FGRadioStack::set_nav2_sel_radial);
166     fgTie("/radios/nav2/volume", this,
167           &FGRadioStack::get_nav2_vol_btn,
168           &FGRadioStack::set_nav2_vol_btn);
169     fgTie("/radios/nav2/ident", this,
170           &FGRadioStack::get_nav2_ident_btn,
171           &FGRadioStack::set_nav2_ident_btn);
172
173                                 // Radio outputs
174     fgTie("/radios/nav2/radials/actual", this, &FGRadioStack::get_nav2_radial);
175     fgTie("/radios/nav2/to-flag", this, &FGRadioStack::get_nav2_to_flag);
176     fgTie("/radios/nav2/from-flag", this, &FGRadioStack::get_nav2_from_flag);
177     fgTie("/radios/nav2/in-range", this, &FGRadioStack::get_nav2_inrange);
178     fgTie("/radios/nav2/dme/distance", this, &FGRadioStack::get_nav2_dme_dist);
179     fgTie("/radios/nav2/dme/in-range", this,
180           &FGRadioStack::get_nav2_dme_inrange);
181     fgTie("/radios/nav2/heading-needle-deflection", this,
182           &FGRadioStack::get_nav2_heading_needle_deflection);
183     fgTie("/radios/nav2/gs-needle-deflection", this,
184           &FGRadioStack::get_nav2_gs_needle_deflection);
185
186                                 // User inputs
187     fgTie("/radios/adf/frequencies/selected", this,
188           &FGRadioStack::get_adf_freq, &FGRadioStack::set_adf_freq);
189     fgTie("/radios/adf/frequencies/standby", this,
190           &FGRadioStack::get_adf_alt_freq, &FGRadioStack::set_adf_alt_freq);
191     fgTie("/radios/adf/rotation", this,
192           &FGRadioStack::get_adf_rotation, &FGRadioStack::set_adf_rotation);
193     fgTie("/radios/adf/volume", this,
194           &FGRadioStack::get_adf_vol_btn,
195           &FGRadioStack::set_adf_vol_btn);
196     fgTie("/radios/adf/ident", this,
197           &FGRadioStack::get_adf_ident_btn,
198           &FGRadioStack::set_adf_ident_btn);
199
200     fgTie("/radios/marker-beacon/inner", this,
201           &FGRadioStack::get_inner_blink);
202     fgTie("/radios/marker-beacon/middle", this,
203           &FGRadioStack::get_middle_blink);
204     fgTie("/radios/marker-beacon/outer", this,
205           &FGRadioStack::get_outer_blink);
206 }
207
208 void
209 FGRadioStack::unbind ()
210 {
211     fgUntie("/radios/nav1/frequencies/selected");
212     fgUntie("/radios/nav1/frequencies/standby");
213     fgUntie("/radios/nav1/radials/actual");
214     fgUntie("/radios/nav1/radials/selected");
215     fgUntie("/radios/nav1/on");
216     fgUntie("/radios/nav1/ident");
217     fgUntie("/radios/nav1/to-flag");
218     fgUntie("/radios/nav1/from-flag");
219     fgUntie("/radios/nav1/in-range");
220     fgUntie("/radios/nav1/dme/distance");
221     fgUntie("/radios/nav1/dme/in-range");
222     fgUntie("/radios/nav1/heading-needle-deflection");
223     fgUntie("/radios/nav1/gs-needle-deflection");
224
225     fgUntie("/radios/nav2/frequencies/selected");
226     fgUntie("/radios/nav2/frequencies/standby");
227     fgUntie("/radios/nav2/radials/actual");
228     fgUntie("/radios/nav2/radials/selected");
229     fgUntie("/radios/nav2/on");
230     fgUntie("/radios/nav2/ident");
231     fgUntie("/radios/nav2/to-flag");
232     fgUntie("/radios/nav2/from-flag");
233     fgUntie("/radios/nav2/in-range");
234     fgUntie("/radios/nav2/dme/distance");
235     fgUntie("/radios/nav2/dme/in-range");
236     fgUntie("/radios/nav2/heading-needle-deflection");
237     fgUntie("/radios/nav2/gs-needle-deflection");
238
239     fgUntie("/radios/adf/frequencies/selected");
240     fgUntie("/radios/adf/frequencies/standby");
241     fgUntie("/radios/adf/rotation");
242     fgUntie("/radios/adf/on");
243     fgUntie("/radios/adf/ident");
244
245     fgUntie("/radios/marker-beacon/inner");
246     fgUntie("/radios/marker-beacon/middle");
247     fgUntie("/radios/marker-beacon/outer");
248 }
249
250
251 // model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
252 double FGRadioStack::adjustNavRange( double stationElev, double aircraftElev,
253                               double nominalRange )
254 {
255     // extend out actual usable range to be 1.3x the published safe range
256     const double usability_factor = 1.3;
257
258     // assumptions we model the standard service volume, plus
259     // ... rather than specifying a cylinder, we model a cone that
260     // contains the cylinder.  Then we put an upside down cone on top
261     // to model diminishing returns at too-high altitudes.
262
263     // altitude difference
264     double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
265     // cout << "aircraft elev = " << aircraftElev * SG_METER_TO_FEET
266     //      << " station elev = " << stationElev << endl;
267
268     if ( nominalRange < 25.0 + SG_EPSILON ) {
269         // Standard Terminal Service Volume
270         return term_tbl->interpolate( alt ) * usability_factor;
271     } else if ( nominalRange < 50.0 + SG_EPSILON ) {
272         // Standard Low Altitude Service Volume
273         // table is based on range of 40, scale to actual range
274         return low_tbl->interpolate( alt ) * nominalRange / 40.0
275             * usability_factor;
276     } else {
277         // Standard High Altitude Service Volume
278         // table is based on range of 130, scale to actual range
279         return high_tbl->interpolate( alt ) * nominalRange / 130.0
280             * usability_factor;
281     }
282 }
283
284
285 // model standard ILS service volumes as per AIM 1-1-9
286 double FGRadioStack::adjustILSRange( double stationElev, double aircraftElev,
287                                      double offsetDegrees, double distance )
288 {
289     // assumptions we model the standard service volume, plus
290
291     // altitude difference
292     // double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
293     double offset = fabs( offsetDegrees );
294
295     if ( offset < 10 ) {
296         return FG_ILS_DEFAULT_RANGE;
297     } else if ( offset < 35 ) {
298         return 10 + (35 - offset) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
299     } else if ( offset < 45 ) {
300         return (45 - offset);
301     } else {
302         return 0;
303     }
304 }
305
306
307 // Update the various nav values based on position and valid tuned in navs
308 void 
309 FGRadioStack::update() 
310 {
311     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
312     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
313     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
314
315     need_update = false;
316
317     Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
318     Point3D station;
319     double az1, az2, s;
320
321     if ( nav1_valid ) {
322         station = Point3D( nav1_x, nav1_y, nav1_z );
323         nav1_loc_dist = aircraft.distance3D( station );
324
325         if ( nav1_has_dme ) {
326             // staightline distance
327             station = Point3D( nav1_dme_x, nav1_dme_y, nav1_dme_z );
328             nav1_dme_dist = aircraft.distance3D( station );
329         } else {
330             nav1_dme_dist = 0.0;
331         }
332
333         if ( nav1_has_gs ) {
334             station = Point3D( nav1_gs_x, nav1_gs_y, nav1_gs_z );
335             nav1_gs_dist = aircraft.distance3D( station );
336         } else {
337             nav1_gs_dist = 0.0;
338         }
339         
340         // wgs84 heading
341         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
342                             nav1_loclat, nav1_loclon,
343                             &az1, &az2, &s );
344         // cout << "az1 = " << az1 << " magvar = " << nav1_magvar << endl;
345         nav1_heading = az1 - nav1_magvar;
346         // cout << " heading = " << nav1_heading
347         //      << " dist = " << nav1_dist << endl;
348
349         if ( nav1_loc ) {
350             double offset = nav1_heading - nav1_radial;
351             while ( offset < -180.0 ) { offset += 360.0; }
352             while ( offset > 180.0 ) { offset -= 360.0; }
353             // cout << "ils offset = " << offset << endl;
354             nav1_effective_range = adjustILSRange(nav1_elev, elev, offset,
355                                                   nav1_loc_dist * SG_METER_TO_NM );
356         } else {
357             nav1_effective_range = adjustNavRange(nav1_elev, elev, nav1_range);
358         }
359         // cout << "nav1 range = " << nav1_effective_range
360         //      << " (" << nav1_range << ")" << endl;
361
362         if ( nav1_loc_dist < nav1_effective_range * SG_NM_TO_METER ) {
363             nav1_inrange = true;
364         } else if ( nav1_loc_dist < 2 * nav1_effective_range * SG_NM_TO_METER ) {
365             nav1_inrange = sg_random() < 
366                 ( 2 * nav1_effective_range * SG_NM_TO_METER - nav1_loc_dist ) /
367                 (nav1_effective_range * SG_NM_TO_METER);
368         } else {
369             nav1_inrange = false;
370         }
371
372         if ( !nav1_loc ) {
373             nav1_radial = nav1_sel_radial;
374         }
375     } else {
376         nav1_inrange = false;
377         nav1_dme_dist = 0.0;
378         // cout << "not picking up vor. :-(" << endl;
379     }
380
381 #ifdef ENABLE_AUDIO_SUPPORT
382     if ( nav1_valid && nav1_inrange ) {
383         // play station ident via audio system if on + ident,
384         // otherwise turn it off
385         if ( nav1_vol_btn > 0.1 && nav1_ident_btn ) {
386             FGSimpleSound *sound;
387             sound = globals->get_soundmgr()->find( "nav1-vor-ident" );
388             sound->set_volume( nav1_vol_btn * 0.3 );
389             sound = globals->get_soundmgr()->find( "nav1-dme-ident" );
390             sound->set_volume( nav1_vol_btn * 0.3 );
391             if ( nav1_last_time <
392                  globals->get_time_params()->get_cur_time() - 30 ) {
393                 nav1_last_time = globals->get_time_params()->get_cur_time();
394                 nav1_play_count = 0;
395             }
396             if ( nav1_play_count < 4 ) {
397                 // play VOR ident
398                 if ( !globals->get_soundmgr()->is_playing("nav1-vor-ident") ) {
399                     globals->get_soundmgr()->play_once( "nav1-vor-ident" );
400                     ++nav1_play_count;
401                 }
402             } else if ( nav1_play_count < 5 && nav1_has_dme ) {
403                 // play DME ident
404                 if ( !globals->get_soundmgr()->is_playing("nav1-vor-ident") &&
405                      !globals->get_soundmgr()->is_playing("nav1-dme-ident") ) {
406                     globals->get_soundmgr()->play_once( "nav1-dme-ident" );
407                     ++nav1_play_count;
408                 }
409             }
410         } else {
411             globals->get_soundmgr()->stop( "nav1-vor-ident" );
412             globals->get_soundmgr()->stop( "nav1-dme-ident" );
413         }
414     }
415 #endif
416
417     if ( nav2_valid ) {
418         station = Point3D( nav2_x, nav2_y, nav2_z );
419         nav2_loc_dist = aircraft.distance3D( station );
420
421         if ( nav2_has_dme ) {
422             // staightline distance
423             station = Point3D( nav2_dme_x, nav2_dme_y, nav2_dme_z );
424             nav2_dme_dist = aircraft.distance3D( station );
425         } else {
426             nav2_dme_dist = 0.0;
427         }
428
429         if ( nav2_has_gs ) {
430             station = Point3D( nav2_gs_x, nav2_gs_y, nav2_gs_z );
431             nav2_gs_dist = aircraft.distance3D( station );
432         } else {
433             nav2_gs_dist = 0.0;
434         }
435
436         // wgs84 heading
437         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
438                             nav2_loclat, nav2_loclon,
439                             &az1, &az2, &s );
440         nav2_heading = az1 - nav2_magvar;
441         // cout << " heading = " << nav2_heading
442         //      << " dist = " << nav2_dist << endl;
443
444         if ( nav2_loc ) {
445             double offset = nav2_heading - nav2_radial;
446             while ( offset < -180.0 ) { offset += 360.0; }
447             while ( offset > 180.0 ) { offset -= 360.0; }
448             // cout << "ils offset = " << offset << endl;
449             nav2_effective_range = adjustILSRange(nav2_elev, elev, offset,
450                                                   nav2_loc_dist * SG_METER_TO_NM );
451         } else {
452             nav2_effective_range = adjustNavRange(nav2_elev, elev, nav2_range);
453         }
454         // cout << "nav2 range = " << nav2_effective_range
455         //      << " (" << nav2_range << ")" << endl;
456
457         if ( nav2_loc_dist < nav2_effective_range * SG_NM_TO_METER ) {
458             nav2_inrange = true;
459         } else if ( nav2_loc_dist < 2 * nav2_effective_range * SG_NM_TO_METER ) {
460             nav2_inrange = sg_random() < 
461                 ( 2 * nav2_effective_range * SG_NM_TO_METER - nav2_loc_dist ) /
462                 (nav2_effective_range * SG_NM_TO_METER);
463         } else {
464             nav2_inrange = false;
465         }
466
467         if ( !nav2_loc ) {
468             nav2_radial = nav2_sel_radial;
469         }
470     } else {
471         nav2_inrange = false;
472         nav2_dme_dist = 0.0;
473         // cout << "not picking up vor. :-(" << endl;
474     }
475
476 #ifdef ENABLE_AUDIO_SUPPORT
477     if ( nav2_valid && nav2_inrange ) {
478         // play station ident via audio system if on + ident,
479         // otherwise turn it off
480         if ( nav2_vol_btn > 0.1 && nav2_ident_btn ) {
481             FGSimpleSound *sound;
482             sound = globals->get_soundmgr()->find( "nav2-vor-ident" );
483             sound->set_volume( nav2_vol_btn * 0.3 );
484             sound = globals->get_soundmgr()->find( "nav2-dme-ident" );
485             sound->set_volume( nav2_vol_btn * 0.3 );
486             if ( nav2_last_time <
487                  globals->get_time_params()->get_cur_time() - 30 ) {
488                 nav2_last_time = globals->get_time_params()->get_cur_time();
489                 nav2_play_count = 0;
490             }
491             if ( nav2_play_count < 4 ) {
492                 // play VOR ident
493                 if ( !globals->get_soundmgr()->is_playing("nav2-vor-ident") ) {
494                     globals->get_soundmgr()->play_once( "nav2-vor-ident" );
495                     ++nav2_play_count;
496                 }
497             } else if ( nav2_play_count < 5 && nav2_has_dme ) {
498                 // play DME ident
499                 if ( !globals->get_soundmgr()->is_playing("nav2-vor-ident") &&
500                      !globals->get_soundmgr()->is_playing("nav2-dme-ident") ) {
501                     globals->get_soundmgr()->play_once( "nav2-dme-ident" );
502                     ++nav2_play_count;
503                 }
504             }
505         } else {
506             globals->get_soundmgr()->stop( "nav2-vor-ident" );
507             globals->get_soundmgr()->stop( "nav2-dme-ident" );
508         }
509     }
510 #endif
511
512     // adf
513     if ( adf_valid ) {
514         // staightline distance
515         station = Point3D( adf_x, adf_y, adf_z );
516         adf_dist = aircraft.distance3D( station );
517
518         // wgs84 heading
519         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
520                             adf_lat, adf_lon,
521                             &az1, &az2, &s );
522         adf_heading = az1;
523         // cout << " heading = " << nav2_heading
524         //      << " dist = " << nav2_dist << endl;
525
526         adf_effective_range = kludgeRange(adf_elev, elev, adf_range);
527         if ( adf_dist < adf_effective_range * SG_NM_TO_METER ) {
528             adf_inrange = true;
529         } else if ( adf_dist < 2 * adf_effective_range * SG_NM_TO_METER ) {
530             adf_inrange = sg_random() < 
531                 ( 2 * adf_effective_range * SG_NM_TO_METER - adf_dist ) /
532                 (adf_effective_range * SG_NM_TO_METER);
533         } else {
534             adf_inrange = false;
535         }
536     } else {
537         adf_inrange = false;
538     }
539
540 #ifdef ENABLE_AUDIO_SUPPORT
541     if ( adf_valid && adf_inrange ) {
542         // play station ident via audio system if on + ident,
543         // otherwise turn it off
544         if ( adf_vol_btn > 0.1 && adf_ident_btn ) {
545             FGSimpleSound *sound;
546             sound = globals->get_soundmgr()->find( "adf-ident" );
547             sound->set_volume( adf_vol_btn * 0.3 );
548             if ( adf_last_time <
549                  globals->get_time_params()->get_cur_time() - 30 ) {
550                 adf_last_time = globals->get_time_params()->get_cur_time();
551                 adf_play_count = 0;
552             }
553             if ( adf_play_count < 4 ) {
554                 // play ADF ident
555                 if ( !globals->get_soundmgr()->is_playing("adf-ident") ) {
556                     globals->get_soundmgr()->play_once( "adf-ident" );
557                     ++adf_play_count;
558                 }
559             }
560         } else {
561             globals->get_soundmgr()->stop( "adf-ident" );
562         }
563     }
564 #endif
565
566     // marker beacon blinking
567     bool light_on = ( outer_blink || middle_blink || inner_blink );
568     SGTimeStamp current;
569     current.stamp();
570
571     if ( light_on && (current - blink > 400000) ) {
572         light_on = false;
573         blink.stamp();
574     } else if ( !light_on && (current - blink > 100000) ) {
575         light_on = true;
576         blink.stamp();
577     }
578
579     if ( outer_marker ) {
580         outer_blink = light_on;
581     } else {
582         outer_blink = false;
583     }
584
585     if ( middle_marker ) {
586         middle_blink = light_on;
587     } else {
588         middle_blink = false;
589     }
590
591     if ( inner_marker ) {
592         inner_blink = light_on;
593     } else {
594         inner_blink = false;
595     }
596
597     // cout << outer_blink << " " << middle_blink << " " << inner_blink << endl;
598 }
599
600
601 // Update current nav/adf radio stations based on current postition
602 void FGRadioStack::search() 
603 {
604     static FGMkrBeacon::fgMkrBeacType last_beacon = FGMkrBeacon::NOBEACON;
605
606     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
607     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
608     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
609
610     // nav1
611     FGILS ils;
612     FGNav nav;
613
614     static string last_nav1_ident = "";
615     static string last_nav2_ident = "";
616     static string last_adf_ident = "";
617     static bool last_nav1_vor = false;
618     static bool last_nav2_vor = false;
619     if ( current_ilslist->query( lon, lat, elev, nav1_freq, &ils ) ) {
620         nav1_ident = ils.get_locident();
621         nav1_valid = true;
622         if ( last_nav1_ident != nav1_ident || last_nav1_vor ) {
623             nav1_trans_ident = ils.get_trans_ident();
624             last_nav1_ident = nav1_ident;
625             last_nav1_vor = false;
626             nav1_loc = true;
627             nav1_has_dme = ils.get_has_dme();
628             nav1_has_gs = ils.get_has_gs();
629
630             nav1_loclon = ils.get_loclon();
631             nav1_loclat = ils.get_loclat();
632             nav1_gslon = ils.get_gslon();
633             nav1_gslat = ils.get_gslat();
634             nav1_dmelon = ils.get_dmelon();
635             nav1_dmelat = ils.get_dmelat();
636             nav1_elev = ils.get_gselev();
637             nav1_magvar = 0;
638             nav1_range = FG_ILS_DEFAULT_RANGE;
639             nav1_effective_range = nav1_range;
640             nav1_target_gs = ils.get_gsangle();
641             nav1_radial = ils.get_locheading();
642             while ( nav1_radial <   0.0 ) { nav1_radial += 360.0; }
643             while ( nav1_radial > 360.0 ) { nav1_radial -= 360.0; }
644             nav1_x = ils.get_x();
645             nav1_y = ils.get_y();
646             nav1_z = ils.get_z();
647             nav1_gs_x = ils.get_gs_x();
648             nav1_gs_y = ils.get_gs_y();
649             nav1_gs_z = ils.get_gs_z();
650             nav1_dme_x = ils.get_dme_x();
651             nav1_dme_y = ils.get_dme_y();
652             nav1_dme_z = ils.get_dme_z();
653
654 #ifdef ENABLE_AUDIO_SUPPORT
655             if ( globals->get_soundmgr()->exists( "nav1-vor-ident" ) ) {
656                 globals->get_soundmgr()->remove( "nav1-vor-ident" );
657             }
658             FGSimpleSound *sound;
659             sound = morse.make_ident( nav1_trans_ident, LO_FREQUENCY );
660             sound->set_volume( 0.3 );
661             globals->get_soundmgr()->add( sound, "nav1-vor-ident" );
662
663             if ( globals->get_soundmgr()->exists( "nav1-dme-ident" ) ) {
664                 globals->get_soundmgr()->remove( "nav1-dme-ident" );
665             }
666             sound = morse.make_ident( nav1_trans_ident, HI_FREQUENCY );
667             sound->set_volume( 0.3 );
668             globals->get_soundmgr()->add( sound, "nav1-dme-ident" );
669
670             int offset = (int)(sg_random() * 30.0);
671             nav1_play_count = offset / 4;
672             nav1_last_time = globals->get_time_params()->get_cur_time() -
673                 offset;
674             // cout << "offset = " << offset << " play_count = "
675             //      << nav1_play_count
676             //      << " nav1_last_time = " << nav1_last_time
677             //      << " current time = "
678             //      << globals->get_time_params()->get_cur_time() << endl;
679 #endif
680
681             // cout << "Found an ils station in range" << endl;
682             // cout << " id = " << ils.get_locident() << endl;
683         }
684     } else if ( current_navlist->query( lon, lat, elev, nav1_freq, &nav ) ) {
685         nav1_ident = nav.get_ident();
686         nav1_valid = true;
687         if ( last_nav1_ident != nav1_ident || !last_nav1_vor ) {
688             last_nav1_ident = nav1_ident;
689             last_nav1_vor = true;
690             nav1_trans_ident = nav.get_trans_ident();
691             nav1_loc = false;
692             nav1_has_dme = nav.get_has_dme();
693             nav1_has_gs = false;
694             nav1_loclon = nav.get_lon();
695             nav1_loclat = nav.get_lat();
696             nav1_elev = nav.get_elev();
697             nav1_magvar = nav.get_magvar();
698             nav1_range = nav.get_range();
699             nav1_effective_range = adjustNavRange(nav1_elev, elev, nav1_range);
700             nav1_target_gs = 0.0;
701             nav1_radial = nav1_sel_radial;
702             nav1_x = nav1_dme_x = nav.get_x();
703             nav1_y = nav1_dme_y = nav.get_y();
704             nav1_z = nav1_dme_z = nav.get_z();
705
706 #ifdef ENABLE_AUDIO_SUPPORT
707             if ( globals->get_soundmgr()->exists( "nav1-vor-ident" ) ) {
708                 globals->get_soundmgr()->remove( "nav1-vor-ident" );
709             }
710             FGSimpleSound *sound;
711             sound = morse.make_ident( nav1_trans_ident, LO_FREQUENCY );
712             sound->set_volume( 0.3 );
713             globals->get_soundmgr()->add( sound, "nav1-vor-ident" );
714
715             if ( globals->get_soundmgr()->exists( "nav1-dme-ident" ) ) {
716                 globals->get_soundmgr()->remove( "nav1-dme-ident" );
717             }
718             sound = morse.make_ident( nav1_trans_ident, HI_FREQUENCY );
719             sound->set_volume( 0.3 );
720             globals->get_soundmgr()->add( sound, "nav1-dme-ident" );
721
722             int offset = (int)(sg_random() * 30.0);
723             nav1_play_count = offset / 4;
724             nav1_last_time = globals->get_time_params()->get_cur_time() -
725                 offset;
726             // cout << "offset = " << offset << " play_count = "
727             //      << nav1_play_count << " nav1_last_time = "
728             //      << nav1_last_time << " current time = "
729             //      << globals->get_time_params()->get_cur_time() << endl;
730 #endif
731
732             // cout << "Found a vor station in range" << endl;
733             // cout << " id = " << nav.get_ident() << endl;
734         }
735     } else {
736         nav1_valid = false;
737         nav1_ident = "";
738         nav1_radial = 0;
739         nav1_dme_dist = 0;
740         nav1_trans_ident = "";
741         last_nav1_ident = "";
742 #ifdef ENABLE_AUDIO_SUPPORT
743         globals->get_soundmgr()->remove( "nav1-vor-ident" );
744         globals->get_soundmgr()->remove( "nav1-dme-ident" );
745 #endif
746         // cout << "not picking up vor1. :-(" << endl;
747     }
748
749     if ( current_ilslist->query( lon, lat, elev, nav2_freq, &ils ) ) {
750         nav2_ident = ils.get_locident();
751         nav2_valid = true;
752         if ( last_nav2_ident != nav2_ident || last_nav2_vor ) {
753             last_nav2_ident = nav2_ident;
754             last_nav2_vor = false;
755             nav2_trans_ident = ils.get_trans_ident();
756             nav2_loc = true;
757             nav2_has_dme = ils.get_has_dme();
758             nav2_has_gs = ils.get_has_gs();
759
760             nav2_loclon = ils.get_loclon();
761             nav2_loclat = ils.get_loclat();
762             nav2_elev = ils.get_gselev();
763             nav2_magvar = 0;
764             nav2_range = FG_ILS_DEFAULT_RANGE;
765             nav2_effective_range = nav2_range;
766             nav2_target_gs = ils.get_gsangle();
767             nav2_radial = ils.get_locheading();
768             while ( nav2_radial <   0.0 ) { nav2_radial += 360.0; }
769             while ( nav2_radial > 360.0 ) { nav2_radial -= 360.0; }
770             nav2_x = ils.get_x();
771             nav2_y = ils.get_y();
772             nav2_z = ils.get_z();
773             nav2_gs_x = ils.get_gs_x();
774             nav2_gs_y = ils.get_gs_y();
775             nav2_gs_z = ils.get_gs_z();
776             nav2_dme_x = ils.get_dme_x();
777             nav2_dme_y = ils.get_dme_y();
778             nav2_dme_z = ils.get_dme_z();
779
780 #ifdef ENABLE_AUDIO_SUPPORT
781             if ( globals->get_soundmgr()->exists( "nav2-vor-ident" ) ) {
782                 globals->get_soundmgr()->remove( "nav2-vor-ident" );
783             }
784             FGSimpleSound *sound;
785             sound = morse.make_ident( nav2_trans_ident, LO_FREQUENCY );
786             sound->set_volume( 0.3 );
787             globals->get_soundmgr()->add( sound, "nav2-vor-ident" );
788
789             if ( globals->get_soundmgr()->exists( "nav2-dme-ident" ) ) {
790                 globals->get_soundmgr()->remove( "nav2-dme-ident" );
791             }
792             sound = morse.make_ident( nav2_trans_ident, HI_FREQUENCY );
793             sound->set_volume( 0.3 );
794             globals->get_soundmgr()->add( sound, "nav2-dme-ident" );
795
796             int offset = (int)(sg_random() * 30.0);
797             nav2_play_count = offset / 4;
798             nav2_last_time = globals->get_time_params()->get_cur_time() -
799                 offset;
800             // cout << "offset = " << offset << " play_count = "
801             //      << nav2_play_count << " nav2_last_time = "
802             //      << nav2_last_time << " current time = "
803             //      << globals->get_time_params()->get_cur_time() << endl;
804 #endif
805
806             // cout << "Found an ils station in range" << endl;
807             // cout << " id = " << ils.get_locident() << endl;
808         }
809     } else if ( current_navlist->query( lon, lat, elev, nav2_freq, &nav ) ) {
810         nav2_ident = nav.get_ident();
811         nav2_valid = true;
812         if ( last_nav2_ident != nav2_ident || !last_nav2_vor ) {
813             last_nav2_ident = nav2_ident;
814             last_nav2_vor = true;
815             nav2_trans_ident = nav.get_trans_ident();
816             nav2_loc = false;
817             nav2_has_dme = nav.get_has_dme();
818             nav2_has_dme = false;
819             nav2_loclon = nav.get_lon();
820             nav2_loclat = nav.get_lat();
821             nav2_elev = nav.get_elev();
822             nav2_magvar = nav.get_magvar();
823             nav2_range = nav.get_range();
824             nav2_effective_range = adjustNavRange(nav2_elev, elev, nav2_range);
825             nav2_target_gs = 0.0;
826             nav2_radial = nav2_sel_radial;
827             nav2_x = nav2_dme_x = nav.get_x();
828             nav2_y = nav2_dme_y = nav.get_y();
829             nav2_z = nav2_dme_z = nav.get_z();
830
831 #ifdef ENABLE_AUDIO_SUPPORT
832             if ( globals->get_soundmgr()->exists( "nav2-vor-ident" ) ) {
833                 globals->get_soundmgr()->remove( "nav2-vor-ident" );
834             }
835             FGSimpleSound *sound;
836             sound = morse.make_ident( nav2_trans_ident, LO_FREQUENCY );
837             sound->set_volume( 0.3 );
838             globals->get_soundmgr()->add( sound, "nav2-vor-ident" );
839
840             if ( globals->get_soundmgr()->exists( "nav2-dme-ident" ) ) {
841                 globals->get_soundmgr()->remove( "nav2-dme-ident" );
842             }
843             sound = morse.make_ident( nav2_trans_ident, HI_FREQUENCY );
844             sound->set_volume( 0.3 );
845             globals->get_soundmgr()->add( sound, "nav2-dme-ident" );
846
847             int offset = (int)(sg_random() * 30.0);
848             nav2_play_count = offset / 4;
849             nav2_last_time = globals->get_time_params()->get_cur_time() -
850                 offset;
851             // cout << "offset = " << offset << " play_count = "
852             //      << nav2_play_count << " nav2_last_time = "
853             //      << nav2_last_time << " current time = "
854             //      << globals->get_time_params()->get_cur_time() << endl;
855 #endif
856
857             // cout << "Found a vor station in range" << endl;
858             // cout << " id = " << nav.get_ident() << endl;
859         }
860     } else {
861         nav2_valid = false;
862         nav2_ident = "";
863         nav2_radial = 0;
864         nav2_dme_dist = 0;
865         nav2_trans_ident = "";
866         last_nav2_ident = "";
867 #ifdef ENABLE_AUDIO_SUPPORT
868         globals->get_soundmgr()->remove( "nav2-vor-ident" );
869         globals->get_soundmgr()->remove( "nav2-dme-ident" );
870 #endif
871         // cout << "not picking up vor1. :-(" << endl;
872     }
873
874     FGMkrBeacon::fgMkrBeacType beacon_type
875         = current_beacons->query( lon * SGD_RADIANS_TO_DEGREES,
876                                   lat * SGD_RADIANS_TO_DEGREES, elev );
877
878     outer_marker = middle_marker = inner_marker = false;
879
880     if ( beacon_type == FGMkrBeacon::OUTER ) {
881         outer_marker = true;
882         // cout << "OUTER MARKER" << endl;
883 #ifdef ENABLE_AUDIO_SUPPORT
884         if ( last_beacon != FGMkrBeacon::OUTER ) {
885             if ( ! globals->get_soundmgr()->exists( "outer-marker" ) ) {
886                 FGSimpleSound *sound = beacon.get_outer();
887                 sound->set_volume( 0.3 );
888                 globals->get_soundmgr()->add( sound, "outer-marker" );
889             }
890             if ( !globals->get_soundmgr()->is_playing("outer-marker") ) {
891                 globals->get_soundmgr()->play_looped( "outer-marker" );
892             }
893         }
894 #endif
895     } else if ( beacon_type == FGMkrBeacon::MIDDLE ) {
896         middle_marker = true;
897         // cout << "MIDDLE MARKER" << endl;
898 #ifdef ENABLE_AUDIO_SUPPORT
899         if ( last_beacon != FGMkrBeacon::MIDDLE ) {
900             if ( ! globals->get_soundmgr()->exists( "middle-marker" ) ) {
901                 FGSimpleSound *sound = beacon.get_middle();
902                 sound->set_volume( 0.3 );
903                 globals->get_soundmgr()->add( sound, "middle-marker" );
904             }
905             if ( !globals->get_soundmgr()->is_playing("middle-marker") ) {
906                 globals->get_soundmgr()->play_looped( "middle-marker" );
907             }
908         }
909 #endif
910     } else if ( beacon_type == FGMkrBeacon::INNER ) {
911         inner_marker = true;
912         // cout << "INNER MARKER" << endl;
913 #ifdef ENABLE_AUDIO_SUPPORT
914         if ( last_beacon != FGMkrBeacon::INNER ) {
915             if ( ! globals->get_soundmgr()->exists( "inner-marker" ) ) {
916                 FGSimpleSound *sound = beacon.get_inner();
917                 sound->set_volume( 0.3 );
918                 globals->get_soundmgr()->add( sound, "inner-marker" );
919             }
920             if ( !globals->get_soundmgr()->is_playing("inner-marker") ) {
921                 globals->get_soundmgr()->play_looped( "inner-marker" );
922             }
923         }
924 #endif
925     } else {
926         // cout << "no marker" << endl;
927 #ifdef ENABLE_AUDIO_SUPPORT
928         globals->get_soundmgr()->stop( "outer-marker" );
929         globals->get_soundmgr()->stop( "middle-marker" );
930         globals->get_soundmgr()->stop( "inner-marker" );
931 #endif
932     }
933     last_beacon = beacon_type;
934
935     // adf
936     if ( current_navlist->query( lon, lat, elev, adf_freq, &nav ) ) {
937         char freq[128];
938 #if defined( _MSC_VER )
939         _snprintf( freq, 10, "%.0f", adf_freq );
940 #else
941         snprintf( freq, 10, "%.0f", adf_freq );
942 #endif
943         adf_ident = freq;
944         adf_ident += nav.get_ident();
945         // cout << "adf ident = " << adf_ident << endl;
946         adf_valid = true;
947         if ( last_adf_ident != adf_ident ) {
948             last_adf_ident = adf_ident;
949
950             adf_trans_ident = nav.get_trans_ident();
951             adf_lon = nav.get_lon();
952             adf_lat = nav.get_lat();
953             adf_elev = nav.get_elev();
954             adf_range = nav.get_range();
955             adf_effective_range = kludgeRange(adf_elev, elev, adf_range);
956             adf_x = nav.get_x();
957             adf_y = nav.get_y();
958             adf_z = nav.get_z();
959
960 #ifdef ENABLE_AUDIO_SUPPORT
961             if ( globals->get_soundmgr()->exists( "adf-ident" ) ) {
962                 globals->get_soundmgr()->remove( "adf-ident" );
963             }
964             FGSimpleSound *sound;
965             sound = morse.make_ident( adf_trans_ident, LO_FREQUENCY );
966             sound->set_volume( 0.3 );
967             globals->get_soundmgr()->add( sound, "adf-ident" );
968
969             int offset = (int)(sg_random() * 30.0);
970             adf_play_count = offset / 4;
971             adf_last_time = globals->get_time_params()->get_cur_time() -
972                 offset;
973             // cout << "offset = " << offset << " play_count = "
974             //      << adf_play_count << " adf_last_time = "
975             //      << adf_last_time << " current time = "
976             //      << globals->get_time_params()->get_cur_time() << endl;
977 #endif
978
979             // cout << "Found an adf station in range" << endl;
980             // cout << " id = " << nav.get_ident() << endl;
981         }
982     } else {
983         adf_valid = false;
984         adf_ident = "";
985         adf_trans_ident = "";
986 #ifdef ENABLE_AUDIO_SUPPORT
987         globals->get_soundmgr()->remove( "adf-ident" );
988 #endif
989         last_adf_ident = "";
990         // cout << "not picking up adf. :-(" << endl;
991     }
992 }
993
994
995 // return the amount of heading needle deflection, returns a value
996 // clamped to the range of ( -10 , 10 )
997 double FGRadioStack::get_nav1_heading_needle_deflection() const {
998     double r;
999
1000     if ( nav1_inrange ) {
1001         r = nav1_heading - nav1_radial;
1002         // cout << "Radial = " << nav1_radial 
1003         //      << "  Bearing = " << nav1_heading << endl;
1004     
1005         while ( r >  180.0 ) { r -= 360.0;}
1006         while ( r < -180.0 ) { r += 360.0;}
1007         if ( fabs(r) > 90.0 ) {
1008             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
1009             if ( nav1_loc ) {
1010                 r = -r;
1011             }
1012         }
1013
1014         // According to Robin Peel, the ILS is 4x more sensitive than a vor
1015         if ( nav1_loc ) { r *= 4.0; }
1016         if ( r < -10.0 ) { r = -10.0; }
1017         if ( r >  10.0 ) { r = 10.0; }
1018     } else {
1019         r = 0.0;
1020     }
1021
1022     return r;
1023 }
1024
1025 // return the amount of heading needle deflection, returns a value
1026 // clamped to the range of ( -10 , 10 )
1027 double FGRadioStack::get_nav2_heading_needle_deflection() const {
1028     double r;
1029
1030     if ( nav2_inrange ) {
1031         r = nav2_heading - nav2_radial;
1032         // cout << "Radial = " << nav2_radial 
1033         //      << "  Bearing = " << nav2_heading << endl;
1034     
1035         while (r> 180.0) r-=360.0;
1036         while (r<-180.0) r+=360.0;
1037         if ( fabs(r) > 90.0 )
1038             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
1039         // According to Robin Peel, the ILS is 4x more sensitive than a vor
1040         if ( nav2_loc ) r *= 4.0;
1041         if ( r < -10.0 ) r = -10.0;
1042         if ( r > 10.0 ) r = 10.0;
1043     } else {
1044         r = 0.0;
1045     }
1046
1047     return r;
1048 }
1049
1050 // return the amount of glide slope needle deflection (.i.e. the
1051 // number of degrees we are off the glide slope * 5.0
1052 double FGRadioStack::get_nav1_gs_needle_deflection() const {
1053     if ( nav1_inrange && nav1_has_gs ) {
1054         double x = nav1_gs_dist;
1055         double y = (fgGetDouble("/position/altitude") - nav1_elev)
1056             * SG_FEET_TO_METER;
1057         double angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
1058         return (nav1_target_gs - angle) * 5.0;
1059     } else {
1060         return 0.0;
1061     }
1062 }
1063
1064
1065 // return the amount of glide slope needle deflection (.i.e. the
1066 // number of degrees we are off the glide slope * 5.0
1067 double FGRadioStack::get_nav2_gs_needle_deflection() const {
1068     if ( nav2_inrange && nav2_has_gs ) {
1069         double x = nav2_gs_dist;
1070         double y = (fgGetDouble("/position/altitude") - nav2_elev)
1071             * SG_FEET_TO_METER;
1072         double angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
1073         return (nav2_target_gs - angle) * 5.0;
1074     } else {
1075         return 0.0;
1076     }
1077 }
1078
1079
1080 /**
1081  * Return true if the NAV1 TO flag should be active.
1082  */
1083 bool 
1084 FGRadioStack::get_nav1_to_flag () const
1085 {
1086   if (nav1_inrange) {
1087     double offset = fabs(nav1_heading - nav1_radial);
1088     if (nav1_loc)
1089       return true;
1090     else
1091       return (offset <= 90.0 || offset >= 270.0);
1092   } else {
1093     return false;
1094   }
1095 }
1096
1097
1098 /**
1099  * Return true if the NAV1 FROM flag should be active.
1100  */
1101 bool
1102 FGRadioStack::get_nav1_from_flag () const
1103 {
1104   if (nav1_inrange) {
1105     double offset = fabs(nav1_heading - nav1_radial);
1106     if (nav1_loc)
1107       return false;
1108     else
1109       return (offset > 90.0 && offset < 270.0);
1110   } else {
1111     return false;
1112   }
1113 }
1114
1115
1116 /**
1117  * Return true if the NAV2 TO flag should be active.
1118  */
1119 bool 
1120 FGRadioStack::get_nav2_to_flag () const
1121 {
1122   if (nav2_inrange) {
1123     double offset = fabs(nav2_heading - nav2_radial);
1124     if (nav2_loc)
1125       return true;
1126     else
1127       return (offset <= 90.0 || offset >= 270.0);
1128   } else {
1129     return false;
1130   }
1131 }
1132
1133
1134 /**
1135  * Return true if the NAV2 FROM flag should be active.
1136  */
1137 bool
1138 FGRadioStack::get_nav2_from_flag () const
1139 {
1140   if (nav2_inrange) {
1141     double offset = fabs(nav2_heading - nav2_radial);
1142     if (nav2_loc)
1143       return false;
1144     else
1145       return (offset > 90.0 && offset < 270.0);
1146   } else {
1147     return false;
1148   }
1149 }
1150