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