/* * fgcom - VoIP-Client for the FlightGear-Radio-Infrastructure * * This program realizes the usage of the VoIP infractructure based * on flight data which is send from FlightGear with an external * protocol to this application. * * For more information read: http://squonk.abacab.org/dokuwiki/fgcom * * (c) H. Wirtz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. * */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include "fgcom.hxx" #include "fgcom_init.hxx" #include "utils.hxx" #include "version.h" /* Global variables */ int exitcode = 0; int initialized = 0; int connected = 0; int reg_id; const char* dialstring; char airport[5]; int codec = DEFAULT_IAX_CODEC; simgear::Socket sgSocket; /* Variables declared as static */ static double selected_frequency = 0.0; static char mode = 0; static char rep_buf[MX_REPORT_BUF+2]; /* report output buffer - used on iax callback thread - note +2 to ensure null termination */ static char num_buf[1024]; /* number generation buffer - used on main thread */ static char states[256]; /* buffer to hold ascii states */ static char delim = '\t'; /* output field delimiter */ static int last_state = 0; /* previous state of the channel */ static const char *map[] = { "unknown", "active", "outgoing", "ringing", "complete", "selected", "busy", "transfer", NULL }; static const char *radio_map[] = {"COM1", "COM2"}; char icao[5]; double special_frq[] = { 910.000, 911.000, 700.000, 123.450, 122.750, 121.500, 123.500, -1.0 }; double *special_frequencies; double previous_com_frequency = 0.0; int previous_ptt = 0; float previous_vol = 0.0; float previous_thd = 0.0; int com_select = 0; int max_com_instruments = 2; struct airport *airportlist; struct fgdata data; char *prog; //program name /* configuration values */ static bool debug = false; static int port; static const char *voipserver; static const char *fgserver; static const char *airport_option; static double frequency = -1.0; static const char *audio_in; static const char *audio_out; static double level_in = 0.7; static double level_out = 0.7; static double silence_thd = -35.0; static bool mic_boost; static char codec_option; static const char *callsign; static const char *username; static const char *password; static bool list_audio; static char *positions_file; static char *frequency_file; static const OptionEntry fgcomOptionArray[] = { {"debug", 'd', false, OPTION_NONE, &debug, 0, "show debugging information", 0}, {"voipserver", 'S', true, OPTION_STRING, &voipserver, 0, "voip server to connect to", &DEFAULT_VOIP_SERVER}, {"fgserver", 's', true, OPTION_STRING, &fgserver, 0, "fg to connect to ", &DEFAULT_FG_SERVER}, {"port", 'p', true, OPTION_INT, &port, 0, "where we should listen to FG", &port}, // hm, good idea? don't think so... {"airport", 'a', true, OPTION_STRING, &airport_option, 0, "airport-id (ICAO) for ATC-mode", 0}, {"frequency", 'f', true, OPTION_DOUBLE, &frequency, 0, "frequency for ATC-mode", 0}, {"callsign", 'C', true, OPTION_STRING, &callsign, 0, "callsign to use", &DEFAULT_USER}, {"user", 'U', true, OPTION_STRING, &username, 0, "username for VoIP account", &DEFAULT_USER}, {"password", 'P', true, OPTION_STRING, &password, 0, "password for VoIP account", &DEFAULT_PASSWORD}, {"mic", 'i', true, OPTION_DOUBLE, &level_in, 0, "mic input level (0.0 - 1.0)", 0}, {"speaker", 'o', true, OPTION_DOUBLE, &level_out, 0, "speaker output level (0.0 - 1.0)", 0}, {"mic-boost", 'b', false, OPTION_BOOL, &mic_boost, 0, "enable mic boost", 0}, {"silence-threshold", 't', false, OPTION_DOUBLE, &silence_thd, 0, "set silence threshold (-60.0 - 0.0)", 0}, {"list-audio", 'l', false, OPTION_BOOL, &list_audio, 0, "list audio devices", 0}, {"set-audio-in", 'r', true, OPTION_STRING, &audio_in, 0, "use as audio input", 0}, {"set-audio-out", 'k', true, OPTION_STRING, &audio_out, 0, "use as audio output", 0}, {"codec", 'c', true, OPTION_CHAR, &codec_option, 0, "use codec as transfer codec", &codec_option}, {"positions", 'T', true, OPTION_STRING, &positions_file, 0, "location positions file", &DEFAULT_POSITIONS_FILE}, {"special", 'Q', true, OPTION_STRING, &frequency_file, 0, "location spl. frequency file (opt)", &SPECIAL_FREQUENCIES_FILE}, {NULL} }; void process_packet (char *buf) { /* cut off ending \n */ buf[strlen (buf) - 1] = '\0'; /* parse the data into a struct */ parse_fgdata (&data, buf); /* get the selected frequency */ if (com_select == 0 && data.COM1_SRV == 1) selected_frequency = data.COM1_FRQ; else if (com_select == 1 && data.COM2_SRV == 1) selected_frequency = data.COM2_FRQ; /* Check for com frequency changes */ if (previous_com_frequency != selected_frequency) { SG_LOG( SG_GENERAL, SG_ALERT, "Selected frequency: " << selected_frequency ); /* remark the new frequency */ previous_com_frequency = selected_frequency; if (connected == 1) { /* hangup call, if connected */ iaxc_dump_call (); iaxc_millisleep (5 * DEFAULT_MILLISLEEP); connected = 0; } strcpy (icao, icaobypos (airportlist, selected_frequency, data.LAT, data.LON, DEFAULT_RANGE)); icao2number (icao, selected_frequency, num_buf); SG_LOG( SG_GENERAL, SG_DEBUG, "Dialing " << icao << " " << selected_frequency << " MHz: " << num_buf ); do_iaxc_call (username, password, voipserver, num_buf); connected = 1; } /* Check for pressed PTT key */ if (previous_ptt != data.PTT) { if (data.PTT == 2) { /* select the next com equipment */ com_select = (com_select + 1) % 2; SG_LOG( SG_GENERAL, SG_ALERT, "Radio selected is " << radio_map[com_select] ); } else if (connected == 1) { ptt (data.PTT); } previous_ptt = data.PTT; } /* Check for output volume change */ if (previous_vol != data.OUTPUT_VOL) { SG_LOG( SG_GENERAL, SG_ALERT, "Set speaker volume to " << data.OUTPUT_VOL ); iaxc_output_level_set( data.OUTPUT_VOL ); previous_vol = data.OUTPUT_VOL; } /* Check for silence threshold change */ if (previous_thd != data.SILENCE_THD) { SG_LOG( SG_GENERAL, SG_ALERT, "Set silent threshold to " << data.SILENCE_THD ); iaxc_set_silence_threshold( data.SILENCE_THD ); previous_thd = data.SILENCE_THD; } /* Check for callsign change */ if (strcmp(callsign, data.CALLSIGN) != 0) { iaxc_dump_call (); callsign = data.CALLSIGN; SG_LOG( SG_GENERAL, SG_ALERT, "FGCom will restart now with callsign " << callsign ); std::string app = "FGCOM-"; app += FGCOM_VERSION; iaxc_set_callerid ( callsign, app.c_str() ); } } static char *base_name( char *name ) { char *bn = name; size_t len = strlen(name); size_t i; int c; for ( i = 0; i < len; i++ ) { c = name[i]; if (( c == '/' ) || ( c == '\\')) bn = &name[i+1]; } return bn; } // if default FAILS, for OSX and WIN try EXE path int fix_input_files() { int iret = 0; char *def_freq = (char *) SPECIAL_FREQUENCIES_FILE; char *def_pos = (char *) DEFAULT_POSITIONS_FILE; char exepath[MX_PATH_SIZE+2]; exepath[0] = 0; if (strcmp( frequency_file,def_freq) == 0) { /* ok is default value - do some fixes */ if (is_file_or_directory( frequency_file ) != 1) { exepath[0] = 0; iret |= get_data_path_per_os( exepath, MX_PATH_SIZE ); strcat(exepath,def_freq); frequency_file = strdup(exepath); } } if (strcmp( positions_file,def_pos) == 0) { // if default FAILS, for OSX and WIN try EXE path if (is_file_or_directory( positions_file ) != 1) { exepath[0] = 0; iret |= get_data_path_per_os( exepath, MX_PATH_SIZE ); strcat(exepath,def_pos); positions_file = strdup(exepath); } } return iret; } int main (int argc, char *argv[]) { int numbytes; static char pkt_buf[MAXBUFLEN+2]; simgear::requestConsole(); sglog().setLogLevels( SG_ALL, SG_ALERT ); prog = strdup( base_name(argv[0]) ); /* program header */ SG_LOG( SG_GENERAL, SG_ALERT, prog << " - a communication radio based on VoIP with IAX/Asterisk" ); SG_LOG( SG_GENERAL, SG_ALERT, "Original (c) 2007-2011 by H. Wirtz " ); SG_LOG( SG_GENERAL, SG_ALERT, "OSX and Windows ports 2012-2013 by Yves Sablonier and Geoff R. McLane, resp." ); SG_LOG( SG_GENERAL, SG_ALERT, "Version " << FGCOM_VERSION << " compiled " << __DATE__ << ", at " << __TIME__ ); SG_LOG( SG_GENERAL, SG_ALERT, "Using iaxclient library Version " << iaxc_version (pkt_buf) ); SG_LOG( SG_GENERAL, SG_ALERT, "" ); /* init values */ voipserver = DEFAULT_VOIP_SERVER; fgserver = DEFAULT_FG_SERVER; port = DEFAULT_FG_PORT; username = DEFAULT_USER; password = DEFAULT_PASSWORD; codec_option = DEFAULT_CODEC; mode = 0; // 0 = ATC mode, 1 = FG mode positions_file = (char *) DEFAULT_POSITIONS_FILE; frequency_file = (char *) SPECIAL_FREQUENCIES_FILE; #ifndef _WIN32 /* catch signals */ signal (SIGINT, quit); signal (SIGQUIT, quit); signal (SIGTERM, quit); #endif /* setup iax */ #ifdef _MSC_VER /* MSVC only - In certain circumstances the addresses placed in iaxc_sendto and iaxc_recvfrom can be an offset to a jump table, making a compare of the current address to the address of the actual imported function fail. So here ensure they are the same. */ iaxc_set_networking( (iaxc_sendto_t)sendto, (iaxc_recvfrom_t)recvfrom ); #endif // _MSC_VER if (iaxc_initialize (DEFAULT_MAX_CALLS)) fatal_error ("cannot initialize iaxclient!\nHINT: Have you checked the mic and speakers?"); initialized = 1; // option parser fgcomInitOptions (fgcomOptionArray, argc, argv); if( debug ) sglog().setLogLevels( SG_ALL, SG_DEBUG ); // codec if (codec_option) { switch (codec_option) { case 'u': codec = IAXC_FORMAT_ULAW; break; case 'a': codec = IAXC_FORMAT_ALAW; break; case 'g': codec = IAXC_FORMAT_GSM; break; case '7': codec = IAXC_FORMAT_G726; break; case 's': codec = IAXC_FORMAT_SPEEX; break; } } // airport if (airport_option) { strtoupper (airport_option, airport, sizeof (airport)); } // input level if (level_in > 1.0) { level_in = 1.0; } if (level_in < 0.0) { level_in = 0.0; } // output level if (level_out > 1.0) { level_out = 1.0; } if (level_out < 0.0) { level_out = 0.0; } // microphone boost if (mic_boost) { iaxc_mic_boost_set (1); } if (list_audio) { SG_LOG( SG_GENERAL, SG_ALERT, "Input audio devices:" ); SG_LOG( SG_GENERAL, SG_ALERT, report_devices(IAXC_AD_INPUT) ); SG_LOG( SG_GENERAL, SG_ALERT, "Output audio devices:" ); SG_LOG( SG_GENERAL, SG_ALERT, report_devices(IAXC_AD_OUTPUT) ); iaxc_shutdown (); exit (1); } if (audio_in) { set_device (audio_in, 0); } if (audio_out) { set_device (audio_out, 1); } /* checking consistency of arguments */ if (frequency > 0.0 && frequency < 1000.0) { if (strlen (airport) == 0 || strlen (airport) > 4) { strcpy (airport, "ZZZZ"); } /* airport and frequency are given => ATC mode */ mode = 0; } else { /* no airport => FG mode */ mode = 1; } /* Read special frequencies file (if exists). * If no file $(INSTALL_DIR)/special_frequencies.txt exists, then default frequencies * are used and are hard coded. */ if (fix_input_files()) { /* adjust default input per OS */ fatal_error ("cannot adjust default input files per OS!\nHINT: Maybe recompile with larger buffer."); } if((special_frequencies = read_special_frequencies(frequency_file)) == 0) { SG_LOG( SG_GENERAL, SG_ALERT, "Failed to load file [" << frequency_file << "] !" ); SG_LOG( SG_GENERAL, SG_ALERT, "Using internal defaults" ); special_frequencies = special_frq; } else { SG_LOG( SG_GENERAL, SG_ALERT, "Loaded file [" << frequency_file << "]." ); } /* read airport frequencies and positions */ airportlist = read_airports (positions_file); /* never returns if fail! */ /* preconfigure iax */ SG_LOG( SG_GENERAL, SG_ALERT, "Initializing IAX client as " << username << ":" << "xxxxxxxxxxx@" << voipserver ); std::string app = "FGCOM-"; app += FGCOM_VERSION; if( !callsign ) callsign = DEFAULT_USER; iaxc_set_callerid ( callsign, app.c_str() ); iaxc_set_formats (IAXC_FORMAT_SPEEX, IAXC_FORMAT_ULAW|IAXC_FORMAT_SPEEX); iaxc_set_speex_settings(1, 5, 0, 1, 0, 3); iaxc_set_filters(IAXC_FILTER_AGC | IAXC_FILTER_DENOISE); iaxc_set_silence_threshold(silence_thd); iaxc_set_event_callback (iaxc_callback); iaxc_start_processing_thread (); if (username && password && voipserver) { reg_id = iaxc_register (const_cast < char *>(username), const_cast < char *>(password), const_cast < char *>(voipserver)); SG_LOG( SG_GENERAL, SG_DEBUG, "Registered as '" << username << "' at '" << voipserver ); } else { SG_LOG( SG_GENERAL, SG_ALERT, "Failed iaxc_register !" ); SG_LOG( SG_GENERAL, SG_ALERT, "HINT: Check username, passwordd and address of server" ); exitcode = 130; quit (0); } iaxc_millisleep (DEFAULT_MILLISLEEP); /* main loop */ if (mode == 1) { SG_LOG( SG_GENERAL, SG_DEBUG, "Entering main loop in mode FGFS" ); /* only in FG mode */ simgear::Socket::initSockets(); sgSocket.open (false); sgSocket.bind (fgserver, port); /* mute mic, speaker on */ iaxc_input_level_set (0); iaxc_output_level_set (level_out); SGTimeStamp sg_clock; sg_clock.stamp(); double sg_next_update = sg_clock.toSecs() + DEFAULT_ALARM_TIMER; /* get data from flightgear */ while (1) { sg_clock.stamp(); double sg_wait = sg_next_update - sg_clock.toSecs(); if (sg_wait > 0.001) { simgear::Socket *readSockets[2] = { &sgSocket, 0 }; if (sgSocket.select (readSockets, readSockets + 1, (int) (sg_wait * 1000)) == 1) { simgear::IPAddress their_addr; numbytes = sgSocket.recvfrom(pkt_buf, MAXBUFLEN - 1, 0, &their_addr); if (numbytes == -1) { perror ("recvfrom"); exit (1); } pkt_buf[numbytes] = '\0'; SG_LOG( SG_GENERAL, SG_DEBUG, "Got packet from " << their_addr.getHost () << ":" << their_addr.getPort () ); SG_LOG( SG_GENERAL, SG_DEBUG, "Packet is " << numbytes << " bytes long" ); SG_LOG( SG_GENERAL, SG_DEBUG, "Packet contains \"" << pkt_buf << "\"" ); process_packet (pkt_buf); } } else { alarm_handler (0); sg_clock.stamp(); sg_next_update = sg_clock.toSecs() + DEFAULT_ALARM_TIMER; } } } else { SG_LOG( SG_GENERAL, SG_DEBUG, "Entering main loop in mode ATC" ); /* mic on, speaker on */ iaxc_input_level_set (1.0); iaxc_output_level_set (1.0); icao2atisnumber (airport, frequency, num_buf); SG_LOG( SG_GENERAL, SG_DEBUG, "Dialing " << airport << " " << frequency << " MHz: " << num_buf ); do_iaxc_call (username, password, voipserver, num_buf); while (1) { /* sleep endless */ SGTimeStamp::sleepForMSec(3600000); } } /* should never be reached */ exitcode = 999; quit (0); } void quit (int signal) { SG_LOG( SG_GENERAL, SG_ALERT, "Stopping service" ); if (initialized) iaxc_shutdown (); if (reg_id) iaxc_unregister (reg_id); exit (exitcode); } void alarm_handler (int signal) { /* Check every DEFAULT_ALARM_TIMER seconds if position related things should happen */ if (check_special_frq (selected_frequency)) { strcpy (icao, "ZZZZ"); } else { strcpy (icao, icaobypos (airportlist, selected_frequency, data.LAT, data.LON, DEFAULT_RANGE)); } /* Check if we are out of range */ if (strlen (icao) == 0 && connected == 1) { /* Yes, we are out of range so hangup */ iaxc_dump_call (); iaxc_millisleep (5 * DEFAULT_MILLISLEEP); connected = 0; } /* Check if we are now in range */ else if (strlen (icao) != 0 && connected == 0) { icao2number (icao, selected_frequency, num_buf); SG_LOG( SG_GENERAL, SG_DEBUG, "Dialing " << icao << " " << selected_frequency << " MHz: " << num_buf ); do_iaxc_call (username, password, voipserver, num_buf); connected = 1; } } void strtoupper (const char *str, char *buf, size_t len) { unsigned int i; for (i = 0; str[i] && i < len - 1; i++) { buf[i] = toupper (str[i]); } buf[i++] = '\0'; } void fatal_error (const char *err) { SG_LOG( SG_GENERAL, SG_ALERT, "FATAL ERROR: " << err ); if (initialized) iaxc_shutdown (); exit (1); } int iaxc_callback (iaxc_event e) { switch (e.type) { case IAXC_EVENT_LEVELS: event_level (e.ev.levels.input, e.ev.levels.output); break; case IAXC_EVENT_TEXT: event_text (e.ev.text.type, e.ev.text.message); break; case IAXC_EVENT_STATE: event_state (e.ev.call.state, e.ev.call.remote, e.ev.call.remote_name, e.ev.call.local, e.ev.call.local_context); break; case IAXC_EVENT_NETSTAT: event_netstats (e.ev.netstats); case IAXC_EVENT_REGISTRATION: event_register (e.ev.reg.id, e.ev.reg.reply, e.ev.reg.msgcount); break; default: event_unknown (e.type); break; } return 1; } void event_state (int state, char *remote, char *remote_name, char *local, char *local_context) { last_state = state; /* This is needed for auto-reconnect */ if (state == 0) { connected = 0; /* FIXME: we should wake up the main thread somehow */ /* in fg mode the next incoming packet will do that anyway */ } snprintf (rep_buf, MX_REPORT_BUF, "S%c0x%x%c%s%c%.50s%c%.50s%c%.50s%c%.50s", delim, state, delim, map_state (state), delim, remote, delim, remote_name, delim, local, delim, local_context); rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ report (rep_buf); } void event_text (int type, char *message) { snprintf (rep_buf, MX_REPORT_BUF, "T%c%d%c%.200s", delim, type, delim, message); SG_LOG( SG_GENERAL, SG_ALERT, message ); rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ report (rep_buf); } void event_register (int id, int reply, int count) { const char *reason; switch (reply) { case IAXC_REGISTRATION_REPLY_ACK: reason = "accepted"; break; case IAXC_REGISTRATION_REPLY_REJ: reason = "denied"; if (strcmp (username, "guest") != 0) { SG_LOG( SG_GENERAL, SG_ALERT, "Registering denied" ); } break; case IAXC_REGISTRATION_REPLY_TIMEOUT: reason = "timeout"; break; default: reason = "unknown"; } snprintf (rep_buf, MX_REPORT_BUF, "R%c%d%c%s%c%d", delim, id, delim, reason, delim, count); rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ report (rep_buf); } void event_netstats (struct iaxc_ev_netstats stat) { struct iaxc_netstat local = stat.local; struct iaxc_netstat remote = stat.remote; snprintf (rep_buf, MX_REPORT_BUF, "N%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d%c%d", delim, stat.callNo, delim, stat.rtt, delim, local.jitter, delim, local.losspct, delim, local.losscnt, delim, local.packets, delim, local.delay, delim, local.dropped, delim, local.ooo, delim, remote.jitter, delim, remote.losspct, delim, remote.losscnt, delim, remote.packets, delim, remote.delay, delim, remote.dropped, delim, remote.ooo); rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ report (rep_buf); } void event_level (double in, double out) { snprintf (rep_buf, MX_REPORT_BUF, "L%c%.1f%c%.1f", delim, in, delim, out); rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ report (rep_buf); } const char * map_state (int state) { int i, j; int next = 0; *states = '\0'; if (state == 0) { return "free"; } for (i = 0, j = 1; map[i] != NULL; i++, j <<= 1) { if (state & j) { if (next) strcat (states, ","); strcat (states, map[i]); next = 1; } } return states; } void event_unknown (int type) { snprintf (rep_buf, MX_REPORT_BUF, "U%c%d", delim, type); rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ report (rep_buf); } void report (char *text) { if (debug) { SG_LOG( SG_GENERAL, SG_ALERT, text ); fflush (stdout); } } void ptt (int mode) { if (mode == 1) { /* mic is muted so unmute and mute speaker */ iaxc_input_level_set (level_in); iaxc_output_level_set (0.0); SG_LOG( SG_GENERAL, SG_ALERT, "[SPEAK] unmute mic, mute speaker" ); } else { /* mic is unmuted so mute and unmute speaker */ iaxc_input_level_set (0.0); iaxc_output_level_set (level_out); SG_LOG( SG_GENERAL, SG_ALERT, "[LISTEN] mute mic, unmute speaker" ); } } int split (char *string, char *fields[], int nfields, const char *sep) { register char *p = string; register char c; /* latest character */ register char sepc = sep[0]; register char sepc2; register int fn; register char **fp = fields; register const char *sepp; register int trimtrail; /* white space */ if (sepc == '\0') { while ((c = *p++) == ' ' || c == '\t') continue; p--; trimtrail = 1; sep = " \t"; /* note, code below knows this is 2 long */ sepc = ' '; } else trimtrail = 0; sepc2 = sep[1]; /* now we can safely pick this up */ /* catch empties */ if (*p == '\0') return (0); /* single separator */ if (sepc2 == '\0') { fn = nfields; for (;;) { *fp++ = p; fn--; if (fn == 0) break; while ((c = *p++) != sepc) if (c == '\0') return (nfields - fn); *(p - 1) = '\0'; } /* we have overflowed the fields vector -- just count them */ fn = nfields; for (;;) { while ((c = *p++) != sepc) if (c == '\0') return (fn); fn++; } /* not reached */ } /* two separators */ if (sep[2] == '\0') { fn = nfields; for (;;) { *fp++ = p; fn--; while ((c = *p++) != sepc && c != sepc2) if (c == '\0') { if (trimtrail && **(fp - 1) == '\0') fn++; return (nfields - fn); } if (fn == 0) break; *(p - 1) = '\0'; while ((c = *p++) == sepc || c == sepc2) continue; p--; } /* we have overflowed the fields vector -- just count them */ fn = nfields; while (c != '\0') { while ((c = *p++) == sepc || c == sepc2) continue; p--; fn++; while ((c = *p++) != '\0' && c != sepc && c != sepc2) continue; } /* might have to trim trailing white space */ if (trimtrail) { p--; while ((c = *--p) == sepc || c == sepc2) continue; p++; if (*p != '\0') { if (fn == nfields + 1) *p = '\0'; fn--; } } return (fn); } /* n separators */ fn = 0; for (;;) { if (fn < nfields) *fp++ = p; fn++; for (;;) { c = *p++; if (c == '\0') return (fn); sepp = sep; while ((sepc = *sepp++) != '\0' && sepc != c) continue; if (sepc != '\0') /* it was a separator */ break; } if (fn < nfields) *(p - 1) = '\0'; for (;;) { c = *p++; sepp = sep; while ((sepc = *sepp++) != '\0' && sepc != c) continue; if (sepc == '\0') /* it wasn't a separator */ break; } p--; } /* not reached */ } /** * * \fn double *read_special_frequencies(const char *file) * * \brief Reads the file "special_frequencies.txt" if it exists. * If no file exists, then no special frequencies are useable. * * \param file pointer on the filename. * * \return Returns the pointer on an array containing doubles if file * has been successfully opened and read, otherwise returns NULL. * */ double *read_special_frequencies(const char *file) { double *l_pfrq = NULL; double l_value; int l_count; int l_allocated = 0; int l_new_size; if((l_pfrq = (double *)malloc(ALLOC_CHUNK_SIZE * sizeof(double))) != NULL) { l_allocated += ALLOC_CHUNK_SIZE; if(FGC_SUCCESS(parser_init(file))) { l_count = 0; while(FGC_SUCCESS(parser_get_next_value(&l_value))) { if(l_count >= l_allocated) { l_new_size = ALLOC_CHUNK_SIZE * (l_count / ALLOC_CHUNK_SIZE + 1); l_pfrq = (double *)realloc(l_pfrq, l_new_size * sizeof(double)); l_allocated += ALLOC_CHUNK_SIZE; } l_pfrq[l_count] = l_value; l_count++; } /* Last value of the array must be -1.0 which is the terminator. */ if(l_count == l_allocated) l_pfrq = (double *)realloc(l_pfrq, (l_count + 1) * sizeof(double)); l_pfrq[l_count] = -1.0; } else { // failed to open file parser_exit(); free(l_pfrq); return 0; } } parser_exit(); return(l_pfrq); } struct airport * read_airports (const char *file) { FILE *fp; int ret; struct airport airport_tmp; struct airport *first = NULL; struct airport *my_airport = NULL; struct airport *previous_airport = NULL; size_t counter = 0; SG_LOG( SG_GENERAL, SG_ALERT, "Reading airports [" << file << "]" ); if ((fp = fopen (file, "rt")) == NULL) { SG_LOG( SG_GENERAL, SG_ALERT, "ERROR: open failed!" ); perror ("fopen"); exitcode = 120; quit (0); } airport_tmp.next = NULL; while ((ret = fscanf (fp, " %4[^,],%f,%lf,%lf,%128[^,],%128[^\r\n]", airport_tmp.icao, &airport_tmp.frequency, &airport_tmp.lat, &airport_tmp.lon, airport_tmp.type, airport_tmp.text)) == 6) { counter++; if ((my_airport = (struct airport *) malloc (sizeof (struct airport))) == NULL) { SG_LOG( SG_GENERAL, SG_ALERT, "Error allocating memory for airport data" ); exitcode = 900; quit (0); } if (first == NULL) first = my_airport; memcpy (my_airport, &airport_tmp, sizeof (airport_tmp)); if (previous_airport != NULL) { previous_airport->next = my_airport; } previous_airport = my_airport; } fclose (fp); if (ret != EOF) { SG_LOG( SG_GENERAL, SG_ALERT, "ERROR during reading airports!" ); exitcode = 900; quit (0); } SG_LOG( SG_GENERAL, SG_ALERT, "loaded " << counter << " entries" ); return (first); } char * report_devices (int in) { struct iaxc_audio_device *devs; //audio devices int ndevs; //audio dedvice count int input, output, ring; //audio device id's int current, i; int flag = in ? IAXC_AD_INPUT : IAXC_AD_OUTPUT; iaxc_audio_devices_get (&devs, &ndevs, &input, &output, &ring); current = in ? input : output; snprintf (rep_buf, MX_REPORT_BUF, "%s\n", devs[current].name); for (i = 0; i < ndevs; i++) { if (devs[i].capabilities & flag && i != current) { snprintf (rep_buf + strlen (rep_buf), MX_REPORT_BUF - strlen (rep_buf), "%s\n", devs[i].name); } rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ if (strlen(rep_buf) >= MX_REPORT_BUF) break; } rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */ return rep_buf; } int set_device (const char *name, int out) { struct iaxc_audio_device *devs; /* audio devices */ int ndevs; /* audio dedvice count */ int input, output, ring; /* audio device id's */ int i; iaxc_audio_devices_get (&devs, &ndevs, &input, &output, &ring); for (i = 0; i < ndevs; i++) { if (devs[i].capabilities & (out ? IAXC_AD_OUTPUT : IAXC_AD_INPUT) && strcmp (name, devs[i].name) == 0) { if (out) { output = devs[i].devID; } else { input = devs[i].devID; } fprintf (stderr, "device %s = %s (%d)\n", out ? "out" : "in", name, devs[i].devID); iaxc_audio_devices_set (input, output, ring); return 1; } } return 0; } void parse_fgdata (struct fgdata *data, char *buf) { char *data_pair = NULL; char *fields[2]; fields[0] = NULL; fields[1] = NULL; SG_LOG( SG_GENERAL, SG_DEBUG, "Parsing data: [" << buf << "]" ); /* Parse data from FG */ data_pair = strtok (buf, ","); while (data_pair != NULL) { split (data_pair, fields, 2, "="); if (strcmp (fields[0], "COM1_FRQ") == 0) { data->COM1_FRQ = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "COM1_FRQ=" << data->COM1_FRQ ); } else if (strcmp (fields[0], "COM2_FRQ") == 0) { data->COM2_FRQ = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "COM2_FRQ=" << data->COM2_FRQ ); } else if (strcmp (fields[0], "NAV1_FRQ") == 0) { data->NAV1_FRQ = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "NAV1_FRQ=" << data->NAV1_FRQ ); } else if (strcmp (fields[0], "NAV2_FRQ") == 0) { data->NAV2_FRQ = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "NAV2_FRQ=" << data->NAV2_FRQ ); } else if (strcmp (fields[0], "PTT") == 0) { data->PTT = atoi (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "PTT=" << data->PTT ); } else if (strcmp (fields[0], "TRANSPONDER") == 0) { data->TRANSPONDER = atoi (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "TRANSPONDER=" << data->TRANSPONDER ); } else if (strcmp (fields[0], "IAS") == 0) { data->IAS = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "IAS=" << data->IAS ); } else if (strcmp (fields[0], "GS") == 0) { data->GS = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "GS=" << data->GS ); } else if (strcmp (fields[0], "LON") == 0) { data->LON = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "LON=" << data->LON ); } else if (strcmp (fields[0], "LAT") == 0) { data->LAT = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "LAT=" << data->LAT ); } else if (strcmp (fields[0], "ALT") == 0) { data->ALT = atoi (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "ALT=" << data->ALT ); } else if (strcmp (fields[0], "HEAD") == 0) { data->HEAD = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "HEAD=" << data->HEAD ); } else if (strcmp (fields[0], "COM1_SRV") == 0) { data->COM1_SRV = atoi (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "COM1_SRV" << data->COM1_SRV ); } else if (strcmp (fields[0], "COM2_SRV") == 0) { data->COM2_SRV = atoi (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "COM2_SRV=" << data->COM2_SRV ); } else if (strcmp (fields[0], "NAV1_SRV") == 0) { data->NAV1_SRV = atoi (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "NAV1_SRV=" << data->NAV1_SRV ); } else if (strcmp (fields[0], "NAV2_SRV") == 0) { data->NAV2_SRV = atoi (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "NAV2_SRV=" << data->NAV2_SRV ); } else if (strcmp (fields[0], "OUTPUT_VOL") == 0) { data->OUTPUT_VOL = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "OUTPUT_VOL=" << data->OUTPUT_VOL ); } else if (strcmp (fields[0], "SILENCE_THD") == 0) { data->SILENCE_THD = atof (fields[1]); SG_LOG( SG_GENERAL, SG_DEBUG, "SILENCE_THD=" << data->SILENCE_THD ); } else if (strcmp (fields[0], "CALLSIGN") == 0) { data->CALLSIGN = fields[1]; SG_LOG( SG_GENERAL, SG_DEBUG, "CALLSIGN=" << data->CALLSIGN ); } else { SG_LOG( SG_GENERAL, SG_DEBUG, "Unknown field " << fields[0] << "=" << fields[1] ); } data_pair = strtok (NULL, ","); } SG_LOG( SG_GENERAL, SG_DEBUG, "" ); } /** * * \fn int check_special_frq (double frq) * * \brief Check to see if specified frequency is a special frequency. * * \param frq frequency to check against special frequencies * * \return Returns 1 if successful, otherwise returns 0. * */ int check_special_frq (double frq) { int i = 0; frq = ceilf(frq*1000.0)/1000.0; // 20130602: By Clement de l'Hamaide, to 'Make 123.450Mhz usable' while (special_frequencies[i] >= 0.0) { if (frq == special_frequencies[i]) { SG_LOG( SG_GENERAL, SG_ALERT, "Special frequency: " << frq ); return (1); } i++; } return (0); } void do_iaxc_call (const char *username, const char *password, const char *voipserver, char *number) { char dest[256]; if( strcmp(number, "9990909090910000") == 0) number = (char *)"0190909090910000"; snprintf (dest, sizeof (dest), "%s:%s@%s/%s", username, password, voipserver, number); iaxc_call (dest); iaxc_millisleep (DEFAULT_MILLISLEEP); } /* eof - fgcom.cpp */