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