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