]> git.mxchange.org Git - flightgear.git/blob - src/Network/fgcom.cxx
2068e03484f85959d57f177a8fdb4efc530fe0de
[flightgear.git] / src / Network / fgcom.cxx
1 // fgcom.cxx -- FGCom: Voice communication
2 //
3 // Written by Clement de l'Hamaide, started May 2013.
4 //
5 // This program is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU General Public License as
7 // published by the Free Software Foundation; either version 2 of the
8 // License, or (at your option) any later version.
9 //
10 // This program is distributed in the hope that it will be useful, but
11 // WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 // General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
18
19 #ifdef HAVE_CONFIG_H
20 #  include <config.h>
21 #endif
22
23 #include "fgcom.hxx"
24
25 // standard library includes
26 #include <cstdio>
27
28 // simgear includes
29 #include <simgear/compiler.h>
30 #include <simgear/sg_inlines.h>
31 #include <simgear/debug/logstream.hxx>
32 #include <simgear/math/sg_geodesy.hxx>
33 #include <simgear/timing/timestamp.hxx>
34
35 // flightgear includes
36 #include <Main/fg_props.hxx>
37 #include <Main/globals.hxx>
38 #include <ATC/CommStation.hxx>
39 #include <Airports/airport.hxx>
40 #include <Navaids/navlist.hxx>
41
42 #include <utils/iaxclient/lib/iaxclient.h>
43
44
45 #define NUM_CALLS 4
46 #define MAX_GND_RANGE 10.0
47 #define MAX_TWR_RANGE 50.0
48 #define MAX_RANGE 100.0
49 #define MIN_RANGE  20.0
50 #define MIN_GNDTWR_RANGE 0.0
51 #define DEFAULT_SERVER "fgcom.flightgear.org"
52 #define IAX_DELAY 300  // delay between calls in milliseconds
53 #define TEST_FREQ 910.00
54 #define NULL_ICAO "ZZZZ"
55
56 const int special_freq[] = { // Define some freq who need to be used with NULL_ICAO
57         910000,
58         911000,
59         700000,
60         123450,
61         122750,
62         121500,
63         123500,
64         121000,
65         723340 };
66
67 static FGCom* static_instance = NULL;
68
69
70
71 static int iaxc_callback( iaxc_event e )
72 {
73   switch( e.type )
74   {
75     case IAXC_EVENT_TEXT:
76       static_instance->iaxTextEvent(e.ev.text);
77       break;
78     default:
79       return 0;
80   }
81   return 1;
82 }
83
84
85
86 void FGCom::iaxTextEvent(struct iaxc_ev_text text)
87 {
88   if( (text.type == IAXC_TEXT_TYPE_STATUS ||
89        text.type == IAXC_TEXT_TYPE_IAX) &&
90        _showMessages_node->getBoolValue() )
91   {
92     _text_node->setStringValue(text.message);
93   }
94 }
95
96
97
98 FGCom::FGCom() :
99     _register(true)
100 {
101   _listener_active = 0;
102 }
103
104
105
106 FGCom::~FGCom()
107 {
108 }
109
110
111
112 void FGCom::bind()
113 {
114   SGPropertyNode *node     = fgGetNode("/sim/fgcom", 0, true);
115   _test_node               = node->getChild( "test", 0, true );
116   _server_node             = node->getChild( "server", 0, true );
117   _enabled_node            = node->getChild( "enabled", 0, true );
118   _micBoost_node           = node->getChild( "mic-boost", 0, true );
119   _micLevel_node           = node->getChild( "mic-level", 0, true );
120   _speakerLevel_node       = node->getChild( "speaker-level", 0, true );
121   _selectedInput_node      = node->getChild( "device-input", 0, true );
122   _selectedOutput_node     = node->getChild( "device-output", 0, true );
123   _showMessages_node       = node->getChild( "show-messages", 0, true );
124
125   SGPropertyNode *reg_node = node->getChild("register", 0, true);
126   _register_node           = reg_node->getChild( "enabled", 0, true );
127   _username_node           = reg_node->getChild( "username", 0, true );
128   _password_node           = reg_node->getChild( "password", 0, true );
129
130   //_nav0_node               = fgGetNode("/instrumentation/nav[0]/frequencies/selected-mhz", true);
131   //_nav1_node               = fgGetNode("/instrumentation/nav[1]/frequencies/selected-mhz", true);
132   _comm0_node              = fgGetNode("/instrumentation/comm[0]/frequencies/selected-mhz", true);
133   //_comm1_node              = fgGetNode("/instrumentation/comm[1]/frequencies/selected-mhz", true);
134   _ptt0_node               = fgGetNode("/instrumentation/comm[0]/ptt", true); //FIXME: what about /instrumentation/comm[1]/ptt ?
135   _callsign_node           = fgGetNode("/sim/multiplay/callsign", true);
136   _text_node               = fgGetNode("/sim/messages/atc", true );
137
138   // Set default values if not provided
139   if ( !_enabled_node->hasValue() )
140       _enabled_node->setBoolValue(true);
141
142   if ( !_test_node->hasValue() )
143       _test_node->setBoolValue(false);
144
145   if ( !_micBoost_node->hasValue() )
146       _micBoost_node->setIntValue(1);
147
148   if ( !_server_node->hasValue() )
149       _server_node->setStringValue(DEFAULT_SERVER);
150
151   if ( !_speakerLevel_node->hasValue() )
152       _speakerLevel_node->setFloatValue(1.0);
153
154   if ( !_micLevel_node->hasValue() )
155       _micLevel_node->setFloatValue(1.0);
156
157   if ( !_register_node->hasValue() )
158       _register_node->setBoolValue(false);
159
160   if ( !_username_node->hasValue() )
161       _username_node->setStringValue("guest");
162
163   if ( !_password_node->hasValue() )
164       _password_node->setStringValue("guest");
165
166   if ( !_showMessages_node->hasValue() )
167       _showMessages_node->setBoolValue(false);
168
169   _selectedOutput_node->addChangeListener(this);
170   _selectedInput_node->addChangeListener(this);
171   _speakerLevel_node->addChangeListener(this);
172   _micBoost_node->addChangeListener(this);
173   _micLevel_node->addChangeListener(this);
174   _enabled_node->addChangeListener(this);
175   _comm0_node->addChangeListener(this);
176   //_comm1_node->addChangeListener(this);
177   //_nav0_node->addChangeListener(this);
178   //_nav1_node->addChangeListener(this);
179   _ptt0_node->addChangeListener(this);
180   _test_node->addChangeListener(this);
181 }
182
183
184
185 void FGCom::unbind()
186 {
187 }
188
189
190
191 void FGCom::init()
192 {
193   _enabled          = _enabled_node->getBoolValue();
194   _server           = _server_node->getStringValue();
195   _register         = _register_node->getBoolValue();
196   _username         = _username_node->getStringValue();
197   _password         = _password_node->getStringValue();
198
199   _currentComm0     = _comm0_node->getDoubleValue();
200   //_currentComm1     = _comm1_node->getDoubleValue();
201   //_currentNav0      = _nav0_node->getDoubleValue();
202   //_currentNav1      = _nav1_node->getDoubleValue();
203
204   _comm0Changed     = false;
205   //_comm1Changed     = false;
206   //_nav0Changed      = false;
207   //_nav1Changed      = false;
208
209   _maxRange         = MAX_RANGE;
210   _minRange         = MIN_RANGE;
211 }
212
213
214
215 void FGCom::postinit()
216 {
217     if( !_enabled ) {
218         return;
219     }
220     
221     //WARNING: this _must_ be executed after sound system is totally initialized !
222     if( iaxc_initialize(NUM_CALLS) ) {
223         SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot initialize iaxclient");
224         _enabled = false;
225         return;
226     }
227
228     assert( static_instance == NULL );
229     static_instance = this;
230     iaxc_set_event_callback( iaxc_callback );
231     
232     // FIXME: To be implemented in IAX audio driver
233     //iaxc_mic_boost_set( _micBoost_node->getIntValue() );
234     iaxc_set_formats( IAXC_FORMAT_GSM, IAXC_FORMAT_GSM );
235     iaxc_start_processing_thread ();
236
237     // Now IAXClient is initialized
238     _initialized = true;
239     
240     if ( _register ) {
241       _regId = iaxc_register( const_cast<char*>(_username.c_str()),
242                               const_cast<char*>(_password.c_str()),
243                               const_cast<char*>(_server.c_str()) );
244         if( _regId == -1 ) {
245             SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot register iaxclient");
246             return;
247         }
248     }
249
250     /*
251       Here we will create the list of available audio devices
252       Each audio device has a name, an ID, and a list of capabilities
253       If an audio device can output sound, available-output=true 
254       If an audio device can input sound, available-input=true 
255
256       /sim/fgcom/selected-input (int)
257       /sim/fgcom/selected-output (int)
258
259       /sim/fgcom/device[n]/id (int)
260       /sim/fgcom/device[n]/name (string)
261       /sim/fgcom/device[n]/available-input (bool)
262       /sim/fgcom/device[n]/available-output (bool)
263     */
264
265     //FIXME: OpenAL driver use an hard-coded device
266     //       so all following is unused finally until someone
267     //       implement "multi-device" support in IAX audio driver
268     SGPropertyNode *node     = fgGetNode("/sim/fgcom", 0, true);
269
270     struct iaxc_audio_device *devs;
271     int nDevs, input, output, ring;
272
273     iaxc_audio_devices_get(&devs,&nDevs, &input, &output, &ring);
274
275     for(int i=0; i<nDevs; i++ ) {
276       SGPropertyNode *in_node = node->getChild("device", i, true);
277
278       // devID
279       _deviceID_node[i] = in_node->getChild("id", 0, true);
280       _deviceID_node[i]->setIntValue(devs[i].devID);
281
282       // name
283       _deviceName_node[i] = in_node->getChild("name", 0, true);
284       _deviceName_node[i]->setStringValue(devs[i].name);
285
286       // input capability
287       _deviceInput_node[i] = in_node->getChild("available-input", 0, true);
288       if( devs[i].capabilities & IAXC_AD_INPUT )
289         _deviceInput_node[i]->setBoolValue(true);
290       else 
291         _deviceInput_node[i]->setBoolValue(false);
292
293       // output capability
294       _deviceOutput_node[i] = in_node->getChild("available-output", 0, true);
295       if( devs[i].capabilities & IAXC_AD_OUTPUT )
296         _deviceOutput_node[i]->setBoolValue(true);
297       else 
298         _deviceOutput_node[i]->setBoolValue(false);
299
300       // use default device at start
301       if( devs[i].capabilities & IAXC_AD_INPUT_DEFAULT )
302         _selectedInput_node->setIntValue(devs[i].devID);
303       if( devs[i].capabilities & IAXC_AD_OUTPUT_DEFAULT )
304         _selectedOutput_node->setIntValue(devs[i].devID);
305     }
306
307     // Mute the mic and set speaker at start
308     iaxc_input_level_set( 0.0 );
309     iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
310
311     iaxc_millisleep(50);
312
313     // Do the first call at start
314     const double freq = _comm0_node->getDoubleValue();
315     _currentFreqKhz = 10 * static_cast<int>(freq * 100 + 0.25);
316     std::string num = computePhoneNumber(freq, getAirportCode(freq));
317     if( !num.empty() ) {
318       _callComm0 = iaxc_call(num.c_str());
319     }
320     if( _callComm0 == -1 )
321       SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
322 }
323
324
325
326 void FGCom::updateCall(bool& changed, int& callNo, double freqMHz)
327 {
328
329     _currentFreqKhz = 10 * static_cast<int>(freqMHz * 100 + 0.25);
330
331     if (!changed) {
332         if( !isInRange(freqMHz) ) {
333             iaxc_dump_call_number(callNo);
334             callNo = -1;
335             return;
336         } else {
337             if(callNo != -1)
338                 return;
339         }
340     }
341
342     changed = false; // FIXME, out-params are confusing
343
344     if( callNo != -1 ) {
345         iaxc_dump_call_number( callNo );
346         callNo = -1;
347     }
348
349     if(_p.elapsedMSec() > IAX_DELAY) {
350         std::string num = computePhoneNumber(freqMHz, getAirportCode(freqMHz));
351         if( !isInRange(freqMHz) )
352             return;
353         if( !num.empty() ) {
354             callNo = iaxc_call_ex(num.c_str(), _callsign.c_str(), NULL, 0 /* no video */);
355
356             if( callNo == -1 )
357                 SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
358         }
359     } else {
360         changed = true;
361     }
362 }
363
364
365
366 void FGCom::update(double dt)
367 {
368     if ( !_enabled || !_initialized ) {
369         return;
370     }
371
372     // For now we manage FGCom for only one freq because IAXClient
373     // is not able to handle multiple calls at same time.
374     updateCall(_comm0Changed, _callComm0, _comm0_node->getDoubleValue());
375     // updateCall(_comm1Changed, _callComm1, _comm1_node->getDoubleValue());
376     // updateCall(_nav0Changed, _callNav0, _nav0_node->getDoubleValue());
377     // updateCall(_nav1Changed, _callNav1, _nav1_node->getDoubleValue());
378 }
379
380
381
382 void FGCom::shutdown()
383 {
384     if( !_enabled ) {
385         return;
386     }
387
388   _initialized = false;
389   _enabled = false;
390
391   iaxc_set_event_callback(NULL);
392   iaxc_unregister(_regId);
393   iaxc_stop_processing_thread();
394   iaxc_shutdown();
395
396   assert( static_instance == this );
397   static_instance = NULL;
398 }
399
400
401
402 void FGCom::valueChanged(SGPropertyNode *prop)
403 {
404   if (prop == _enabled_node) {
405       bool isEnabled = prop->getBoolValue();
406       if (_enabled == isEnabled) {
407           return;
408       }
409       
410     if( isEnabled ) {
411       init();
412       postinit();
413     } else {
414       shutdown();
415     }
416     return;
417   }
418
419   if (prop == _ptt0_node && _enabled) {
420     if( _ptt0_node->getBoolValue() ) {
421       iaxc_output_level_set( 0.0 );
422       iaxc_input_level_set( _micLevel_node->getFloatValue() ); //0.0 = min , 1.0 = max
423     } else {
424       iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
425       iaxc_input_level_set( 0.0 );
426     }
427   }
428
429   if (prop == _test_node) {
430     testMode( prop->getBoolValue() );
431     return;
432   }
433
434   //FIXME: not implemented in IAX audio driver (audio_openal.c)
435   if (prop == _micBoost_node && _initialized) {
436     int micBoost = prop->getIntValue();
437     SG_CLAMP_RANGE<int>( micBoost, 0, 1 );
438     iaxc_mic_boost_set( micBoost ) ; // 0 = enabled , 1 = disabled
439     return;
440   }
441
442   //FIXME: not implemented in IAX audio driver (audio_openal.c)
443   if ((prop == _selectedInput_node || prop == _selectedOutput_node) && _initialized) {
444     int selectedInput = _selectedInput_node->getIntValue();
445     int selectedOutput = _selectedOutput_node->getIntValue();
446     iaxc_audio_devices_set(selectedInput, selectedOutput, 0);
447     return;
448   }
449
450   if (_listener_active)
451     return;
452
453   _listener_active++;
454
455   if (prop == _speakerLevel_node && _enabled) {
456     float speakerLevel = prop->getFloatValue();
457     SG_CLAMP_RANGE<float>( speakerLevel, 0.0, 1.0 );
458     _speakerLevel_node->setFloatValue(speakerLevel);
459     //iaxc_output_level_set(speakerLevel);
460   }
461
462   if (prop == _micLevel_node && _enabled) {
463     float micLevel = prop->getFloatValue();
464     SG_CLAMP_RANGE<float>( micLevel, 0.0, 1.0 );
465     _micLevel_node->setFloatValue(micLevel);
466     //iaxc_input_level_set(micLevel);
467   }
468
469   if (prop == _comm0_node) {
470     if( _currentComm0 != prop->getDoubleValue() ) {
471       _currentComm0 = prop->getDoubleValue();
472       _p.stamp();
473       _comm0Changed = true;
474     }
475   }
476 /*
477   if (prop == _comm1_node) {
478     if( _currentComm1 != prop->getDoubleValue() ) {
479       _currentComm1 = prop->getDoubleValue();
480       _p.stamp();
481       _comm1Changed = true;
482     }
483   }
484
485   if (prop == _nav0_node) {
486     if( _currentNav0 != prop->getDoubleValue() ) {
487       _currentNav0 = prop->getDoubleValue();
488       _nav0Changed = true;
489     }
490   }
491
492   if (prop == _nav1_node) {
493     if( _currentNav1 != prop->getDoubleValue() ) {
494       _currentNav1 = prop->getDoubleValue();
495       _nav1Changed = true;
496     }
497   }
498 */
499
500   _listener_active--;
501 }
502
503
504
505 void FGCom::testMode(bool testMode)
506 {
507   if(testMode && _initialized) {
508     _enabled = false;
509     iaxc_dump_call_number(_callComm0);
510     iaxc_input_level_set( _micLevel_node->getFloatValue() );
511     iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
512     std::string num = computePhoneNumber(TEST_FREQ, NULL_ICAO);
513     if( num.size() > 0 ) {
514       iaxc_millisleep(IAX_DELAY);
515       _callComm0 = iaxc_call(num.c_str());
516     }
517     if( _callComm0 == -1 )
518       SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
519   } else {
520     if( _initialized ) {
521       iaxc_dump_call_number(_callComm0);
522       iaxc_millisleep(IAX_DELAY);
523       _callComm0 = -1;
524       _enabled = true;
525     }
526   }
527 }
528
529
530
531 /*
532   \param freq The requested frequency e.g 120.825
533   \return The ICAO code as string e.g LFMV
534 */
535
536 std::string FGCom::getAirportCode(const double& freq)
537 {
538   SGGeod aircraftPos = globals->get_aircraft_position();
539
540   for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
541     if(special_freq[i] == _currentFreqKhz) {
542       return NULL_ICAO;
543     }
544   }
545
546   flightgear::CommStation* apt = flightgear::CommStation::findByFreq(_currentFreqKhz, aircraftPos);
547   if( !apt ) {
548     return std::string();
549   }
550
551   if( apt->type() == FGPositioned::FREQ_TOWER ) {
552     _maxRange = MAX_TWR_RANGE;
553     _minRange = MIN_GNDTWR_RANGE;
554   } else if( apt->type() == FGPositioned::FREQ_GROUND ) {
555     _maxRange = MAX_GND_RANGE;
556     _minRange = MIN_GNDTWR_RANGE;
557   } else {
558     _maxRange = MAX_RANGE;
559     _minRange = MIN_RANGE;
560   }
561
562   _aptPos = apt->geod();
563   return apt->airport()->ident();
564 }
565
566
567
568 /*
569   \param freq The requested frequency e.g 112.7
570   \return The ICAO code as string e.g ITS
571 */
572 /*
573 std::string FGCom::getVorCode(const double& freq) const
574 {
575   SGGeod aircraftPos = globals->get_aircraft_position();
576   FGNavList::TypeFilter filter(FGPositioned::VOR);
577
578   FGNavRecord* vor = FGNavList::findByFreq( freq, aircraftPos, &filter);
579   if( !vor ) {
580     return std::string();
581   }
582
583   return vor->get_ident();
584 }
585 */
586
587
588 /*
589   \param freq The requested frequency e.g 120.825
590   \param iaco The associated ICAO code e.g LFMV
591   \return The phone number as string i.e username:password@fgcom.flightgear.org/0176707786120825
592 */
593
594 std::string FGCom::computePhoneNumber(const double& freq, const std::string& icao) const
595 {
596   if( icao.empty() )
597     return std::string(); 
598
599   char phoneNumber[256];
600   char exten[32];
601   char tmp[5];
602
603   /*Convert ICAO to ASCII */
604   sprintf( tmp, "%4s", icao.c_str() );
605
606   /*Built the phone number */
607   sprintf( exten,
608            "%02d%02d%02d%02d%02d%06d",
609            01,
610            tmp[0],
611            tmp[1],
612            tmp[2],
613            tmp[3],
614            (int) (freq * 1000 + 0.5) );
615   exten[16] = '\0';
616
617   snprintf( phoneNumber,
618             sizeof (phoneNumber),
619             "%s:%s@%s/%s",
620             _username.c_str(),
621             _password.c_str(),
622             _server.c_str(),
623             exten);
624
625   return phoneNumber;
626 }
627
628
629
630 /*
631   \return A boolean value, 1=in range, 0=out of range
632 */
633
634 bool FGCom::isInRange(const double &freq) const
635 {
636     for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
637       if( (special_freq[i]) == _currentFreqKhz ) {
638         return 1;
639       }
640     }
641
642     SGGeod acftPos = globals->get_aircraft_position();
643     double distNm = SGGeodesy::distanceNm(_aptPos, acftPos);
644     double delta_elevation_ft = fabs(acftPos.getElevationFt() - _aptPos.getElevationFt());
645     double rangeNm = 1.23 * sqrt(delta_elevation_ft);
646
647     if (rangeNm > _maxRange) rangeNm = _maxRange;
648     if (rangeNm < _minRange) rangeNm = _minRange;
649     if( distNm > rangeNm )   return 0;
650     return 1;
651 }
652
653