1 // fgcom.cxx -- FGCom: Voice communication
3 // Written by Clement de l'Hamaide, started May 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 <3rdparty/iaxclient/lib/iaxclient.h>
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"
56 const int special_freq[] = { // Define some freq who need to be used with NULL_ICAO
65 static FGCom* static_instance = NULL;
69 static int iaxc_callback( iaxc_event e )
74 static_instance->iaxTextEvent(e.ev.text);
84 void FGCom::iaxTextEvent(struct iaxc_ev_text text)
86 if( (text.type == IAXC_TEXT_TYPE_STATUS ||
87 text.type == IAXC_TEXT_TYPE_IAX) &&
88 _showMessages_node->getBoolValue() )
90 _text_node->setStringValue(text.message);
114 SGPropertyNode *node = fgGetNode("/sim/fgcom", 0, true);
115 _test_node = node->getChild( "test", 0, true );
116 _server_node = node->getChild( "server", 0, true );
117 _enabled_node = node->getChild( "enabled", 0, true );
118 _micBoost_node = node->getChild( "mic-boost", 0, true );
119 _micLevel_node = node->getChild( "mic-level", 0, true );
120 _silenceThd_node = node->getChild( "silence-threshold", 0, true );
121 _speakerLevel_node = node->getChild( "speaker-level", 0, true );
122 _selectedInput_node = node->getChild( "device-input", 0, true );
123 _selectedOutput_node = node->getChild( "device-output", 0, true );
124 _showMessages_node = node->getChild( "show-messages", 0, true );
126 SGPropertyNode *reg_node = node->getChild("register", 0, true);
127 _register_node = reg_node->getChild( "enabled", 0, true );
128 _username_node = reg_node->getChild( "username", 0, true );
129 _password_node = reg_node->getChild( "password", 0, true );
131 _comm0_node = fgGetNode("/instrumentation/comm[0]/frequencies/selected-mhz", true);
132 _comm1_node = fgGetNode("/instrumentation/comm[1]/frequencies/selected-mhz", true);
133 _ptt0_node = fgGetNode("/instrumentation/comm[0]/ptt", true); //FIXME: what about /instrumentation/comm[1]/ptt ?
134 _callsign_node = fgGetNode("/sim/multiplay/callsign", true);
135 _text_node = fgGetNode("/sim/messages/atc", true );
136 _version_node = fgGetNode("/sim/version/flightgear", true );
138 // Set default values if not provided
139 if ( !_enabled_node->hasValue() )
140 _enabled_node->setBoolValue(true);
142 if ( !_test_node->hasValue() )
143 _test_node->setBoolValue(false);
145 if ( !_micBoost_node->hasValue() )
146 _micBoost_node->setIntValue(1);
148 if ( !_server_node->hasValue() )
149 _server_node->setStringValue(DEFAULT_SERVER);
151 if ( !_speakerLevel_node->hasValue() )
152 _speakerLevel_node->setFloatValue(1.0);
154 if ( !_micLevel_node->hasValue() )
155 _micLevel_node->setFloatValue(1.0);
157 if ( !_silenceThd_node->hasValue() )
158 _silenceThd_node->setFloatValue(-35.0);
160 if ( !_register_node->hasValue() )
161 _register_node->setBoolValue(false);
163 if ( !_username_node->hasValue() )
164 _username_node->setStringValue("guest");
166 if ( !_password_node->hasValue() )
167 _password_node->setStringValue("guest");
169 if ( !_showMessages_node->hasValue() )
170 _showMessages_node->setBoolValue(false);
172 _selectedOutput_node->addChangeListener(this);
173 _selectedInput_node->addChangeListener(this);
174 _speakerLevel_node->addChangeListener(this);
175 _silenceThd_node->addChangeListener(this);
176 _micBoost_node->addChangeListener(this);
177 _micLevel_node->addChangeListener(this);
178 _enabled_node->addChangeListener(this);
179 _comm0_node->addChangeListener(this);
180 _comm1_node->addChangeListener(this);
181 _ptt0_node->addChangeListener(this);
182 _test_node->addChangeListener(this);
195 _enabled = _enabled_node->getBoolValue();
196 _server = _server_node->getStringValue();
197 _register = _register_node->getBoolValue();
198 _username = _username_node->getStringValue();
199 _password = _password_node->getStringValue();
202 _currentComm0 = _comm0_node->getDoubleValue();
203 _currentComm1 = _comm1_node->getDoubleValue();
205 _comm0Changed = false;
206 _comm1Changed = false;
208 _maxRange = MAX_RANGE;
209 _minRange = MIN_RANGE;
214 void FGCom::postinit()
220 //WARNING: this _must_ be executed after sound system is totally initialized !
221 if( iaxc_initialize(NUM_CALLS) ) {
222 SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot initialize iaxclient");
227 assert( static_instance == NULL );
228 static_instance = this;
229 iaxc_set_event_callback( iaxc_callback );
231 // FIXME: To be implemented in IAX audio driver
232 //iaxc_mic_boost_set( _micBoost_node->getIntValue() );
233 std::string app = "FGFS-";
234 app += _version_node->getStringValue();
236 iaxc_set_callerid( _callsign_node->getStringValue(), app.c_str() );
237 iaxc_set_formats (IAXC_FORMAT_SPEEX, IAXC_FORMAT_ULAW|IAXC_FORMAT_SPEEX);
238 iaxc_set_speex_settings(1, 5, 0, 1, 0, 3);
239 iaxc_set_filters(IAXC_FILTER_AGC | IAXC_FILTER_DENOISE);
240 iaxc_set_silence_threshold(_silenceThd_node->getFloatValue());
241 iaxc_start_processing_thread ();
243 // Now IAXClient is initialized
247 _regId = iaxc_register( const_cast<char*>(_username.c_str()),
248 const_cast<char*>(_password.c_str()),
249 const_cast<char*>(_server.c_str()) );
251 SG_LOG(SG_IO, SG_ALERT, "FGCom: cannot register iaxclient");
257 Here we will create the list of available audio devices
258 Each audio device has a name, an ID, and a list of capabilities
259 If an audio device can output sound, available-output=true
260 If an audio device can input sound, available-input=true
262 /sim/fgcom/selected-input (int)
263 /sim/fgcom/selected-output (int)
265 /sim/fgcom/device[n]/id (int)
266 /sim/fgcom/device[n]/name (string)
267 /sim/fgcom/device[n]/available-input (bool)
268 /sim/fgcom/device[n]/available-output (bool)
271 //FIXME: OpenAL driver use an hard-coded device
272 // so all following is unused finally until someone
273 // implement "multi-device" support in IAX audio driver
274 SGPropertyNode *node = fgGetNode("/sim/fgcom", 0, true);
276 struct iaxc_audio_device *devs;
277 int nDevs, input, output, ring;
279 iaxc_audio_devices_get(&devs,&nDevs, &input, &output, &ring);
281 for(int i=0; i<nDevs; i++ ) {
282 SGPropertyNode *in_node = node->getChild("device", i, true);
285 _deviceID_node[i] = in_node->getChild("id", 0, true);
286 _deviceID_node[i]->setIntValue(devs[i].devID);
289 _deviceName_node[i] = in_node->getChild("name", 0, true);
290 _deviceName_node[i]->setStringValue(devs[i].name);
293 _deviceInput_node[i] = in_node->getChild("available-input", 0, true);
294 if( devs[i].capabilities & IAXC_AD_INPUT )
295 _deviceInput_node[i]->setBoolValue(true);
297 _deviceInput_node[i]->setBoolValue(false);
300 _deviceOutput_node[i] = in_node->getChild("available-output", 0, true);
301 if( devs[i].capabilities & IAXC_AD_OUTPUT )
302 _deviceOutput_node[i]->setBoolValue(true);
304 _deviceOutput_node[i]->setBoolValue(false);
306 // use default device at start
307 if( devs[i].capabilities & IAXC_AD_INPUT_DEFAULT )
308 _selectedInput_node->setIntValue(devs[i].devID);
309 if( devs[i].capabilities & IAXC_AD_OUTPUT_DEFAULT )
310 _selectedOutput_node->setIntValue(devs[i].devID);
313 // Mute the mic and set speaker at start
314 iaxc_input_level_set( 0.0 );
315 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
319 // Do the first call at start
320 const double freq = _comm0_node->getDoubleValue();
321 _currentFreqKhz = 10 * static_cast<int>(freq * 100 + 0.25);
322 std::string num = computePhoneNumber(freq, getAirportCode(freq));
324 _callComm0 = iaxc_call(num.c_str());
326 if( _callComm0 == -1 )
327 SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
332 void FGCom::updateCall(bool& changed, int& callNo, double freqMHz)
335 _currentFreqKhz = 10 * static_cast<int>(freqMHz * 100 + 0.25);
338 if( !isInRange(freqMHz) ) {
339 iaxc_dump_call_number(callNo);
348 changed = false; // FIXME, out-params are confusing
351 iaxc_dump_call_number( callNo );
355 if(_p.elapsedMSec() > IAX_DELAY) {
356 std::string num = computePhoneNumber(freqMHz, getAirportCode(freqMHz));
357 if( !isInRange(freqMHz) )
360 callNo = iaxc_call(num.c_str());
363 SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
372 void FGCom::update(double dt)
374 if ( !_enabled || !_initialized ) {
378 // For now we manage FGCom for only one freq because IAXClient
379 // is not able to handle multiple calls at same time.
380 if( _selectedComm == 0) {
381 updateCall(_comm0Changed, _callComm0, _comm0_node->getDoubleValue());
383 updateCall(_comm1Changed, _callComm0, _comm1_node->getDoubleValue());
389 void FGCom::shutdown()
395 _initialized = false;
398 iaxc_set_event_callback(NULL);
399 iaxc_unregister(_regId);
400 iaxc_stop_processing_thread();
403 assert( static_instance == this );
404 static_instance = NULL;
409 void FGCom::valueChanged(SGPropertyNode *prop)
411 if (prop == _enabled_node) {
412 bool isEnabled = prop->getBoolValue();
413 if (_enabled == isEnabled) {
426 if (prop == _ptt0_node && _enabled) {
427 if( _ptt0_node->getIntValue() == 2 ) {
428 if( _selectedComm == 0 ) {
429 SG_LOG( SG_IO, SG_INFO, "FGCom: change comm source to comm[1]" );
430 _comm1Changed = true;
433 SG_LOG( SG_IO, SG_INFO, "FGCom: change comm source to comm[0]" );
434 _comm0Changed = true;
439 if( _ptt0_node->getBoolValue() ) {
440 iaxc_output_level_set( 0.0 );
441 iaxc_input_level_set( _micLevel_node->getFloatValue() ); //0.0 = min , 1.0 = max
443 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
444 iaxc_input_level_set( 0.0 );
448 if (prop == _test_node) {
449 testMode( prop->getBoolValue() );
453 if (prop == _silenceThd_node && _initialized) {
454 float silenceThd = prop->getFloatValue();
455 SG_CLAMP_RANGE<float>( silenceThd, -60, 0 );
456 iaxc_set_silence_threshold( silenceThd );
460 //FIXME: not implemented in IAX audio driver (audio_openal.c)
461 if (prop == _micBoost_node && _initialized) {
462 int micBoost = prop->getIntValue();
463 SG_CLAMP_RANGE<int>( micBoost, 0, 1 );
464 iaxc_mic_boost_set( micBoost ) ; // 0 = enabled , 1 = disabled
468 //FIXME: not implemented in IAX audio driver (audio_openal.c)
469 if ((prop == _selectedInput_node || prop == _selectedOutput_node) && _initialized) {
470 int selectedInput = _selectedInput_node->getIntValue();
471 int selectedOutput = _selectedOutput_node->getIntValue();
472 iaxc_audio_devices_set(selectedInput, selectedOutput, 0);
476 if (_listener_active)
481 if (prop == _speakerLevel_node && _enabled) {
482 float speakerLevel = prop->getFloatValue();
483 SG_CLAMP_RANGE<float>( speakerLevel, 0.0, 1.0 );
484 _speakerLevel_node->setFloatValue(speakerLevel);
485 iaxc_output_level_set(speakerLevel);
488 if (prop == _micLevel_node && _enabled) {
489 float micLevel = prop->getFloatValue();
490 SG_CLAMP_RANGE<float>( micLevel, 0.0, 1.0 );
491 _micLevel_node->setFloatValue(micLevel);
492 //iaxc_input_level_set(micLevel);
495 if (prop == _comm0_node) {
496 if( _currentComm0 != prop->getDoubleValue() ) {
497 _currentComm0 = prop->getDoubleValue();
499 _comm0Changed = true;
503 if (prop == _comm1_node) {
504 if( _currentComm1 != prop->getDoubleValue() ) {
505 _currentComm1 = prop->getDoubleValue();
507 _comm1Changed = true;
516 void FGCom::testMode(bool testMode)
518 if(testMode && _initialized) {
520 iaxc_dump_all_calls();
521 iaxc_input_level_set( 1.0 );
522 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
523 std::string num = computePhoneNumber(TEST_FREQ, NULL_ICAO);
524 if( num.size() > 0 ) {
525 iaxc_millisleep(IAX_DELAY);
526 _callComm0 = iaxc_call(num.c_str());
528 if( _callComm0 == -1 )
529 SG_LOG( SG_IO, SG_DEBUG, "FGCom: cannot call " << num.c_str() );
532 iaxc_dump_all_calls();
533 iaxc_millisleep(IAX_DELAY);
534 iaxc_input_level_set( 0.0 );
535 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
545 \param freq The requested frequency e.g 120.825
546 \return The ICAO code as string e.g LFMV
549 std::string FGCom::getAirportCode(const double& freq)
551 SGGeod aircraftPos = globals->get_aircraft_position();
553 for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
554 if(special_freq[i] == _currentFreqKhz) {
559 flightgear::CommStation* apt = flightgear::CommStation::findByFreq(_currentFreqKhz, aircraftPos);
561 apt = flightgear::CommStation::findByFreq(_currentFreqKhz-10, aircraftPos); // Check for 8.33KHz
563 return std::string();
567 if( apt->type() == FGPositioned::FREQ_TOWER ) {
568 _maxRange = MAX_TWR_RANGE;
569 _minRange = MIN_GNDTWR_RANGE;
570 } else if( apt->type() == FGPositioned::FREQ_GROUND ) {
571 _maxRange = MAX_GND_RANGE;
572 _minRange = MIN_GNDTWR_RANGE;
574 _maxRange = MAX_RANGE;
575 _minRange = MIN_RANGE;
578 _aptPos = apt->geod();
579 return apt->airport()->ident();
585 \param freq The requested frequency e.g 120.825
586 \param iaco The associated ICAO code e.g LFMV
587 \return The phone number as string i.e username:password@fgcom.flightgear.org/0176707786120825
590 std::string FGCom::computePhoneNumber(const double& freq, const std::string& icao) const
593 return std::string();
595 char phoneNumber[256];
599 /*Convert ICAO to ASCII */
600 sprintf( tmp, "%4s", icao.c_str() );
602 /*Built the phone number */
604 "%02d%02d%02d%02d%02d%06d",
610 (int) (freq * 1000 + 0.5) );
613 snprintf( phoneNumber,
614 sizeof (phoneNumber),
627 \return A boolean value, 1=in range, 0=out of range
630 bool FGCom::isInRange(const double &freq) const
632 for(size_t i=0; i<sizeof(special_freq)/sizeof(special_freq[0]); i++) { // Check if it's a special freq
633 if( (special_freq[i]) == _currentFreqKhz ) {
638 SGGeod acftPos = globals->get_aircraft_position();
639 double distNm = SGGeodesy::distanceNm(_aptPos, acftPos);
640 double delta_elevation_ft = fabs(acftPos.getElevationFt() - _aptPos.getElevationFt());
641 double rangeNm = 1.23 * sqrt(delta_elevation_ft);
643 if (rangeNm > _maxRange) rangeNm = _maxRange;
644 if (rangeNm < _minRange) rangeNm = _minRange;
645 if( distNm > rangeNm ) return 0;