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