1 // fgcom.cxx -- FGCom: Voice communication
3 // Written by Clement de l'Hamaide, started Mai 2013.
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.
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.
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.
25 // standard library 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>
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>
42 #include <utils/iaxclient/lib/iaxclient.h>
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"
53 const int special_freq[] = { // Define some freq who need to be used with NULL_ICAO
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 );
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 );
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);
103 // Set default values if not provided
104 if ( !_enabled_node->hasValue() )
105 _enabled_node->setBoolValue(true);
107 if ( !_test_node->hasValue() )
108 _test_node->setBoolValue(false);
110 if ( !_micBoost_node->hasValue() )
111 _micBoost_node->setIntValue(1);
113 if ( !_server_node->hasValue() )
114 _server_node->setStringValue(DEFAULT_SERVER);
116 if ( !_speakerLevel_node->hasValue() )
117 _speakerLevel_node->setFloatValue(1.0);
119 if ( !_micLevel_node->hasValue() )
120 _micLevel_node->setFloatValue(1.0);
122 if ( !_register_node->hasValue() )
123 _register_node->setBoolValue(false);
125 if ( !_username_node->hasValue() )
126 _username_node->setStringValue("guest");
128 if ( !_password_node->hasValue() )
129 _password_node->setStringValue("guest");
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);
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();
161 _currentComm0 = _comm0_node->getDoubleValue();
162 //_currentComm1 = _comm1_node->getDoubleValue();
163 //_currentNav0 = _nav0_node->getDoubleValue();
164 //_currentNav1 = _nav1_node->getDoubleValue();
166 _comm0Changed = false;
167 //_comm1Changed = false;
168 //_nav0Changed = false;
169 //_nav1Changed = false;
174 void FGCom::postinit()
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!");
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 ();
192 // Now IAXClient is initialized
196 _regId = iaxc_register( const_cast<char*>(_username.c_str()),
197 const_cast<char*>(_password.c_str()),
198 const_cast<char*>(_server.c_str()) );
200 SG_LOG(SG_IO, SG_INFO, "FGCom: cannot register iaxclient!");
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
211 /sim/fgcom/selected-input (int)
212 /sim/fgcom/selected-output (int)
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)
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);
225 struct iaxc_audio_device *devs;
226 int nDevs, input, output, ring;
228 iaxc_audio_devices_get(&devs,&nDevs, &input, &output, &ring);
230 for(int i=0; i<nDevs; i++ ) {
231 SGPropertyNode *in_node = node->getChild("device", i, true);
234 _deviceID_node[i] = in_node->getChild("id", 0, true);
235 _deviceID_node[i]->setIntValue(devs[i].devID);
238 _deviceName_node[i] = in_node->getChild("name", 0, true);
239 _deviceName_node[i]->setStringValue(devs[i].name);
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);
246 _deviceInput_node[i]->setBoolValue(false);
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);
253 _deviceOutput_node[i]->setBoolValue(false);
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);
262 // Mute the mic and set speaker at start
263 iaxc_input_level_set( 0.0 );
264 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
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));
273 SG_LOG( SG_IO, SG_INFO, "FGCom comm[0] number=" << num );
274 _callComm0 = iaxc_call(num.c_str());
276 if( _callComm0 == -1 )
277 SG_LOG( SG_IO, SG_ALERT, "FGCom cannot call selected freq" );
282 void FGCom::updateCall(bool& changed, int& callNo, double freqMHz)
285 _currentFreqKhz = 10 * static_cast<int>(freqMHz * 100 + 0.25);
288 if( !isInRange(freqMHz) ) {
289 iaxc_dump_call_number(callNo);
298 SG_LOG( SG_IO, SG_INFO, "FGCom manage change" );
299 changed = false; // FIXME, out-params are confusing
302 iaxc_dump_call_number( callNo );
306 if(_p.elapsedMSec() > IAX_DELAY) {
307 std::string num = computePhoneNumber(freqMHz, getAirportCode(freqMHz));
308 if( !isInRange(freqMHz) )
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 */);
315 SG_LOG( SG_IO, SG_ALERT, "FGCom cannot call selected freq" );
324 void FGCom::update(double dt)
326 if ( !_enabled || !_initialized ) {
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 );
334 iaxc_input_level_set( 0.0 );
335 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
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());
348 void FGCom::shutdown()
354 SG_LOG( SG_IO, SG_INFO, "FGCom shutdown()" );
355 _initialized = false;
358 iaxc_unregister(_regId);
359 iaxc_stop_processing_thread();
365 void FGCom::valueChanged(SGPropertyNode *prop)
367 if (prop == _enabled_node) {
368 SG_LOG( SG_IO, SG_INFO, "FGCom enabled= " << prop->getBoolValue() );
369 if( prop->getBoolValue() ) {
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 );
383 iaxc_input_level_set( 0.0 );
384 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
388 if (prop == _test_node) {
389 SG_LOG( SG_IO, SG_INFO, "FGCom test= " << prop->getBoolValue() );
390 testMode( prop->getBoolValue() );
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
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);
413 if (_listener_active)
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);
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);
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();
439 _comm0Changed = true;
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();
448 _comm1Changed = true;
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();
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();
474 void FGCom::testMode(bool testMode)
476 if(testMode && _initialized) {
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());
487 if( _callComm0 == -1 )
488 SG_LOG( SG_IO, SG_ALERT, "FGCom cannot call selected freq (test mode)" );
491 iaxc_dump_call_number(_callComm0);
492 iaxc_millisleep(IAX_DELAY);
502 \param freq The requested frequency e.g 120.825
503 \return The ICAO code as string e.g LFMV
506 std::string FGCom::getAirportCode(const double& freq)
508 SGGeod aircraftPos = globals->get_aircraft_position();
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 );
517 flightgear::CommStation* apt = flightgear::CommStation::findByFreq(_currentFreqKhz, aircraftPos);
519 SG_LOG( SG_IO, SG_INFO, "FGCom getAirportCode: not found" );
520 return std::string();
522 SG_LOG( SG_IO, SG_INFO, "FGCom getAirportCode: found " << apt->airport()->ident() );
524 _aptPos = apt->geod();
525 return apt->airport()->ident();
531 \param freq The requested frequency e.g 112.7
532 \return The ICAO code as string e.g ITS
535 std::string FGCom::getVorCode(const double& freq) const
537 SGGeod aircraftPos = globals->get_aircraft_position();
538 FGNavList::TypeFilter filter(FGPositioned::VOR);
540 FGNavRecord* vor = FGNavList::findByFreq( freq, aircraftPos, &filter);
542 SG_LOG( SG_IO, SG_INFO, "FGCom getVorCode: not found" );
543 return std::string();
545 SG_LOG( SG_IO, SG_INFO, "FGCom getVorCode: found " << vor->get_ident(); );
547 return vor->get_ident();
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
558 std::string FGCom::computePhoneNumber(const double& freq, const std::string& icao) const
561 return std::string();
563 char phoneNumber[256];
567 /*Convert ICAO to ASCII */
568 sprintf( tmp, "%4s", icao.c_str() );
570 /*Built the phone number */
572 "%02d%02d%02d%02d%02d%06d",
578 (int) (freq * 1000 + 0.5) );
581 snprintf( phoneNumber,
582 sizeof (phoneNumber),
595 \return A boolean value, 1=in range, 0=out of range
598 bool FGCom::isInRange(const double &freq) const
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 ) {
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);
611 if (rangeNm > MAX_RANGE) rangeNm = MAX_RANGE;
612 if (rangeNm < MIN_RANGE) rangeNm = MIN_RANGE;
613 if( distNm > rangeNm ) return 0;