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