// // This file is part of Dire Wolf, an amateur radio packet TNC. // // Copyright (C) 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018 John Langner, WB2OSZ // // 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, see . // #define CONFIG_C 1 // influences behavior of aprs_tt.h // #define DEBUG 1 /*------------------------------------------------------------------ * * Module: config.c * * Purpose: Read configuration information from a file. * * Description: This started out as a simple little application with a few * command line options. Due to creeping featurism, it's now * time to add a configuration file to specify options. * *---------------------------------------------------------------*/ #include "direwolf.h" #include #include #include #include #include #include #include #if ENABLE_GPSD #include /* for DEFAULT_GPSD_PORT (2947) */ #endif #include "ax25_pad.h" #include "textcolor.h" #include "audio.h" #include "digipeater.h" #include "cdigipeater.h" #include "config.h" #include "aprs_tt.h" #include "igate.h" #include "latlong.h" #include "symbols.h" #include "xmit.h" #include "tt_text.h" #include "ax25_link.h" #ifdef USE_CM108 // Linux only #include "cm108.h" #endif // geotranz #include "utm.h" #include "mgrs.h" #include "usng.h" #include "error_string.h" #define D2R(d) ((d) * M_PI / 180.) #define R2D(r) ((r) * 180. / M_PI) /* * Conversions from various units to meters. * There is some disagreement about the exact values for some of these. * Close enough for our purposes. * Parsec, light year, and angstrom are probably not useful. */ static const struct units_s { char *name; float meters; } units[] = { { "barleycorn", 0.008466667 }, { "inch", 0.0254 }, { "in", 0.0254 }, { "hand", 0.1016 }, { "shaku", 0.3030 }, { "foot", 0.304801 }, { "ft", 0.304801 }, { "cubit", 0.4572 }, { "megalithicyard", 0.8296 }, { "my", 0.8296 }, { "yard", 0.914402 }, { "yd", 0.914402 }, { "m", 1. }, { "meter", 1. }, { "metre", 1. }, { "ell", 1.143 }, { "ken", 1.818 }, { "hiro", 1.818 }, { "fathom", 1.8288 }, { "fath", 1.8288 }, { "toise", 1.949 }, { "jo", 3.030 }, { "twain", 3.6576074 }, { "rod", 5.0292 }, { "rd", 5.0292 }, { "perch", 5.0292 }, { "pole", 5.0292 }, { "rope", 6.096 }, { "dekameter", 10. }, { "dekametre", 10. }, { "dam", 10. }, { "chain", 20.1168 }, { "ch", 20.1168 }, { "actus", 35.47872 }, { "arpent", 58.471 }, { "hectometer", 100. }, { "hectometre", 100. }, { "hm", 100. }, { "cho", 109.1 }, { "furlong", 201.168 }, { "fur", 201.168 }, { "kilometer", 1000. }, { "kilometre", 1000. }, { "km", 1000. }, { "mile", 1609.344 }, { "mi", 1609.344 }, { "ri", 3927. }, { "league", 4828.032 }, { "lea", 4828.032 } }; #define NUM_UNITS ((int)((sizeof(units) / sizeof(struct units_s)))) static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config); /* Do we have a string of all digits? */ static int alldigits(char *p) { if (p == NULL) return (0); if (strlen(p) == 0) return (0); while (*p != '\0') { if ( ! isdigit(*p)) return (0); p++; } return (1); } /* Do we have a string of all letters or + or - ? */ static int alllettersorpm(char *p) { if (p == NULL) return (0); if (strlen(p) == 0) return (0); while (*p != '\0') { if ( ! isalpha(*p) && *p != '+' && *p != '-') return (0); p++; } return (1); } /*------------------------------------------------------------------ * * Name: parse_ll * * Purpose: Parse latitude or longitude from configuration file. * * Inputs: str - String like [-]deg[^min][hemisphere] * * which - LAT or LON for error checking and message. * * line - Line number for use in error message. * * Returns: Coordinate in signed degrees. * *----------------------------------------------------------------*/ /* Acceptable symbols to separate degrees & minutes. */ /* Degree symbol is not in ASCII so documentation says to use "^" instead. */ /* Some wise guy will try to use degree symbol. */ /* UTF-8 is more difficult because it is a two byte sequence, c2 b0. */ #define DEG1 '^' #define DEG2 0xb0 /* ISO Latin1 */ #define DEG3 0xf8 /* Microsoft code page 437 */ enum parse_ll_which_e { LAT, LON }; static double parse_ll (char *str, enum parse_ll_which_e which, int line) { char stemp[40]; int sign; double degrees, minutes; char *endptr; char hemi; int limit; unsigned char sep; /* * Remove any negative sign. */ strlcpy (stemp, str, sizeof(stemp)); sign = +1; if (stemp[0] == '-') { sign = -1; stemp[0] = ' '; } /* * Process any hemisphere on the end. */ if (strlen(stemp) >= 2) { endptr = stemp + strlen(stemp) - 1; if (isalpha(*endptr)) { hemi = *endptr; *endptr = '\0'; if (islower(hemi)) { hemi = toupper(hemi); } if (hemi == 'W' || hemi == 'S') { sign = -sign; } if (which == LAT) { if (hemi != 'N' && hemi != 'S') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Latitude hemisphere in \"%s\" is not N or S.\n", line, str); } } else { if (hemi != 'E' && hemi != 'W') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Longitude hemisphere in \"%s\" is not E or W.\n", line, str); } } } } /* * Parse the degrees part. */ degrees = strtod (stemp, &endptr); /* * Is there a minutes part? */ sep = *endptr; if (sep != '\0') { if (sep == DEG1 || sep == DEG2 || sep == DEG3) { minutes = strtod (endptr+1, &endptr); if (*endptr != '\0') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str); } if (minutes >= 60.0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of minutes in \"%s\" is >= 60.\n", line, str); } degrees += minutes / 60.0; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unexpected character '%c' in location \"%s\"\n", line, sep, str); } } degrees = degrees * sign; limit = which == LAT ? 90 : 180; if (degrees < -limit || degrees > limit) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of degrees in \"%s\" is out of range for %s\n", line, str, which == LAT ? "latitude" : "longitude"); } //dw_printf ("%s = %f\n", str, degrees); return (degrees); } /*------------------------------------------------------------------ * * Name: parse_utm_zone * * Purpose: Parse UTM zone from configuration file. * * Inputs: szone - String like [-]number[letter] * * Outputs: latband - Latitude band if specified, otherwise space or -. * * hemi - Hemisphere, always one of 'N' or 'S'. * * Returns: Zone as number. * Type is long because Convert_UTM_To_Geodetic expects that. * * Errors: Prints message and return 0. * * Description: * It seems there are multiple conventions for specifying the UTM hemisphere. * * - MGRS latitude band. North if missing or >= 'N'. * - Negative zone for south. * - Separate North or South. * * I'm using the first alternatve. * GEOTRANS uses the third. * We will also recognize the second one but I'm not sure if I want to document it. * *----------------------------------------------------------------*/ long parse_utm_zone (char *szone, char *latband, char *hemi) { long lzone; char *zlet; *latband = ' '; *hemi = 'N'; /* default */ lzone = strtol(szone, &zlet, 10); if (*zlet == '\0') { /* Number is not followed by letter something else. */ /* Allow negative number to mean south. */ if (lzone < 0) { *latband = '-'; *hemi = 'S'; lzone = (- lzone); } } else { if (islower (*zlet)) { *zlet = toupper(*zlet); } *latband = *zlet; if (strchr ("CDEFGHJKLMNPQRSTUVWX", *zlet) != NULL) { if (*zlet < 'N') { *hemi = 'S'; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Latitudinal band in \"%s\" must be one of CDEFGHJKLMNPQRSTUVWX.\n", szone); *hemi = '?'; } } if (lzone < 1 || lzone > 60) { text_color_set(DW_COLOR_ERROR); dw_printf ("UTM Zone number %ld must be in range of 1 to 60.\n", lzone); } return (lzone); } /* end parse_utm_zone */ #if 0 main () { parse_ll ("12.5", LAT); parse_ll ("12.5N", LAT); parse_ll ("12.5E", LAT); // error parse_ll ("-12.5", LAT); parse_ll ("12.5S", LAT); parse_ll ("12.5W", LAT); // error parse_ll ("12.5", LON); parse_ll ("12.5E", LON); parse_ll ("12.5N", LON); // error parse_ll ("-12.5", LON); parse_ll ("12.5W", LON); parse_ll ("12.5S", LON); // error parse_ll ("12^30", LAT); parse_ll ("12\xb030", LAT); // ISO Latin-1 degree symbol parse_ll ("91", LAT); // out of range parse_ll ("91", LON); parse_ll ("181", LON); // out of range parse_ll ("12&5", LAT); // bad character } #endif /*------------------------------------------------------------------ * * Name: parse_interval * * Purpose: Parse time interval from configuration file. * * Inputs: str - String like 10 or 9:30 * * line - Line number for use in error message. * * Returns: Number of seconds. * * Description: This is used by the BEACON configuration items * for initial delay or time between beacons. * * The format is either minutes or minutes:seconds. * *----------------------------------------------------------------*/ static int parse_interval (char *str, int line) { char *p; int sec; int nc = 0; int bad = 0; for (p = str; *p != '\0'; p++) { if (*p == ':') nc++; else if ( ! isdigit(*p)) bad++; } if (bad > 0 || nc > 1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Time interval must be of the form minutes or minutes:seconds.\n", line); } p = strchr (str, ':'); if (p != NULL) { sec = atoi(str) * 60 + atoi(p+1); } else { sec = atoi(str) * 60; } return (sec); } /* end parse_interval */ /*------------------------------------------------------------------ * * Name: check_via_path * * Purpose: Check for valid path in beacons, IGate, and APRStt configuration. * * Inputs: via_path - Zero or more comma separated stations. * * Returns: Maximum number of digipeater hops or -1 for error. * * Description: Beacons and IGate can use via paths such as: * * WIDE1-1,MA3-3 * N2GH,RARA-7 * * Each part could be a specific station, an alias, or a path * from the "New n-N Paradigm." * In the first example above, the maximum number of digipeater * hops would be 4. In the second example, 2. * *----------------------------------------------------------------*/ // Put something like this in the config file as a quick test. // Not worth adding to "make check" regression tests. // // IBEACON via= // IBEACON via=W2UB // IBEACON via=W2UB-7 // IBEACON via=WIDE1-1,WIDE2-2,WIDE3-3 // IBEACON via=Lower // IBEACON via=T00LONG // IBEACON via=W2UB-16 // IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8 // IBEACON via=D1,D2,D3,D4,D5,D6,D7,D8,D9 // // Define below and visually check results. //#define DEBUG8 1 static int check_via_path (char *via_path) { char stemp[AX25_MAX_REPEATERS * (AX25_MAX_ADDR_LEN + 1)]; int num_digi = 0; int max_digi_hops = 0; char *r; char *a; #if DEBUG8 text_color_set(DW_COLOR_DEBUG); dw_printf ("check_via_path %s\n", via_path); #endif if (strlen(via_path) == 0) { return (0); } strlcpy (stemp, via_path, sizeof(stemp)); r = stemp; while (( a = strsep(&r,",")) != NULL) { int strict = 2; int ok; char addr[AX25_MAX_ADDR_LEN]; int ssid; int heard; num_digi++; ok = ax25_parse_addr (AX25_REPEATER_1 - 1 + num_digi, a, strict, addr, &ssid, &heard); if ( ! ok) { #if DEBUG8 text_color_set(DW_COLOR_DEBUG); dw_printf ("check_via_path bad address\n"); #endif return (-1); } /* Based on assumption that a callsign can't end with a digit. */ /* For something of the form xxx9-9, we take the ssid as max hop count. */ if (ssid > 0 && strlen(addr) >= 2 && isdigit(addr[strlen(addr)-1])) { max_digi_hops += ssid; } else { max_digi_hops++; } } if (num_digi > AX25_MAX_REPEATERS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Maximum of 8 digipeaters has been exceeded.\n"); return (-1); } #if DEBUG8 text_color_set(DW_COLOR_DEBUG); dw_printf ("check_via_path %d addresses, %d max digi hops\n", num_digi, max_digi_hops); #endif return (max_digi_hops); } /* end check_via_path */ /*------------------------------------------------------------------- * * Name: split * * Purpose: Separate a line into command and parameters. * * Inputs: string - Complete command line to start process. * NULL for subsequent calls. * * rest_of_line - Caller wants remainder of line, not just * the next parameter. * * Returns: Pointer to next part with any quoting removed. * * Description: the configuration file started out very simple and strtok * was used to split up the lines. As more complicated options * were added, there were several different situations where * parameter values might contain spaces. These were handled * inconsistently in different places. In version 1.3, we now * treat them consistently in one place. * * *--------------------------------------------------------------------*/ #define MAXCMDLEN 256 static char *split (char *string, int rest_of_line) { static char cmd[MAXCMDLEN]; static char token[MAXCMDLEN]; static char shutup[] = " "; // Shut up static analysis which gets upset // over the case where this could be called with // string NULL and c was not yet initialized. static char *c = shutup; // Current position in command line. char *s, *t; int in_quotes; /* * If string is provided, make a copy. * Drop any CRLF at the end. * Change any tabs to spaces so we don't have to check for it later. */ if (string != NULL) { // dw_printf("split in: '%s'\n", string); c = cmd; for (s = string; *s != '\0'; s++) { if (*s == '\t') { *c++ = ' '; } else if (*s == '\r' || *s == '\n') { ; } else { *c++ = *s; } } *c = '\0'; c = cmd; } /* * Get next part, separated by whitespace, keeping spaces within quotes. * Quotation marks inside need to be doubled. */ while (*c == ' ') { c++; }; t = token; in_quotes = 0; for ( ; *c != '\0'; c++) { if (*c == '"') { if (in_quotes) { if (c[1] == '"') { *t++ = *c++; } else { in_quotes = 0; } } else { in_quotes = 1; } } else if (*c == ' ') { if (in_quotes || rest_of_line) { *t++ = *c; } else { break; } } else { *t++ = *c; } } *t = '\0'; // dw_printf("split out: '%s'\n", token); t = token; if (*t == '\0') { return (NULL); } return (t); } /* end split */ /*------------------------------------------------------------------- * * Name: config_init * * Purpose: Read configuration file when application starts up. * * Inputs: fname - Name of configuration file. * * Outputs: p_audio_config - Radio channel parameters stored here. * * p_digi_config - APRS Digipeater configuration stored here. * * p_cdigi_config - Connected Digipeater configuration stored here. * * p_tt_config - APRStt stuff. * * p_igate_config - Internet Gateway. * * p_misc_config - Everything else. This wasn't thought out well. * * Description: Apply default values for various parameters then read the * the configuration file which can override those values. * * Errors: For invalid input, display line number and message on stdout (not stderr). * In many cases this will result in keeping the default rather than aborting. * * Bugs: Very simple-minded parsing. * Not much error checking. (e.g. atoi() will return 0 for invalid string.) * Not very forgiving about sloppy input. * *--------------------------------------------------------------------*/ void config_init (char *fname, struct audio_s *p_audio_config, struct digi_config_s *p_digi_config, struct cdigi_config_s *p_cdigi_config, struct tt_config_s *p_tt_config, struct igate_config_s *p_igate_config, struct misc_config_s *p_misc_config) { FILE *fp; char filepath[128]; char stuff[MAXCMDLEN]; int line; int channel; int adevice; int m; #if DEBUG text_color_set(DW_COLOR_DEBUG); dw_printf ("config_init ( %s )\n", fname); #endif /* * First apply defaults. */ memset (p_audio_config, 0, sizeof(struct audio_s)); /* First audio device is always available with defaults. */ /* Others must be explicitly defined before use. */ for (adevice=0; adeviceadev[adevice].adevice_in, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_in)); strlcpy (p_audio_config->adev[adevice].adevice_out, DEFAULT_ADEVICE, sizeof(p_audio_config->adev[adevice].adevice_out)); p_audio_config->adev[adevice].defined = 0; p_audio_config->adev[adevice].num_channels = DEFAULT_NUM_CHANNELS; /* -2 stereo */ p_audio_config->adev[adevice].samples_per_sec = DEFAULT_SAMPLES_PER_SEC; /* -r option */ p_audio_config->adev[adevice].bits_per_sample = DEFAULT_BITS_PER_SAMPLE; /* -8 option for 8 instead of 16 bits */ } p_audio_config->adev[0].defined = 1; for (channel=0; channelachan[channel].medium = MEDIUM_NONE; /* One or both channels will be */ /* set to radio when corresponding */ /* audio device is defined. */ p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].v26_alternative = V26_UNSPECIFIED; p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; /* -m option */ p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; /* -s option */ p_audio_config->achan[channel].baud = DEFAULT_BAUD; /* -b option */ /* None. Will set default later based on other factors. */ strlcpy (p_audio_config->achan[channel].profiles, "", sizeof(p_audio_config->achan[channel].profiles)); p_audio_config->achan[channel].num_freq = 1; p_audio_config->achan[channel].offset = 0; p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS; p_audio_config->achan[channel].sanity_test = SANITY_APRS; p_audio_config->achan[channel].passall = 0; for (ot = 0; ot < NUM_OCTYPES; ot++) { p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_NONE; strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, "", sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_NONE; p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_NONE; p_audio_config->achan[channel].octrl[ot].out_gpio_num = 0; p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = 0; p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0; } for (it = 0; it < NUM_ICTYPES; it++) { p_audio_config->achan[channel].ictrl[it].method = PTT_METHOD_NONE; p_audio_config->achan[channel].ictrl[it].in_gpio_num = 0; p_audio_config->achan[channel].ictrl[it].invert = 0; } p_audio_config->achan[channel].dwait = DEFAULT_DWAIT; p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME; p_audio_config->achan[channel].persist = DEFAULT_PERSIST; p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY; p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL; p_audio_config->achan[channel].fulldup = DEFAULT_FULLDUP; } p_audio_config->fx25_auto_enable = AX25_N2_RETRY_DEFAULT / 2; /* First channel should always be valid. */ /* If there is no ADEVICE, it uses default device in mono. */ p_audio_config->achan[0].medium = MEDIUM_RADIO; memset (p_digi_config, 0, sizeof(struct digi_config_s)); // APRS digipeater p_digi_config->dedupe_time = DEFAULT_DEDUPE; memset (p_cdigi_config, 0, sizeof(struct cdigi_config_s)); // Connected mode digipeater memset (p_tt_config, 0, sizeof(struct tt_config_s)); p_tt_config->gateway_enabled = 0; p_tt_config->ttloc_size = 2; /* Start with at least 2. */ /* When full, it will be increased by 50 %. */ p_tt_config->ttloc_ptr = malloc (sizeof(struct ttloc_s) * p_tt_config->ttloc_size); p_tt_config->ttloc_len = 0; /* Retention time and decay algorithm from 13 Feb 13 version of */ /* http://www.aprs.org/aprstt/aprstt-coding24.txt */ /* Reduced by transmit count by one. An 8 minute delay in between transmissions seems awful long. */ p_tt_config->retain_time = 80 * 60; p_tt_config->num_xmits = 6; assert (p_tt_config->num_xmits <= TT_MAX_XMITS); p_tt_config->xmit_delay[0] = 3; /* Before initial transmission. */ p_tt_config->xmit_delay[1] = 16; p_tt_config->xmit_delay[2] = 32; p_tt_config->xmit_delay[3] = 64; p_tt_config->xmit_delay[4] = 2 * 60; p_tt_config->xmit_delay[5] = 4 * 60; p_tt_config->xmit_delay[6] = 8 * 60; // not currently used. strlcpy (p_tt_config->status[0], "", sizeof(p_tt_config->status[0])); strlcpy (p_tt_config->status[1], "/off duty", sizeof(p_tt_config->status[1])); strlcpy (p_tt_config->status[2], "/enroute", sizeof(p_tt_config->status[2])); strlcpy (p_tt_config->status[3], "/in service", sizeof(p_tt_config->status[3])); strlcpy (p_tt_config->status[4], "/returning", sizeof(p_tt_config->status[4])); strlcpy (p_tt_config->status[5], "/committed", sizeof(p_tt_config->status[5])); strlcpy (p_tt_config->status[6], "/special", sizeof(p_tt_config->status[6])); strlcpy (p_tt_config->status[7], "/priority", sizeof(p_tt_config->status[7])); strlcpy (p_tt_config->status[8], "/emergency", sizeof(p_tt_config->status[8])); strlcpy (p_tt_config->status[9], "/custom 1", sizeof(p_tt_config->status[9])); for (m = 0; m < TT_ERROR_MAXP1; m++) { strlcpy (p_tt_config->response[m].method, "MORSE", sizeof(p_tt_config->response[m].method)); strlcpy (p_tt_config->response[m].mtext, "?", sizeof(p_tt_config->response[m].mtext)); } strlcpy (p_tt_config->response[TT_ERROR_OK].mtext, "R", sizeof(p_tt_config->response[TT_ERROR_OK].mtext)); memset (p_misc_config, 0, sizeof(struct misc_config_s)); p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT; p_misc_config->kiss_port = DEFAULT_KISS_PORT; p_misc_config->enable_kiss_pt = 0; /* -p option */ p_misc_config->kiss_copy = 0; /* Defaults from http://info.aprs.net/index.php?title=SmartBeaconing */ p_misc_config->sb_configured = 0; /* TRUE if SmartBeaconing is configured. */ p_misc_config->sb_fast_speed = 60; /* MPH */ p_misc_config->sb_fast_rate = 180; /* seconds */ p_misc_config->sb_slow_speed = 5; /* MPH */ p_misc_config->sb_slow_rate = 1800; /* seconds */ p_misc_config->sb_turn_time = 15; /* seconds */ p_misc_config->sb_turn_angle = 30; /* degrees */ p_misc_config->sb_turn_slope = 255; /* degrees * MPH */ memset (p_igate_config, 0, sizeof(struct igate_config_s)); p_igate_config->t2_server_port = DEFAULT_IGATE_PORT; p_igate_config->tx_chan = -1; /* IS->RF not enabled */ p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_DEFAULT; p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_DEFAULT; p_igate_config->igmsp = 1; p_igate_config->rx2ig_dedupe_time = IGATE_RX2IG_DEDUPE_TIME; /* People find this confusing. */ /* Ideally we'd like to figure out if com0com is installed */ /* and automatically enable this. */ strlcpy (p_misc_config->kiss_serial_port, "", sizeof(p_misc_config->kiss_serial_port)); p_misc_config->kiss_serial_speed = 0; p_misc_config->kiss_serial_poll = 0; strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port)); strlcpy (p_misc_config->waypoint_serial_port, "", sizeof(p_misc_config->waypoint_serial_port)); p_misc_config->log_daily_names = 0; strlcpy (p_misc_config->log_path, "", sizeof(p_misc_config->log_path)); /* connected mode. */ p_misc_config->frack = AX25_T1V_FRACK_DEFAULT; /* Number of seconds to wait for ack to transmission. */ p_misc_config->retry = AX25_N2_RETRY_DEFAULT; /* Number of times to retry before giving up. */ p_misc_config->paclen = AX25_N1_PACLEN_DEFAULT; /* Max number of bytes in information part of frame. */ p_misc_config->maxframe_basic = AX25_K_MAXFRAME_BASIC_DEFAULT; /* Max frames to send before ACK. mod 8 "Window" size. */ p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; /* Max frames to send before ACK. mod 128 "Window" size. */ p_misc_config->maxv22 = AX25_N2_RETRY_DEFAULT / 3; /* Max SABME before falling back to SABM. */ p_misc_config->v20_addrs = NULL; /* Go directly to v2.0 for stations listed. */ p_misc_config->v20_count = 0; p_misc_config->noxid_addrs = NULL; /* Don't send XID to these stations. */ p_misc_config->noxid_count = 0; /* * Try to extract options from a file. * * Windows: File must be in current working directory. * * Linux: Search current directory then home directory. * * Future possibility - Could also search home directory * for Windows by combinting two variables: * HOMEDRIVE=C: * HOMEPATH=\Users\John * * It's not clear if this always points to same location: * USERPROFILE=C:\Users\John */ channel = 0; adevice = 0; // TODO: Would be better to have a search list and loop thru it. strlcpy(filepath, fname, sizeof(filepath)); fp = fopen (filepath, "r"); #ifndef __WIN32__ if (fp == NULL && strcmp(fname, "direwolf.conf") == 0) { /* Failed to open the default location. Try home dir. */ char *p; strlcpy (filepath, "", sizeof(filepath)); p = getenv("HOME"); if (p != NULL) { strlcpy (filepath, p, sizeof(filepath)); strlcat (filepath, "/direwolf.conf", sizeof(filepath)); fp = fopen (filepath, "r"); } } #endif if (fp == NULL) { // TODO: not exactly right for all situations. text_color_set(DW_COLOR_ERROR); dw_printf ("ERROR - Could not open config file %s\n", filepath); dw_printf ("Try using -c command line option for alternate location.\n"); return; } dw_printf ("\nReading config file %s\n", filepath); line = 0; while (fgets(stuff, sizeof(stuff), fp) != NULL) { char *t; line++; t = split(stuff,0); if (t == NULL) { continue; } if (*t == '#' || *t == '*') { continue; } /* * ==================== Audio device parameters ==================== */ /* * ADEVICE[n] - Name of input sound device, and optionally output, if different. * * ADEVICE plughw:1,0 -- same for in and out. * ADEVICE plughw:2,0 plughw:3,0 -- different in/out for a channel or channel pair. * ADEVICE1 udp:7355 default -- from Software defined radio (SDR) via UDP. * */ /* Note that ALSA name can contain comma such as hw:1,0 */ if (strncasecmp(t, "ADEVICE", 7) == 0) { /* "ADEVICE" is equivalent to "ADEVICE0". */ adevice = 0; if (strlen(t) >= 8) { adevice = atoi(t+7); } if (adevice < 0 || adevice >= MAX_ADEVS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Device number %d out of range for ADEVICE command on line %d.\n", adevice, line); dw_printf ("If you really need more than %d audio devices, increase MAX_ADEVS and recompile.\n", MAX_ADEVS); adevice = 0; continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing name of audio device for ADEVICE command on line %d.\n", line); continue; } p_audio_config->adev[adevice].defined = 1; /* First channel of device is valid. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO; strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)); strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); t = split(NULL,0); if (t != NULL) { strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); } } /* * PAIDEVICE[n] input-device * PAODEVICE[n] output-device * * This was submitted by KK5VD for the Mac OS X version. (__APPLE__) * * It looks like device names can contain spaces making it a little * more difficult to put two names on the same line unless we come up with * some other delimiter between them or a quoting scheme to handle * embedded spaces in a name. * * It concerns me that we could have one defined without the other * if we don't put in more error checking later. * * version 1.3 dev snapshot C: * * We now have a general quoting scheme so the original ADEVICE can handle this. * These options will probably be removed before general 1.3 release. */ else if (strcasecmp(t, "PAIDEVICE") == 0) { adevice = 0; if (isdigit(t[9])) { adevice = t[9] - '0'; } if (adevice < 0 || adevice >= MAX_ADEVS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line); adevice = 0; continue; } t = split(NULL,1); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line); continue; } p_audio_config->adev[adevice].defined = 1; /* First channel of device is valid. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO; strlcpy (p_audio_config->adev[adevice].adevice_in, t, sizeof(p_audio_config->adev[adevice].adevice_in)); } else if (strcasecmp(t, "PAODEVICE") == 0) { adevice = 0; if (isdigit(t[9])) { adevice = t[9] - '0'; } if (adevice < 0 || adevice >= MAX_ADEVS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Device number %d out of range for PADEVICE command on line %d.\n", adevice, line); adevice = 0; continue; } t = split(NULL,1); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing name of audio device for PADEVICE command on line %d.\n", line); continue; } p_audio_config->adev[adevice].defined = 1; /* First channel of device is valid. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO; strlcpy (p_audio_config->adev[adevice].adevice_out, t, sizeof(p_audio_config->adev[adevice].adevice_out)); } /* * ARATE - Audio samples per second, 11025, 22050, 44100, etc. */ else if (strcasecmp(t, "ARATE") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing audio sample rate for ARATE command.\n", line); continue; } n = atoi(t); if (n >= MIN_SAMPLES_PER_SEC && n <= MAX_SAMPLES_PER_SEC) { p_audio_config->adev[adevice].samples_per_sec = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Use a more reasonable audio sample rate in range of %d - %d.\n", line, MIN_SAMPLES_PER_SEC, MAX_SAMPLES_PER_SEC); } } /* * ACHANNELS - Number of audio channels for current device: 1 or 2 */ else if (strcasecmp(t, "ACHANNELS") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing number of audio channels for ACHANNELS command.\n", line); continue; } n = atoi(t); if (n ==1 || n == 2) { p_audio_config->adev[adevice].num_channels = n; /* Set valid channels depending on mono or stereo. */ p_audio_config->achan[ADEVFIRSTCHAN(adevice)].medium = MEDIUM_RADIO; if (n == 2) { p_audio_config->achan[ADEVFIRSTCHAN(adevice) + 1].medium = MEDIUM_RADIO; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of audio channels must be 1 or 2.\n", line); } } /* * ==================== Radio channel parameters ==================== */ /* * CHANNEL - Set channel for following commands. */ else if (strcasecmp(t, "CHANNEL") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing channel number for CHANNEL command.\n", line); continue; } n = atoi(t); if (n >= 0 && n < MAX_CHANS) { channel = n; if (p_audio_config->achan[n].medium != MEDIUM_RADIO) { if ( ! p_audio_config->adev[ACHAN2ADEV(n)].defined) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not defined.\n", line, n, ACHAN2ADEV(n)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Channel number %d is not valid because audio device %d is not in stereo.\n", line, n, ACHAN2ADEV(n)); } } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Channel number must in range of 0 to %d.\n", line, MAX_CHANS-1); } } /* * MYCALL station */ else if (strcasecmp(t, "mycall") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing value for MYCALL command on line %d.\n", line); continue; } else { char *p; int const strict = 2; char call_no_ssid[AX25_MAX_ADDR_LEN]; int ssid, heard; for (p = t; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* Silently force upper case. */ /* Might change to warning someday. */ } } if ( ! ax25_parse_addr (-1, t, strict, call_no_ssid, &ssid, &heard)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid value for MYCALL command on line %d.\n", line); continue; } // Definitely set for current channel. // Set for other channels which have not been set yet. int c; for (c = 0; c < MAX_CHANS; c++) { if (c == channel || strlen(p_audio_config->achan[c].mycall) == 0 || strcasecmp(p_audio_config->achan[c].mycall, "NOCALL") == 0 || strcasecmp(p_audio_config->achan[c].mycall, "N0CALL") == 0) { strlcpy (p_audio_config->achan[c].mycall, t, sizeof(p_audio_config->achan[c].mycall)); } } } } /* * MODEM - Set modem properties for current channel. * * * Old style: * MODEM baud [ mark space [A][B][C][+] [ num-decoders spacing ] ] * * New style, version 1.2: * MODEM speed [ option ] ... * * Options: * mark:space - AFSK tones. Defaults based on speed. * num@offset - Multiple decoders on different frequencies. * /9 - Divide sample rate by specified number. * *9 - Upsample ratio for G3RUH. * [A-Z+-]+ - Letters, plus, minus for the demodulator "profile." * g3ruh - This modem type regardless of default for speed. * v26a or v26b - V.26 alternative. a=original, b=MFJ compatible */ else if (strcasecmp(t, "MODEM") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing data transmission speed for MODEM command.\n", line); continue; } if (strcasecmp(t,"AIS") == 0) { n = MAX_BAUD-1; // Hack - See special case later. } else if (strcasecmp(t,"EAS") == 0) { n = MAX_BAUD-2; // Hack - See special case later. } else { n = atoi(t); } if (n >= MIN_BAUD && n <= MAX_BAUD) { p_audio_config->achan[channel].baud = n; if (n != 300 && n != 1200 && n != 2400 && n != 4800 && n != 9600 && n != 19200 && n != MAX_BAUD-1 && n != MAX_BAUD-2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Warning: Non-standard data rate of %d bits per second. Are you sure?\n", line, n); } } else { p_audio_config->achan[channel].baud = DEFAULT_BAUD; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable data rate. Using %d bits per second.\n", line, p_audio_config->achan[channel].baud); } /* Set defaults based on speed. */ /* Should be same as -B command line option in direwolf.c. */ /* We have similar logic in direwolf.c, config.c, gen_packets.c, and atest.c, */ /* that need to be kept in sync. Maybe it could be a common function someday. */ if (p_audio_config->achan[channel].baud < 600) { p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].mark_freq = 1600; p_audio_config->achan[channel].space_freq = 1800; } else if (p_audio_config->achan[channel].baud < 1800) { p_audio_config->achan[channel].modem_type = MODEM_AFSK; p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; } else if (p_audio_config->achan[channel].baud < 3600) { p_audio_config->achan[channel].modem_type = MODEM_QPSK; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } else if (p_audio_config->achan[channel].baud < 7200) { p_audio_config->achan[channel].modem_type = MODEM_8PSK; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } else if (p_audio_config->achan[channel].baud == MAX_BAUD-1) { p_audio_config->achan[channel].modem_type = MODEM_AIS; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } else if (p_audio_config->achan[channel].baud == MAX_BAUD-2) { p_audio_config->achan[channel].modem_type = MODEM_EAS; p_audio_config->achan[channel].baud = 521; // Actually 520.83 but we have an integer field here. // Will make more precise in afsk demod init. p_audio_config->achan[channel].mark_freq = 2083; // Actually 2083.3 - logic 1. p_audio_config->achan[channel].space_freq = 1563; // Actually 1562.5 - logic 0. // ? strlcpy (p_audio_config->achan[channel].profiles, "D", sizeof(p_audio_config->achan[channel].profiles)); } else { p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } /* Get any options. */ t = split(NULL,0); if (t == NULL) { /* all done. */ continue; } if (alldigits(t)) { /* old style */ text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Old style (pre version 1.2) format will no longer be supported in next version.\n", line); n = atoi(t); /* Originally the upper limit was 3000. */ /* Version 1.0 increased to 5000 because someone */ /* wanted to use 2400/4800 Hz AFSK. */ /* Of course the MIC and SPKR connections won't */ /* have enough bandwidth so radios must be modified. */ if (n >= 300 && n <= 5000) { p_audio_config->achan[channel].mark_freq = n; } else { p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d.\n", line, p_audio_config->achan[channel].mark_freq); } /* Get space frequency */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing tone frequency for space.\n", line); continue; } n = atoi(t); if (n >= 300 && n <= 5000) { p_audio_config->achan[channel].space_freq = n; } else { p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable space tone frequency. Using %d.\n", line, p_audio_config->achan[channel].space_freq); } /* Gently guide users toward new format. */ if (p_audio_config->achan[channel].baud == 1200 && p_audio_config->achan[channel].mark_freq == 1200 && p_audio_config->achan[channel].space_freq == 2200) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 1200 baud default 1200:2200.\n", line); } if (p_audio_config->achan[channel].baud == 300 && p_audio_config->achan[channel].mark_freq == 1600 && p_audio_config->achan[channel].space_freq == 1800) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: The AFSK frequencies can be omitted when using the 300 baud default 1600:1800.\n", line); } /* New feature in 0.9 - Optional filter profile(s). */ t = split(NULL,0); if (t != NULL) { /* Look for some combination of letter(s) and + */ if (isalpha(t[0]) || t[0] == '+') { char *pc; /* Here we only catch something other than letters and + mixed in. */ /* Later, we check for valid letters and no more than one letter if + specified. */ for (pc = t; *pc != '\0'; pc++) { if ( ! isalpha(*pc) && ! (*pc == '+')) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Demodulator type can only contain letters and + character.\n", line); } } strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles)); t = split(NULL,0); if (strlen(p_audio_config->achan[channel].profiles) > 1 && t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Can't combine multiple demodulator types and multiple frequencies.\n", line); continue; } } } /* New feature in 0.9 - optional number of decoders and frequency offset between. */ if (t != NULL) { n = atoi(t); if (n < 1 || n > MAX_SUBCHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line); n = 3; } p_audio_config->achan[channel].num_freq = n; t = split(NULL,0); if (t != NULL) { n = atoi(t); if (n < 5 || n > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable value for offset between modems. Using 50 Hz.\n", line); n = 50; } p_audio_config->achan[channel].offset = n; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: New style for multiple demodulators is %d@%d\n", line, p_audio_config->achan[channel].num_freq, p_audio_config->achan[channel].offset); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing frequency offset between modems. Using 50 Hz.\n", line); p_audio_config->achan[channel].offset = 50; } } } else { /* New style in version 1.2. */ while (t != NULL) { char *s; if ((s = strchr(t, ':')) != NULL) { /* mark:space */ p_audio_config->achan[channel].mark_freq = atoi(t); p_audio_config->achan[channel].space_freq = atoi(s+1); if (p_audio_config->achan[channel].mark_freq == 0 && p_audio_config->achan[channel].space_freq == 0) { p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; } else { p_audio_config->achan[channel].modem_type = MODEM_AFSK; if (p_audio_config->achan[channel].mark_freq < 300 || p_audio_config->achan[channel].mark_freq > 5000) { p_audio_config->achan[channel].mark_freq = DEFAULT_MARK_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable mark tone frequency. Using %d instead.\n", line, p_audio_config->achan[channel].mark_freq); } if (p_audio_config->achan[channel].space_freq < 300 || p_audio_config->achan[channel].space_freq > 5000) { p_audio_config->achan[channel].space_freq = DEFAULT_SPACE_FREQ; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable space tone frequency. Using %d instead.\n", line, p_audio_config->achan[channel].space_freq); } } } else if ((s = strchr(t, '@')) != NULL) { /* num@offset */ p_audio_config->achan[channel].num_freq = atoi(t); p_audio_config->achan[channel].offset = atoi(s+1); if (p_audio_config->achan[channel].num_freq < 1 || p_audio_config->achan[channel].num_freq > MAX_SUBCHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Number of demodulators is out of range. Using 3.\n", line); p_audio_config->achan[channel].num_freq = 3; } if (p_audio_config->achan[channel].offset < 5 || p_audio_config->achan[channel].offset > abs(p_audio_config->achan[channel].mark_freq - p_audio_config->achan[channel].space_freq)/2) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Offset between demodulators is unreasonable. Using 50 Hz.\n", line); p_audio_config->achan[channel].offset = 50; } } else if (alllettersorpm(t)) { /* profile of letter(s) + - */ // Will be validated later. strlcpy (p_audio_config->achan[channel].profiles, t, sizeof(p_audio_config->achan[channel].profiles)); } else if (*t == '/') { /* /div */ int n = atoi(t+1); if (n >= 1 && n <= 8) { p_audio_config->achan[channel].decimate = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Ignoring unreasonable sample rate division factor of %d.\n", line, n); } } else if (*t == '*') { /* *upsample */ int n = atoi(t+1); if (n >= 1 && n <= 4) { p_audio_config->achan[channel].upsample = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Ignoring unreasonable upsample ratio of %d.\n", line, n); } } else if (strcasecmp(t, "G3RUH") == 0) { /* Force G3RUH modem regardless of default for speed. New in 1.6. */ p_audio_config->achan[channel].modem_type = MODEM_SCRAMBLE; p_audio_config->achan[channel].mark_freq = 0; p_audio_config->achan[channel].space_freq = 0; } else if (strcasecmp(t, "V26A") == 0 || /* Compatible with direwolf versions <= 1.5. New in 1.6. */ strcasecmp(t, "V26B") == 0) { /* Compatible with MFJ-2400. New in 1.6. */ if (p_audio_config->achan[channel].modem_type != MODEM_QPSK || p_audio_config->achan[channel].baud != 2400) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: %s option can only be used with 2400 bps PSK.\n", line, t); continue; } p_audio_config->achan[channel].v26_alternative = (strcasecmp(t, "V26A") == 0) ? V26_A : V26_B; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unrecognized option for MODEM: %s\n", line, t); } t = split(NULL,0); } /* A later place catches disallowed combination of + and @. */ /* A later place sets /n for 300 baud if not specified by user. */ //dw_printf ("debug: div = %d\n", p_audio_config->achan[channel].decimate); } } /* * DTMF - Enable DTMF decoder. * * Future possibilities: * Option to determine if it goes to APRStt gateway and/or application. * Disable normal demodulator to reduce CPU requirements. */ else if (strcasecmp(t, "DTMF") == 0) { p_audio_config->achan[channel].dtmf_decode = DTMF_DECODE_ON; } /* * FIX_BITS n [ APRS | AX25 | NONE ] [ PASSALL ] * * - Attempt to fix frames with bad FCS. * - n is maximum number of bits to attempt fixing. * - Optional sanity check & allow everything even with bad FCS. */ else if (strcasecmp(t, "FIX_BITS") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for FIX_BITS command.\n", line); continue; } n = atoi(t); if (n >= RETRY_NONE && n < RETRY_MAX) { // MAX is actually last valid +1 p_audio_config->achan[channel].fix_bits = (retry_t)n; } else { p_audio_config->achan[channel].fix_bits = DEFAULT_FIX_BITS; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid value %d for FIX_BITS. Using default of %d.\n", line, n, p_audio_config->achan[channel].fix_bits); } if (p_audio_config->achan[channel].fix_bits > DEFAULT_FIX_BITS) { text_color_set(DW_COLOR_INFO); dw_printf ("Line %d: Using a FIX_BITS value greater than %d is not recommended for normal operation.\n", line, DEFAULT_FIX_BITS); } t = split(NULL,0); while (t != NULL) { // If more than one sanity test, we silently take the last one. if (strcasecmp(t, "APRS") == 0) { p_audio_config->achan[channel].sanity_test = SANITY_APRS; } else if (strcasecmp(t, "AX25") == 0 || strcasecmp(t, "AX.25") == 0) { p_audio_config->achan[channel].sanity_test = SANITY_AX25; } else if (strcasecmp(t, "NONE") == 0) { p_audio_config->achan[channel].sanity_test = SANITY_NONE; } else if (strcasecmp(t, "PASSALL") == 0) { p_audio_config->achan[channel].passall = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: There is an old saying, \"Be careful what you ask for because you might get it.\"\n", line); dw_printf ("The PASSALL option means allow all frames even when they are invalid.\n"); dw_printf ("You are asking to receive random trash and you WILL get your wish.\n"); dw_printf ("Don't complain when you see all sorts of random garbage. That's what you asked for.\n"); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid option '%s' for FIX_BITS.\n", line, t); } t = split(NULL,0); } } /* * PTT - Push To Talk signal line. * DCD - Data Carrier Detect indicator. * CON - Connected to another station indicator. * * xxx serial-port [-]rts-or-dtr [ [-]rts-or-dtr ] * xxx GPIO [-]gpio-num * xxx LPT [-]bit-num * PTT RIG model port [ rate ] * PTT RIG AUTO port [ rate ] * PTT CM108 [ [-]bit-num ] [ hid-device ] * * When model is 2, port would host:port like 127.0.0.1:4532 * Otherwise, port would be a serial port like /dev/ttyS0 * * * Applies to most recent CHANNEL command. */ else if (strcasecmp(t, "PTT") == 0 || strcasecmp(t, "DCD") == 0 || strcasecmp(t, "CON") == 0) { int ot; char otname[8]; if (strcasecmp(t, "PTT") == 0) { ot = OCTYPE_PTT; strlcpy (otname, "PTT", sizeof(otname)); } else if (strcasecmp(t, "DCD") == 0) { ot = OCTYPE_DCD; strlcpy (otname, "DCD", sizeof(otname)); } else { ot = OCTYPE_CON; strlcpy (otname, "CON", sizeof(otname)); } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing output control device for %s command.\n", line, otname); continue; } if (strcasecmp(t, "GPIO") == 0) { /* GPIO case, Linux only. */ #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, otname); #else t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, otname); continue; } if (*t == '-') { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1); p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_GPIO; #endif } else if (strcasecmp(t, "LPT") == 0) { /* Parallel printer case, x86 Linux only. */ #if ( defined(__i386__) || defined(__x86_64__) ) && ( defined(__linux__) || defined(__unix__) ) t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing LPT bit number for %s.\n", line, otname); continue; } if (*t == '-') { p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t+1); p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { p_audio_config->achan[channel].octrl[ot].ptt_lpt_bit = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_LPT; #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with LPT is only available on x86 Linux.\n", line, otname); #endif } else if (strcasecmp(t, "RIG") == 0) { #ifdef USE_HAMLIB t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing model number for hamlib.\n", line); continue; } if (strcasecmp(t, "AUTO") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_model = -1; } else { if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: A rig number, not a name, is required here.\n", line); dw_printf ("For example, if you have a Yaesu FT-847, specify 101.\n"); dw_printf ("See https://github.com/Hamlib/Hamlib/wiki/Supported-Radios for more details.\n"); continue; } int n = atoi(t); if (n < 1 || n > 9999) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Unreasonable model number %d for hamlib.\n", line, n); continue; } p_audio_config->achan[channel].octrl[ot].ptt_model = n; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing port for hamlib.\n", line); continue; } strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); // Optional serial port rate for CAT controll PTT. t = split(NULL,0); if (t != NULL) { if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: An optional number is required here for CAT serial port speed: %s\n", line, t); continue; } int n = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_rate = n; } t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s was not expected after model & port for hamlib.\n", line, t); } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_HAMLIB; #else #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Windows version of direwolf does not support HAMLIB.\n", line); exit (EXIT_FAILURE); #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with RIG is only available when hamlib support is enabled.\n", line, otname); dw_printf ("You must rebuild direwolf with hamlib support.\n"); dw_printf ("See User Guide for details.\n"); #endif #endif } else if (strcasecmp(t, "CM108") == 0) { /* CM108 - GPIO of USB sound card. case, Linux only. */ #ifdef USE_CM108 if (ot != OCTYPE_PTT) { // Future project: Allow DCD and CON via the same device. // This gets more complicated because we can't selectively change a single GPIO bit. // We would need to keep track of what is currently there, change one bit, in our local // copy of the status and then write out the byte for all of the pins. // Let's keep it simple with just PTT for the first stab at this. text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: PTT CM108 option is only valid for PTT, not %s.\n", line, otname); continue; } p_audio_config->achan[channel].octrl[ot].out_gpio_num = 3; // All known designs use GPIO 3. // User can override for special cases. p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; // High for transmit. strcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, ""); // Try to find PTT device for audio output device. // Simplifiying assumption is that we have one radio per USB Audio Adapter. // Failure at this point is not an error. // See if config file sets it explicitly before complaining. cm108_find_ptt (p_audio_config->adev[ACHAN2ADEV(channel)].adevice_out, p_audio_config->achan[channel].octrl[ot].ptt_device, (int)sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); while ((t = split(NULL,0)) != NULL) { if (*t == '-') { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t+1); p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else if (isdigit(*t)) { p_audio_config->achan[channel].octrl[ot].out_gpio_num = atoi(t); p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } else if (*t == '/') { strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Found \"%s\" when expecting GPIO number or device name like /dev/hidraw1.\n", line, t); continue; } } if (p_audio_config->achan[channel].octrl[ot].out_gpio_num < 1 || p_audio_config->achan[channel].octrl[ot].out_gpio_num > 8) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: CM108 GPIO number %d is not in range of 1 thru 8.\n", line, p_audio_config->achan[channel].octrl[ot].out_gpio_num); continue; } if (strlen(p_audio_config->achan[channel].octrl[ot].ptt_device) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Could not determine USB Audio GPIO PTT device for audio output %s.\n", line, p_audio_config->adev[ACHAN2ADEV(channel)].adevice_out); dw_printf ("You must explicitly mention a device name such as /dev/hidraw1.\n"); dw_printf ("See User Guide for details.\n"); continue; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_CM108; #else #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: CM108 USB Audio GPIO PTT is not available for Windows.\n", line); #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with CM108 is only available when USB Audio GPIO support is enabled.\n", line, otname); dw_printf ("You must rebuild direwolf with CM108 Audio Adapter GPIO PTT support.\n"); dw_printf ("See User Guide for details.\n"); #endif exit (EXIT_FAILURE); #endif } else { /* serial port case. */ strlcpy (p_audio_config->achan[channel].octrl[ot].ptt_device, t, sizeof(p_audio_config->achan[channel].octrl[ot].ptt_device)); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing RTS or DTR after %s device name.\n", line, otname); continue; } if (strcasecmp(t, "rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } else if (strcasecmp(t, "dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert = 0; } else if (strcasecmp(t, "-rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else if (strcasecmp(t, "-dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert = 1; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Expected RTS or DTR after %s device name.\n", line, otname); continue; } p_audio_config->achan[channel].octrl[ot].ptt_method = PTT_METHOD_SERIAL; /* In version 1.2, we allow a second one for same serial port. */ /* Some interfaces want the two control lines driven with opposite polarity. */ /* e.g. PTT COM1 RTS -DTR */ t = split(NULL,0); if (t != NULL) { if (strcasecmp(t, "rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0; } else if (strcasecmp(t, "dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 0; } else if (strcasecmp(t, "-rts") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_RTS; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1; } else if (strcasecmp(t, "-dtr") == 0) { p_audio_config->achan[channel].octrl[ot].ptt_line2 = PTT_LINE_DTR; p_audio_config->achan[channel].octrl[ot].ptt_invert2 = 1; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Expected RTS or DTR after first RTS or DTR.\n", line); continue; } /* Would not make sense to specify the same one twice. */ if (p_audio_config->achan[channel].octrl[ot].ptt_line == p_audio_config->achan[channel].octrl[ot].ptt_line2) { dw_printf ("Config file line %d: Doesn't make sense to specify the some control line twice.\n", line); } } /* end of second serial port control line. */ } /* end of serial port case. */ } /* end of PTT, DCD, CON */ /* * INPUTS * * TXINH - TX holdoff input * * TXINH GPIO [-]gpio-num (only type supported so far) */ else if (strcasecmp(t, "TXINH") == 0) { char itname[8]; strlcpy (itname, "TXINH", sizeof(itname)); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing input type name for %s command.\n", line, itname); continue; } if (strcasecmp(t, "GPIO") == 0) { #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: %s with GPIO is only available on Linux.\n", line, itname); #else t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file line %d: Missing GPIO number for %s.\n", line, itname); continue; } if (*t == '-') { p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t+1); p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 1; } else { p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].in_gpio_num = atoi(t); p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].invert = 0; } p_audio_config->achan[channel].ictrl[ICTYPE_TXINH].method = PTT_METHOD_GPIO; #endif } } /* * DWAIT n - Extra delay for receiver squelch. n = 10 mS units. */ else if (strcasecmp(t, "DWAIT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing delay time for DWAIT command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].dwait = n; } else { p_audio_config->achan[channel].dwait = DEFAULT_DWAIT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid delay time for DWAIT. Using %d.\n", line, p_audio_config->achan[channel].dwait); } } /* * SLOTTIME n - For non-digipeat transmit delay timing. n = 10 mS units. */ else if (strcasecmp(t, "SLOTTIME") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing delay time for SLOTTIME command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].slottime = n; } else { p_audio_config->achan[channel].slottime = DEFAULT_SLOTTIME; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid delay time for persist algorithm. Using %d.\n", line, p_audio_config->achan[channel].slottime); } } /* * PERSIST - For non-digipeat transmit delay timing. */ else if (strcasecmp(t, "PERSIST") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing probability for PERSIST command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].persist = n; } else { p_audio_config->achan[channel].persist = DEFAULT_PERSIST; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid probability for persist algorithm. Using %d.\n", line, p_audio_config->achan[channel].persist); } } /* * TXDELAY n - For transmit delay timing. n = 10 mS units. */ else if (strcasecmp(t, "TXDELAY") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for TXDELAY command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].txdelay = n; } else { p_audio_config->achan[channel].txdelay = DEFAULT_TXDELAY; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid time for transmit delay. Using %d.\n", line, p_audio_config->achan[channel].txdelay); } } /* * TXTAIL n - For transmit timing. n = 10 mS units. */ else if (strcasecmp(t, "TXTAIL") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for TXTAIL command.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= 255) { p_audio_config->achan[channel].txtail = n; } else { p_audio_config->achan[channel].txtail = DEFAULT_TXTAIL; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid time for transmit timing. Using %d.\n", line, p_audio_config->achan[channel].txtail); } } /* * FULLDUP {on|off} - Full Duplex */ else if (strcasecmp(t, "FULLDUP") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing parameter for FULLDUP command. Expecting ON or OFF.\n", line); continue; } if (strcasecmp(t, "ON") == 0) { p_audio_config->achan[channel].fulldup = 1; } else if (strcasecmp(t, "OFF") == 0) { p_audio_config->achan[channel].fulldup = 0; } else { p_audio_config->achan[channel].fulldup = 0; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Expected ON or OFF for FULLDUP.\n", line); } } /* * SPEECH script * * Specify script for text-to-speech function. */ else if (strcasecmp(t, "SPEECH") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing script for Text-to-Speech function.\n", line); continue; } /* See if we can run it. */ if (xmit_speak_it(t, -1, " ") == 0) { if (strlcpy (p_audio_config->tts_script, t, sizeof(p_audio_config->tts_script)) >= sizeof(p_audio_config->tts_script)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Script for text-to-speech function is too long.\n", line); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Error trying to run Text-to-Speech function.\n", line); continue; } } /* * FX25TX n - Enable FX.25 transmission. Default off. * 0 = off, 1 = auto mode, others are suggestions for testing * or special cases. 16, 32, 64 is number of parity bytes to add. * Also set by "-X n" command line option. * Current a global setting. Could be per channel someday. */ else if (strcasecmp(t, "FX25TX") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing FEC mode for FX25TX command.\n", line); continue; } n = atoi(t); if (n >= 0 && n < 200) { p_audio_config->fx25_xmit_enable = n; } else { p_audio_config->fx25_xmit_enable = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable value for FX.25 transmission mode. Using %d.\n", line, p_audio_config->fx25_xmit_enable); } } /* * FX25AUTO n - Enable Automatic use of FX.25 for connected mode. * Automatically enable, for that session only, when an identical * frame is sent more than this number of times. * Default 5 based on half of default RETRY. * 0 to disable feature. * Current a global setting. Could be per channel someday. */ else if (strcasecmp(t, "FX25AUTO") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing count for FX25AUTO command.\n", line); continue; } n = atoi(t); if (n >= 0 && n < 20) { p_audio_config->fx25_auto_enable = n; } else { p_audio_config->fx25_auto_enable = AX25_N2_RETRY_DEFAULT / 2; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable count for connected mode automatic FX.25. Using %d.\n", line, p_audio_config->fx25_auto_enable); } } /* * ==================== APRS Digipeater parameters ==================== */ /* * DIGIPEAT from-chan to-chan alias-pattern wide-pattern [ OFF|DROP|MARK|TRACE ] */ else if (strcasecmp(t, "DIGIPEAT") == 0 || strcasecmp(t, "DIGIPEATER") == 0) { int from_chan, to_chan; int e; char message[100]; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: '%s' is not allowed for FROM-channel. It must be a number.\n", line, t); continue; } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } // Channels specified must be radio channels or network TNCs. if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO && p_audio_config->achan[from_chan].medium != MEDIUM_NETTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: '%s' is not allowed for TO-channel. It must be a number.\n", line, t); continue; } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO && p_audio_config->achan[to_chan].medium != MEDIUM_NETTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing alias pattern on line %d.\n", line); continue; } e = regcomp (&(p_digi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB); if (e != 0) { regerror (e, &(p_digi_config->alias[from_chan][to_chan]), message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n", line, message); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing wide pattern on line %d.\n", line); continue; } e = regcomp (&(p_digi_config->wide[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB); if (e != 0) { regerror (e, &(p_digi_config->wide[from_chan][to_chan]), message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid wide matching pattern on line %d:\n%s\n", line, message); continue; } p_digi_config->enabled[from_chan][to_chan] = 1; p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF; t = split(NULL,0); if (t != NULL) { if (strcasecmp(t, "OFF") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_OFF; t = split(NULL,0); } else if (strcasecmp(t, "DROP") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Preemptive digipeating DROP option is discouraged.\n", line); dw_printf ("It can create a via path which is misleading about the actual path taken.\n"); dw_printf ("TRACE is the best choice for this feature.\n"); p_digi_config->preempt[from_chan][to_chan] = PREEMPT_DROP; t = split(NULL,0); } else if (strcasecmp(t, "MARK") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Preemptive digipeating MARK option is discouraged.\n", line); dw_printf ("It can create a via path which is misleading about the actual path taken.\n"); dw_printf ("TRACE is the best choice for this feature.\n"); p_digi_config->preempt[from_chan][to_chan] = PREEMPT_MARK; t = split(NULL,0); } else if (strcasecmp(t, "TRACE") == 0) { p_digi_config->preempt[from_chan][to_chan] = PREEMPT_TRACE; t = split(NULL,0); } } if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t); } } /* * DEDUPE - Time to suppress digipeating of duplicate APRS packets. */ else if (strcasecmp(t, "DEDUPE") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing time for DEDUPE command.\n", line); continue; } n = atoi(t); if (n >= 0 && n < 600) { p_digi_config->dedupe_time = n; } else { p_digi_config->dedupe_time = DEFAULT_DEDUPE; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable value for dedupe time. Using %d.\n", line, p_digi_config->dedupe_time); } } /* * REGEN - Signal regeneration. */ else if (strcasecmp(t, "regen") == 0) { int from_chan, to_chan; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: '%s' is not allowed for FROM-channel. It must be a number.\n", line, t); continue; } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } // Only radio channels are valid for regenerate. if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: '%s' is not allowed for TO-channel. It must be a number.\n", line, t); continue; } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } p_digi_config->regen[from_chan][to_chan] = 1; } /* * ==================== Connected Digipeater parameters ==================== */ /* * CDIGIPEAT from-chan to-chan [ alias-pattern ] */ else if (strcasecmp(t, "CDIGIPEAT") == 0 || strcasecmp(t, "CDIGIPEATER") == 0) { int from_chan, to_chan; int e; char message[100]; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: '%s' is not allowed for FROM-channel. It must be a number.\n", line, t); continue; } from_chan = atoi(t); if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } // For connected mode Link layer, only internal modems should be allowed. // A network TNC probably would not provide information about channel status. // There is discussion about this in the document called // Why-is-9600-only-twice-as-fast-as-1200.pdf if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); dw_printf ("Only internal modems can be used for connected mode packet.\n"); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } if ( ! alldigits(t)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: '%s' is not allowed for TO-channel. It must be a number.\n", line, t); continue; } to_chan = atoi(t); if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); dw_printf ("Only internal modems can be used for connected mode packet.\n"); continue; } t = split(NULL,0); if (t != NULL) { e = regcomp (&(p_cdigi_config->alias[from_chan][to_chan]), t, REG_EXTENDED|REG_NOSUB); if (e == 0) { p_cdigi_config->has_alias[from_chan][to_chan] = 1; } else { regerror (e, &(p_cdigi_config->alias[from_chan][to_chan]), message, sizeof(message)); text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid alias matching pattern on line %d:\n%s\n", line, message); continue; } t = split(NULL,0); } p_cdigi_config->enabled[from_chan][to_chan] = 1; if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Found \"%s\" where end of line was expected.\n", line, t); } } /* * ==================== Packet Filtering for APRS digipeater or IGate ==================== */ /* * FILTER from-chan to-chan filter_specification_expression * FILTER from-chan IG filter_specification_expression * FILTER IG to-chan filter_specification_expression * * * Note that we have three different config file filter commands: * * FILTER - Originally for APRS digipeating but later enhanced * to include IGate client side. Maybe it should be * renamed AFILTER to make it clearer after adding CFILTER. * * Both internal modem and NET TNC channels allowed here. * "IG" should be used for the IGate, NOT a virtual channel * assigned to it. * * CFILTER - Similar for connected moded digipeater. * * Only internal modems can be used because they provide * information about radio channel status. * A remote network TNC might not provide the necessary * status for correct operation. * There is discussion about this in the document called * Why-is-9600-only-twice-as-fast-as-1200.pdf * * IGFILTER - APRS-IS (IGate) server side - completely diffeent. * I'm not happy with this name because IG sounds like IGate * which is really the client side. More comments later. * Maybe it should be called subscribe or something like that * because the subscriptions are cummulative. */ else if (strcasecmp(t, "FILTER") == 0) { int from_chan, to_chan; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } if (*t == 'i' || *t == 'I') { from_chan = MAX_CHANS; } else { from_chan = isdigit(*t) ? atoi(t) : -999; if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d or \"IG\" on line %d.\n", MAX_CHANS-1, line); continue; } if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO && p_audio_config->achan[from_chan].medium != MEDIUM_NETTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } if (p_audio_config->achan[from_chan].medium == MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Use 'IG' rather than %d for FROM-channel.\n", line, from_chan); continue; } } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } if (*t == 'i' || *t == 'I') { to_chan = MAX_CHANS; } else { to_chan = isdigit(*t) ? atoi(t) : -999; if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d or \"IG\" on line %d.\n", MAX_CHANS-1, line); continue; } if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO && p_audio_config->achan[to_chan].medium != MEDIUM_NETTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } if (p_audio_config->achan[to_chan].medium == MEDIUM_IGATE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Use 'IG' rather than %d for TO-channel.\n", line, to_chan); continue; } } t = split(NULL,1); /* Take rest of line including spaces. */ if (t == NULL) { t = " "; /* Empty means permit nothing. */ } if (p_digi_config->filter_str[from_chan][to_chan] != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Replacing previous filter for same from/to pair:\n %s\n", line, p_digi_config->filter_str[from_chan][to_chan]); free (p_digi_config->filter_str[from_chan][to_chan]); p_digi_config->filter_str[from_chan][to_chan] = NULL; } p_digi_config->filter_str[from_chan][to_chan] = strdup(t); //TODO: Do a test run to see errors now instead of waiting. } /* * ==================== Packet Filtering for connected digipeater ==================== */ /* * CFILTER from-chan to-chan filter_specification_expression */ else if (strcasecmp(t, "CFILTER") == 0) { int from_chan, to_chan; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing FROM-channel on line %d.\n", line); continue; } from_chan = isdigit(*t) ? atoi(t) : -999; if (from_chan < 0 || from_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter FROM-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } // DO NOT allow a network TNC here. // Must be internal modem to have necessary knowledge about channel status. if (p_audio_config->achan[from_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: FROM-channel %d is not valid.\n", line, from_chan); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing TO-channel on line %d.\n", line); continue; } to_chan = isdigit(*t) ? atoi(t) : -999; if (to_chan < 0 || to_chan >= MAX_CHANS) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Filter TO-channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } if (p_audio_config->achan[to_chan].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TO-channel %d is not valid.\n", line, to_chan); continue; } t = split(NULL,1); /* Take rest of line including spaces. */ if (t == NULL) { t = " "; /* Empty means permit nothing. */ } p_cdigi_config->cfilter_str[from_chan][to_chan] = strdup(t); //TODO1.2: Do a test run to see errors now instead of waiting. } /* * ==================== APRStt gateway ==================== */ /* * TTCORRAL - How to handle unknown positions * * TTCORRAL latitude longitude offset-or-ambiguity */ else if (strcasecmp(t, "TTCORRAL") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTCORRAL command.\n", line); continue; } p_tt_config->corral_lat = parse_ll(t,LAT,line); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line); continue; } p_tt_config->corral_lon = parse_ll(t,LON,line); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTCORRAL command.\n", line); continue; } p_tt_config->corral_offset = parse_ll(t,LAT,line); if (p_tt_config->corral_offset == 1 || p_tt_config->corral_offset == 2 || p_tt_config->corral_offset == 3) { p_tt_config->corral_ambiguity = p_tt_config->corral_offset; p_tt_config->corral_offset = 0; } //dw_printf ("DEBUG: corral %f %f %f %d\n", p_tt_config->corral_lat, // p_tt_config->corral_lon, p_tt_config->corral_offset, p_tt_config->corral_ambiguity); } /* * TTPOINT - Define a point represented by touch tone sequence. * * TTPOINT pattern latitude longitude */ else if (strcasecmp(t, "TTPOINT") == 0) { struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); // Should make this a function/macro instead of repeating code. /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_POINT; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->point.lat = 0; tl->point.lon = 0; /* Pattern: B and digits */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTPOINT command.\n", line); continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTPOINT pattern must begin with upper case 'B'.\n", line); } for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j])) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTPOINT pattern must be B and digits only.\n", line); } } /* Latitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTPOINT command.\n", line); continue; } tl->point.lat = parse_ll(t,LAT,line); /* Longitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTPOINT command.\n", line); continue; } tl->point.lon = parse_ll(t,LON,line); /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTVECTOR - Touch tone location with bearing and distance. * * TTVECTOR pattern latitude longitude scale unit */ else if (strcasecmp(t, "TTVECTOR") == 0) { struct ttloc_s *tl; int j; double scale; double meters; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_VECTOR; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->vector.lat = 0; tl->vector.lon = 0; tl->vector.scale = 1; /* Pattern: B5bbbd... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTVECTOR command.\n", line); continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTVECTOR pattern must begin with upper case 'B'.\n", line); } if (strncmp(t+1, "5bbb", 4) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTVECTOR pattern would normally contain \"5bbb\".\n", line); } for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'b' && t[j] != 'd') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTVECTOR pattern must contain only B, digits, b, and d.\n", line); } } /* Latitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTVECTOR command.\n", line); continue; } tl->vector.lat = parse_ll(t,LAT,line); /* Longitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTVECTOR command.\n", line); continue; } tl->vector.lon = parse_ll(t,LON,line); /* Longitude */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing scale for TTVECTOR command.\n", line); continue; } scale = atof(t); /* Unit. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing unit for TTVECTOR command.\n", line); continue; } meters = 0; for (j=0; jvector.scale = scale * meters; //dw_printf ("ttvector: %f meters\n", tl->vector.scale); /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTGRID - Define a grid for touch tone locations. * * TTGRID pattern min-latitude min-longitude max-latitude max-longitude */ else if (strcasecmp(t, "TTGRID") == 0) { struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_GRID; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->grid.lat0 = 0; tl->grid.lon0 = 0; tl->grid.lat9 = 0; tl->grid.lon9 = 0; /* Pattern: B [digit] x... y... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTGRID command.\n", line); continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTGRID pattern must begin with upper case 'B'.\n", line); } for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTGRID pattern must be B, optional digit, xxx, yyy.\n", line); } } /* Minimum Latitude - all zeros in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line); continue; } tl->grid.lat0 = parse_ll(t,LAT,line); /* Minimum Longitude - all zeros in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line); continue; } tl->grid.lon0 = parse_ll(t,LON,line); /* Maximum Latitude - all nines in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing latitude for TTGRID command.\n", line); continue; } tl->grid.lat9 = parse_ll(t,LAT,line); /* Maximum Longitude - all nines in received data */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing longitude for TTGRID command.\n", line); continue; } tl->grid.lon0 = parse_ll(t,LON,line); /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTUTM - Specify UTM zone for touch tone locations. * * TTUTM pattern zone [ scale [ x-offset y-offset ] ] */ else if (strcasecmp(t, "TTUTM") == 0) { struct ttloc_s *tl; int j; double dlat, dlon; long lerr; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_UTM; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->utm.lzone = 0; tl->utm.scale = 1; tl->utm.x_offset = 0; tl->utm.y_offset = 0; /* Pattern: B [digit] x... y... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTUTM command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUTM pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } for (j=1; j < (int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUTM pattern must be B, optional digit, xxx, yyy.\n", line); // Bail out somehow. continue would match inner for. } } /* Zone 1 - 60 and optional latitudinal letter. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing zone for TTUTM command.\n", line); p_tt_config->ttloc_len--; continue; } tl->utm.lzone = parse_utm_zone (t, &(tl->utm.latband), &(tl->utm.hemi)); /* Optional scale. */ t = split(NULL,0); if (t != NULL) { tl->utm.scale = atof(t); /* Optional x offset. */ t = split(NULL,0); if (t != NULL) { tl->utm.x_offset = atof(t); /* Optional y offset. */ t = split(NULL,0); if (t != NULL) { tl->utm.y_offset = atof(t); } } } /* Practice run to see if conversion might fail later with actual location. */ lerr = Convert_UTM_To_Geodetic(tl->utm.lzone, tl->utm.hemi, tl->utm.x_offset + 5 * tl->utm.scale, tl->utm.y_offset + 5 * tl->utm.scale, &dlat, &dlon); if (lerr != 0) { char message [300]; utm_error_string (lerr, message); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message); p_tt_config->ttloc_len--; continue; } } /* * TTUSNG, TTMGRS - Specify zone/square for touch tone locations. * * TTUSNG pattern zone_square * TTMGRS pattern zone_square */ else if (strcasecmp(t, "TTUSNG") == 0 || strcasecmp(t, "TTMGRS") == 0) { struct ttloc_s *tl; int j; int num_x, num_y; double lat, lon; long lerr; char message[300]; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); // TODO1.2: in progress... if (strcasecmp(t, "TTMGRS") == 0) { tl->type = TTLOC_MGRS; } else { tl->type = TTLOC_USNG; } strlcpy(tl->pattern, "", sizeof(tl->pattern)); strlcpy(tl->mgrs.zone, "", sizeof(tl->mgrs.zone)); /* Pattern: B [digit] x... y... */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTUSNG/TTMGRS command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUSNG/TTMGRS pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } num_x = 0; num_y = 0; for (j=1; j<(int)(strlen(t)); j++) { if ( ! isdigit(t[j]) && t[j] != 'x' && t[j] != 'y') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUSNG/TTMGRS pattern must be B, optional digit, xxx, yyy.\n", line); // Bail out somehow. continue would match inner for. } if (t[j] == 'x') num_x++; if (t[j] == 'y') num_y++; } if (num_x < 1 || num_x > 5 || num_x != num_y) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTUSNG/TTMGRS must have 1 to 5 x and same number y.\n", line); p_tt_config->ttloc_len--; continue; } /* Zone 1 - 60 and optional latitudinal letter. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing zone & square for TTUSNG/TTMGRS command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->mgrs.zone, t, sizeof(tl->mgrs.zone)); /* Try converting it rather do our own error checking. */ if (tl->type == TTLOC_MGRS) { lerr = Convert_MGRS_To_Geodetic (tl->mgrs.zone, &lat, &lon); } else { lerr = Convert_USNG_To_Geodetic (tl->mgrs.zone, &lat, &lon); } if (lerr != 0) { mgrs_error_string (lerr, message); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid USNG/MGRS zone & square: %s\n%s\n", line, tl->mgrs.zone, message); p_tt_config->ttloc_len--; continue; } /* Should be the end. */ t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unexpected stuff at end ignored: %s\n", line, t); } } /* * TTMHEAD - Define pattern to be used for Maidenhead Locator. * * TTMHEAD pattern [ prefix ] * * Pattern would be B[0-9A-D]xxxx... * Optional prefix is 10, 6, or 4 digits. * * The total number of digts in both must be 4, 6, 10, or 12. */ else if (strcasecmp(t, "TTMHEAD") == 0) { // TODO1.3: TTMHEAD needs testing. struct ttloc_s *tl; int j; int k; int count_x; int count_other; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_MHEAD; strlcpy(tl->pattern, "", sizeof(tl->pattern)); strlcpy(tl->mhead.prefix, "", sizeof(tl->mhead.prefix)); /* Pattern: B, optional additional button, some number of xxxx... for matching */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTMHEAD command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } /* Optionally one of 0-9ABCD */ if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) { j = 2; } else { j = 1; } count_x = 0; count_other = 0; for (k = j ; k < (int)(strlen(t)); k++) { if (t[k] == 'x') count_x++; else count_other++; } if (count_other != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD must have only lower case x to match received data.\n", line); p_tt_config->ttloc_len--; continue; } // optional prefix t = split(NULL,0); if (t != NULL) { char mh[30]; strlcpy(tl->mhead.prefix, t, sizeof(tl->mhead.prefix)); if (!alldigits(t) || (strlen(t) != 4 && strlen(t) != 6 && strlen(t) != 10)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD prefix must be 4, 6, or 10 digits.\n", line); p_tt_config->ttloc_len--; continue; } if (tt_mhead_to_text(t, 0, mh, sizeof(mh)) != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD prefix not a valid DTMF sequence.\n", line); p_tt_config->ttloc_len--; continue; } } k = strlen(tl->mhead.prefix) + count_x; if (k != 4 && k != 6 && k != 10 && k != 12 ) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMHEAD prefix and user data must have a total of 4, 6, 10, or 12 digits.\n", line); p_tt_config->ttloc_len--; continue; } } /* * TTSATSQ - Define pattern to be used for Satellite square. * * TTSATSQ pattern * * Pattern would be B[0-9A-D]xxxx * * Must have exactly 4 x. */ else if (strcasecmp(t, "TTSATSQ") == 0) { // TODO1.2: TTSATSQ To be continued... struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_SATSQ; strlcpy(tl->pattern, "", sizeof(tl->pattern)); tl->point.lat = 0; tl->point.lon = 0; /* Pattern: B, optional additional button, exactly xxxx for matching */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTSATSQ command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTSATSQ pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } /* Optionally one of 0-9ABCD */ if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) { j = 2; } else { j = 1; } if (strcmp(t+j, "xxxx") != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTSATSQ pattern must end with exactly xxxx in lower case.\n", line); p_tt_config->ttloc_len--; continue; } /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTAMBIG - Define pattern to be used for Object Location Ambiguity. * * TTAMBIG pattern * * Pattern would be B[0-9A-D]x * * Must have exactly one x. */ else if (strcasecmp(t, "TTAMBIG") == 0) { // TODO1.3: TTAMBIG To be continued... struct ttloc_s *tl; int j; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len > 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_AMBIG; strlcpy(tl->pattern, "", sizeof(tl->pattern)); /* Pattern: B, optional additional button, exactly x for matching */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTAMBIG command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); if (t[0] != 'B') { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTAMBIG pattern must begin with upper case 'B'.\n", line); p_tt_config->ttloc_len--; continue; } /* Optionally one of 0-9ABCD */ if (strchr("ABCD", t[1]) != NULL || isdigit(t[1])) { j = 2; } else { j = 1; } if (strcmp(t+j, "x") != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTAMBIG pattern must end with exactly one x in lower case.\n", line); p_tt_config->ttloc_len--; continue; } /* temp debugging */ //for (j=0; jttloc_len; j++) { // dw_printf ("debug ttloc %d/%d %s\n", j, p_tt_config->ttloc_size, // p_tt_config->ttloc_ptr[j].pattern); //} } /* * TTMACRO - Define compact message format with full expansion * * TTMACRO pattern definition * * pattern can contain: * 0-9 which must match exactly. * In version 1.2, also allow A,B,C,D for exact match. * x, y, z which are used for matching of variable fields. * * definition can contain: * 0-9, A, B, C, D, *, #, x, y, z. * Not sure why # was included in there. * * new for version 1.3 - in progress * * AA{objname} * AB{symbol} * AC{call} * * These provide automatic conversion from plain text to the TT encoding. * */ else if (strcasecmp(t, "TTMACRO") == 0) { struct ttloc_s *tl; int j; int p_count[3], d_count[3]; int tt_error = 0; assert (p_tt_config->ttloc_size >= 2); assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); /* Allocate new space, but first, if already full, make larger. */ if (p_tt_config->ttloc_len == p_tt_config->ttloc_size) { p_tt_config->ttloc_size += p_tt_config->ttloc_size / 2; p_tt_config->ttloc_ptr = realloc (p_tt_config->ttloc_ptr, sizeof(struct ttloc_s) * p_tt_config->ttloc_size); } p_tt_config->ttloc_len++; assert (p_tt_config->ttloc_len >= 0 && p_tt_config->ttloc_len <= p_tt_config->ttloc_size); tl = &(p_tt_config->ttloc_ptr[p_tt_config->ttloc_len-1]); tl->type = TTLOC_MACRO; strlcpy(tl->pattern, "", sizeof(tl->pattern)); /* Pattern: Any combination of digits, x, y, and z. */ /* Also make note of which letters are used in pattern and defintition. */ /* Version 1.2: also allow A,B,C,D in the pattern. */ t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing pattern for TTMACRO command.\n", line); p_tt_config->ttloc_len--; continue; } strlcpy (tl->pattern, t, sizeof(tl->pattern)); p_count[0] = p_count[1] = p_count[2] = 0; for (j=0; j<(int)(strlen(t)); j++) { if ( strchr ("0123456789ABCDxyz", t[j]) == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMACRO pattern can contain only digits, A, B, C, D, and lower case x, y, or z.\n", line); p_tt_config->ttloc_len--; continue; } /* Count how many x, y, z in the pattern. */ if (t[j] >= 'x' && t[j] <= 'z') { p_count[t[j]-'x']++; } } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Line %d: TTMACRO pattern \"%s\" p_count = %d %d %d.\n", line, t, p_count[0], p_count[1], p_count[2]); /* Next we should find the definition. */ /* It can contain touch tone characters and lower case x, y, z for substitutions. */ t = split(NULL,1);; if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing definition for TTMACRO command.\n", line); tl->macro.definition = ""; /* Don't die on null pointer later. */ p_tt_config->ttloc_len--; continue; } /* Make a pass over the definition, looking for the xx{...} substitutions. */ /* These are done just once when reading the configuration file. */ char *pi; char *ps; char stemp[100]; // text inside of xx{...} char ttemp[300]; // Converted to tone sequences. char otemp[1000]; // Result after any substitutions. char t2[2]; strlcpy (otemp, "", sizeof(otemp)); t2[1] = '\0'; pi = t; while (*pi == ' ' || *pi == '\t') { pi++; } for ( ; *pi != '\0'; pi++) { if (strncmp(pi, "AC{", 3) == 0) { // Convert to fixed length 10 digit callsign. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { *ps = '\0'; if (tt_text_to_call10 (stemp, 0, ttemp) == 0) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Line %d: AC{%s} -> AC%s\n", line, stemp, ttemp); strlcat (otemp, "AC", sizeof(otemp)); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AC{%s} could not be converted to tones for callsign.\n", line, stemp); tt_error++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AC{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strncmp(pi, "AA{", 3) == 0) { // Convert to object name. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { *ps = '\0'; if (strlen(stemp) > 9) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Object name %s has been truncated to 9 characters.\n", line, stemp); stemp[9] = '\0'; } if (tt_text_to_two_key (stemp, 0, ttemp) == 0) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Line %d: AA{%s} -> AA%s\n", line, stemp, ttemp); strlcat (otemp, "AA", sizeof(otemp)); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AA{%s} could not be converted to tones for object name.\n", line, stemp); tt_error++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AA{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strncmp(pi, "AB{", 3) == 0) { // Attempt conversion from description to symbol code. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { char symtab; char symbol; *ps = '\0'; // First try to find something matching the description. if (symbols_code_from_description (' ', stemp, &symtab, &symbol) == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Couldn't convert \"%s\" to APRS symbol code. Using default.\n", line, stemp); symtab = '\\'; // Alternate symbol = 'A'; // Box } // Convert symtab(overlay) & symbol to tone sequence. symbols_to_tones (symtab, symbol, ttemp, sizeof(ttemp)); //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG config file Line %d: AB{%s} -> %s\n", line, stemp, ttemp); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: AB{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strncmp(pi, "CA{", 3) == 0) { // Convert to enhanced comment that can contain any ASCII character. pi += 3; ps = stemp; while (*pi != '}' && *pi != '*' && *pi != '\0') { *ps++ = *pi++; } if (*pi == '}') { *ps = '\0'; if (tt_text_to_ascii2d (stemp, 0, ttemp) == 0) { //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Line %d: CA{%s} -> CA%s\n", line, stemp, ttemp); strlcat (otemp, "CA", sizeof(otemp)); strlcat (otemp, ttemp, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: CA{%s} could not be converted to tones for enhanced comment.\n", line, stemp); tt_error++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: CA{... is missing matching } in TTMACRO definition.\n", line); tt_error++; } } else if (strchr("0123456789ABCD*#xyz", *pi) != NULL) { t2[0] = *pi; strlcat (otemp, t2, sizeof(otemp)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: TTMACRO definition can contain only 0-9, A, B, C, D, *, #, x, y, z.\n", line); tt_error++; } } /* Make sure that number of x, y, z, in pattern and definition match. */ d_count[0] = d_count[1] = d_count[2] = 0; for (j=0; j<(int)(strlen(otemp)); j++) { if (otemp[j] >= 'x' && otemp[j] <= 'z') { d_count[otemp[j]-'x']++; } } /* A little validity checking. */ for (j=0; j<3; j++) { if (p_count[j] > 0 && d_count[j] == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: '%c' is in TTMACRO pattern but is not used in definition.\n", line, 'x'+j); } if (d_count[j] > 0 && p_count[j] == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: '%c' is referenced in TTMACRO definition but does not appear in the pattern.\n", line, 'x'+j); } } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("DEBUG Config Line %d: %s -> %s\n", line, t, otemp); if (tt_error == 0) { tl->macro.definition = strdup(otemp); } else { p_tt_config->ttloc_len--; } } /* * TTOBJ - TT Object Report options. * * TTOBJ recv-chan where-to [ via-path ] * * whereto is any combination of transmit channel, APP, IG. */ else if (strcasecmp(t, "TTOBJ") == 0) { int r, x = -1; int app = 0; int ig = 0; char *p; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing DTMF receive channel for TTOBJ command.\n", line); continue; } r = atoi(t); if (r < 0 || r > MAX_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: DTMF receive channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } // I suppose we need internal modem channel here. // otherwise a DTMF decoder would not be available. if (p_audio_config->achan[r].medium != MEDIUM_RADIO) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TTOBJ DTMF receive channel %d is not valid.\n", line, r); continue; } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing transmit channel for TTOBJ command.\n", line); continue; } // Can have any combination of number, APP, IG. // Would it be easier with strtok? for (p = t; *p != '\0'; p++) { if (isdigit(*p)) { x = *p - '0'; if (x < 0 || x > MAX_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); x = -1; } else if (p_audio_config->achan[x].medium != MEDIUM_RADIO && p_audio_config->achan[x].medium != MEDIUM_NETTNC) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: TTOBJ transmit channel %d is not valid.\n", line, x); x = -1; } } else if (*p == 'a' || *p == 'A') { app = 1; } else if (*p == 'i' || *p == 'I') { ig = 1; } else if (strchr("pPgG,", *p) != NULL) { ; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Expected comma separated list with some combination of transmit channel, APP, and IG.\n", line); } } // This enables the DTMF decoder on the specified channel. // Additional channels can be enabled with the DTMF command. // Note that DTMF command does not enable the APRStt gateway. //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Debug TTOBJ r=%d, x=%d, app=%d, ig=%d\n", r, x, app, ig); p_audio_config->achan[r].dtmf_decode = DTMF_DECODE_ON; p_tt_config->gateway_enabled = 1; p_tt_config->obj_recv_chan = r; p_tt_config->obj_xmit_chan = x; p_tt_config->obj_send_to_app = app; p_tt_config->obj_send_to_ig = ig; t = split(NULL,0); if (t != NULL) { if (check_via_path(t) >= 0) { strlcpy (p_tt_config->obj_xmit_via, t, sizeof(p_tt_config->obj_xmit_via)); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: invalid via path.\n", line); } } } /* * TTERR - TT responses for success or errors. * * TTERR msg_id method text... */ else if (strcasecmp(t, "TTERR") == 0) { int n, msg_num; char *p; char method[AX25_MAX_ADDR_LEN]; int ssid; int heard; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing message identifier for TTERR command.\n", line); continue; } msg_num = -1; for (n=0; n= 0 && msg_num < TT_ERROR_MAXP1); strlcpy (p_tt_config->response[msg_num].method, method, sizeof(p_tt_config->response[msg_num].method)); // TODO1.3: Need SSID too! strlcpy (p_tt_config->response[msg_num].mtext, t, sizeof(p_tt_config->response[msg_num].mtext)); p_tt_config->response[msg_num].mtext[TT_MTEXT_LEN-1] = '\0'; } /* * TTSTATUS - TT custom status messages. * * TTSTATUS status_id text... */ else if (strcasecmp(t, "TTSTATUS") == 0) { int status_num; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing status number for TTSTATUS command.\n", line); continue; } status_num = atoi(t); if (status_num < 1 || status_num > 9) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Status number for TTSTATUS command must be in range of 1 to 9.\n", line); continue; } t = split(NULL,1);; if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing status text for TTSTATUS command.\n", line); continue; } //text_color_set(DW_COLOR_DEBUG); //dw_printf ("Line %d: TTSTATUS debug %d \"%s\"\n", line, status_num, t); while (*t == ' ' || *t == '\t') t++; // remove leading white space. strlcpy (p_tt_config->status[status_num], t, sizeof(p_tt_config->status[status_num])); } /* * TTCMD - Command to run when valid sequence is received. * Any text generated will be sent back to user. * * TTCMD ... */ else if (strcasecmp(t, "TTCMD") == 0) { t = split(NULL,1); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing command for TTCMD command.\n", line); continue; } strlcpy (p_tt_config->ttcmd, t, sizeof(p_tt_config->ttcmd)); } /* * ==================== Internet gateway ==================== */ /* * IGSERVER - Name of IGate server. * * IGSERVER hostname [ port ] -- original implementation. * * IGSERVER hostname:port -- more in line with usual conventions. */ else if (strcasecmp(t, "IGSERVER") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing IGate server name for IGSERVER command.\n", line); continue; } strlcpy (p_igate_config->t2_server_name, t, sizeof(p_igate_config->t2_server_name)); /* If there is a : in the name, split it out as the port number. */ t = strchr (p_igate_config->t2_server_name, ':'); if (t != NULL) { *t = '\0'; t++; int n = atoi(t); if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { p_igate_config->t2_server_port = n; } else { p_igate_config->t2_server_port = DEFAULT_IGATE_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", line, p_igate_config->t2_server_port); } } /* Alternatively, the port number could be separated by white space. */ t = split(NULL,0); if (t != NULL) { int n = atoi(t); if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { p_igate_config->t2_server_port = n; } else { p_igate_config->t2_server_port = DEFAULT_IGATE_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for IGate server. Using default %d.\n", line, p_igate_config->t2_server_port); } } //dw_printf ("DEBUG server=%s port=%d\n", p_igate_config->t2_server_name, p_igate_config->t2_server_port); //exit (0); } /* * IGLOGIN - Login callsign and passcode for IGate server * * IGLOGIN callsign passcode */ else if (strcasecmp(t, "IGLOGIN") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing login callsign for IGLOGIN command.\n", line); continue; } // TODO: Wouldn't hurt to do validity checking of format. strlcpy (p_igate_config->t2_login, t, sizeof(p_igate_config->t2_login)); t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing passcode for IGLOGIN command.\n", line); continue; } strlcpy (p_igate_config->t2_passcode, t, sizeof(p_igate_config->t2_passcode)); } /* * IGTXVIA - Transmit channel and VIA path for messages from IGate server * * IGTXVIA channel [ path ] */ else if (strcasecmp(t, "IGTXVIA") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing transmit channel for IGTXVIA command.\n", line); continue; } n = atoi(t); if (n < 0 || n > MAX_CHANS-1) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Transmit channel must be in range of 0 to %d on line %d.\n", MAX_CHANS-1, line); continue; } p_igate_config->tx_chan = n; t = split(NULL,0); if (t != NULL) { #if 1 // proper checking n = check_via_path(t); if (n >= 0) { p_igate_config->max_digi_hops = n; p_igate_config->tx_via[0] = ','; strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: invalid via path.\n", line); } #else // previously char *p; p_igate_config->tx_via[0] = ','; strlcpy (p_igate_config->tx_via + 1, t, sizeof(p_igate_config->tx_via)-1); for (p = p_igate_config->tx_via; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } } #endif } } /* * IGFILTER - IGate Server side filters. * Is this name too confusing. Too similar to FILTER IG 0 ... * Maybe SSFILTER suggesting Server Side. * SUBSCRIBE might be better because it's not a filter that limits. * * IGFILTER filter-spec ... */ else if (strcasecmp(t, "IGFILTER") == 0) { t = split(NULL,1); /* Take rest of line as one string. */ if (p_igate_config->t2_filter != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Warning - Earlier IGFILTER value will be replaced by this one.\n", line); continue; } if (t != NULL && strlen(t) > 0) { p_igate_config->t2_filter = strdup (t); } } /* * IGTXLIMIT - Limit transmissions during 1 and 5 minute intervals. * * IGTXLIMIT one-minute-limit five-minute-limit */ else if (strcasecmp(t, "IGTXLIMIT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing one minute limit for IGTXLIMIT command.\n", line); continue; } n = atoi(t); if (n < 1) { p_igate_config->tx_limit_1 = 1; } else if (n <= IGATE_TX_LIMIT_1_MAX) { p_igate_config->tx_limit_1 = n; } else { p_igate_config->tx_limit_1 = IGATE_TX_LIMIT_1_MAX; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: One minute transmit limit has been reduced to %d.\n", line, p_igate_config->tx_limit_1); dw_printf ("You won't make friends by setting a limit this high.\n"); } t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing five minute limit for IGTXLIMIT command.\n", line); continue; } n = atoi(t); if (n < 1) { p_igate_config->tx_limit_5 = 1; } else if (n <= IGATE_TX_LIMIT_5_MAX) { p_igate_config->tx_limit_5 = n; } else { p_igate_config->tx_limit_5 = IGATE_TX_LIMIT_5_MAX; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Five minute transmit limit has been reduced to %d.\n", line, p_igate_config->tx_limit_5); dw_printf ("You won't make friends by setting a limit this high.\n"); } } /* * IGMSP - Number of times to send position of message sender. * * IGMSP n */ else if (strcasecmp(t, "IGMSP") == 0) { t = split(NULL,0); if (t != NULL) { int n = atoi(t); if (n >= 0 && n <= 10) { p_igate_config->igmsp = n; } else { p_igate_config->igmsp = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable number of times for message sender position. Using default 1.\n", line); } } else { p_igate_config->igmsp = 1; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing number of times for message sender position. Using default 1.\n", line); } } /* * SATGATE - Special SATgate mode to delay packets heard directly. * * SATGATE [ n ] */ else if (strcasecmp(t, "SATGATE") == 0) { text_color_set(DW_COLOR_INFO); dw_printf ("Line %d: SATGATE is pretty useless and will be removed in a future version.\n", line); t = split(NULL,0); if (t != NULL) { int n = atoi(t); if (n >= MIN_SATGATE_DELAY && n <= MAX_SATGATE_DELAY) { p_igate_config->satgate_delay = n; } else { p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Unreasonable SATgate delay. Using default.\n", line); } } else { p_igate_config->satgate_delay = DEFAULT_SATGATE_DELAY; } } /* * ==================== All the left overs ==================== */ /* * AGWPORT - Port number for "AGW TCPIP Socket Interface" * * In version 1.2 we allow 0 to disable listening. */ else if (strcasecmp(t, "AGWPORT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing port number for AGWPORT command.\n", line); continue; } n = atoi(t); if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->agwpe_port = n; } else { p_misc_config->agwpe_port = DEFAULT_AGWPE_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for AGW TCPIP Socket Interface. Using %d.\n", line, p_misc_config->agwpe_port); } } /* * KISSPORT - Port number for KISS over IP. */ else if (strcasecmp(t, "KISSPORT") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing port number for KISSPORT command.\n", line); continue; } n = atoi(t); if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->kiss_port = n; } else { p_misc_config->kiss_port = DEFAULT_KISS_PORT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for KISS TCPIP Socket Interface. Using %d.\n", line, p_misc_config->kiss_port); } } /* * NULLMODEM name [ speed ] - Device name for serial port or our end of the virtual "null modem" * SERIALKISS name [ speed ] * * Version 1.5: Added SERIALKISS which is equivalent to NULLMODEM. * The original name sort of made sense when it was used only for one end of a virtual * null modem cable on Windows only. Now it is also available for Linux. * TODO1.5: In retrospect, this doesn't seem like such a good name. */ else if (strcasecmp(t, "NULLMODEM") == 0 || strcasecmp(t, "SERIALKISS") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing serial port name on line %d.\n", line); continue; } else { if (strlen(p_misc_config->kiss_serial_port) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Warning serial port name on line %d replaces earlier value.\n", line); } strlcpy (p_misc_config->kiss_serial_port, t, sizeof(p_misc_config->kiss_serial_port)); p_misc_config->kiss_serial_speed = 0; p_misc_config->kiss_serial_poll = 0; } t = split(NULL,0); if (t != NULL) { p_misc_config->kiss_serial_speed = atoi(t); } } /* * SERIALKISSPOLL name - Poll for serial port name that might come and go. * e.g. /dev/rfcomm0 for bluetooth. */ else if (strcasecmp(t, "SERIALKISSPOLL") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing serial port name on line %d.\n", line); continue; } else { if (strlen(p_misc_config->kiss_serial_port) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Warning serial port name on line %d replaces earlier value.\n", line); } strlcpy (p_misc_config->kiss_serial_port, t, sizeof(p_misc_config->kiss_serial_port)); p_misc_config->kiss_serial_speed = 0; p_misc_config->kiss_serial_poll = 1; // set polling. } } /* * KISSCOPY - Data from network KISS client is copied to all others. */ else if (strcasecmp(t, "KISSCOPY") == 0) { p_misc_config->kiss_copy = 1; } /* * GPSNMEA - Device name for reading from GPS receiver. */ else if (strcasecmp(t, "gpsnmea") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Missing serial port name for GPS receiver.\n", line); continue; } else { strlcpy (p_misc_config->gpsnmea_port, t, sizeof(p_misc_config->gpsnmea_port)); } } /* * GPSD - Use GPSD server. * * GPSD [ host [ port ] ] */ else if (strcasecmp(t, "gpsd") == 0) { #if __WIN32__ text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: The GPSD interface is not available for Windows.\n", line); continue; #elif ENABLE_GPSD strlcpy (p_misc_config->gpsd_host, "localhost", sizeof(p_misc_config->gpsd_host)); p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT); t = split(NULL,0); if (t != NULL) { strlcpy (p_misc_config->gpsd_host, t, sizeof(p_misc_config->gpsd_host)); t = split(NULL,0); if (t != NULL) { int n = atoi(t); if ((n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) || n == 0) { p_misc_config->gpsd_port = n; } else { p_misc_config->gpsd_port = atoi(DEFAULT_GPSD_PORT); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid port number for GPSD Socket Interface. Using default of %d.\n", line, p_misc_config->gpsd_port); } } } #else text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: The GPSD interface has not been enabled.\n", line); dw_printf ("Install gpsd and libgps-dev packages then rebuild direwolf.\n"); continue; #endif } /* * WAYPOINT - Generate WPL and AIS NMEA sentences for display on map. * * WAYPOINT serial-device [ formats ] * WAYPOINT host:udpport [ formats ] * */ else if (strcasecmp(t, "waypoint") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing output device for WAYPOINT on line %d.\n", line); continue; } /* If there is a ':' in the name, split it into hostname:udpportnum. */ /* Otherwise assume it is serial port name. */ char *p = strchr (t, ':'); if (p != NULL) { *p = '\0'; int n = atoi(p+1); if (n >= MIN_IP_PORT_NUMBER && n <= MAX_IP_PORT_NUMBER) { strlcpy (p_misc_config->waypoint_udp_hostname, t, sizeof(p_misc_config->waypoint_udp_hostname)); if (strlen(p_misc_config->waypoint_udp_hostname) == 0) { strlcpy (p_misc_config->waypoint_udp_hostname, "localhost", sizeof(p_misc_config->waypoint_udp_hostname)); } p_misc_config->waypoint_udp_portnum = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid UDP port number %d for sending waypoints.\n", line, n); } } else { strlcpy (p_misc_config->waypoint_serial_port, t, sizeof(p_misc_config->waypoint_serial_port)); } /* Anthing remaining is the formats to enable. */ t = split(NULL,1); if (t != NULL) { for ( ; *t != '\0' ; t++ ) { switch (toupper(*t)) { case 'N': p_misc_config->waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; break; case 'G': p_misc_config->waypoint_formats |= WPT_FORMAT_GARMIN; break; case 'M': p_misc_config->waypoint_formats |= WPT_FORMAT_MAGELLAN; break; case 'K': p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD; break; case 'A': p_misc_config->waypoint_formats |= WPT_FORMAT_AIS; break; case ' ': case ',': break; default: text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Invalid output format '%c' for WAYPOINT on line %d.\n", *t, line); break; } } } } /* * LOGDIR - Directory name for automatically named daily log files. Use "." for current working directory. */ else if (strcasecmp(t, "logdir") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing directory name for LOGDIR on line %d.\n", line); continue; } else { if (strlen(p_misc_config->log_path) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGDIR on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line); } p_misc_config->log_daily_names = 1; strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path)); } t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGDIR on line %d should have directory path and nothing more.\n", line); } } /* * LOGFILE - Log file name, including any directory part. */ else if (strcasecmp(t, "logfile") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Missing file name for LOGFILE on line %d.\n", line); continue; } else { if (strlen(p_misc_config->log_path) > 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGFILE on line %d is replacing an earlier LOGDIR or LOGFILE.\n", line); } p_misc_config->log_daily_names = 0; strlcpy (p_misc_config->log_path, t, sizeof(p_misc_config->log_path)); } t = split(NULL,0); if (t != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: LOGFILE on line %d should have file name and nothing more.\n", line); } } /* * BEACON channel delay every message * * Original handcrafted style. Removed in version 1.0. */ else if (strcasecmp(t, "BEACON") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Old style 'BEACON' has been replaced with new commands.\n", line); dw_printf ("Use PBEACON, OBEACON, or CBEACON instead.\n"); } /* * PBEACON keyword=value ... * OBEACON keyword=value ... * TBEACON keyword=value ... * CBEACON keyword=value ... * IBEACON keyword=value ... * * New style with keywords for options. */ else if (strcasecmp(t, "PBEACON") == 0 || strcasecmp(t, "OBEACON") == 0 || strcasecmp(t, "TBEACON") == 0 || strcasecmp(t, "CBEACON") == 0 || strcasecmp(t, "IBEACON") == 0) { if (p_misc_config->num_beacons < MAX_BEACONS) { memset (&(p_misc_config->beacon[p_misc_config->num_beacons]), 0, sizeof(struct beacon_s)); if (strcasecmp(t, "PBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_POSITION; } else if (strcasecmp(t, "OBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_OBJECT; } else if (strcasecmp(t, "TBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_TRACKER; } else if (strcasecmp(t, "IBEACON") == 0) { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_IGATE; } else { p_misc_config->beacon[p_misc_config->num_beacons].btype = BEACON_CUSTOM; } /* Save line number because some errors will be reported later. */ p_misc_config->beacon[p_misc_config->num_beacons].lineno = line; if (beacon_options(t + strlen("xBEACON") + 1, &(p_misc_config->beacon[p_misc_config->num_beacons]), line, p_audio_config)) { p_misc_config->num_beacons++; } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Maximum number of beacons exceeded on line %d.\n", line); continue; } } /* * SMARTBEACONING [ fast_speed fast_rate slow_speed slow_rate turn_time turn_angle turn_slope ] * * Parameters must be all or nothing. */ else if (strcasecmp(t, "SMARTBEACON") == 0 || strcasecmp(t, "SMARTBEACONING") == 0) { int n; #define SB_NUM(name,sbvar,minn,maxx,unit) \ t = split(NULL,0); \ if (t == NULL) { \ if (strcmp(name, "fast speed") == 0) { \ p_misc_config->sb_configured = 1; \ continue; \ } \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \ continue; \ } \ n = atoi(t); \ if (n >= minn && n <= maxx) { \ p_misc_config->sbvar = n; \ } \ else { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n", \ line, name, p_misc_config->sbvar, unit); \ } #define SB_TIME(name,sbvar,minn,maxx,unit) \ t = split(NULL,0); \ if (t == NULL) { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Missing %s for SmartBeaconing.\n", line, name); \ continue; \ } \ n = parse_interval(t,line); \ if (n >= minn && n <= maxx) { \ p_misc_config->sbvar = n; \ } \ else { \ text_color_set(DW_COLOR_ERROR); \ dw_printf ("Line %d: Invalid %s for SmartBeaconing. Using default %d %s.\n", \ line, name, p_misc_config->sbvar, unit); \ } SB_NUM ("fast speed", sb_fast_speed, 2, 90, "MPH") SB_TIME ("fast rate", sb_fast_rate, 10, 300, "seconds") SB_NUM ("slow speed", sb_slow_speed, 1, 30, "MPH") SB_TIME ("slow rate", sb_slow_rate, 30, 3600, "seconds") SB_TIME ("turn time", sb_turn_time, 5, 180, "seconds") SB_NUM ("turn angle", sb_turn_angle, 5, 90, "degrees") SB_NUM ("turn slope", sb_turn_slope, 1, 255, "deg*mph") /* If I was ambitious, I might allow optional */ /* unit at end for miles or km / hour. */ p_misc_config->sb_configured = 1; } /* * ==================== AX.25 connected mode ==================== */ /* * FRACK n - Number of seconds to wait for ack to transmission. */ else if (strcasecmp(t, "FRACK") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for FRACK.\n", line); continue; } n = atoi(t); if (n >= AX25_T1V_FRACK_MIN && n <= AX25_T1V_FRACK_MAX) { p_misc_config->frack = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid FRACK time. Using default %d.\n", line, p_misc_config->frack); } } /* * RETRY n - Number of times to retry before giving up. */ else if (strcasecmp(t, "RETRY") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for RETRY.\n", line); continue; } n = atoi(t); if (n >= AX25_N2_RETRY_MIN && n <= AX25_N2_RETRY_MAX) { p_misc_config->retry = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid RETRY number. Using default %d.\n", line, p_misc_config->retry); } } /* * PACLEN n - Maximum number of bytes in information part. */ else if (strcasecmp(t, "PACLEN") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for PACLEN.\n", line); continue; } n = atoi(t); if (n >= AX25_N1_PACLEN_MIN && n <= AX25_N1_PACLEN_MAX) { p_misc_config->paclen = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid PACLEN value. Using default %d.\n", line, p_misc_config->paclen); } } /* * MAXFRAME n - Max frames to send before ACK. mod 8 "Window" size. * * Window size would make more sense but everyone else calls it MAXFRAME. */ else if (strcasecmp(t, "MAXFRAME") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for MAXFRAME.\n", line); continue; } n = atoi(t); if (n >= AX25_K_MAXFRAME_BASIC_MIN && n <= AX25_K_MAXFRAME_BASIC_MAX) { p_misc_config->maxframe_basic = n; } else { p_misc_config->maxframe_basic = AX25_K_MAXFRAME_BASIC_DEFAULT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid MAXFRAME value outside range of %d to %d. Using default %d.\n", line, AX25_K_MAXFRAME_BASIC_MIN, AX25_K_MAXFRAME_BASIC_MAX, p_misc_config->maxframe_basic); } } /* * EMAXFRAME n - Max frames to send before ACK. mod 128 "Window" size. */ else if (strcasecmp(t, "EMAXFRAME") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for EMAXFRAME.\n", line); continue; } n = atoi(t); if (n >= AX25_K_MAXFRAME_EXTENDED_MIN && n <= AX25_K_MAXFRAME_EXTENDED_MAX) { p_misc_config->maxframe_extended = n; } else { p_misc_config->maxframe_extended = AX25_K_MAXFRAME_EXTENDED_DEFAULT; text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid EMAXFRAME value outside of range %d to %d. Using default %d.\n", line, AX25_K_MAXFRAME_EXTENDED_MIN, AX25_K_MAXFRAME_EXTENDED_MAX, p_misc_config->maxframe_extended); } } /* * MAXV22 n - Max number of SABME sent before trying SABM. */ else if (strcasecmp(t, "MAXV22") == 0) { int n; t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing value for MAXV22.\n", line); continue; } n = atoi(t); if (n >= 0 && n <= AX25_N2_RETRY_MAX) { p_misc_config->maxv22 = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid MAXV22 number. Will use half of RETRY.\n", line); } } /* * V20 address [ address ... ] - Stations known to support only AX.25 v2.0. * When connecting to these, skip SABME and go right to SABM. * Possible to have multiple and they are cummulative. */ else if (strcasecmp(t, "V20") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing address(es) for V20.\n", line); continue; } while (t != NULL) { int const strict = 2; char call_no_ssid[AX25_MAX_ADDR_LEN]; int ssid, heard; if (ax25_parse_addr (AX25_DESTINATION, t, strict, call_no_ssid, &ssid, &heard)) { p_misc_config->v20_addrs = (char**)realloc (p_misc_config->v20_addrs, sizeof(char*) * (p_misc_config->v20_count + 1)); p_misc_config->v20_addrs[p_misc_config->v20_count++] = strdup(t); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid station address for V20 command.\n", line); // continue processing any others following. } t = split(NULL,0); } } /* * NOXID address [ address ... ] - Stations known not to understand XID. * After connecting to these (with v2.2 obviously), don't try using XID commmand. * AX.25 for Linux is the one known case so far. * Possible to have multiple and they are cummulative. */ else if (strcasecmp(t, "NOXID") == 0) { t = split(NULL,0); if (t == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Missing address(es) for NOXID.\n", line); continue; } while (t != NULL) { int const strict = 2; char call_no_ssid[AX25_MAX_ADDR_LEN]; int ssid, heard; if (ax25_parse_addr (AX25_DESTINATION, t, strict, call_no_ssid, &ssid, &heard)) { p_misc_config->noxid_addrs = (char**)realloc (p_misc_config->noxid_addrs, sizeof(char*) * (p_misc_config->noxid_count + 1)); p_misc_config->noxid_addrs[p_misc_config->noxid_count++] = strdup(t); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid station address for NOXID command.\n", line); // continue processing any others following. } t = split(NULL,0); } } /* * Invalid command. */ else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Unrecognized command '%s' on line %d.\n", t, line); } } fclose (fp); /* * A little error checking for option interactions. */ /* * Require that MYCALL be set when digipeating or IGating. * * Suggest that beaconing be enabled when digipeating. */ int i, j, k, b; for (i=0; ienabled[i][j]) { if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i); p_digi_config->enabled[i][j] = 0; } if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); p_digi_config->enabled[i][j] = 0; } b = 0; for (k=0; knum_beacons; k++) { if (p_misc_config->beacon[k].sendto_chan == j) b++; } if (b == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", j); // It's a recommendation, not a requirement. // Was there some good reason to turn it off in earlier version? //p_digi_config->enabled[i][j] = 0; } } /* Connected mode digipeating. */ if (p_cdigi_config->enabled[i][j]) { if ( strcmp(p_audio_config->achan[i].mycall, "") == 0 || strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before digipeating is allowed.\n", i); p_cdigi_config->enabled[i][j] = 0; } if ( strcmp(p_audio_config->achan[j].mycall, "") == 0 || strcmp(p_audio_config->achan[j].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[j].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before digipeating is allowed.\n", i); p_cdigi_config->enabled[i][j] = 0; } b = 0; for (k=0; knum_beacons; k++) { if (p_misc_config->beacon[k].sendto_chan == j) b++; } if (b == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Beaconing should be configured for channel %d when digipeating is enabled.\n", j); // It's a recommendation, not a requirement. } } } /* When IGate is enabled, all radio channels must have a callsign associated. */ if (strlen(p_igate_config->t2_login) > 0 && (p_audio_config->achan[i].medium == MEDIUM_RADIO || p_audio_config->achan[i].medium == MEDIUM_NETTNC)) { if (strcmp(p_audio_config->achan[i].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[i].mycall, "N0CALL") == 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for receive channel %d before Rx IGate is allowed.\n", i); strlcpy (p_igate_config->t2_login, "", sizeof(p_igate_config->t2_login)); } // Currently we can have only one transmit channel. // This might be generalized someday to allow more. if (p_igate_config->tx_chan >= 0 && ( strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "") == 0 || strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[p_igate_config->tx_chan].mycall, "N0CALL") == 0)) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for transmit channel %d before Tx IGate is allowed.\n", i); p_igate_config->tx_chan = -1; } } } // Apply default IS>RF IGate filter if none specified. New in 1.4. // This will handle eventual case of multiple transmit channels. if (strlen(p_igate_config->t2_login) > 0) { for (j=0; jachan[j].medium == MEDIUM_RADIO || p_audio_config->achan[j].medium == MEDIUM_NETTNC) { if (p_digi_config->filter_str[MAX_CHANS][j] == NULL) { p_digi_config->filter_str[MAX_CHANS][j] = strdup("i/60"); } } } } // Terrible hack. But what can we do? if (p_misc_config->maxv22 < 0) { p_misc_config->maxv22 = p_misc_config->retry / 3; } } /* end config_init */ /* * Parse the PBEACON or OBEACON options. * Returns 1 for success, 0 for serious error. */ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_s *p_audio_config) { char *t; char temp_symbol[100]; int ok; char zone[8]; double easting = G_UNKNOWN; double northing = G_UNKNOWN; strlcpy (temp_symbol, "", sizeof(temp_symbol)); strlcpy (zone, "", sizeof(zone)); b->sendto_type = SENDTO_XMIT; b->sendto_chan = 0; b->delay = 60; b->slot = G_UNKNOWN; b->every = 600; //b->delay = 6; // temp test. //b->every = 3600; b->lat = G_UNKNOWN; b->lon = G_UNKNOWN; b->ambiguity = 0; b->alt_m = G_UNKNOWN; b->symtab = '/'; b->symbol = '-'; /* house */ b->freq = G_UNKNOWN; b->tone = G_UNKNOWN; b->offset = G_UNKNOWN; while ((t = split(NULL,0)) != NULL) { char keyword[20]; char value[200]; char *e; char *p; e = strchr(t, '='); if (e == NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: No = found in, %s, on line %d.\n", t, line); return (0); } *e = '\0'; strlcpy (keyword, t, sizeof(keyword)); strlcpy (value, e+1, sizeof(value)); if (strcasecmp(keyword, "DELAY") == 0) { b->delay = parse_interval(value,line); } else if (strcasecmp(keyword, "SLOT") == 0) { int n = parse_interval(value,line); if ( n < 1 || n > 3600) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Beacon time slot, %d, must be in range of 1 to 3600 seconds.\n", line, n); continue; } b->slot = n; } else if (strcasecmp(keyword, "EVERY") == 0) { b->every = parse_interval(value,line); } else if (strcasecmp(keyword, "SENDTO") == 0) { if (value[0] == 'i' || value[0] == 'I') { b->sendto_type = SENDTO_IGATE; b->sendto_chan = 0; } else if (value[0] == 'r' || value[0] == 'R') { int n = atoi(value+1); if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Simulated receive on channel %d is not valid.\n", line, n); continue; } b->sendto_type = SENDTO_RECV; b->sendto_chan = n; } else if (value[0] == 't' || value[0] == 'T' || value[0] == 'x' || value[0] == 'X') { int n = atoi(value+1); if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; } b->sendto_type = SENDTO_XMIT; b->sendto_chan = n; } else { int n = atoi(value); if ( n < 0 || n >= MAX_CHANS || p_audio_config->achan[n].medium == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, n); continue; } b->sendto_type = SENDTO_XMIT; b->sendto_chan = n; } } else if (strcasecmp(keyword, "DEST") == 0) { b->dest = strdup(value); for (p = b->dest; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } } if (strlen(b->dest) > 9) { b->dest[9] = '\0'; } } else if (strcasecmp(keyword, "VIA") == 0) { #if 1 // proper checking if (check_via_path(value) >= 0) { b->via = strdup(value); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: invalid via path.\n", line); } #else // previously b->via = strdup(value); for (p = b->via; *p != '\0'; p++) { if (islower(*p)) { *p = toupper(*p); /* silently force upper case. */ } } #endif } else if (strcasecmp(keyword, "INFO") == 0) { b->custom_info = strdup(value); } else if (strcasecmp(keyword, "INFOCMD") == 0) { b->custom_infocmd = strdup(value); } else if (strcasecmp(keyword, "OBJNAME") == 0) { strlcpy(b->objname, value, sizeof(b->objname)); } else if (strcasecmp(keyword, "LAT") == 0) { b->lat = parse_ll (value, LAT, line); } else if (strcasecmp(keyword, "LONG") == 0 || strcasecmp(keyword, "LON") == 0) { b->lon = parse_ll (value, LON, line); } else if (strcasecmp(keyword, "AMBIGUITY") == 0 || strcasecmp(keyword, "AMBIG") == 0) { int n = atoi(value); if (n >= 0 && n <= 4) { b->ambiguity = n; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Location ambiguity, on line %d, must be in range of 0 to 4.\n", line); } } else if (strcasecmp(keyword, "ALT") == 0 || strcasecmp(keyword, "ALTITUDE") == 0) { b->alt_m = atof(value); } else if (strcasecmp(keyword, "ZONE") == 0) { strlcpy(zone, value, sizeof(zone)); } else if (strcasecmp(keyword, "EAST") == 0 || strcasecmp(keyword, "EASTING") == 0) { easting = atof(value); } else if (strcasecmp(keyword, "NORTH") == 0 || strcasecmp(keyword, "NORTHING") == 0) { northing = atof(value); } else if (strcasecmp(keyword, "SYMBOL") == 0) { /* Defer processing in case overlay appears later. */ strlcpy (temp_symbol, value, sizeof(temp_symbol)); } else if (strcasecmp(keyword, "OVERLAY") == 0) { if (strlen(value) == 1 && (isupper(value[0]) || isdigit(value[0]))) { b->symtab = value[0]; } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: Overlay must be one character in range of 0-9 or A-Z, upper case only, on line %d.\n", line); } } else if (strcasecmp(keyword, "POWER") == 0) { b->power = atoi(value); } else if (strcasecmp(keyword, "HEIGHT") == 0) { b->height = atoi(value); } else if (strcasecmp(keyword, "GAIN") == 0) { b->gain = atoi(value); } else if (strcasecmp(keyword, "DIR") == 0 || strcasecmp(keyword, "DIRECTION") == 0) { strlcpy(b->dir, value, sizeof(b->dir)); } else if (strcasecmp(keyword, "FREQ") == 0) { b->freq = atof(value); } else if (strcasecmp(keyword, "TONE") == 0) { b->tone = atof(value); } else if (strcasecmp(keyword, "OFFSET") == 0 || strcasecmp(keyword, "OFF") == 0) { b->offset = atof(value); } else if (strcasecmp(keyword, "COMMENT") == 0) { b->comment = strdup(value); } else if (strcasecmp(keyword, "COMMENTCMD") == 0) { b->commentcmd = strdup(value); } else if (strcasecmp(keyword, "COMPRESS") == 0 || strcasecmp(keyword, "COMPRESSED") == 0) { b->compress = atoi(value); } else if (strcasecmp(keyword, "MESSAGING") == 0) { b->messaging = atoi(value); } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Invalid option keyword, %s.\n", line, keyword); return (0); } } if (b->custom_info != NULL && b->custom_infocmd != NULL) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Can't use both INFO and INFOCMD at the same time.\n", line); } if (b->compress && b->ambiguity != 0) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Position ambiguity can't be used with compressed location format.\n", line); b->ambiguity = 0; } /* * Convert UTM coordintes to lat / long. */ if (strlen(zone) > 0 || easting != G_UNKNOWN || northing != G_UNKNOWN) { if (strlen(zone) > 0 && easting != G_UNKNOWN && northing != G_UNKNOWN) { long lzone; char latband, hemi; long lerr; double dlat, dlon; lzone = parse_utm_zone (zone, &latband, &hemi); lerr = Convert_UTM_To_Geodetic(lzone, hemi, easting, northing, &dlat, &dlon); if (lerr == 0) { b->lat = R2D(dlat); b->lon = R2D(dlon); } else { char message [300]; utm_error_string (lerr, message); text_color_set(DW_COLOR_ERROR); dw_printf ("Line %d: Invalid UTM location: \n%s\n", line, message); } } else { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: When any of ZONE, EASTING, NORTHING specified, they must all be specified.\n", line); } } /* * Process symbol now that we have any later overlay. */ if (strlen(temp_symbol) > 0) { if (strlen(temp_symbol) == 2 && (temp_symbol[0] == '/' || temp_symbol[0] == '\\' || isupper(temp_symbol[0]) || isdigit(temp_symbol[0])) && temp_symbol[1] >= '!' && temp_symbol[1] <= '~') { /* Explicit table and symbol. */ if (isupper(b->symtab) || isdigit(b->symtab)) { b->symbol = temp_symbol[1]; } else { b->symtab = temp_symbol[0]; b->symbol = temp_symbol[1]; } } else { /* Try to look up by description. */ ok = symbols_code_from_description (b->symtab, temp_symbol, &(b->symtab), &(b->symbol)); if (!ok) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Could not find symbol matching %s.\n", line, temp_symbol); } } } /* Check is here because could be using default channel when SENDTO= is not specified. */ if (b->sendto_type == SENDTO_XMIT) { if ( b->sendto_chan < 0 || b->sendto_chan >= MAX_CHANS || p_audio_config->achan[b->sendto_chan].medium == MEDIUM_NONE) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file, line %d: Send to channel %d is not valid.\n", line, b->sendto_chan); return (0); } if ( strcmp(p_audio_config->achan[b->sendto_chan].mycall, "") == 0 || strcmp(p_audio_config->achan[b->sendto_chan].mycall, "NOCALL") == 0 || strcmp(p_audio_config->achan[b->sendto_chan].mycall, "N0CALL") == 0 ) { text_color_set(DW_COLOR_ERROR); dw_printf ("Config file: MYCALL must be set for channel %d before beaconing is allowed.\n", b->sendto_chan); return (0); } } return (1); } /* end config.c */