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