]> git.mxchange.org Git - flightgear.git/blob - src/Cockpit/navcom.cxx
Removed print statements accidentally committed.
[flightgear.git] / src / Cockpit / navcom.cxx
1 // navcom.cxx -- class to manage a navcom instance
2 //
3 // Written by Curtis Olson, started April 2000.
4 //
5 // Copyright (C) 2000 - 2002  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/navlist.hxx>
36 #include <Time/FGEventMgr.hxx>
37
38 #include "navcom.hxx"
39
40 #include <string>
41 SG_USING_STD(string);
42
43
44 // Constructor
45 FGNavCom::FGNavCom() :
46     lon_node(fgGetNode("/position/longitude-deg", true)),
47     lat_node(fgGetNode("/position/latitude-deg", true)),
48     alt_node(fgGetNode("/position/altitude-ft", true)),
49     last_nav_id(""),
50     last_nav_vor(false),
51     nav_play_count(0),
52     nav_last_time(0),
53     need_update(true),
54     power_btn(true),
55     comm_freq(0.0),
56     comm_alt_freq(0.0),
57     comm_vol_btn(0.0),
58     nav_freq(0.0),
59     nav_alt_freq(0.0),
60     nav_radial(0.0),
61     nav_vol_btn(0.0),
62     nav_ident_btn(true)
63 {
64     SGPath path( globals->get_fg_root() );
65     SGPath term = path;
66     term.append( "Navaids/range.term" );
67     SGPath low = path;
68     low.append( "Navaids/range.low" );
69     SGPath high = path;
70     high.append( "Navaids/range.high" );
71
72     term_tbl = new SGInterpTable( term.str() );
73     low_tbl = new SGInterpTable( low.str() );
74     high_tbl = new SGInterpTable( high.str() );
75
76 }
77
78
79 // Destructor
80 FGNavCom::~FGNavCom() 
81 {
82     delete term_tbl;
83     delete low_tbl;
84     delete high_tbl;
85 }
86
87
88 void
89 FGNavCom::init ()
90 {
91     morse.init();
92
93     search();
94
95     update(0);                  // FIXME: use dt
96 }
97
98 void
99 FGNavCom::bind ()
100 {
101     char propname[256];
102
103                                 // User inputs
104     sprintf( propname, "/radios/comm[%d]/inputs/power-btn", index );
105     fgTie( propname, this,
106            &FGNavCom::get_power_btn, &FGNavCom::set_power_btn );
107     fgSetArchivable( propname );
108
109     sprintf( propname, "/radios/comm[%d]/frequencies/selected-mhz", index );
110     fgTie( propname, this, &FGNavCom::get_comm_freq, &FGNavCom::set_comm_freq );
111     fgSetArchivable( propname );
112
113     sprintf( propname, "/radios/comm[%d]/frequencies/standby-mhz", index );
114     fgTie( propname, this,
115            &FGNavCom::get_comm_alt_freq, &FGNavCom::set_comm_alt_freq );
116     fgSetArchivable( propname );
117
118     sprintf( propname, "/radios/comm[%d]/volume", index );
119     fgTie( propname, this,
120            &FGNavCom::get_comm_vol_btn, &FGNavCom::set_comm_vol_btn );
121     fgSetArchivable( propname );
122
123     sprintf( propname, "/radios/nav[%d]/frequencies/selected-mhz", index );
124     fgTie( propname, this,
125           &FGNavCom::get_nav_freq, &FGNavCom::set_nav_freq );
126     fgSetArchivable( propname );
127
128     sprintf( propname, "/radios/nav[%d]/frequencies/standby-mhz", index );
129     fgTie( propname , this,
130            &FGNavCom::get_nav_alt_freq, &FGNavCom::set_nav_alt_freq);
131     fgSetArchivable( propname );
132
133     sprintf( propname, "/radios/nav[%d]/radials/selected-deg", index );
134     fgTie( propname, this,
135            &FGNavCom::get_nav_sel_radial, &FGNavCom::set_nav_sel_radial );
136     fgSetArchivable( propname );
137
138     sprintf( propname, "/radios/nav[%d]/volume", index );
139     fgTie( propname, this,
140            &FGNavCom::get_nav_vol_btn, &FGNavCom::set_nav_vol_btn );
141     fgSetArchivable( propname );
142
143     sprintf( propname, "/radios/nav[%d]/ident", index );
144     fgTie( propname, this,
145            &FGNavCom::get_nav_ident_btn, &FGNavCom::set_nav_ident_btn );
146     fgSetArchivable( propname );
147
148                                 // Radio outputs
149     sprintf( propname, "/radios/nav[%d]/radials/actual-deg", index );
150     fgTie( propname,  this, &FGNavCom::get_nav_radial );
151
152     sprintf( propname, "/radios/nav[%d]/to-flag", index );
153     fgTie( propname, this, &FGNavCom::get_nav_to_flag );
154
155     sprintf( propname, "/radios/nav[%d]/from-flag", index );
156     fgTie( propname, this, &FGNavCom::get_nav_from_flag );
157
158     sprintf( propname, "/radios/nav[%d]/in-range", index );
159     fgTie( propname, this, &FGNavCom::get_nav_inrange );
160
161     sprintf( propname, "/radios/nav[%d]/heading-needle-deflection", index );
162     fgTie( propname, this, &FGNavCom::get_nav_heading_needle_deflection );
163
164     sprintf( propname, "/radios/nav[%d]/gs-needle-deflection", index );
165     fgTie( propname, this, &FGNavCom::get_nav_gs_needle_deflection );
166
167     // end of binding
168
169     // We know index is valid now so lets bind to the bus property
170     // here.
171     sprintf( propname, "/systems/electrical/outputs/navcom[%d]", index );
172     // default to true in case no electrical system defined.
173     fgSetDouble( propname, 60.0 );
174     bus_power = fgGetNode( propname, true );
175 }
176
177
178 void
179 FGNavCom::unbind ()
180 {
181     char propname[256];
182
183     sprintf( propname, "/radios/comm[%d]/inputs/power-btn", index );
184     fgUntie( propname );
185     sprintf( propname, "/radios/comm[%d]/frequencies/selected-mhz", index );
186     fgUntie( propname );
187     sprintf( propname, "/radios/comm[%d]/frequencies/standby-mhz", index );
188     fgUntie( propname );
189
190     sprintf( propname, "/radios/nav[%d]/frequencies/selected-mhz", index );
191     fgUntie( propname );
192     sprintf( propname, "/radios/nav[%d]/frequencies/standby-mhz", index );
193     fgUntie( propname );
194     sprintf( propname, "/radios/nav[%d]/radials/actual-deg", index );
195     fgUntie( propname );
196     sprintf( propname, "/radios/nav[%d]/radials/selected-deg", index );
197     fgUntie( propname );
198     sprintf( propname, "/radios/nav[%d]/ident", index );
199     fgUntie( propname );
200     sprintf( propname, "/radios/nav[%d]/to-flag", index );
201     fgUntie( propname );
202     sprintf( propname, "/radios/nav[%d]/from-flag", index );
203     fgUntie( propname );
204     sprintf( propname, "/radios/nav[%d]/in-range", index );
205     fgUntie( propname );
206     sprintf( propname, "/radios/nav[%d]/heading-needle-deflection", index );
207     fgUntie( propname );
208     sprintf( propname, "/radios/nav[%d]/gs-needle-deflection", index );
209     fgUntie( propname );
210 }
211
212
213 // model standard VOR/DME/TACAN service volumes as per AIM 1-1-8
214 double FGNavCom::adjustNavRange( double stationElev, double aircraftElev,
215                               double nominalRange )
216 {
217     // extend out actual usable range to be 1.3x the published safe range
218     const double usability_factor = 1.3;
219
220     // assumptions we model the standard service volume, plus
221     // ... rather than specifying a cylinder, we model a cone that
222     // contains the cylinder.  Then we put an upside down cone on top
223     // to model diminishing returns at too-high altitudes.
224
225     // altitude difference
226     double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
227     // cout << "aircraft elev = " << aircraftElev * SG_METER_TO_FEET
228     //      << " station elev = " << stationElev << endl;
229
230     if ( nominalRange < 25.0 + SG_EPSILON ) {
231         // Standard Terminal Service Volume
232         return term_tbl->interpolate( alt ) * usability_factor;
233     } else if ( nominalRange < 50.0 + SG_EPSILON ) {
234         // Standard Low Altitude Service Volume
235         // table is based on range of 40, scale to actual range
236         return low_tbl->interpolate( alt ) * nominalRange / 40.0
237             * usability_factor;
238     } else {
239         // Standard High Altitude Service Volume
240         // table is based on range of 130, scale to actual range
241         return high_tbl->interpolate( alt ) * nominalRange / 130.0
242             * usability_factor;
243     }
244 }
245
246
247 // model standard ILS service volumes as per AIM 1-1-9
248 double FGNavCom::adjustILSRange( double stationElev, double aircraftElev,
249                                      double offsetDegrees, double distance )
250 {
251     // assumptions we model the standard service volume, plus
252
253     // altitude difference
254     // double alt = ( aircraftElev * SG_METER_TO_FEET - stationElev );
255     double offset = fabs( offsetDegrees );
256
257     if ( offset < 10 ) {
258         return FG_ILS_DEFAULT_RANGE;
259     } else if ( offset < 35 ) {
260         return 10 + (35 - offset) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
261     } else if ( offset < 45 ) {
262         return (45 - offset);
263     } else if ( offset > 170 ) {
264         return FG_ILS_DEFAULT_RANGE;
265     } else if ( offset > 145 ) {
266         return 10 + (offset - 145) * (FG_ILS_DEFAULT_RANGE - 10) / 25;
267     } else if ( offset > 135 ) {
268         return (offset - 135);
269     } else {
270         return 0;
271     }
272 }
273
274
275 // Update the various nav values based on position and valid tuned in navs
276 void 
277 FGNavCom::update(double dt) 
278 {
279     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
280     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
281     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
282
283     need_update = false;
284
285     Point3D aircraft = sgGeodToCart( Point3D( lon, lat, elev ) );
286     Point3D station;
287     double az1, az2, s;
288
289     ////////////////////////////////////////////////////////////////////////
290     // Nav.
291     ////////////////////////////////////////////////////////////////////////
292
293     if ( nav_valid && power_btn && (bus_power->getDoubleValue() > 1.0) ) {
294         station = Point3D( nav_x, nav_y, nav_z );
295         nav_loc_dist = aircraft.distance3D( station );
296
297         if ( nav_has_gs ) {
298             station = Point3D( nav_gs_x, nav_gs_y, nav_gs_z );
299             nav_gs_dist = aircraft.distance3D( station );
300         } else {
301             nav_gs_dist = 0.0;
302         }
303         
304         // wgs84 heading
305         geo_inverse_wgs_84( elev, lat * SGD_RADIANS_TO_DEGREES, lon * SGD_RADIANS_TO_DEGREES, 
306                             nav_loclat, nav_loclon,
307                             &az1, &az2, &s );
308         // cout << "az1 = " << az1 << " magvar = " << nav_magvar << endl;
309         nav_heading = az1 - nav_magvar;
310         // cout << " heading = " << nav_heading
311         //      << " dist = " << nav_dist << endl;
312
313         if ( nav_loc ) {
314             double offset = nav_heading - nav_radial;
315             while ( offset < -180.0 ) { offset += 360.0; }
316             while ( offset > 180.0 ) { offset -= 360.0; }
317             // cout << "ils offset = " << offset << endl;
318             nav_effective_range = adjustILSRange(nav_elev, elev, offset,
319                                                   nav_loc_dist * SG_METER_TO_NM );
320         } else {
321             nav_effective_range = adjustNavRange(nav_elev, elev, nav_range);
322         }
323         // cout << "nav range = " << nav_effective_range
324         //      << " (" << nav_range << ")" << endl;
325
326         if ( nav_loc_dist < nav_effective_range * SG_NM_TO_METER ) {
327             nav_inrange = true;
328         } else if ( nav_loc_dist < 2 * nav_effective_range * SG_NM_TO_METER ) {
329             nav_inrange = sg_random() < 
330                 ( 2 * nav_effective_range * SG_NM_TO_METER - nav_loc_dist ) /
331                 (nav_effective_range * SG_NM_TO_METER);
332         } else {
333             nav_inrange = false;
334         }
335
336         if ( !nav_loc ) {
337             nav_radial = nav_sel_radial;
338         }
339     } else {
340         nav_inrange = false;
341         // cout << "not picking up vor. :-(" << endl;
342     }
343
344 #ifdef ENABLE_AUDIO_SUPPORT
345     if ( nav_valid && nav_inrange ) {
346         // play station ident via audio system if on + ident,
347         // otherwise turn it off
348         if ( power_btn && (bus_power->getDoubleValue() > 1.0)
349              && nav_ident_btn )
350         {
351             FGSimpleSound *sound;
352             sound = globals->get_soundmgr()->find( nav_fx_name );
353             if ( sound != NULL ) {
354                 sound->set_volume( nav_vol_btn );
355             } else {
356                 SG_LOG( SG_COCKPIT, SG_ALERT,
357                         "Can't find nav-vor-ident sound" );
358             }
359             sound = globals->get_soundmgr()->find( dme_fx_name );
360             if ( sound != NULL ) {
361                 sound->set_volume( nav_vol_btn );
362             } else {
363                 SG_LOG( SG_COCKPIT, SG_ALERT,
364                         "Can't find nav-dme-ident sound" );
365             }
366             // cout << "nav_last_time = " << nav_last_time << " ";
367             // cout << "cur_time = "
368             //      << globals->get_time_params()->get_cur_time();
369             if ( nav_last_time <
370                  globals->get_time_params()->get_cur_time() - 30 ) {
371                 nav_last_time = globals->get_time_params()->get_cur_time();
372                 nav_play_count = 0;
373             }
374             // cout << " nav_play_count = " << nav_play_count << endl;
375             // cout << "playing = "
376             //      << globals->get_soundmgr()->is_playing(nav_fx_name)
377             //      << endl;
378             if ( nav_play_count < 4 ) {
379                 // play VOR ident
380                 if ( !globals->get_soundmgr()->is_playing(nav_fx_name) ) {
381                     globals->get_soundmgr()->play_once( nav_fx_name );
382                     ++nav_play_count;
383                 }
384             } else if ( nav_play_count < 5 && nav_has_dme ) {
385                 // play DME ident
386                 if ( !globals->get_soundmgr()->is_playing(nav_fx_name) &&
387                      !globals->get_soundmgr()->is_playing(dme_fx_name) ) {
388                     globals->get_soundmgr()->play_once( dme_fx_name );
389                     ++nav_play_count;
390                 }
391             }
392         } else {
393             globals->get_soundmgr()->stop( nav_fx_name );
394             globals->get_soundmgr()->stop( dme_fx_name );
395         }
396     }
397 #endif
398
399 }
400
401
402 // Update current nav/adf radio stations based on current postition
403 void FGNavCom::search() 
404 {
405     double lon = lon_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
406     double lat = lat_node->getDoubleValue() * SGD_DEGREES_TO_RADIANS;
407     double elev = alt_node->getDoubleValue() * SG_FEET_TO_METER;
408
409     FGILS ils;
410     FGNav nav;
411
412     ////////////////////////////////////////////////////////////////////////
413     // Nav.
414     ////////////////////////////////////////////////////////////////////////
415
416     if ( current_ilslist->query( lon, lat, elev, nav_freq, &ils ) ) {
417         nav_id = ils.get_locident();
418         nav_valid = true;
419         if ( last_nav_id != nav_id || last_nav_vor ) {
420             nav_trans_ident = ils.get_trans_ident();
421             last_nav_id = nav_id;
422             last_nav_vor = false;
423             nav_loc = true;
424             nav_has_dme = ils.get_has_dme();
425             nav_has_gs = ils.get_has_gs();
426
427             nav_loclon = ils.get_loclon();
428             nav_loclat = ils.get_loclat();
429             nav_gslon = ils.get_gslon();
430             nav_gslat = ils.get_gslat();
431             nav_elev = ils.get_gselev();
432             nav_magvar = 0;
433             nav_range = FG_ILS_DEFAULT_RANGE;
434             nav_effective_range = nav_range;
435             nav_target_gs = ils.get_gsangle();
436             nav_radial = ils.get_locheading();
437             while ( nav_radial <   0.0 ) { nav_radial += 360.0; }
438             while ( nav_radial > 360.0 ) { nav_radial -= 360.0; }
439             nav_x = ils.get_x();
440             nav_y = ils.get_y();
441             nav_z = ils.get_z();
442             nav_gs_x = ils.get_gs_x();
443             nav_gs_y = ils.get_gs_y();
444             nav_gs_z = ils.get_gs_z();
445
446 #ifdef ENABLE_AUDIO_SUPPORT
447             if ( globals->get_soundmgr()->exists( nav_fx_name ) ) {
448                 globals->get_soundmgr()->remove( nav_fx_name );
449             }
450             FGSimpleSound *sound;
451             sound = morse.make_ident( nav_trans_ident, LO_FREQUENCY );
452             sound->set_volume( 0.3 );
453             globals->get_soundmgr()->add( sound, nav_fx_name );
454
455             if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
456                 globals->get_soundmgr()->remove( dme_fx_name );
457             }
458             sound = morse.make_ident( nav_trans_ident, HI_FREQUENCY );
459             sound->set_volume( 0.3 );
460             globals->get_soundmgr()->add( sound, dme_fx_name );
461
462             int offset = (int)(sg_random() * 30.0);
463             nav_play_count = offset / 4;
464             nav_last_time = globals->get_time_params()->get_cur_time() -
465                 offset;
466             // cout << "offset = " << offset << " play_count = "
467             //      << nav_play_count
468             //      << " nav_last_time = " << nav_last_time
469             //      << " current time = "
470             //      << globals->get_time_params()->get_cur_time() << endl;
471 #endif
472
473             // cout << "Found an ils station in range" << endl;
474             // cout << " id = " << ils.get_locident() << endl;
475         }
476     } else if ( current_navlist->query( lon, lat, elev, nav_freq, &nav ) ) {
477         nav_id = nav.get_ident();
478         nav_valid = true;
479         if ( last_nav_id != nav_id || !last_nav_vor ) {
480             last_nav_id = nav_id;
481             last_nav_vor = true;
482             nav_trans_ident = nav.get_trans_ident();
483             nav_loc = false;
484             nav_has_dme = nav.get_has_dme();
485             nav_has_gs = false;
486             nav_loclon = nav.get_lon();
487             nav_loclat = nav.get_lat();
488             nav_elev = nav.get_elev();
489             nav_magvar = nav.get_magvar();
490             nav_range = nav.get_range();
491             nav_effective_range = adjustNavRange(nav_elev, elev, nav_range);
492             nav_target_gs = 0.0;
493             nav_radial = nav_sel_radial;
494             nav_x = nav.get_x();
495             nav_y = nav.get_y();
496             nav_z = nav.get_z();
497
498 #ifdef ENABLE_AUDIO_SUPPORT
499             if ( globals->get_soundmgr()->exists( nav_fx_name ) ) {
500                 globals->get_soundmgr()->remove( nav_fx_name );
501             }
502             FGSimpleSound *sound;
503             sound = morse.make_ident( nav_trans_ident, LO_FREQUENCY );
504             sound->set_volume( 0.3 );
505             if ( globals->get_soundmgr()->add( sound, nav_fx_name ) ) {
506                 // cout << "Added nav-vor-ident sound" << endl;
507             } else {
508                 cout << "Failed to add v1-vor-ident sound" << endl;
509             }
510
511             if ( globals->get_soundmgr()->exists( dme_fx_name ) ) {
512                 globals->get_soundmgr()->remove( dme_fx_name );
513             }
514             sound = morse.make_ident( nav_trans_ident, HI_FREQUENCY );
515             sound->set_volume( 0.3 );
516             globals->get_soundmgr()->add( sound, dme_fx_name );
517
518             int offset = (int)(sg_random() * 30.0);
519             nav_play_count = offset / 4;
520             nav_last_time = globals->get_time_params()->get_cur_time() -
521                 offset;
522             // cout << "offset = " << offset << " play_count = "
523             //      << nav_play_count << " nav_last_time = "
524             //      << nav_last_time << " current time = "
525             //      << globals->get_time_params()->get_cur_time() << endl;
526 #endif
527
528             // cout << "Found a vor station in range" << endl;
529             // cout << " id = " << nav.get_ident() << endl;
530         }
531     } else {
532         nav_valid = false;
533         nav_id = "";
534         nav_radial = 0;
535         nav_trans_ident = "";
536         last_nav_id = "";
537 #ifdef ENABLE_AUDIO_SUPPORT
538         if ( ! globals->get_soundmgr()->remove( nav_fx_name ) ) {
539             cout << "Failed to remove nav-vor-ident sound" << endl;
540         }
541         globals->get_soundmgr()->remove( dme_fx_name );
542 #endif
543         // cout << "not picking up vor1. :-(" << endl;
544     }
545 }
546
547
548 // return the amount of heading needle deflection, returns a value
549 // clamped to the range of ( -10 , 10 )
550 double FGNavCom::get_nav_heading_needle_deflection() const {
551     double r;
552
553     if ( nav_inrange ) {
554         r = nav_heading - nav_radial;
555         // cout << "Radial = " << nav_radial 
556         //      << "  Bearing = " << nav_heading << endl;
557     
558         while ( r >  180.0 ) { r -= 360.0;}
559         while ( r < -180.0 ) { r += 360.0;}
560         if ( fabs(r) > 90.0 )
561             r = ( r<0.0 ? -r-180.0 : -r+180.0 );
562
563         // According to Robin Peel, the ILS is 4x more sensitive than a vor
564         if ( nav_loc ) { r *= 4.0; }
565         if ( r < -10.0 ) { r = -10.0; }
566         if ( r >  10.0 ) { r = 10.0; }
567     } else {
568         r = 0.0;
569     }
570
571     return r;
572 }
573
574
575 // return the amount of glide slope needle deflection (.i.e. the
576 // number of degrees we are off the glide slope * 5.0
577 double FGNavCom::get_nav_gs_needle_deflection() const {
578     if ( nav_inrange && nav_has_gs ) {
579         double x = nav_gs_dist;
580         double y = (fgGetDouble("/position/altitude-ft") - nav_elev)
581             * SG_FEET_TO_METER;
582         double angle = atan2( y, x ) * SGD_RADIANS_TO_DEGREES;
583         return (nav_target_gs - angle) * 5.0;
584     } else {
585         return 0.0;
586     }
587 }
588
589
590 /**
591  * Return true if the NAV TO flag should be active.
592  */
593 bool 
594 FGNavCom::get_nav_to_flag () const
595 {
596   if (nav_inrange) {
597     double offset = fabs(nav_heading - nav_radial);
598     if (nav_loc)
599       return true;
600     else
601       return (offset <= 90.0 || offset >= 270.0);
602   } else {
603     return false;
604   }
605 }
606
607
608 /**
609  * Return true if the NAV FROM flag should be active.
610  */
611 bool
612 FGNavCom::get_nav_from_flag () const
613 {
614   if (nav_inrange) {
615     double offset = fabs(nav_heading - nav_radial);
616     if (nav_loc)
617       return false;
618     else
619       return (offset > 90.0 && offset < 270.0);
620   } else {
621     return false;
622   }
623 }