]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/radiostack.cxx
Split Nav/Com units out into their own source code file to continue the
[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 <stdio.h>      // snprintf
29
30 #include <simgear/compiler.h>
31 #include <simgear/math/sg_random.h>
32
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>
38
39 #include "radiostack.hxx"
40
41 #include <string>
42 SG_USING_STD(string);
43
44
45 FGRadioStack *current_radiostack;
46
47
48 /**
49  * Boy, this is ugly!  Make the VOR range vary by altitude difference.
50  */
51 static double kludgeRange ( double stationElev, double aircraftElev,
52                             double nominalRange)
53 {
54                                 // Assume that the nominal range (usually
55                                 // 50nm) applies at a 5,000 ft difference.
56                                 // Just a wild guess!
57   double factor = ((aircraftElev*SG_METER_TO_FEET) - stationElev) / 5000.0;
58   double range = fabs(nominalRange * factor);
59
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;
67   }
68
69   return range;
70 }
71
72
73 // Constructor
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     need_update(true),
79     dme_freq(0.0),
80     dme_dist(0.0),
81     dme_prev_dist(0.0),
82     dme_spd(0.0),
83     dme_ete(0.0),
84     outer_blink(false),
85     middle_blink(false),
86     inner_blink(false)
87 {
88     SGPath path( globals->get_fg_root() );
89     SGPath term = path;
90     term.append( "Navaids/range.term" );
91     SGPath low = path;
92     low.append( "Navaids/range.low" );
93     SGPath high = path;
94     high.append( "Navaids/range.high" );
95
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();
100 }
101
102
103 // Destructor
104 FGRadioStack::~FGRadioStack() 
105 {
106     navcom1.unbind();
107     navcom2.unbind();
108     adf.unbind();
109     xponder.unbind();
110     unbind();                   // FIXME: should be called externally
111
112     delete term_tbl;
113     delete low_tbl;
114     delete high_tbl;
115 }
116
117
118 void
119 FGRadioStack::init ()
120 {
121     navcom1.set_bind_index( 0 );
122     navcom1.init();
123
124     navcom2.set_bind_index( 1 );
125     navcom2.init();
126
127     adf.init();
128     xponder.init();
129
130     morse.init();
131     beacon.init();
132     blink.stamp();
133
134     search();
135     navcom1.search();
136     navcom2.search();
137     adf.search();
138     xponder.search();
139
140     update(0);                  // FIXME: use dt
141     navcom1.update(0);
142     navcom2.update(0);
143     adf.update(0);
144     xponder.update(0);
145
146     // Search radio database once per second
147     global_events.Register( "fgRadioSearch()",
148                             current_radiostack, &FGRadioStack::search,
149                             1000 );
150 }
151
152 void
153 FGRadioStack::bind ()
154 {
155
156                                 // User inputs
157     fgTie("/radios/dme/frequencies/selected-khz", this,
158           &FGRadioStack::get_dme_freq, &FGRadioStack::set_dme_freq);
159
160                                 // Radio outputs
161     fgTie("/radios/dme/in-range", this, &FGRadioStack::get_dme_inrange);
162
163     fgTie("/radios/dme/distance-nm", this, &FGRadioStack::get_dme_dist);
164
165     fgTie("/radios/dme/speed-kt", this, &FGRadioStack::get_dme_spd);
166
167     fgTie("/radios/dme/ete-min", this, &FGRadioStack::get_dme_ete);
168
169     fgTie("/radios/marker-beacon/inner", this,
170           &FGRadioStack::get_inner_blink);
171
172     fgTie("/radios/marker-beacon/middle", this,
173           &FGRadioStack::get_middle_blink);
174
175     fgTie("/radios/marker-beacon/outer", this,
176           &FGRadioStack::get_outer_blink);
177
178     navcom1.set_bind_index( 0 );
179     navcom1.bind();
180     navcom2.set_bind_index( 1 );
181     navcom2.bind();
182     adf.bind();
183     xponder.bind();
184 }
185
186 void
187 FGRadioStack::unbind ()
188 {
189     fgUntie("/radios/dme/frequencies/selected-khz");
190
191                                 // Radio outputs
192     fgUntie("/radios/dme/in-range");
193     fgUntie("/radios/dme/distance-nm");
194     fgUntie("/radios/dme/speed-kt");
195     fgUntie("/radios/dme/ete-min");
196
197     fgUntie("/radios/marker-beacon/inner");
198     fgUntie("/radios/marker-beacon/middle");
199     fgUntie("/radios/marker-beacon/outer");
200
201     navcom1.unbind();
202     navcom2.unbind();
203     adf.unbind();
204     xponder.unbind();
205 }
206
207
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 )
211 {
212     // extend out actual usable range to be 1.3x the published safe range
213     const double usability_factor = 1.3;
214
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.
219
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;
224
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
232             * usability_factor;
233     } else {
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
237             * usability_factor;
238     }
239 }
240
241
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 )
245 {
246     // assumptions we model the standard service volume, plus
247
248     // altitude difference
249     // double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
250     double offset = fabs( offsetDegrees );
251
252     if ( offset < 10 ) {
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);
264     } else {
265         return 0;
266     }
267 }
268
269
270 // Update the various nav values based on position and valid tuned in navs
271 void 
272 FGRadioStack::update(double dt) 
273 {
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;
277
278     need_update = false;
279
280     Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
281     Point3D station;
282
283     navcom1.update( dt );
284     navcom2.update( dt );
285
286     ////////////////////////////////////////////////////////////////////////
287     // DME.
288     ////////////////////////////////////////////////////////////////////////
289
290     if (dme_valid) {
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) {
295           dme_inrange = true;
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);
300         } else {
301           dme_inrange = false;
302         }
303         if (dme_inrange) {
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
310           if (dMs >= 1000) {
311             double dDist = dme_dist - dme_prev_dist;
312             dme_spd = fabs((dDist/dMs) * 3600000);
313                                 // FIXME: the panel should be able to
314                                 // handle this!!!
315             if (dme_spd > 999.0)
316               dme_spd = 999.0;
317             dme_ete = fabs((dme_dist/dme_spd) * 60.0);
318                                 // FIXME: the panel should be able to
319                                 // handle this!!!
320             if (dme_ete > 99.0)
321               dme_ete = 99.0;
322             dme_prev_dist = dme_dist;
323             dme_last_time.stamp();
324           }
325         }
326     } else {
327       dme_inrange = false;
328       dme_dist = 0.0;
329       dme_prev_dist = 0.0;
330     }
331
332     // marker beacon blinking
333     bool light_on = ( outer_blink || middle_blink || inner_blink );
334     SGTimeStamp current;
335     current.stamp();
336
337     if ( light_on && (current - blink > 400000) ) {
338         light_on = false;
339         blink.stamp();
340     } else if ( !light_on && (current - blink > 100000) ) {
341         light_on = true;
342         blink.stamp();
343     }
344
345     if ( outer_marker ) {
346         outer_blink = light_on;
347     } else {
348         outer_blink = false;
349     }
350
351     if ( middle_marker ) {
352         middle_blink = light_on;
353     } else {
354         middle_blink = false;
355     }
356
357     if ( inner_marker ) {
358         inner_blink = light_on;
359     } else {
360         inner_blink = false;
361     }
362
363     // cout << outer_blink << " " << middle_blink << " " << inner_blink << endl;
364
365     adf.update( dt );
366     xponder.update( dt );
367 }
368
369
370 // Update current nav/adf radio stations based on current postition
371 void FGRadioStack::search() 
372 {
373     static FGMkrBeacon::fgMkrBeacType last_beacon = FGMkrBeacon::NOBEACON;
374
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;
378
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) {
384       dme_freq = 0;
385       dme_inrange = false;
386     } else if (dme_switch_pos == 1) {
387       if (dme_freq != navcom1.get_nav_freq()) {
388         dme_freq = navcom1.get_nav_freq();
389         need_update = true;
390       }
391     } else if (dme_switch_pos == 3) {
392       if (dme_freq != navcom2.get_nav_freq()) {
393         dme_freq = navcom2.get_nav_freq();
394         need_update = true;
395       }
396     }
397
398     FGILS ils;
399     FGNav nav;
400
401     navcom1.search();
402     navcom2.search();
403
404     ////////////////////////////////////////////////////////////////////////
405     // DME
406     ////////////////////////////////////////////////////////////////////////
407
408     if ( current_ilslist->query( lon, lat, elev, dme_freq, &ils ) ) {
409       if (ils.get_has_dme()) {
410         dme_valid = true;
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();
419       }
420     } else if ( current_navlist->query( lon, lat, elev, dme_freq, &nav ) ) {
421       if (nav.get_has_dme()) {
422         dme_valid = true;
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);
428         dme_x = nav.get_x();
429         dme_y = nav.get_y();
430         dme_z = nav.get_z();
431       }
432     } else {
433       dme_valid = false;
434       dme_dist = 0;
435     }
436
437
438     ////////////////////////////////////////////////////////////////////////
439     // Beacons.
440     ////////////////////////////////////////////////////////////////////////
441
442     FGMkrBeacon::fgMkrBeacType beacon_type
443         = current_beacons->query( lon * SGD_RADIANS_TO_DEGREES,
444                                   lat * SGD_RADIANS_TO_DEGREES, elev );
445
446     outer_marker = middle_marker = inner_marker = false;
447
448     if ( beacon_type == FGMkrBeacon::OUTER ) {
449         outer_marker = true;
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" );
457             }
458             if ( !globals->get_soundmgr()->is_playing("outer-marker") ) {
459                 globals->get_soundmgr()->play_looped( "outer-marker" );
460             }
461         }
462 #endif
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" );
472             }
473             if ( !globals->get_soundmgr()->is_playing("middle-marker") ) {
474                 globals->get_soundmgr()->play_looped( "middle-marker" );
475             }
476         }
477 #endif
478     } else if ( beacon_type == FGMkrBeacon::INNER ) {
479         inner_marker = true;
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" );
487             }
488             if ( !globals->get_soundmgr()->is_playing("inner-marker") ) {
489                 globals->get_soundmgr()->play_looped( "inner-marker" );
490             }
491         }
492 #endif
493     } else {
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" );
499 #endif
500     }
501     last_beacon = beacon_type;
502
503     navcom1.search();
504     navcom2.search();
505     adf.search();
506     xponder.search();
507 }