]> git.mxchange.org Git - flightgear.git/blob - utils/fgcom/fgcom.cxx
FGCom: add silence threshold setting
[flightgear.git] / utils / fgcom / fgcom.cxx
1 /*
2  * fgcom - VoIP-Client for the FlightGear-Radio-Infrastructure
3  *
4  * This program realizes the usage of the VoIP infractructure based
5  * on flight data which is send from FlightGear with an external
6  * protocol to this application.
7  *
8  * For more information read: http://squonk.abacab.org/dokuwiki/fgcom
9  *
10  * (c) H. Wirtz <dcoredump@gmail.com>
11  *
12  * This program is free software; you can redistribute it and/or
13  * modify it under the terms of the GNU General Public License as
14  * published by the Free Software Foundation; either version 2 of the
15  * License, or (at your option) any later version.
16  * 
17  * This program is distributed in the hope that it will be useful, but
18  * WITHOUT ANY WARRANTY; without even the implied warranty of
19  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20  * General Public License for more details.
21  * 
22  * You should have received a copy of the GNU General Public License
23  * along with this program; if not, write to the Free Software
24  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
25  * MA  02110-1301, USA.
26  *
27  */
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32
33 #include <simgear/debug/logstream.hxx>
34 #include <simgear/io/raw_socket.hxx>
35 #include <simgear/timing/timestamp.hxx>
36
37 #include "fgcom.hxx"
38 #include "fgcom_init.hxx"
39 #include "utils.hxx"
40 #include "version.h"
41
42 /* Global variables */
43 int exitcode = 0;
44 int initialized = 0;
45 int connected = 0;
46 int reg_id;
47 const char* dialstring;
48 char airport[5];
49 int codec = DEFAULT_IAX_CODEC;
50 simgear::Socket sgSocket;
51
52 /* Variables declared as static */
53 static double selected_frequency = 0.0;
54 static char mode = 0;
55 static char rep_buf[MX_REPORT_BUF+2]; /* report output buffer - used on iax callback thread - note +2 to ensure null termination */
56 static char num_buf[1024];            /* number generation buffer - used on main thread */
57 static char states[256];              /* buffer to hold ascii states */
58 static char delim = '\t';             /* output field delimiter */
59 static int last_state = 0;            /* previous state of the channel */
60
61 static const char *map[] = {
62   "unknown", "active", "outgoing", "ringing", "complete", "selected",
63   "busy", "transfer", NULL
64 };
65
66 static const char *radio_map[] = {"COM1", "COM2"};
67
68 char icao[5];
69 double special_frq[] = {
70         910.000,
71         911.000,
72         700.000,
73         123.450,
74         122.750,
75         121.500,
76         123.500,
77          -1.0 };
78 double *special_frequencies;
79
80 double previous_com_frequency = 0.0;
81 int previous_ptt = 0;
82 float previous_vol = 0.0;
83 float previous_thd = 0.0;
84 int com_select = 0;
85 int max_com_instruments = 2;
86 struct airport *airportlist;
87 struct fgdata data;
88 char *prog; //program name
89
90 /* configuration values */
91 static bool debug = false;
92 static int port;
93 static const char *voipserver;
94 static const char *fgserver;
95 static const char *airport_option;
96 static double frequency = -1.0;
97 static const char *audio_in;
98 static const char *audio_out;
99 static double level_in = 0.7;
100 static double level_out = 0.7;
101 static double silence_thd = -20.0;
102 static bool mic_boost;
103 static char codec_option;
104 static const char *callsign;
105 static const char *username;
106 static const char *password;
107 static bool list_audio;
108 static char *positions_file;
109 static char *frequency_file;
110
111 static const OptionEntry fgcomOptionArray[] = {
112   {"debug", 'd', false, OPTION_NONE, &debug, 0, "show debugging information",
113    0},
114   {"voipserver", 'S', true, OPTION_STRING, &voipserver, 0,
115    "voip server to connect to", &DEFAULT_VOIP_SERVER},
116   {"fgserver", 's', true, OPTION_STRING, &fgserver, 0, "fg to connect to ",
117    &DEFAULT_FG_SERVER},
118   {"port", 'p', true, OPTION_INT, &port, 0, "where we should listen to FG", &port},     // hm, good idea? don't think so...
119   {"airport", 'a', true, OPTION_STRING, &airport_option, 0,
120    "airport-id (ICAO) for ATC-mode", 0},
121   {"frequency", 'f', true, OPTION_DOUBLE, &frequency, 0,
122    "frequency for ATC-mode", 0},
123   {"callsign", 'C', true, OPTION_STRING, &callsign, 0,
124    "callsign to use", &DEFAULT_USER},
125   {"user", 'U', true, OPTION_STRING, &username, 0,
126    "username for VoIP account", &DEFAULT_USER},
127   {"password", 'P', true, OPTION_STRING, &password, 0,
128    "password for VoIP account", &DEFAULT_PASSWORD},
129   {"mic", 'i', true, OPTION_DOUBLE, &level_in, 0,
130    "mic input level (0.0 - 1.0)", 0},
131   {"speaker", 'o', true, OPTION_DOUBLE, &level_out, 0,
132    "speaker output level (0.0 - 1.0)", 0},
133   {"mic-boost", 'b', false, OPTION_BOOL, &mic_boost, 0, "enable mic boost",
134    0},
135   {"silence-threshold", 't', false, OPTION_DOUBLE, &silence_thd, 0, "set silence threshold (-60.0 - 0.0)",
136    0},
137   {"list-audio", 'l', false, OPTION_BOOL, &list_audio, 0,
138    "list audio devices", 0},
139   {"set-audio-in", 'r', true, OPTION_STRING, &audio_in, 0,
140    "use <devicename> as audio input", 0},
141   {"set-audio-out", 'k', true, OPTION_STRING, &audio_out, 0,
142    "use <devicename> as audio output", 0},
143   {"codec", 'c', true, OPTION_CHAR, &codec_option, 0,
144    "use codec <codec> as transfer codec", &codec_option},
145   {"positions", 'T', true, OPTION_STRING, &positions_file, 0,
146    "location positions file", &DEFAULT_POSITIONS_FILE},
147   {"special", 'Q', true, OPTION_STRING, &frequency_file, 0,
148    "location spl. frequency file (opt)", &SPECIAL_FREQUENCIES_FILE},
149   {NULL}
150 };
151
152 void
153 process_packet (char *buf)
154 {
155
156   /* cut off ending \n */
157   buf[strlen (buf) - 1] = '\0';
158
159   /* parse the data into a struct */
160   parse_fgdata (&data, buf);
161
162   /* get the selected frequency */
163   if (com_select == 0 && data.COM1_SRV == 1)
164     selected_frequency = data.COM1_FRQ;
165   else if (com_select == 1 && data.COM2_SRV == 1)
166     selected_frequency = data.COM2_FRQ;
167
168   /* Check for com frequency changes */
169   if (previous_com_frequency != selected_frequency)
170     {
171       SG_LOG( SG_GENERAL, SG_ALERT, "Selected frequency: " << selected_frequency );
172
173       /* remark the new frequency */
174       previous_com_frequency = selected_frequency;
175
176       if (connected == 1)
177         {
178           /* hangup call, if connected */
179           iaxc_dump_call ();
180           iaxc_millisleep (5 * DEFAULT_MILLISLEEP);
181           connected = 0;
182         }
183
184       strcpy (icao,
185               icaobypos (airportlist, selected_frequency, data.LAT,
186                          data.LON, DEFAULT_RANGE));
187       icao2number (icao, selected_frequency, num_buf);
188
189       SG_LOG( SG_GENERAL, SG_DEBUG, "Dialing " << icao << " " << selected_frequency << " MHz: " << num_buf );
190       do_iaxc_call (username, password, voipserver, num_buf);
191
192       connected = 1;
193     }
194
195   /* Check for pressed PTT key */
196   if (previous_ptt != data.PTT)
197     {
198       if (data.PTT == 2)
199         {
200           /* select the next com equipment */
201           com_select = (com_select + 1) % 2;
202           SG_LOG( SG_GENERAL, SG_ALERT, "Radio selected is " << radio_map[com_select] );
203         }
204       else if (connected == 1)
205         {
206           ptt (data.PTT);
207         }
208       previous_ptt = data.PTT;
209     }
210
211   /* Check for output volume change */
212   if (previous_vol != data.OUTPUT_VOL)
213     {
214       SG_LOG( SG_GENERAL, SG_ALERT, "Set speaker volume to " << data.OUTPUT_VOL );
215
216       iaxc_output_level_set( data.OUTPUT_VOL );
217       previous_vol = data.OUTPUT_VOL;
218     }
219
220   /* Check for silence threshold change */
221   if (previous_thd != data.SILENCE_THD)
222     {
223       SG_LOG( SG_GENERAL, SG_ALERT, "Set silent threshold to " << data.SILENCE_THD );
224
225       iaxc_set_silence_threshold( data.SILENCE_THD );
226       previous_thd = data.SILENCE_THD;
227     }
228
229   /* Check for callsign change */
230   if (strcmp(callsign, data.CALLSIGN) != 0)
231     {
232       iaxc_dump_call ();
233       callsign = data.CALLSIGN;
234       SG_LOG( SG_GENERAL, SG_ALERT, "FGCom will restart now with callsign " << callsign );
235       std::string app = "FGCOM-";
236       app += FGCOM_VERSION;
237       iaxc_set_callerid ( callsign, app.c_str() );
238     }
239 }
240
241 static char *base_name( char *name )
242 {
243     char *bn = name;
244     size_t len = strlen(name);
245     size_t i;
246     int c;
247     for ( i = 0; i < len; i++ ) {
248         c = name[i];
249         if (( c == '/' ) || ( c == '\\'))
250             bn = &name[i+1];
251     }
252     return bn;
253 }
254
255 // if default FAILS, for OSX and WIN try EXE path
256 int fix_input_files()
257 {
258     int iret = 0;
259     char *def_freq = (char *) SPECIAL_FREQUENCIES_FILE;
260     char *def_pos  = (char *) DEFAULT_POSITIONS_FILE;
261     char exepath[MX_PATH_SIZE+2];
262     exepath[0] = 0;
263     if (strcmp( frequency_file,def_freq) == 0) {
264         /* ok is default value - do some fixes */
265         if (is_file_or_directory( frequency_file ) != 1) {
266             exepath[0] = 0;
267             iret |= get_data_path_per_os( exepath, MX_PATH_SIZE );
268             strcat(exepath,def_freq);
269             frequency_file = strdup(exepath);
270         }
271     }
272     if (strcmp( positions_file,def_pos) == 0) {
273         // if default FAILS, for OSX and WIN try EXE path
274         if (is_file_or_directory( positions_file ) != 1) {
275             exepath[0] = 0;
276             iret |= get_data_path_per_os( exepath, MX_PATH_SIZE );
277             strcat(exepath,def_pos);
278             positions_file = strdup(exepath);
279         }
280     }
281     return iret;
282 }
283
284 int
285 main (int argc, char *argv[])
286 {
287   int numbytes;
288   static char pkt_buf[MAXBUFLEN+2];
289
290   simgear::requestConsole();
291   sglog().setLogLevels( SG_ALL, SG_ALERT );
292
293   prog = strdup( base_name(argv[0]) );
294   
295   /* program header */
296   SG_LOG( SG_GENERAL, SG_ALERT, prog << " - a communication radio based on VoIP with IAX/Asterisk" );
297   SG_LOG( SG_GENERAL, SG_ALERT, "Original (c) 2007-2011 by H. Wirtz <wirtz@dfn.de>" );
298   SG_LOG( SG_GENERAL, SG_ALERT, "OSX and Windows ports 2012-2013 by Yves Sablonier and Geoff R. McLane, resp." );
299   SG_LOG( SG_GENERAL, SG_ALERT, "Version " << FGCOM_VERSION << " compiled " << __DATE__ << ", at " << __TIME__ );
300   SG_LOG( SG_GENERAL, SG_ALERT, "Using iaxclient library Version " << iaxc_version (pkt_buf) );
301   SG_LOG( SG_GENERAL, SG_ALERT, "" );
302
303   /* init values */
304   voipserver = DEFAULT_VOIP_SERVER;
305   fgserver = DEFAULT_FG_SERVER;
306   port = DEFAULT_FG_PORT;
307   username = DEFAULT_USER;
308   password = DEFAULT_PASSWORD;
309   codec_option = DEFAULT_CODEC;
310   mode = 0; // 0 = ATC mode, 1 = FG mode
311   positions_file = (char *) DEFAULT_POSITIONS_FILE;
312   frequency_file = (char *) SPECIAL_FREQUENCIES_FILE;
313
314 #ifndef _WIN32
315   /* catch signals */
316   signal (SIGINT, quit);
317   signal (SIGQUIT, quit);
318   signal (SIGTERM, quit);
319 #endif
320
321   /* setup iax */
322 #ifdef _MSC_VER
323   /* MSVC only - In certain circumstances the addresses placed in iaxc_sendto and iaxc_recvfrom 
324      can be an offset to a jump table, making a compare of the current address to the address of 
325      the actual imported function fail. So here ensure they are the same. */
326   iaxc_set_networking( (iaxc_sendto_t)sendto, (iaxc_recvfrom_t)recvfrom );
327 #endif // _MSC_VER
328
329   if (iaxc_initialize (DEFAULT_MAX_CALLS))
330     fatal_error ("cannot initialize iaxclient!\nHINT: Have you checked the mic and speakers?");
331
332   initialized = 1;
333
334   // option parser
335   fgcomInitOptions (fgcomOptionArray, argc, argv);
336
337   if( debug )
338     sglog().setLogLevels( SG_ALL, SG_DEBUG );
339
340   // codec
341   if (codec_option)
342     {
343       switch (codec_option)
344         {
345         case 'u':
346           codec = IAXC_FORMAT_ULAW;
347           break;
348         case 'a':
349           codec = IAXC_FORMAT_ALAW;
350           break;
351         case 'g':
352           codec = IAXC_FORMAT_GSM;
353           break;
354         case '7':
355           codec = IAXC_FORMAT_G726;
356           break;
357         case 's':
358           codec = IAXC_FORMAT_SPEEX;
359           break;
360         }
361     }
362
363   // airport
364   if (airport_option)
365     {
366       strtoupper (airport_option, airport, sizeof (airport));
367     }
368
369   // input level
370   if (level_in > 1.0)
371     {
372       level_in = 1.0;
373     }
374   if (level_in < 0.0)
375     {
376       level_in = 0.0;
377     }
378
379   // output level
380   if (level_out > 1.0)
381     {
382       level_out = 1.0;
383     }
384   if (level_out < 0.0)
385     {
386       level_out = 0.0;
387     }
388
389   // microphone boost
390   if (mic_boost)
391     {
392       iaxc_mic_boost_set (1);
393     }
394
395   if (list_audio)
396     {
397       SG_LOG( SG_GENERAL, SG_ALERT, "Input audio devices:" );
398       SG_LOG( SG_GENERAL, SG_ALERT, report_devices(IAXC_AD_INPUT) );
399
400       SG_LOG( SG_GENERAL, SG_ALERT, "Output audio devices:" );
401       SG_LOG( SG_GENERAL, SG_ALERT, report_devices(IAXC_AD_OUTPUT) );
402
403       iaxc_shutdown ();
404       exit (1);
405     }
406
407
408   if (audio_in)
409     {
410       set_device (audio_in, 0);
411     }
412
413   if (audio_out)
414     {
415       set_device (audio_out, 1);
416     }
417
418   /* checking consistency of arguments */
419   if (frequency > 0.0 && frequency < 1000.0)
420     {
421       if (strlen (airport) == 0 || strlen (airport) > 4)
422         {
423           strcpy (airport, "ZZZZ");
424         }
425       /* airport and frequency are given => ATC mode */
426       mode = 0;
427     }
428   else
429     {
430       /* no airport => FG mode */
431       mode = 1;
432     }
433
434         /* Read special frequencies file (if exists).
435          * If no file $(INSTALL_DIR)/special_frequencies.txt exists, then default frequencies
436          * are used and are hard coded.
437          */
438
439     if (fix_input_files()) { /* adjust default input per OS */
440         fatal_error ("cannot adjust default input files per OS!\nHINT: Maybe recompile with larger buffer.");
441     }
442
443         if((special_frequencies = read_special_frequencies(frequency_file)) == 0) {
444         SG_LOG( SG_GENERAL, SG_ALERT, "Failed to load file [" << frequency_file << "] !" );
445         SG_LOG( SG_GENERAL, SG_ALERT, "Using internal defaults" );
446         special_frequencies = special_frq;
447     } else {
448         SG_LOG( SG_GENERAL, SG_ALERT, "Loaded file [" << frequency_file << "]." );
449     }
450
451         /* read airport frequencies and positions */
452         airportlist = read_airports (positions_file);   /* never returns if fail! */
453
454   /* preconfigure iax */
455   SG_LOG( SG_GENERAL, SG_ALERT, "Initializing IAX client as " << username << ":" << "xxxxxxxxxxx@" << voipserver );
456
457   std::string app = "FGCOM-";
458   app += FGCOM_VERSION;
459
460   if( !callsign )
461     callsign = DEFAULT_USER;
462
463   iaxc_set_callerid ( callsign, app.c_str() );
464   iaxc_set_formats (IAXC_FORMAT_SPEEX, IAXC_FORMAT_ULAW|IAXC_FORMAT_SPEEX);
465   iaxc_set_speex_settings(1, 5, 0, 1, 0, 3);
466   iaxc_set_filters(IAXC_FILTER_AGC | IAXC_FILTER_DENOISE);
467   iaxc_set_silence_threshold(silence_thd);
468   iaxc_set_event_callback (iaxc_callback);
469
470   iaxc_start_processing_thread ();
471
472   if (username && password && voipserver)
473     {
474       reg_id =
475         iaxc_register (const_cast < char *>(username),
476                        const_cast < char *>(password),
477                        const_cast < char *>(voipserver));
478       SG_LOG( SG_GENERAL, SG_DEBUG, "Registered as '" << username << "' at '" << voipserver );
479     }
480   else
481     {
482       SG_LOG( SG_GENERAL, SG_ALERT, "Failed iaxc_register !" );
483       SG_LOG( SG_GENERAL, SG_ALERT, "HINT: Check username, passwordd and address of server" );
484       exitcode = 130;
485       quit (0);
486     }
487
488   iaxc_millisleep (DEFAULT_MILLISLEEP);
489
490   /* main loop */
491   if (mode == 1)
492     {
493       SG_LOG( SG_GENERAL, SG_DEBUG, "Entering main loop in mode FGFS" );
494       /* only in FG mode */
495       simgear::Socket::initSockets();
496       sgSocket.open (false);
497       sgSocket.bind (fgserver, port);
498
499       /* mute mic, speaker on */
500       iaxc_input_level_set (0);
501       iaxc_output_level_set (level_out);
502
503       SGTimeStamp sg_clock;
504       sg_clock.stamp();
505
506       double sg_next_update = sg_clock.toSecs() + DEFAULT_ALARM_TIMER;
507       /* get data from flightgear */
508       while (1)
509         {
510           sg_clock.stamp();
511           double sg_wait = sg_next_update - sg_clock.toSecs();
512           if (sg_wait > 0.001)
513             {
514              simgear::Socket *readSockets[2] = { &sgSocket, 0 };
515              if (sgSocket.select (readSockets, readSockets + 1,
516                                    (int) (sg_wait * 1000)) == 1)
517                 {
518                   simgear::IPAddress their_addr;
519                   numbytes = sgSocket.recvfrom(pkt_buf, MAXBUFLEN - 1, 0, &their_addr);
520                   if (numbytes == -1)
521                     {
522                       perror ("recvfrom");
523                       exit (1);
524                     }
525                   pkt_buf[numbytes] = '\0';
526                   SG_LOG( SG_GENERAL, SG_DEBUG, "Got packet from " << their_addr.getHost () << ":" << their_addr.getPort () );
527                   SG_LOG( SG_GENERAL, SG_DEBUG, "Packet is " << numbytes << " bytes long" );
528                   SG_LOG( SG_GENERAL, SG_DEBUG, "Packet contains \"" << pkt_buf << "\"" );
529                   process_packet (pkt_buf);
530                 }
531             }
532           else
533             {
534               alarm_handler (0);
535               sg_clock.stamp();
536               sg_next_update = sg_clock.toSecs() + DEFAULT_ALARM_TIMER;
537             }
538         }
539     }
540   else
541     {
542       SG_LOG( SG_GENERAL, SG_DEBUG, "Entering main loop in mode ATC" );
543       /* mic on, speaker on */
544       iaxc_input_level_set (1.0);
545       iaxc_output_level_set (1.0);
546
547       icao2atisnumber (airport, frequency, num_buf);
548       SG_LOG( SG_GENERAL, SG_DEBUG, "Dialing " << airport << " " << frequency << " MHz: " << num_buf );
549       do_iaxc_call (username, password, voipserver, num_buf);
550
551       while (1)
552         {
553           /* sleep endless */
554           SGTimeStamp::sleepForMSec(3600000);
555         }
556     }
557
558   /* should never be reached */
559   exitcode = 999;
560   quit (0);
561 }
562
563 void
564 quit (int signal)
565 {
566   SG_LOG( SG_GENERAL, SG_ALERT, "Stopping service" );
567
568   if (initialized)
569     iaxc_shutdown ();
570   if (reg_id)
571     iaxc_unregister (reg_id);
572
573   exit (exitcode);
574 }
575
576 void
577 alarm_handler (int signal)
578 {
579   /* Check every DEFAULT_ALARM_TIMER seconds if position related things should happen */
580   if (check_special_frq (selected_frequency))
581     {
582       strcpy (icao, "ZZZZ");
583     }
584   else
585     {
586       strcpy (icao,
587               icaobypos (airportlist, selected_frequency, data.LAT, data.LON,
588                          DEFAULT_RANGE));
589     }
590
591   /* Check if we are out of range */
592   if (strlen (icao) == 0 && connected == 1)
593     {
594       /* Yes, we are out of range so hangup */
595       iaxc_dump_call ();
596       iaxc_millisleep (5 * DEFAULT_MILLISLEEP);
597       connected = 0;
598     }
599
600   /* Check if we are now in range */
601   else if (strlen (icao) != 0 && connected == 0)
602     {
603       icao2number (icao, selected_frequency, num_buf);
604       SG_LOG( SG_GENERAL, SG_DEBUG, "Dialing " << icao << " " << selected_frequency << " MHz: " << num_buf );
605       do_iaxc_call (username, password, voipserver, num_buf);
606       connected = 1;
607     }
608 }
609
610 void
611 strtoupper (const char *str, char *buf, size_t len)
612 {
613   unsigned int i;
614   for (i = 0; str[i] && i < len - 1; i++)
615     {
616       buf[i] = toupper (str[i]);
617     }
618
619   buf[i++] = '\0';
620 }
621
622 void
623 fatal_error (const char *err)
624 {
625   SG_LOG( SG_GENERAL, SG_ALERT, "FATAL ERROR: " << err );
626   if (initialized)
627     iaxc_shutdown ();
628   exit (1);
629 }
630
631 int
632 iaxc_callback (iaxc_event e)
633 {
634   switch (e.type)
635     {
636     case IAXC_EVENT_LEVELS:
637       event_level (e.ev.levels.input, e.ev.levels.output);
638       break;
639     case IAXC_EVENT_TEXT:
640       event_text (e.ev.text.type, e.ev.text.message);
641       break;
642     case IAXC_EVENT_STATE:
643       event_state (e.ev.call.state, e.ev.call.remote, e.ev.call.remote_name,
644                    e.ev.call.local, e.ev.call.local_context);
645       break;
646     case IAXC_EVENT_NETSTAT:
647       event_netstats (e.ev.netstats);
648     case IAXC_EVENT_REGISTRATION:
649       event_register (e.ev.reg.id, e.ev.reg.reply, e.ev.reg.msgcount);
650       break;
651     default:
652       event_unknown (e.type);
653       break;
654     }
655   return 1;
656 }
657
658 void
659 event_state (int state, char *remote, char *remote_name,
660              char *local, char *local_context)
661 {
662   last_state = state;
663   /* This is needed for auto-reconnect */
664   if (state == 0)
665     {
666       connected = 0;
667       /* FIXME: we should wake up the main thread somehow */
668       /* in fg mode the next incoming packet will do that anyway */
669     }
670
671   snprintf (rep_buf, MX_REPORT_BUF,
672             "S%c0x%x%c%s%c%.50s%c%.50s%c%.50s%c%.50s", delim, state,
673             delim, map_state (state), delim, remote, delim, remote_name,
674             delim, local, delim, local_context);
675   rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
676   report (rep_buf);
677 }
678
679 void
680 event_text (int type, char *message)
681 {
682   snprintf (rep_buf, MX_REPORT_BUF, "T%c%d%c%.200s", delim, type, delim, message);
683   SG_LOG( SG_GENERAL, SG_ALERT, message );
684   rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
685   report (rep_buf);
686 }
687
688 void
689 event_register (int id, int reply, int count)
690 {
691   const char *reason;
692   switch (reply)
693     {
694     case IAXC_REGISTRATION_REPLY_ACK:
695       reason = "accepted";
696       break;
697     case IAXC_REGISTRATION_REPLY_REJ:
698       reason = "denied";
699       if (strcmp (username, "guest") != 0)
700         {
701           SG_LOG( SG_GENERAL, SG_ALERT, "Registering denied" );
702         }
703       break;
704     case IAXC_REGISTRATION_REPLY_TIMEOUT:
705       reason = "timeout";
706       break;
707     default:
708       reason = "unknown";
709     }
710   snprintf (rep_buf, MX_REPORT_BUF, "R%c%d%c%s%c%d", delim, id, delim,
711             reason, delim, count);
712   rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
713   report (rep_buf);
714 }
715
716 void
717 event_netstats (struct iaxc_ev_netstats stat)
718 {
719   struct iaxc_netstat local = stat.local;
720   struct iaxc_netstat remote = stat.remote;
721   snprintf (rep_buf, MX_REPORT_BUF,
722             "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",
723             delim, stat.callNo, delim, stat.rtt,
724             delim, local.jitter, delim, local.losspct, delim,
725             local.losscnt, delim, local.packets, delim, local.delay,
726             delim, local.dropped, delim, local.ooo, delim,
727             remote.jitter, delim, remote.losspct, delim, remote.losscnt,
728             delim, remote.packets, delim, remote.delay, delim,
729             remote.dropped, delim, remote.ooo);
730   rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
731   report (rep_buf);
732 }
733
734 void
735 event_level (double in, double out)
736 {
737   snprintf (rep_buf, MX_REPORT_BUF, "L%c%.1f%c%.1f", delim, in, delim, out);
738   rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
739   report (rep_buf);
740 }
741
742 const char *
743 map_state (int state)
744 {
745   int i, j;
746   int next = 0;
747   *states = '\0';
748   if (state == 0)
749     {
750       return "free";
751     }
752   for (i = 0, j = 1; map[i] != NULL; i++, j <<= 1)
753     {
754       if (state & j)
755         {
756           if (next)
757             strcat (states, ",");
758           strcat (states, map[i]);
759           next = 1;
760         }
761     }
762   return states;
763 }
764
765 void
766 event_unknown (int type)
767 {
768   snprintf (rep_buf, MX_REPORT_BUF, "U%c%d", delim, type);
769   rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
770   report (rep_buf);
771 }
772
773 void
774 report (char *text)
775 {
776   if (debug)
777     {
778       SG_LOG( SG_GENERAL, SG_ALERT, text );
779       fflush (stdout);
780     }
781 }
782
783 void
784 ptt (int mode)
785 {
786   if (mode == 1)
787     {
788       /* mic is muted so unmute and mute speaker */
789       iaxc_input_level_set (level_in);
790       iaxc_output_level_set (0.0);
791       SG_LOG( SG_GENERAL, SG_ALERT, "[SPEAK] unmute mic, mute speaker" );
792     }
793   else
794     {
795       /* mic is unmuted so mute and unmute speaker */
796       iaxc_input_level_set (0.0);
797       iaxc_output_level_set (level_out);
798       SG_LOG( SG_GENERAL, SG_ALERT, "[LISTEN] mute mic, unmute speaker" );
799     }
800 }
801
802 int
803 split (char *string, char *fields[], int nfields, const char *sep)
804 {
805   register char *p = string;
806   register char c;              /* latest character */
807   register char sepc = sep[0];
808   register char sepc2;
809   register int fn;
810   register char **fp = fields;
811   register const char *sepp;
812   register int trimtrail;
813   /* white space */
814   if (sepc == '\0')
815     {
816       while ((c = *p++) == ' ' || c == '\t')
817         continue;
818       p--;
819       trimtrail = 1;
820       sep = " \t";              /* note, code below knows this is 2 long */
821       sepc = ' ';
822     }
823   else
824     trimtrail = 0;
825   sepc2 = sep[1];               /* now we can safely pick this up */
826   /* catch empties */
827   if (*p == '\0')
828     return (0);
829   /* single separator */
830   if (sepc2 == '\0')
831     {
832       fn = nfields;
833       for (;;)
834         {
835           *fp++ = p;
836           fn--;
837           if (fn == 0)
838             break;
839           while ((c = *p++) != sepc)
840             if (c == '\0')
841               return (nfields - fn);
842           *(p - 1) = '\0';
843         }
844       /* we have overflowed the fields vector -- just count them */
845       fn = nfields;
846       for (;;)
847         {
848           while ((c = *p++) != sepc)
849             if (c == '\0')
850               return (fn);
851           fn++;
852         }
853       /* not reached */
854     }
855
856   /* two separators */
857   if (sep[2] == '\0')
858     {
859       fn = nfields;
860       for (;;)
861         {
862           *fp++ = p;
863           fn--;
864           while ((c = *p++) != sepc && c != sepc2)
865             if (c == '\0')
866               {
867                 if (trimtrail && **(fp - 1) == '\0')
868                   fn++;
869                 return (nfields - fn);
870               }
871           if (fn == 0)
872             break;
873           *(p - 1) = '\0';
874           while ((c = *p++) == sepc || c == sepc2)
875             continue;
876           p--;
877         }
878       /* we have overflowed the fields vector -- just count them */
879       fn = nfields;
880       while (c != '\0')
881         {
882           while ((c = *p++) == sepc || c == sepc2)
883             continue;
884           p--;
885           fn++;
886           while ((c = *p++) != '\0' && c != sepc && c != sepc2)
887             continue;
888         }
889       /* might have to trim trailing white space */
890       if (trimtrail)
891         {
892           p--;
893           while ((c = *--p) == sepc || c == sepc2)
894             continue;
895           p++;
896           if (*p != '\0')
897             {
898               if (fn == nfields + 1)
899                 *p = '\0';
900               fn--;
901             }
902         }
903       return (fn);
904     }
905
906   /* n separators */
907   fn = 0;
908   for (;;)
909     {
910       if (fn < nfields)
911         *fp++ = p;
912       fn++;
913       for (;;)
914         {
915           c = *p++;
916           if (c == '\0')
917             return (fn);
918           sepp = sep;
919           while ((sepc = *sepp++) != '\0' && sepc != c)
920             continue;
921           if (sepc != '\0')     /* it was a separator */
922             break;
923         }
924       if (fn < nfields)
925         *(p - 1) = '\0';
926       for (;;)
927         {
928           c = *p++;
929           sepp = sep;
930           while ((sepc = *sepp++) != '\0' && sepc != c)
931             continue;
932           if (sepc == '\0')     /* it wasn't a separator */
933             break;
934         }
935       p--;
936     }
937
938   /* not reached */
939 }
940
941 /**
942  *
943  * \fn double *read_special_frequencies(const char *file)
944  *
945  * \brief Reads the file "special_frequencies.txt" if it exists.
946  * If no file exists, then no special frequencies are useable.
947  *
948  * \param file  pointer on the filename.
949  *
950  * \return Returns the pointer on an array containing doubles if file
951  * has been successfully opened and read, otherwise returns NULL.
952  *
953  */
954 double *read_special_frequencies(const char *file)
955 {
956         double *l_pfrq = NULL;
957         double l_value;
958         int l_count;
959         int l_allocated = 0;
960         int l_new_size;
961
962         if((l_pfrq = (double *)malloc(ALLOC_CHUNK_SIZE * sizeof(double))) != NULL)
963         {
964                 l_allocated += ALLOC_CHUNK_SIZE;
965
966                 if(FGC_SUCCESS(parser_init(file)))
967                 {
968                         l_count = 0;
969
970                         while(FGC_SUCCESS(parser_get_next_value(&l_value)))
971                         {
972                                 if(l_count >= l_allocated)
973                                 {
974                                         l_new_size = ALLOC_CHUNK_SIZE * (l_count / ALLOC_CHUNK_SIZE + 1);
975                                         l_pfrq = (double *)realloc(l_pfrq, l_new_size * sizeof(double));
976                                         l_allocated += ALLOC_CHUNK_SIZE;
977                                 }
978
979                                 l_pfrq[l_count] = l_value;
980
981                                 l_count++;
982                         }
983
984                         /* Last value of the array must be -1.0 which is the terminator. */
985                         if(l_count == l_allocated)
986                                 l_pfrq = (double *)realloc(l_pfrq, (l_count + 1) * sizeof(double));
987                         l_pfrq[l_count] = -1.0;
988                 } else {
989             // failed to open file
990             parser_exit();
991             free(l_pfrq);
992             return 0;
993         }
994         }
995
996         parser_exit();
997
998         return(l_pfrq);
999 }
1000
1001
1002 struct airport *
1003 read_airports (const char *file)
1004 {
1005   FILE *fp;
1006   int ret;
1007   struct airport airport_tmp;
1008   struct airport *first = NULL;
1009   struct airport *my_airport = NULL;
1010   struct airport *previous_airport = NULL;
1011   size_t counter = 0;
1012
1013   SG_LOG( SG_GENERAL, SG_ALERT, "Reading airports [" << file << "]" );
1014
1015   if ((fp = fopen (file, "rt")) == NULL)
1016     {
1017       SG_LOG( SG_GENERAL, SG_ALERT, "ERROR: open failed!" );
1018       perror ("fopen");
1019       exitcode = 120;
1020       quit (0);
1021     }
1022
1023   airport_tmp.next = NULL;
1024   while ((ret = fscanf (fp, " %4[^,],%f,%lf,%lf,%128[^,],%128[^\r\n]",
1025                         airport_tmp.icao, &airport_tmp.frequency,
1026                         &airport_tmp.lat, &airport_tmp.lon,
1027                         airport_tmp.type, airport_tmp.text)) == 6)
1028     {
1029         counter++;
1030       if ((my_airport =
1031            (struct airport *) malloc (sizeof (struct airport))) == NULL)
1032         {
1033           SG_LOG( SG_GENERAL, SG_ALERT, "Error allocating memory for airport data" );
1034           exitcode = 900;
1035           quit (0);
1036         }
1037
1038       if (first == NULL)
1039         first = my_airport;
1040       memcpy (my_airport, &airport_tmp, sizeof (airport_tmp));
1041       if (previous_airport != NULL)
1042         {
1043           previous_airport->next = my_airport;
1044         }
1045       previous_airport = my_airport;
1046     }
1047
1048   fclose (fp);
1049   if (ret != EOF)
1050     {
1051       SG_LOG( SG_GENERAL, SG_ALERT, "ERROR during reading airports!" );
1052       exitcode = 900;
1053       quit (0);
1054     }
1055
1056   SG_LOG( SG_GENERAL, SG_ALERT, "loaded " << counter << " entries" );
1057   return (first);
1058 }
1059
1060 char *
1061 report_devices (int in)
1062 {
1063   struct iaxc_audio_device *devs;  //audio devices
1064   int ndevs;                       //audio dedvice count
1065   int input, output, ring;         //audio device id's
1066   int current, i;
1067   int flag = in ? IAXC_AD_INPUT : IAXC_AD_OUTPUT;
1068   iaxc_audio_devices_get (&devs, &ndevs, &input, &output, &ring);
1069   current = in ? input : output;
1070   snprintf (rep_buf, MX_REPORT_BUF, "%s\n", devs[current].name);
1071   for (i = 0; i < ndevs; i++)
1072     {
1073       if (devs[i].capabilities & flag && i != current)
1074         {
1075           snprintf (rep_buf + strlen (rep_buf), MX_REPORT_BUF - strlen (rep_buf), "%s\n",
1076                     devs[i].name);
1077         }
1078         rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
1079         if (strlen(rep_buf) >= MX_REPORT_BUF)
1080             break;
1081     }
1082   rep_buf[MX_REPORT_BUF] = 0; /* ensure null termination */
1083   return rep_buf;
1084 }
1085
1086 int
1087 set_device (const char *name, int out)
1088 {
1089   struct iaxc_audio_device *devs;       /* audio devices */
1090   int ndevs;                    /* audio dedvice count */
1091   int input, output, ring;      /* audio device id's */
1092   int i;
1093   iaxc_audio_devices_get (&devs, &ndevs, &input, &output, &ring);
1094   for (i = 0; i < ndevs; i++)
1095     {
1096       if (devs[i].capabilities & (out ? IAXC_AD_OUTPUT : IAXC_AD_INPUT) &&
1097           strcmp (name, devs[i].name) == 0)
1098         {
1099           if (out)
1100             {
1101               output = devs[i].devID;
1102             }
1103           else
1104             {
1105               input = devs[i].devID;
1106             }
1107           fprintf (stderr, "device %s = %s (%d)\n", out ? "out" : "in", name,
1108                    devs[i].devID);
1109           iaxc_audio_devices_set (input, output, ring);
1110           return 1;
1111         }
1112     }
1113   return 0;
1114 }
1115
1116 void
1117 parse_fgdata (struct fgdata *data, char *buf)
1118 {
1119   char *data_pair = NULL;
1120   char *fields[2];
1121   fields[0] = '\0';
1122   fields[1] = '\0';
1123   SG_LOG( SG_GENERAL, SG_DEBUG, "Parsing data: [" << buf << "]" );
1124   /* Parse data from FG */
1125   data_pair = strtok (buf, ",");
1126   while (data_pair != NULL)
1127     {
1128       split (data_pair, fields, 2, "=");
1129       if (strcmp (fields[0], "COM1_FRQ") == 0)
1130         {
1131           data->COM1_FRQ = atof (fields[1]);
1132           SG_LOG( SG_GENERAL, SG_DEBUG, "COM1_FRQ=" << data->COM1_FRQ );
1133         }
1134       else if (strcmp (fields[0], "COM2_FRQ") == 0)
1135         {
1136           data->COM2_FRQ = atof (fields[1]);
1137           SG_LOG( SG_GENERAL, SG_DEBUG, "COM2_FRQ=" << data->COM2_FRQ );
1138         }
1139       else if (strcmp (fields[0], "NAV1_FRQ") == 0)
1140         {
1141           data->NAV1_FRQ = atof (fields[1]);
1142           SG_LOG( SG_GENERAL, SG_DEBUG, "NAV1_FRQ=" << data->NAV1_FRQ );
1143         }
1144       else if (strcmp (fields[0], "NAV2_FRQ") == 0)
1145         {
1146           data->NAV2_FRQ = atof (fields[1]);
1147           SG_LOG( SG_GENERAL, SG_DEBUG, "NAV2_FRQ=" << data->NAV2_FRQ );
1148         }
1149       else if (strcmp (fields[0], "PTT") == 0)
1150         {
1151           data->PTT = atoi (fields[1]);
1152           SG_LOG( SG_GENERAL, SG_DEBUG, "PTT=" << data->PTT );
1153         }
1154       else if (strcmp (fields[0], "TRANSPONDER") == 0)
1155         {
1156           data->TRANSPONDER = atoi (fields[1]);
1157           SG_LOG( SG_GENERAL, SG_DEBUG, "TRANSPONDER=" << data->TRANSPONDER );
1158         }
1159       else if (strcmp (fields[0], "IAS") == 0)
1160         {
1161           data->IAS = atof (fields[1]);
1162           SG_LOG( SG_GENERAL, SG_DEBUG, "IAS=" << data->IAS );
1163         }
1164       else if (strcmp (fields[0], "GS") == 0)
1165         {
1166           data->GS = atof (fields[1]);
1167           SG_LOG( SG_GENERAL, SG_DEBUG, "GS=" << data->GS );
1168         }
1169       else if (strcmp (fields[0], "LON") == 0)
1170         {
1171           data->LON = atof (fields[1]);
1172           SG_LOG( SG_GENERAL, SG_DEBUG, "LON=" << data->LON );
1173         }
1174       else if (strcmp (fields[0], "LAT") == 0)
1175         {
1176           data->LAT = atof (fields[1]);
1177           SG_LOG( SG_GENERAL, SG_DEBUG, "LAT=" << data->LAT );
1178         }
1179       else if (strcmp (fields[0], "ALT") == 0)
1180         {
1181           data->ALT = atoi (fields[1]);
1182           SG_LOG( SG_GENERAL, SG_DEBUG, "ALT=" << data->ALT );
1183         }
1184       else if (strcmp (fields[0], "HEAD") == 0)
1185         {
1186           data->HEAD = atof (fields[1]);
1187           SG_LOG( SG_GENERAL, SG_DEBUG, "HEAD=" << data->HEAD );
1188         }
1189       else if (strcmp (fields[0], "COM1_SRV") == 0)
1190         {
1191           data->COM1_SRV = atoi (fields[1]);
1192           SG_LOG( SG_GENERAL, SG_DEBUG, "COM1_SRV" << data->COM1_SRV );
1193         }
1194       else if (strcmp (fields[0], "COM2_SRV") == 0)
1195         {
1196           data->COM2_SRV = atoi (fields[1]);
1197           SG_LOG( SG_GENERAL, SG_DEBUG, "COM2_SRV=" << data->COM2_SRV );
1198         }
1199       else if (strcmp (fields[0], "NAV1_SRV") == 0)
1200         {
1201           data->NAV1_SRV = atoi (fields[1]);
1202           SG_LOG( SG_GENERAL, SG_DEBUG, "NAV1_SRV=" << data->NAV1_SRV );
1203         }
1204       else if (strcmp (fields[0], "NAV2_SRV") == 0)
1205         {
1206           data->NAV2_SRV = atoi (fields[1]);
1207           SG_LOG( SG_GENERAL, SG_DEBUG, "NAV2_SRV=" << data->NAV2_SRV );
1208         }
1209       else if (strcmp (fields[0], "OUTPUT_VOL") == 0)
1210         {
1211           data->OUTPUT_VOL = atof (fields[1]);
1212           SG_LOG( SG_GENERAL, SG_DEBUG, "OUTPUT_VOL=" << data->OUTPUT_VOL );
1213         }
1214       else if (strcmp (fields[0], "SILENCE_THD") == 0)
1215         {
1216           data->SILENCE_THD = atof (fields[1]);
1217           SG_LOG( SG_GENERAL, SG_DEBUG, "SILENCE_THD=" << data->SILENCE_THD );
1218         }
1219       else if (strcmp (fields[0], "CALLSIGN") == 0)
1220         {
1221           data->CALLSIGN = fields[1];
1222           SG_LOG( SG_GENERAL, SG_DEBUG, "CALLSIGN=" << data->CALLSIGN );
1223         }
1224       else
1225         {
1226           SG_LOG( SG_GENERAL, SG_DEBUG, "Unknown field " << fields[0] << "=" << fields[1] );
1227         }
1228
1229       data_pair = strtok (NULL, ",");
1230     }
1231   SG_LOG( SG_GENERAL, SG_DEBUG, "" );
1232 }
1233
1234 /**
1235  *
1236  * \fn int check_special_frq (double frq)
1237  *
1238  * \brief Check to see if specified frequency is a special frequency.
1239  *
1240  * \param frq   frequency to check against special frequencies
1241  *
1242  * \return Returns 1 if successful, otherwise returns 0.
1243  *
1244  */
1245 int check_special_frq (double frq)
1246 {
1247         int i = 0;
1248     frq = ceilf(frq*1000.0)/1000.0; // 20130602: By Clement de l'Hamaide, to 'Make 123.450Mhz usable'
1249         while (special_frequencies[i] >= 0.0)
1250         {
1251                 if (frq == special_frequencies[i])
1252                 {
1253                         SG_LOG( SG_GENERAL, SG_ALERT, "Special frequency: " << frq );
1254                         return (1);
1255                 }
1256                 i++;
1257         }
1258         return (0);
1259 }
1260
1261 void
1262 do_iaxc_call (const char *username, const char *password,
1263               const char *voipserver, char *number)
1264 {
1265   char dest[256];
1266
1267   if( strcmp(number, "9990909090910000") == 0)
1268     number = (char *)"0190909090910000";
1269
1270   snprintf (dest, sizeof (dest), "%s:%s@%s/%s", username, password,
1271             voipserver, number);
1272   iaxc_call (dest);
1273   iaxc_millisleep (DEFAULT_MILLISLEEP);
1274 }
1275
1276 /* eof - fgcom.cpp */