27#include <simgear/compiler.h>
28#include <simgear/sg_inlines.h>
29#include <simgear/debug/logstream.hxx>
30#include <simgear/math/sg_geodesy.hxx>
31#include <simgear/timing/timestamp.hxx>
45#define MAX_GND_RANGE 10.0
46#define MAX_TWR_RANGE 50.0
47#define MAX_RANGE 100.0
49#define MIN_GNDTWR_RANGE 0.0
50#define DEFAULT_SERVER "fgcom.flightgear.org"
52#define TEST_FREQ 910.00
53#define NULL_ICAO "ZZZZ"
85 if( (text.type == IAXC_TEXT_TYPE_STATUS ||
86 text.type == IAXC_TEXT_TYPE_IAX) &&
87 _showMessages_node->getBoolValue() )
89 _text_node->setStringValue(text.message);
93 if ((text.type == IAXC_TEXT_TYPE_ERROR) || (text.type == IAXC_TEXT_TYPE_FATALERROR)) {
97 SG_LOG(SG_SOUND, level, std::string{
"FCCom IAX:"} + text.message);
118 SGPropertyNode *node =
fgGetNode(
"/sim/fgcom", 0,
true);
119 _test_node = node->getChild(
"test", 0,
true );
120 _server_node = node->getChild(
"server", 0,
true );
121 _enabled_node = node->getChild(
"enabled", 0,
true );
122 _micBoost_node = node->getChild(
"mic-boost", 0,
true );
123 _micLevel_node = node->getChild(
"mic-level", 0,
true );
124 _silenceThd_node = node->getChild(
"silence-threshold", 0,
true );
125 _speakerLevel_node = node->getChild(
"speaker-level", 0,
true );
126 _selectedInput_node = node->getChild(
"device-input", 0,
true );
127 _selectedOutput_node = node->getChild(
"device-output", 0,
true );
128 _showMessages_node = node->getChild(
"show-messages", 0,
true );
130 SGPropertyNode *reg_node = node->getChild(
"register", 0,
true);
131 _register_node = reg_node->getChild(
"enabled", 0,
true );
132 _username_node = reg_node->getChild(
"username", 0,
true );
133 _password_node = reg_node->getChild(
"password", 0,
true );
135 _ptt_node =
fgGetNode(
"/controls/radios/comm-ptt",
true);
136 _selected_comm_node =
fgGetNode(
"/controls/radios/comm-radio-selected",
true);
137 _callsign_node =
fgGetNode(
"/sim/multiplay/callsign",
true);
138 _text_node =
fgGetNode(
"/sim/messages/atc",
true );
139 _version_node =
fgGetNode(
"/sim/version/flightgear",
true );
142 if ( !_enabled_node->hasValue() )
143 _enabled_node->setBoolValue(
true);
145 if ( !_test_node->hasValue() )
146 _test_node->setBoolValue(
false);
148 if ( !_micBoost_node->hasValue() )
149 _micBoost_node->setIntValue(1);
151 if ( !_server_node->hasValue() )
154 if ( !_speakerLevel_node->hasValue() )
155 _speakerLevel_node->setFloatValue(1.0);
157 if ( !_micLevel_node->hasValue() )
158 _micLevel_node->setFloatValue(1.0);
160 if ( !_silenceThd_node->hasValue() )
161 _silenceThd_node->setFloatValue(-35.0);
163 if ( !_register_node->hasValue() )
164 _register_node->setBoolValue(
false);
166 if ( !_username_node->hasValue() )
167 _username_node->setStringValue(
"guest");
169 if ( !_password_node->hasValue() )
170 _password_node->setStringValue(
"guest");
172 if ( !_showMessages_node->hasValue() )
173 _showMessages_node->setBoolValue(
false);
175 _mpTransmitFrequencyNode =
fgGetNode(
"sim/multiplay/comm-transmit-frequency-hz", 0,
true);
176 _mpTransmitPowerNode =
fgGetNode(
"sim/multiplay/comm-transmit-power-norm", 0,
true);
178 _selectedOutput_node->addChangeListener(
this);
179 _selectedInput_node->addChangeListener(
this);
180 _speakerLevel_node->addChangeListener(
this);
181 _silenceThd_node->addChangeListener(
this);
182 _micBoost_node->addChangeListener(
this);
183 _micLevel_node->addChangeListener(
this);
184 _enabled_node->addChangeListener(
this);
185 _ptt_node->addChangeListener(
this);
186 _selected_comm_node->addChangeListener(
this);
187 _test_node->addChangeListener(
this);
196 _selectedOutput_node->removeChangeListener(
this);
197 _selectedInput_node->removeChangeListener(
this);
198 _speakerLevel_node->removeChangeListener(
this);
199 _silenceThd_node->removeChangeListener(
this);
200 _micBoost_node->removeChangeListener(
this);
201 _micLevel_node->removeChangeListener(
this);
202 _enabled_node->removeChangeListener(
this);
203 _ptt_node->removeChangeListener(
this);
204 _selected_comm_node->removeChangeListener(
this);
205 _test_node->removeChangeListener(
this);
212 _enabled = _enabled_node->getBoolValue();
213 _server = _server_node->getStringValue();
214 _register = _register_node->getBoolValue();
215 _username = _username_node->getStringValue();
216 _password = _password_node->getStringValue();
218 _currentCommFrequency = 0.0;
234 SG_LOG(SG_IO, SG_ALERT,
"FGCom: cannot initialize iaxclient");
245 std::string app =
"FGFS-";
246 app += _version_node->getStringValue();
248 iaxc_set_callerid( _callsign_node->getStringValue().c_str(), app.c_str() );
249 iaxc_set_formats (IAXC_FORMAT_SPEEX, IAXC_FORMAT_ULAW|IAXC_FORMAT_SPEEX);
250 iaxc_set_speex_settings(1, 5, 0, 1, 0, 3);
251 iaxc_set_filters(IAXC_FILTER_AGC | IAXC_FILTER_DENOISE);
252 iaxc_set_silence_threshold(_silenceThd_node->getFloatValue());
253 iaxc_start_processing_thread ();
259 _regId = iaxc_register(
const_cast<char*
>(_username.c_str()),
260 const_cast<char*
>(_password.c_str()),
261 const_cast<char*
>(_server.c_str()) );
263 SG_LOG(SG_IO, SG_ALERT,
"FGCom: cannot register iaxclient");
286 SGPropertyNode *node =
fgGetNode(
"/sim/fgcom", 0,
true);
288 struct iaxc_audio_device *devs;
289 int nDevs, input, output, ring;
291 iaxc_audio_devices_get(&devs,&nDevs, &input, &output, &ring);
293 for(
int i=0;
i<nDevs;
i++ ) {
294 SGPropertyNode *in_node = node->getChild(
"device",
i,
true);
297 _deviceID_node[
i] = in_node->getChild(
"id", 0,
true);
298 _deviceID_node[
i]->setIntValue(devs[
i].devID);
301 _deviceName_node[
i] = in_node->getChild(
"name", 0,
true);
302 _deviceName_node[
i]->setStringValue(devs[
i].
name);
305 _deviceInput_node[
i] = in_node->getChild(
"available-input", 0,
true);
306 if( devs[
i].capabilities & IAXC_AD_INPUT )
307 _deviceInput_node[
i]->setBoolValue(
true);
309 _deviceInput_node[
i]->setBoolValue(
false);
312 _deviceOutput_node[
i] = in_node->getChild(
"available-output", 0,
true);
313 if( devs[
i].capabilities & IAXC_AD_OUTPUT )
314 _deviceOutput_node[
i]->setBoolValue(
true);
316 _deviceOutput_node[
i]->setBoolValue(
false);
319 if( devs[
i].capabilities & IAXC_AD_INPUT_DEFAULT )
320 _selectedInput_node->setIntValue(devs[
i].devID);
321 if( devs[
i].capabilities & IAXC_AD_OUTPUT_DEFAULT )
322 _selectedOutput_node->setIntValue(devs[
i].devID);
326 iaxc_input_level_set( 0.0 );
327 iaxc_output_level_set(getCurrentCommVolume());
332 setupCommFrequency();
333 connectToCommFrequency();
336double FGCom::getCurrentCommVolume()
const {
339 if (_speakerLevel_node)
340 rv = _speakerLevel_node->getFloatValue();
343 rv = rv * _commVolumeNode->getFloatValue();
347double FGCom::getCurrentFrequencyKhz()
const {
348 return 10 *
static_cast<int>(_currentCommFrequency * 100 + 0.25);
351void FGCom::setupCommFrequency(
int channel) {
353 if (_selected_comm_node !=
nullptr) {
354 channel = _selected_comm_node->getIntValue();
359 if (_currentCallIdent != -1) {
360 iaxc_dump_call_number(_currentCallIdent);
361 SG_LOG(SG_IO, SG_INFO,
"FGCom: disconnect as channel 0 " << _currentCallIdent);
362 _currentCallIdent = -1;
364 _currentCommFrequency = 0.0;
371 SGPropertyNode *commRadioNode =
fgGetNode(
"/instrumentation/")->getChild(
"comm", channel,
false);
373 SGPropertyNode *frequencyNode = commRadioNode->getChild(
"frequencies");
375 _commVolumeNode->removeChangeListener(
this);
376 _commVolumeNode = commRadioNode->getChild(
"volume");
378 frequencyNode = frequencyNode->getChild(
"selected-mhz");
380 if (_commFrequencyNode != frequencyNode) {
381 if (_commFrequencyNode)
382 _commFrequencyNode->removeChangeListener(
this);
383 _commFrequencyNode = frequencyNode;
384 _commFrequencyNode->addChangeListener(
this);
385 _commVolumeNode->addChangeListener(
this);
387 _currentCommFrequency = frequencyNode->getDoubleValue();
392 SG_LOG(SG_IO, SG_INFO,
"FGCom: setupCommFrequency node listener failed: channel " << channel);
395 if (_commFrequencyNode)
396 _commFrequencyNode->removeChangeListener(
this);
397 SG_LOG(SG_IO, SG_INFO,
"FGCom: setupCommFrequency invalid channel " << channel);
399 _currentCommFrequency = 0.0;
402void FGCom::connectToCommFrequency() {
404 if ((_currentCallFrequency > 0.0) && !isInRange(_currentCallFrequency)) {
405 SG_LOG(SG_IO, SG_WARN,
"FGCom: call out of range of: " << _currentCallFrequency);
406 _currentCallFrequency = 0.0;
410 if (_currentCommFrequency < 1.0) {
411 if (_currentCallIdent != -1) {
412 iaxc_dump_call_number(_currentCallIdent);
413 SG_LOG(SG_IO, SG_INFO,
"FGCom: disconnect as freq 0: current call " << _currentCallIdent);
414 _currentCallIdent = -1;
419 if (_currentCallFrequency != _currentCommFrequency || _currentCallIdent == -1) {
420 if (_currentCallIdent != -1) {
421 iaxc_dump_call_number(_currentCallIdent);
422 SG_LOG(SG_IO, SG_INFO,
"FGCom: dump_call_number " << _currentCallIdent);
423 _currentCallIdent = -1;
426 if (_currentCallIdent == -1)
428 std::string num = computePhoneNumber(_currentCommFrequency, getAirportCode(_currentCommFrequency));
429 _processingTimer.stamp();
430 if (!isInRange(_currentCommFrequency)) {
431 if (_currentCallIdent != -1) {
432 SG_LOG(SG_IO, SG_INFO,
"FGCom: disconnect call as not in range " << _currentCallIdent);
433 if (_currentCallIdent != -1) {
434 iaxc_dump_call_number(_currentCallIdent);
435 _currentCallIdent = -1;
441 _currentCallIdent = iaxc_call(num.c_str());
442 if (_currentCallIdent == -1)
443 SG_LOG(SG_IO, SG_DEBUG,
"FGCom: cannot call " << num.c_str());
445 SG_LOG(SG_IO, SG_DEBUG,
"FGCom: call established " << num.c_str() <<
" Freq: " << _currentCommFrequency);
446 _currentCallFrequency = _currentCommFrequency;
450 SG_LOG(SG_IO, SG_WARN,
"FGCom: frequency " << _currentCommFrequency <<
" does not map to valid IAX address");
455void FGCom::updateCall()
457 if (_processingTimer.elapsedMSec() >
IAX_DELAY) {
458 _processingTimer.stamp();
459 connectToCommFrequency();
467 if (!_enabled || !_initialized) {
482 _initialized =
false;
485 iaxc_set_event_callback(NULL);
486 iaxc_unregister(_regId);
487 iaxc_stop_processing_thread();
499 if (prop == _enabled_node) {
500 bool isEnabled = prop->getBoolValue();
501 if (_enabled == isEnabled) {
520 if (prop == _commVolumeNode && _enabled) {
521 if (_ptt_node->getIntValue()) {
522 SG_LOG(SG_IO, SG_INFO,
"FGCom: ignoring change comm volume as PTT pressed");
526 iaxc_input_level_set(0.0);
527 iaxc_output_level_set(getCurrentCommVolume());
528 SG_LOG(SG_IO, SG_INFO,
"FGCom: change comm volume=" << _commVolumeNode->getFloatValue());
532 if (prop == _selected_comm_node && _enabled) {
533 setupCommFrequency();
534 SG_LOG(SG_IO, SG_INFO,
"FGCom: change comm frequency (selected node): set to " << _currentCommFrequency);
537 if (prop == _commFrequencyNode && _enabled) {
538 setupCommFrequency();
539 SG_LOG(SG_IO, SG_INFO,
"FGCom: change comm frequency (property updated): set to " << _currentCommFrequency);
542 if (prop == _ptt_node && _enabled) {
543 if (_ptt_node->getIntValue()) {
545 setupCommFrequency();
546 iaxc_output_level_set(0.0);
547 iaxc_input_level_set(_micLevel_node->getFloatValue());
548 _mpTransmitFrequencyNode->setValue(_currentCallFrequency * 1000000);
549 _mpTransmitPowerNode->setValue(1.0);
550 SG_LOG(SG_IO, SG_INFO,
"FGCom: PTT active: " << _currentCallFrequency);
553 iaxc_output_level_set(getCurrentCommVolume());
554 iaxc_input_level_set(0.0);
555 SG_LOG(SG_IO, SG_INFO,
"FGCom: PTT release: " << _currentCallFrequency <<
" vol=" << getCurrentCommVolume());
556 _mpTransmitFrequencyNode->setValue(0);
557 _mpTransmitPowerNode->setValue(0);
561 if (prop == _test_node) {
562 testMode( prop->getBoolValue() );
566 if (prop == _silenceThd_node && _initialized) {
567 float silenceThd = prop->getFloatValue();
568 SG_CLAMP_RANGE<float>( silenceThd, -60, 0 );
569 iaxc_set_silence_threshold( silenceThd );
574 if (prop == _micBoost_node && _initialized) {
575 int micBoost = prop->getIntValue();
576 SG_CLAMP_RANGE<int>( micBoost, 0, 1 );
577 iaxc_mic_boost_set( micBoost ) ;
582 if ((prop == _selectedInput_node || prop == _selectedOutput_node) && _initialized) {
583 int selectedInput = _selectedInput_node->getIntValue();
584 int selectedOutput = _selectedOutput_node->getIntValue();
585 iaxc_audio_devices_set(selectedInput, selectedOutput, 0);
589 if (_listener_active)
594 if (prop == _speakerLevel_node && _enabled) {
595 float speakerLevel = prop->getFloatValue();
596 SG_CLAMP_RANGE<float>( speakerLevel, 0.0, 1.0 );
597 _speakerLevel_node->setFloatValue(speakerLevel);
598 iaxc_output_level_set(speakerLevel);
601 if (prop == _micLevel_node && _enabled) {
602 float micLevel = prop->getFloatValue();
603 SG_CLAMP_RANGE<float>( micLevel, 0.0, 1.0 );
604 _micLevel_node->setFloatValue(micLevel);
613void FGCom::testMode(
bool testMode)
615 if(testMode && _initialized) {
617 iaxc_dump_all_calls();
618 iaxc_input_level_set( 1.0 );
619 iaxc_output_level_set( _speakerLevel_node->getFloatValue() );
621 if( num.size() > 0 ) {
623 _currentCallIdent = iaxc_call(num.c_str());
625 if( _currentCallIdent == -1 )
626 SG_LOG( SG_IO, SG_DEBUG,
"FGCom: cannot call " << num.c_str() );
629 iaxc_dump_all_calls();
631 iaxc_input_level_set( 0.0 );
632 iaxc_output_level_set(getCurrentCommVolume());
633 _currentCallIdent = -1;
646std::string FGCom::getAirportCode(
const double& freq)
660 return std::string();
675 _aptPos = apt->
geod();
676 return apt->
airport()->ident();
687std::string FGCom::computePhoneNumber(
const double& freq,
const std::string& icao)
const
690 return std::string();
692 char phoneNumber[256];
697 snprintf(tmp, 5,
"%4s", icao.c_str());
701 "%02d%02d%02d%02d%02d%06d",
707 (
int)(freq * 1000 + 0.5));
710 snprintf( phoneNumber,
711 sizeof (phoneNumber),
727bool FGCom::isInRange(
const double &freq)
const
736 double distNm = SGGeodesy::distanceNm(_aptPos, acftPos);
737 double delta_elevation_ft = fabs(acftPos.getElevationFt() - _aptPos.getElevationFt());
738 double rangeNm = 1.23 * sqrt(delta_elevation_ft);
740 if (rangeNm > _maxRange) rangeNm = _maxRange;
741 if (rangeNm < _minRange) rangeNm = _minRange;
742 if( distNm > rangeNm )
return 0;
749 SGSubsystemMgr::SOUND);
static std::unique_ptr< FavouriteAircraftData > static_instance
void update(double dt) override
void iaxTextEvent(struct iaxc_ev_text text)
virtual void valueChanged(SGPropertyNode *prop)
SGGeod get_aircraft_position() const
virtual const SGGeod & geod() const
FGAirportRef airport() const
static CommStationRef findByFreq(int freqKhz, const SGGeod &pos, FGPositioned::Filter *filt=NULL)
static int iaxc_callback(iaxc_event e)
SGSubsystemMgr::Registrant< FGCom > registrantFGCom(SGSubsystemMgr::SOUND)
void addSentryBreadcrumb(const std::string &, const std::string &)
SGPropertyNode * fgGetNode(const char *path, bool create)
Get a property node.