]> git.mxchange.org Git - flightgear.git/blob - src/Network/fgcom.cxx
5d9319c2053139b9eaf2d7a94354ec2858200205
[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     _currentFreqKhz = 10 * static_cast<int>(freq * 100 + 0.25);
263     std::string num = computePhoneNumber(freq, getAirportCode(freq));
264     if( !num.empty() ) {
265       SG_LOG( SG_IO, SG_INFO, "FGCom comm[0] number=" << num );
266       _callComm0 = iaxc_call(num.c_str());
267     }
268     if( _callComm0 == -1 )
269       SG_LOG( SG_IO, SG_ALERT, "FGCom cannot call selected freq" );
270 }
271
272
273
274 void FGCom::updateCall(bool& changed, int& callNo, double freqMHz)
275 {
276
277     _currentFreqKhz = 10 * static_cast<int>(freqMHz * 100 + 0.25);
278
279     if (!changed) {
280         if( !isInRange(freqMHz) ) {
281             iaxc_dump_call_number(callNo);
282             callNo = -1;
283             return;
284         } else {
285             if(callNo != -1)
286                 return;
287         }
288     }
289     
290     SG_LOG( SG_IO, SG_INFO, "FGCom manage change" );
291     changed = false; // FIXME, out-params are confusing
292
293     if( callNo != -1 ) {
294         iaxc_dump_call_number( callNo );
295         callNo = -1;
296     }
297
298     if(_p.elapsedMSec() > IAX_DELAY) {
299         std::string num = computePhoneNumber(freqMHz, getAirportCode(freqMHz));
300         if( !isInRange(freqMHz) )
301             return;
302         if( !num.empty() ) {
303             SG_LOG( SG_IO, SG_INFO, "FGCom number=" << num );
304             callNo = iaxc_call_ex(num.c_str(), _callsign.c_str(), NULL, 0 /* no video */);
305
306             if( callNo == -1 )
307                 SG_LOG( SG_IO, SG_ALERT, "FGCom cannot call selected freq" );
308         }
309     } else {
310         changed = true;
311     }
312 }
313
314
315
316 void FGCom::update(double dt)
317 {
318     if ( !_enabled ) {
319         return;
320     }
321     // For now we manage FGCom for only one freq because IAXClient
322     // is not able to handle multiple calls at same time.
323     updateCall(_comm0Changed, _callComm0, _comm0_node->getDoubleValue());
324     // updateCall(_comm1Changed, _callComm1, _comm1_node->getDoubleValue());
325     // updateCall(_nav0Changed, _callNav0, _nav0_node->getDoubleValue());
326     // updateCall(_nav1Changed, _callNav1, _nav1_node->getDoubleValue());
327 }
328
329
330
331 void FGCom::shutdown()
332 {
333   SG_LOG( SG_IO, SG_INFO, "FGCom shutdown()" );
334   _enabled = false;
335
336   iaxc_unregister(_regId);
337   iaxc_stop_processing_thread();
338   iaxc_shutdown();
339 }
340
341
342
343 void FGCom::valueChanged(SGPropertyNode *prop)
344 {
345   if (prop == _enabled_node) {
346     SG_LOG( SG_IO, SG_INFO, "FGCom enabled= " << prop->getBoolValue() );
347     if( prop->getBoolValue() ) {
348       init();
349       postinit();
350     } else {
351       shutdown();
352     }
353     return;
354   }
355
356   if (prop == _ptt0_node && _enabled) {
357     if( _ptt0_node->getBoolValue() ) {
358       iaxc_input_level_set( _micLevel_node->getFloatValue() ); //0.0 = min , 1.0 = max
359       iaxc_output_level_set( 0.0 );
360     } else {
361       iaxc_input_level_set( 0.0 );
362       iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
363     }
364   }
365
366   if (prop == _test_node) {
367     SG_LOG( SG_IO, SG_INFO, "FGCom test= " << prop->getBoolValue() );
368     testMode( prop->getBoolValue() );
369     return;
370   }
371
372   //FIXME: not implemented in IAX audio driver (audio_openal.c)
373   if (prop == _micBoost_node && _enabled) {
374     int micBoost = prop->getIntValue();
375     SG_LOG( SG_IO, SG_INFO, "FGCom mic-boost= " << micBoost );
376     SG_CLAMP_RANGE<int>( micBoost, 0, 1 );
377     iaxc_mic_boost_set( micBoost ) ; // 0 = enabled , 1 = disabled
378     return;
379   }
380
381   //FIXME: not implemented in IAX audio driver (audio_openal.c)
382   if ((prop == _selectedInput_node || prop == _selectedOutput_node) && _enabled) {
383     int selectedInput = _selectedInput_node->getIntValue();
384     int selectedOutput = _selectedOutput_node->getIntValue();
385     SG_LOG( SG_IO, SG_INFO, "FGCom selected-input= " << selectedInput );
386     SG_LOG( SG_IO, SG_INFO, "FGCom selected-output= " << selectedOutput );
387     iaxc_audio_devices_set(selectedInput, selectedOutput, 0);
388     return;
389   }
390
391   if (_listener_active)
392     return;
393
394   _listener_active++;
395
396   if (prop == _speakerLevel_node && _enabled) {
397     float speakerLevel = prop->getFloatValue();
398     SG_LOG( SG_IO, SG_INFO, "FGCom speaker-level= " << speakerLevel );
399     SG_CLAMP_RANGE<float>( speakerLevel, 0.0, 1.0 );
400     _speakerLevel_node->setFloatValue(speakerLevel);
401     iaxc_output_level_set(speakerLevel);
402   }
403
404   if (prop == _micLevel_node && _enabled) {
405     float micLevel = prop->getFloatValue();
406     SG_LOG( SG_IO, SG_INFO, "FGCom mic-level= " << micLevel );
407     SG_CLAMP_RANGE<float>( micLevel, 0.0, 1.0 );
408     _micLevel_node->setFloatValue(micLevel);
409     iaxc_input_level_set(micLevel);
410   }
411
412   if (prop == _comm0_node) {
413     if( _currentComm0 != prop->getDoubleValue() ) {
414       SG_LOG( SG_IO, SG_INFO, "FGCom comm[0]/freq= " << prop->getDoubleValue() );
415       _currentComm0 = prop->getDoubleValue();
416       _p.stamp();
417       _comm0Changed = true;
418     }
419   }
420 /*
421   if (prop == _comm1_node) {
422     if( _currentComm1 != prop->getDoubleValue() ) {
423       SG_LOG( SG_IO, SG_INFO, "FGCom comm[1]/freq= " << prop->getDoubleValue() );
424       _currentComm1 = prop->getDoubleValue();
425       _p.stamp();
426       _comm1Changed = true;
427     }
428   }
429
430   if (prop == _nav0_node) {
431     if( _currentNav0 != prop->getDoubleValue() ) {
432       SG_LOG( SG_IO, SG_INFO, "FGCom nav[0]/freq= " << prop->getDoubleValue() );
433       _currentNav0 = prop->getDoubleValue();
434       _nav0Changed = true;
435     }
436   }
437
438   if (prop == _nav1_node) {
439     if( _currentNav1 != prop->getDoubleValue() ) {
440       SG_LOG( SG_IO, SG_INFO, "FGCom nav[1]/freq= " << prop->getDoubleValue() );
441       _currentNav1 = prop->getDoubleValue();
442       _nav1Changed = true;
443     }
444   }
445 */
446
447   _listener_active--;
448 }
449
450
451
452 void FGCom::testMode(bool testMode)
453 {
454   if(testMode) {
455     _enabled = false;
456     iaxc_dump_call_number(_callComm0);
457     iaxc_input_level_set( _micLevel_node->getFloatValue() );
458     iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
459     std::string num = computePhoneNumber(TEST_FREQ, NULL_ICAO);
460     if( num.size() > 0 ) {
461       SG_LOG( SG_IO, SG_INFO, "FGCom test mode =" << num );
462       iaxc_millisleep(IAX_DELAY);
463       _callComm0 = iaxc_call(num.c_str());
464     }
465     if( _callComm0 == -1 )
466       SG_LOG( SG_IO, SG_ALERT, "FGCom cannot call selected freq (test mode)" );
467   } else {
468     iaxc_dump_call_number(_callComm0);
469     iaxc_millisleep(IAX_DELAY);
470     _callComm0 = -1;
471     _enabled = true;
472   }
473 }
474
475
476
477 /*
478   \param freq The requested frequency e.g 120.825
479   \return The ICAO code as string e.g LFMV
480 */
481
482 std::string FGCom::getAirportCode(const double& freq)
483 {
484   SGGeod aircraftPos = globals->get_aircraft_position();
485
486   for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
487     if(special_freq[i] == _currentFreqKhz) {
488       SG_LOG( SG_IO, SG_INFO, "FGCom getAirportCode: " << freq << " is specially associated to " << NULL_ICAO );
489       return NULL_ICAO;
490     }
491   }
492
493   flightgear::CommStation* apt = flightgear::CommStation::findByFreq(_currentFreqKhz, aircraftPos);
494   if( !apt ) {
495     SG_LOG( SG_IO, SG_INFO, "FGCom getAirportCode: not found" );
496     return std::string();
497   }
498   SG_LOG( SG_IO, SG_INFO, "FGCom getAirportCode: found " << apt->airport()->ident() );
499
500   _aptPos = apt->geod();
501   return apt->airport()->ident();
502 }
503
504
505
506 /*
507   \param freq The requested frequency e.g 112.7
508   \return The ICAO code as string e.g ITS
509 */
510 /*
511 std::string FGCom::getVorCode(const double& freq) const
512 {
513   SGGeod aircraftPos = globals->get_aircraft_position();
514   FGNavList::TypeFilter filter(FGPositioned::VOR);
515
516   FGNavRecord* vor = FGNavList::findByFreq( freq, aircraftPos, &filter);
517   if( !vor ) {
518     SG_LOG( SG_IO, SG_INFO, "FGCom getVorCode: not found" );
519     return std::string();
520   }
521   SG_LOG( SG_IO, SG_INFO, "FGCom getVorCode: found " << vor->get_ident(); );
522
523   return vor->get_ident();
524 }
525 */
526
527
528 /*
529   \param freq The requested frequency e.g 120.825
530   \param iaco The associated ICAO code e.g LFMV
531   \return The phone number as string i.e username:password@fgcom.flightgear.org/0176707786120825
532 */
533
534 std::string FGCom::computePhoneNumber(const double& freq, const std::string& icao) const
535 {
536   if( icao.empty() )
537     return std::string(); 
538
539   char phoneNumber[256];
540   char exten[32];
541   char tmp[5];
542
543   /*Convert ICAO to ASCII */
544   sprintf( tmp, "%4s", icao.c_str() );
545
546   /*Built the phone number */
547   sprintf( exten,
548            "%02d%02d%02d%02d%02d%06d",
549            01,
550            tmp[0],
551            tmp[1],
552            tmp[2],
553            tmp[3],
554            (int) (freq * 1000 + 0.5) );
555   exten[16] = '\0';
556
557   snprintf( phoneNumber,
558             sizeof (phoneNumber),
559             "%s:%s@%s/%s",
560             _username.c_str(),
561             _password.c_str(),
562             _server.c_str(),
563             exten);
564
565   return phoneNumber;
566 }
567
568
569
570 /*
571   \return A boolean value, 1=in range, 0=out of range
572 */
573
574 bool FGCom::isInRange(const double &freq) const
575 {
576     for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
577       if( (special_freq[i]) == _currentFreqKhz ) {
578         return 1;
579       }
580     }
581
582     SGGeod acftPos = globals->get_aircraft_position();
583     double distNm = SGGeodesy::distanceNm(_aptPos, acftPos);
584     double delta_elevation_ft = fabs(acftPos.getElevationFt() - _aptPos.getElevationFt());
585     double rangeNm = 1.23 * sqrt(delta_elevation_ft);
586
587     if (rangeNm > MAX_RANGE) rangeNm = MAX_RANGE;
588     if (rangeNm < MIN_RANGE) rangeNm = MIN_RANGE;
589     if( distNm > rangeNm )   return 0;
590     return 1;
591 }
592
593