diff --git a/CHANGES.md b/CHANGES.md index cab5571..7f1a73f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -9,6 +9,9 @@ - New experimental demodulator. More details later. +- APRStt enhancements including new 5 digit callsign suffix abbreviation and +position ambiguity for latitude and longitude in object reports. + ### Bugs Fixed: ### - "INTERNAL ERROR: dlq_append NULL packet pointer." when using PASSALL. diff --git a/Makefile.win b/Makefile.win index 133392e..5fc7281 100644 --- a/Makefile.win +++ b/Makefile.win @@ -127,7 +127,7 @@ tocalls-symbols : # Separate application to decode raw data. -decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.o regex.a misc.a geotranz.a +decode_aprs : decode_aprs.c dwgpsnmea.o dwgps.o serial_port.o symbols.o ax25_pad.o textcolor.o fcs_calc.o latlong.o log.o telemetry.o tt_text.c regex.a misc.a geotranz.a $(CC) $(CFLAGS) -DDECAMAIN -o decode_aprs $^ diff --git a/aclients.c b/aclients.c index 226e00f..a579285 100644 --- a/aclients.c +++ b/aclients.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013 John Langner, WB2OSZ +// Copyright (C) 2013, 2015 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 @@ -28,11 +28,20 @@ * Description: Establish connection with multiple servers and * compare results side by side. * - * Usage: aclients 8000=AGWPE 8002=DireWolf COM1=D710A + * Usage: aclients port1=name1 port2=name2 ... + * + * Example: aclients 8000=AGWPE 192.168.1.64:8002=DireWolf COM1=D710A * * This will connect to multiple physical or virtual * TNCs, read packets from them, and display results. * + * Each port can have the following forms: + * + * * host-name:tcp-port + * * ip-addr:tcp-port + * * tcp-port + * * serial port name (e.g. COM1, /dev/ttyS0) + * *---------------------------------------------------------------*/ @@ -40,7 +49,6 @@ /* * Native Windows: Use the Winsock interface. * Linux: Use the BSD socket interface. - * Cygwin: Can use either one. */ @@ -156,14 +164,7 @@ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t S * Purpose: Start up multiple client threads listening to different * TNCs. Print packets. Tally up statistics. * - * Usage: aclients 8000=AGWPE 8002=DireWolf COM1=D710A - * - * Each command line argument is TCP port number or a - * serial port name. Follow by = and a text description - * of what is connected. - * - * For now, everything is assumed to be on localhost. - * Maybe someday we might recognize host:port=description. + * Usage: Described above. * *---------------------------------------------------------------*/ @@ -173,9 +174,17 @@ static char * ia_to_text (int Family, void * pAddr, char * pStringBuf, size_t S static int num_clients; -static char hostname[MAX_CLIENTS][50]; -static char port[MAX_CLIENTS][30]; -static char description[MAX_CLIENTS][50]; +static char hostname[MAX_CLIENTS][50]; /* DNS host name or IPv4 address. */ + /* Some of the code is there for IPv6 but */ + /* needs more work. */ + /* Defaults to "localhost" if not specified. */ + +static char port[MAX_CLIENTS][30]; /* If it begins with a digit, it is considered */ + /* a TCP port number at the hostname. */ + /* Otherwise, we treat it as a serial port name. */ + +static char description[MAX_CLIENTS][50]; /* Name used in the output. */ + #if __WIN32__ static HANDLE client_th[MAX_CLIENTS]; @@ -222,7 +231,9 @@ int main (int argc, char *argv[]) for (j=0; j @@ -101,6 +101,7 @@ static int parse_fields (char *msg); static int parse_callsign (char *e); static int parse_object_name (char *e); static int parse_symbol (char *e); +static int parse_aprstt3_call (char *e); static int parse_location (char *e); static int parse_comment (char *e); static int expand_macro (char *e); @@ -324,6 +325,7 @@ static double m_latitude; // Set to G_UNKNOWN if not defined. static int m_ambiguity; static char m_comment[200]; static char m_freq[12]; +static char m_ctcss[8]; static char m_mic_e; static char m_dao[6]; static int m_ssid; // Default 12 for APRStt user. @@ -351,14 +353,15 @@ void aprs_tt_sequence (int chan, char *msg) * The parse functions will fill these in. */ strlcpy (m_callsign, "", sizeof(m_callsign)); - m_symtab_or_overlay = '\\'; - m_symbol_code = 'A'; + m_symtab_or_overlay = APRSTT_DEFAULT_SYMTAB; + m_symbol_code = APRSTT_DEFAULT_SYMBOL; strlcpy (m_loc_text, "", sizeof(m_loc_text)); m_longitude = G_UNKNOWN; m_latitude = G_UNKNOWN; m_ambiguity = 0; strlcpy (m_comment, "", sizeof(m_comment)); strlcpy (m_freq, "", sizeof(m_freq)); + strlcpy (m_ctcss, "", sizeof(m_ctcss)); m_mic_e = ' '; strlcpy (m_dao, "!T !", sizeof(m_dao)); /* start out unknown */ m_ssid = 12; @@ -370,8 +373,8 @@ void aprs_tt_sequence (int chan, char *msg) #if defined(DEBUG) text_color_set(DW_COLOR_DEBUG); - dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n", - m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_comment, m_latitude, m_longitude, m_dao); + dw_printf ("callsign=\"%s\", ssid=%d, symbol=\"%c%c\", freq=\"%s\", ctcss=\"%s\", comment=\"%s\", lat=%.4f, lon=%.4f, dao=\"%s\"\n", + m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_freq, m_ctcss, m_comment, m_latitude, m_longitude, m_dao); #endif #if TT_MAIN @@ -386,20 +389,22 @@ void aprs_tt_sequence (int chan, char *msg) err = tt_user_heard (m_callsign, m_ssid, m_symtab_or_overlay, m_symbol_code, m_loc_text, m_latitude, m_longitude, m_ambiguity, - m_freq, m_comment, m_mic_e, m_dao); + m_freq, m_ctcss, m_comment, m_mic_e, m_dao); } /* * If a command / script was supplied, run it now. * This can do additional processing and provide a custom audible response. - * This is done even for the error case. + * This is done only for the success case. + * It might be useful to run it for error cases as well but we currently + * don't pass in the success / failure code to know the difference. */ char script_response[1000]; strlcpy (script_response, "", sizeof(script_response)); - if (strlen(tt_config.ttcmd) > 0) { + if (err == 0 && strlen(tt_config.ttcmd) > 0) { dw_run_cmd (tt_config.ttcmd, 1, script_response, sizeof(script_response)); @@ -408,7 +413,9 @@ void aprs_tt_sequence (int chan, char *msg) /* * Send response to user by constructing packet with SPEECH or MORSE as destination. * Source shouldn't matter because it doesn't get transmitted as AX.25 frame. - * Use high priority queue for consistent timing. + * Use high priority queue for consistent timing. + * + * Anything from script, above, will override other predefined responses. */ char audible_response[1000]; @@ -484,25 +491,26 @@ static int parse_fields (char *msg) case 'A': switch (e[1]) { - case 'A': + + case 'A': /* AA object-name */ err = parse_object_name (e); if (err != 0) return (err); break; - case 'B': + + case 'B': /* AB symbol */ err = parse_symbol (e); if (err != 0) return (err); break; - case 'C': - /* - * New in 1.2: test for 10 digit callsign. - */ - if (tt_call10_to_text(e+2,1,stemp) == 0) { - strlcpy(m_callsign, stemp, sizeof(m_callsign)); - } - // TODO1.3: else return (?) + + case 'C': /* AC new-style-callsign */ + + err = parse_aprstt3_call (e); + if (err != 0) return (err); break; - default: + + default: /* Traditional style call or suffix */ err = parse_callsign (e); + if (err != 0) return (err); break; } break; @@ -674,7 +682,7 @@ static int expand_macro (char *e) * * Name: parse_callsign * - * Purpose: Extract callsign or object name from touch tone message. + * Purpose: Extract traditional format callsign or object name from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. @@ -685,6 +693,10 @@ static int expand_macro (char *e) * m_symtab_or_overlay - Set to 0-9 or A-Z if specified. * * m_symbol_code - Always set to 'A'. + * NO! This should be applied only if we + * have the default value at this point. + * The symbol might have been explicitly + * set already and we don't want to overwrite that. * * Returns: 0 for success or one of the TT_ERROR_... codes. * @@ -734,7 +746,6 @@ static int checksum_not_ok (char *str, int len, char found) static int parse_callsign (char *e) { int len; - //int c_length; char tttemp[40], stemp[30]; assert (*e == 'A'); @@ -772,11 +783,11 @@ static int parse_callsign (char *e) tttemp[1] = e[len-2]; tttemp[2] = '\0'; tt_two_key_to_text (tttemp, 0, stemp); - m_symbol_code = 'A'; + m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = stemp[0]; } else { - m_symbol_code = 'A'; + m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = e[len-2]; } return (0); @@ -793,7 +804,6 @@ static int parse_callsign (char *e) if (cs_err != 0) { return (cs_err); } - if (isupper(e[len-2])) { strncpy (tttemp, e+1, len-4); @@ -804,7 +814,7 @@ static int parse_callsign (char *e) tttemp[1] = e[len-2]; tttemp[2] = '\0'; tt_two_key_to_text (tttemp, 0, stemp); - m_symbol_code = 'A'; + m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = stemp[0]; } else { @@ -812,7 +822,7 @@ static int parse_callsign (char *e) tttemp[len-3] = '\0'; tt_two_key_to_text (tttemp, 0, m_callsign); - m_symbol_code = 'A'; + m_symbol_code = APRSTT_DEFAULT_SYMBOL; m_symtab_or_overlay = e[len-2]; } return (0); @@ -823,11 +833,12 @@ static int parse_callsign (char *e) return (TT_ERROR_INVALID_CALL); } + /*------------------------------------------------------------------ * * Name: parse_object_name * - * Purpose: Extract object name from touch tone message. + * Purpose: Extract object name from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. @@ -883,7 +894,7 @@ static int parse_object_name (char *e) * * Name: parse_symbol * - * Purpose: Extract symbol from touch tone message. + * Purpose: Extract symbol from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. @@ -973,11 +984,89 @@ static int parse_symbol (char *e) } /* end parse_oject_name */ +/*------------------------------------------------------------------ + * + * Name: parse_aprstt3_call + * + * Purpose: Extract QIKcom-2 / APRStt 3 ten digit call or five digit suffix. + * + * Inputs: e - An "entry" extracted from a complete + * APRStt messsage. + * In this case, it should start with "AC". + * + * Outputs: m_callsign + * + * Returns: 0 for success or one of the TT_ERROR_... codes. + * + * Description: We recognize 3 different formats: + * + * ACxxxxxxxxxx - 10 digit full callsign. + * + * ACxxxxx - 5 digit suffix. If we can find a corresponding full + * callsign, that will be substituted. + * Error condition is returned if we can't find one. + * + *----------------------------------------------------------------*/ + +static int parse_aprstt3_call (char *e) +{ + + assert (e[0] == 'A'); + assert (e[1] == 'C'); + + if (strlen(e) == 2+10) { + char call[12]; + + if (tt_call10_to_text(e+2,1,call) == 0) { + strlcpy(m_callsign, call, sizeof(m_callsign)); + } + else { + return (TT_ERROR_INVALID_CALL); /* Could not convert to text */ + } + } + else if (strlen(e) == 2+5) { + char suffix[8]; + if (tt_call5_suffix_to_text(e+2,1,suffix) == 0) { + +#if TT_MAIN + /* For unit test, use suffix rather than trying lookup. */ + strlcpy (m_callsign, suffix, sizeof(m_callsign)); +#else + char call[12]; + + /* In normal operation, try to find full callsign for the suffix received. */ + + if (tt_3char_suffix_search (suffix, call) >= 0) { + text_color_set(DW_COLOR_INFO); + dw_printf ("Suffix \"%s\" was converted to full callsign \"%s\"\n", suffix, call); + + strlcpy(m_callsign, call, sizeof(m_callsign)); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't find full callsign for suffix \"%s\"\n", suffix); + return (TT_ERROR_SUFFIX_NO_CALL); /* Don't know this user. */ + } +#endif + } + else { + return (TT_ERROR_INVALID_CALL); /* Could not convert to text */ + } + } + else { + return (TT_ERROR_INVALID_CALL); /* Invalid length, not 2+ (10 ir 5) */ + } + + return (0); + +} /* end parse_aprstt3_call */ + + /*------------------------------------------------------------------ * * Name: parse_location * - * Purpose: Extract location from touch tone message. + * Purpose: Extract location from touch tone sequence. * * Inputs: e - An "entry" extracted from a complete * APRStt messsage. @@ -985,7 +1074,16 @@ static int parse_symbol (char *e) * * Outputs: m_latitude * m_longitude - * m_dao + * + * m_dao It should previously be "!T !" to mean unknown or none. + * We generally take the first two tones of the field. + * For example, "!TB5!" for the standard bearing & range. + * The point type is an exception where we use "!Tn !" for + * one of ten positions or "!Tnn" for one of a hundred. + * If this ever changes, be sure to update corresponding + * section in process_comment() in decode_aprs.c + * + * m_ambiguity * * Returns: 0 for success or one of the TT_ERROR_... codes. * @@ -1001,6 +1099,10 @@ static int parse_symbol (char *e) * * utm * * usng / mgrs * + * Position ambiguity is also handled here. + * Latitude, Longitude, and DAO should not be touched in this case. + * We only record a position ambiguity value. + * *----------------------------------------------------------------*/ /* Average radius of earth in meters. */ @@ -1009,7 +1111,6 @@ static int parse_symbol (char *e) static int parse_location (char *e) { - //int len; int ipat; char xstr[VALSTRSIZE], ystr[VALSTRSIZE], zstr[VALSTRSIZE], bstr[VALSTRSIZE], dstr[VALSTRSIZE]; double x, y, dist, bearing; @@ -1023,14 +1124,6 @@ static int parse_location (char *e) assert (*e == 'B'); - m_dao[2] = e[0]; - m_dao[3] = e[1]; /* Type of location. e.g. !TB6! */ - /* Will be changed by point types. */ - - /* If this ever changes, be sure to update corresponding */ - /* section in process_comment() in decode_aprs.c */ - - //len = strlen(e); ipat = find_ttloc_match (e, xstr, ystr, zstr, bstr, dstr, VALSTRSIZE); if (ipat >= 0) { @@ -1047,6 +1140,9 @@ static int parse_location (char *e) /* It's not hardwired to always be B0n or B9nn. */ /* This is a pretty good approximation. */ + m_dao[2] = e[0]; + m_dao[3] = e[1]; + if (strlen(e) == 3) { /* probably B0n --> !Tn ! */ m_dao[2] = e[2]; m_dao[3] = ' '; @@ -1083,6 +1179,10 @@ static int parse_location (char *e) m_longitude = R2D(lon0 + atan2(sin(bearing) * sin(dist/R) * cos(lat0), cos(dist/R) - sin(lat0) * sin(D2R(m_latitude)))); + + m_dao[2] = e[0]; + m_dao[3] = e[1]; + break; case TTLOC_GRID: @@ -1108,6 +1208,9 @@ static int parse_location (char *e) x = atof(xstr); m_longitude = lon0 + x * (lon9-lon0) / (pow(10., strlen(xstr)) - 1.); + m_dao[2] = e[0]; + m_dao[3] = e[1]; + break; case TTLOC_UTM: @@ -1158,6 +1261,9 @@ static int parse_location (char *e) dw_printf ("Conversion from UTM failed:\n%s\n\n", message); } + m_dao[2] = e[0]; + m_dao[3] = e[1]; + break; @@ -1207,6 +1313,10 @@ static int parse_location (char *e) mgrs_error_string (lerr, message); dw_printf ("Conversion from MGRS/USNG failed:\n%s\n\n", message); } + + m_dao[2] = e[0]; + m_dao[3] = e[1]; + break; case TTLOC_MHEAD: @@ -1235,6 +1345,10 @@ static int parse_location (char *e) ll_from_grid_square (mh, &m_latitude, &m_longitude); } + + m_dao[2] = e[0]; + m_dao[3] = e[1]; + break; case TTLOC_SATSQ: @@ -1253,6 +1367,10 @@ static int parse_location (char *e) ll_from_grid_square (mh, &m_latitude, &m_longitude); } + + m_dao[2] = e[0]; + m_dao[3] = e[1]; + break; case TTLOC_AMBIG: @@ -1267,7 +1385,6 @@ static int parse_location (char *e) break; - default: assert (0); } @@ -1462,6 +1579,9 @@ static int find_ttloc_match (char *e, char *xstr, char *ystr, char *zstr, char * * * Cnnnnnn - Six digit frequency reformatted as nnn.nnnMHz * + * Cnnn - Three digit are for CTCSS tone. Use only integer part + * and leading 0 if necessary to make exactly 3 digits. + * * Cttt...tttt - General comment in Multi-press encoding. * * CAttt...tttt - New enhanced comment format that can handle all ASCII characters. @@ -1501,6 +1621,11 @@ static int parse_comment (char *e) return (0); } + if (len == 4 && isdigit(e[1]) && isdigit(e[2]) && isdigit(e[3])) { + strlcpy (m_ctcss, e+1, sizeof(m_ctcss)); + return (0); + } + tt_multipress_to_text (e+1, 0, m_comment); return (0); } @@ -1705,9 +1830,6 @@ int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz) *----------------------------------------------------------------*/ -// TODO: add this to "make check" - - #if TT_MAIN /* @@ -1715,6 +1837,9 @@ int dw_run_cmd (char *cmd, int oneline, char *result, size_t resultsiz) * It does not maintain any history so abbreviation will not invoke previous full call. */ +/* Some examples are derived from http://www.aprs.org/aprstt/aprstt-coding24.txt */ + + static const struct { char *toneseq; /* Tone sequence in. */ @@ -1728,16 +1853,17 @@ static const struct { char *dao; } testcases[] = { - /* Callsigns & abbreviations. */ + /* Callsigns & abbreviations, traditional */ { "A9A2B42A7A7C71#", "WB4APR", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB4APR/7 */ { "A27773#", "277", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* abbreviated form */ - /* Example in http://www.aprs.org/aprstt/aprstt-coding24.txt has a bad checksum! */ - /* Bad checksum for "2777". Expected 3 but received 6. */ + + /* Intentionally wrong - Has 6 for checksum when it should be 3. */ { "A27776#", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Expect error message. */ - /* Bad checksum for "2A7A7C7". E xpected 5 but received 1. */ + /* Example in spec is wrong. checksum should be 5 in this case. */ { "A2A7A7C71#", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Spelled suffix, overlay, checksum */ + { "A2A7A7C75#", "APR", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Spelled suffix, overlay, checksum */ { "A27773#", "277", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Suffix digits, overlay, checksum */ { "A9A2B26C7D9D71#", "WB2OSZ", "12", "7A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* WB2OSZ/7 numeric overlay */ @@ -1748,6 +1874,11 @@ static const struct { { "A277#", "277", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, /* Tactical call "277" no overlay and no checksum */ + /* QIKcom-2 style 10 digit call & 5 digit suffix */ + + { "AC9242771558#", "WB4APR", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, + { "AC27722#", "APR", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, + /* Locations */ { "B01*A67979#", "679", "12", "7A", "", "", "12.2500", "56.2500", "!T1 !" }, @@ -1761,6 +1892,8 @@ static const struct { { "B21234*A67979#", "679", "12", "7A", "", "", "12.3400", "56.1200", "!TB2!" }, { "B533686*A67979#", "679", "12", "7A", "", "", "37.9222", "81.1143", "!TB5!" }, +// TODO: should test other coordinate systems. + /* Comments */ { "C1", "", "12", "\\A", "", "", "-999999.0000", "-999999.0000", "!T !" }, diff --git a/aprs_tt.h b/aprs_tt.h index 3861a16..8cc845d 100644 --- a/aprs_tt.h +++ b/aprs_tt.h @@ -80,8 +80,9 @@ struct ttloc_s { #define TT_ERROR_NO_CALL 9 /* No call or object name included. */ #define TT_ERROR_INVALID_MHEAD 10 /* Invalid Maidenhead Locator. */ #define TT_ERROR_INVALID_SATSQ 11 /* Satellite square must be 4 digits. */ +#define TT_ERROR_SUFFIX_NO_CALL 12 /* No known callsign for suffix. */ -#define TT_ERROR_MAXP1 12 /* Number of items above. i.e. Last number plus 1. */ +#define TT_ERROR_MAXP1 13 /* Number of items above. i.e. Last number plus 1. */ #if CONFIG_C /* Is this being included from config.c? */ @@ -100,7 +101,8 @@ static const char *tt_msg_id[TT_ERROR_MAXP1] = { "INVALID_LOC", "NO_CALL", "INVALID_MHEAD", - "INVALID_SATSQ" + "INVALID_SATSQ", + "SUFFIX_NO_CALL" }; #endif @@ -173,6 +175,10 @@ void aprs_tt_button (int chan, char button); #define APRSTT_LOC_DESC_LEN 32 /* Need at least 26 */ +#define APRSTT_DEFAULT_SYMTAB '\\' +#define APRSTT_DEFAULT_SYMBOL 'A' + + void aprs_tt_dao_to_desc (char *dao, char *str); void aprs_tt_sequence (int chan, char *msg); diff --git a/config.c b/config.c index 48e6b36..799b0de 100644 --- a/config.c +++ b/config.c @@ -4021,7 +4021,9 @@ static int beacon_options(char *cmd, struct beacon_s *b, int line, struct audio_ 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) { diff --git a/doc/APRStt-Implementation-Notes.pdf b/doc/APRStt-Implementation-Notes.pdf index eefd7d6..10014e6 100644 Binary files a/doc/APRStt-Implementation-Notes.pdf and b/doc/APRStt-Implementation-Notes.pdf differ diff --git a/doc/APRStt-Listening-Example.pdf b/doc/APRStt-Listening-Example.pdf new file mode 100644 index 0000000..84e07c4 Binary files /dev/null and b/doc/APRStt-Listening-Example.pdf differ diff --git a/doc/README.md b/doc/README.md index 46eaadd..a88ac33 100644 --- a/doc/README.md +++ b/doc/README.md @@ -28,6 +28,11 @@ These dive into more detail for specialized topics or typical usage scenarios. This example illustrates how APRStt can be integrated with other applications such as SARTrack, APRSISCE/32, YAAC, or Xastir. +- [APRStt Listening Example](APRStt-Listening-Example.pdf) + + WB4APR described a useful application for the [QIKCOM-2 Satallite Transponder](http://www.tapr.org/pipermail/aprssig/2015-November/045035.html). + + Don’t have your own QIKCOM-2 Satellite Transponder? No Problem. You can do the same thing with an ordinary computer and the APRStt gateway built into Dire Wolf. Here’s how. - [Raspberry Pi SDR IGate](Raspberry-Pi-SDR-IGate.pdf) diff --git a/doc/User-Guide.pdf b/doc/User-Guide.pdf index 3259f14..a684d71 100644 Binary files a/doc/User-Guide.pdf and b/doc/User-Guide.pdf differ diff --git a/encode_aprs.c b/encode_aprs.c index 3458980..67476f4 100644 --- a/encode_aprs.c +++ b/encode_aprs.c @@ -411,62 +411,57 @@ static int cse_spd_data_extension (int course, int speed, char *presult) * * Offset must always be preceded by tone. * + * Resulting formats are all fixed width and have a trailing space: + * + * "999.999MHz " + * "T999 " + * "+999 " (10 kHz units) + * + * Reference: http://www.aprs.org/info/freqspec.txt + * *----------------------------------------------------------------*/ -typedef struct freq_s { - char f[7]; /* format 999.999 */ - char mhz[3]; - char space; - } freq_t; - -typedef struct to_s { - char T; - char ttt[3]; /* format 999 (drop fraction) or 'off'. */ - char space1; - char oooo[4]; /* leading sign, 3 digits, tens of KHz. */ - char space2; - } to_t; - - static int frequency_spec (float freq, float tone, float offset, char *presult) { - int result_len = 0; + int result_size = 24; // TODO: add as parameter. + + *presult = '\0'; - if (freq != 0) { - freq_t *f = (freq_t*)presult; - char stemp[12]; /* Frequency should be exactly 7 characters: 999.999 */ - /* Offset shouldbe exactly 4 characters: +999 */ + if (freq > 0) { + char stemp[16]; /* TODO: Should use letters for > 999.999. */ - snprintf (stemp, sizeof(stemp), "%07.3f", freq); - memcpy (f->f, stemp, 7); - memcpy (f->mhz, "MHz", 3); - f->space = ' '; - result_len = sizeof (freq_t); + /* For now, just be sure we have proper field width. */ + + if (freq > 999.999) freq = 999.999; + + snprintf (stemp, sizeof(stemp), "%07.3fMHz ", freq); + + strlcpy (presult, stemp, result_size); } - - if (tone != 0 || offset != 0) { - to_t *to = (to_t*)(presult + result_len); + + if (tone != G_UNKNOWN) { char stemp[12]; - to->T = 'T'; if (tone == 0) { - memcpy(to->ttt, "off", 3); + strlcpy (stemp, "Toff ", sizeof (stemp)); } else { - snprintf (stemp, sizeof(stemp), "%03d", (int)tone); - memcpy (to->ttt, stemp, 3); + snprintf (stemp, sizeof(stemp), "T%03d ", (int)tone); } - to->space1 = ' '; - snprintf (stemp, sizeof(stemp), "%+04d", (int)round(offset * 100)); - memcpy (to->oooo, stemp, 4); - to->space2 = ' '; - result_len += sizeof (to_t); + strlcat (presult, stemp, result_size); } - return (result_len); + if (offset != G_UNKNOWN) { + char stemp[12]; + + snprintf (stemp, sizeof(stemp), "%+04d ", (int)round(offset * 100)); + strlcat (presult, stemp, result_size); + } + + return (strlen(presult)); } @@ -800,13 +795,23 @@ int main (int argc, char *argv[]) dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&PHG7368") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } -/* with freq. */ +/* with freq & tone. minus offset, no offset, explict simplex. */ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, -0.6, NULL, result, sizeof(result)); dw_printf ("%s\n", result); if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 -060 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', + 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, G_UNKNOWN, NULL, result, sizeof(result)); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + + encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', + 0, 0, 0, NULL, G_UNKNOWN, 0, 146.955, 74.4, 0, NULL, result, sizeof(result)); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&146.955MHz T074 +000 ") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + /* with course/speed, freq, and comment! */ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', @@ -817,12 +822,17 @@ int main (int argc, char *argv[]) /* Course speed, no tone, + offset */ encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, G_UNKNOWN, 'D', '&', - 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result, sizeof(result)); + 0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); - if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz Toff +060 River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } /* Course speed, no tone, + offset + altitude */ + encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&', + 0, 0, 0, NULL, 180, 55, 146.955, G_UNKNOWN, 0.6, "River flooding", result, sizeof(result)); + dw_printf ("%s\n", result); + if (strcmp(result, "!4234.61ND07126.47W&180/055146.955MHz +060 /A=012345River flooding") != 0) { dw_printf ("ERROR! line %d\n", __LINE__); errors++; } + encode_position (0, 0, 42+34.61/60, -(71+26.47/60), 0, 12345, 'D', '&', 0, 0, 0, NULL, 180, 55, 146.955, 0, 0.6, "River flooding", result, sizeof(result)); dw_printf ("%s\n", result); diff --git a/generic.conf b/generic.conf index 14ad98f..223007f 100644 --- a/generic.conf +++ b/generic.conf @@ -569,4 +569,5 @@ C#TTERR INVALID_SYMBOL SPEECH Invalid symbol. C#TTERR INVALID_LOC SPEECH Invalid location. C#TTERR NO_CALL SPEECH No call or object name. C#TTERR SATSQ SPEECH Satellite square must be 4 digits. +C#TTERR SUFFIX_NO_CALL SPEECH Send full call before using suffix. C \ No newline at end of file diff --git a/man1/aclients.1 b/man1/aclients.1 index 38762a1..578c1f3 100644 --- a/man1/aclients.1 +++ b/man1/aclients.1 @@ -9,12 +9,13 @@ aclients \- Test program for side-by-side TNC performance comparison. .I tnc ... .RS .P -Each \fItnc\fR reference is either a serial port or TCP network port followed by = and a comment. +Each \fItnc\fR reference is a port, =, and a description. .P .RE .SH DESCRIPTION \fBaclients\fR is used to compare how well different TNCs decode AS.25 frames. +The port can be a serial port name, host_name:tcp_port, ip_addr:port, or simply tcp_port. .P @@ -26,7 +27,7 @@ None. .SH EXAMPLES -.B aclients /dev/ttyS0=KPC3+ /dev/ttyUSB0=D710A 8000=DireWolf +.B aclients /dev/ttyS0=KPC3+ /dev/ttyUSB0=D710A 8000=DireWolf 192.168.1.64:8002=other .P Serial port /dev/ttyS0 is connected to a KPC3+ with monitor mode turned on. .P @@ -34,6 +35,8 @@ Serial port /dev/ttyUSB0 is connected to a TM-D710A with monitor mode turned on. .P The Dire Wolf software TNC is available on network port 8000. .P +Some other software TNC is available on network port 8002 on host 192.168.1.64. +.P Packets from each are displayed in columns so it is easy to see how well each decodes the received signals. .P diff --git a/multi_modem.c b/multi_modem.c index e5c4c55..f9d57fd 100644 --- a/multi_modem.c +++ b/multi_modem.c @@ -652,7 +652,7 @@ static void pick_best_candidate (int chan) /* Clear in preparation for next time. */ - memset (candidate, 0, sizeof(candidate)); + memset (candidate[chan], 0, sizeof(candidate[chan])); } /* end pick_best_candidate */ diff --git a/rrbb.h b/rrbb.h index 8e94bd7..4b28372 100644 --- a/rrbb.h +++ b/rrbb.h @@ -51,7 +51,7 @@ rrbb_t rrbb_new (int chan, int subchan, int slice, int is_scrambled, int descram void rrbb_clear (rrbb_t b, int is_scrambled, int descram_state, int prev_descram); -static __attribute__((always_inline)) void rrbb_append_bit (rrbb_t b, const unsigned char val) +static inline /*__attribute__((always_inline))*/ void rrbb_append_bit (rrbb_t b, const unsigned char val) { if (b->len >= MAX_NUM_BITS) { return; /* Silently discard if full. */ @@ -60,7 +60,7 @@ static __attribute__((always_inline)) void rrbb_append_bit (rrbb_t b, const unsi b->len++; } -static __attribute__((always_inline)) unsigned char rrbb_get_bit (const rrbb_t b, const int ind) +static inline /*__attribute__((always_inline))*/ unsigned char rrbb_get_bit (const rrbb_t b, const int ind) { return (b->fdata[ind]); } diff --git a/tt_text.c b/tt_text.c index 375aa63..f2803f2 100644 --- a/tt_text.c +++ b/tt_text.c @@ -19,7 +19,7 @@ /*------------------------------------------------------------------ * - * Module: tt-text.c + * Module: tt_text.c * * Purpose: Translate between text and touch tone representation. * @@ -1079,6 +1079,102 @@ int tt_call10_to_text (const char *buttons, int quiet, char *text) } /* end tt_call10_to_text */ + +/*------------------------------------------------------------------ + * + * Name: tt_call5_suffix_to_text + * + * Purpose: Convert the 5 digit APRStt 3 style callsign suffix + * representation to text. + * + * Inputs: buttons - Input string. + * Should contain exactly 5 digits. + * + * quiet - True to suppress error messages. + * + * Outputs: text - Converted to 3 upper case letters and/or digits. + * + * Returns: Number of errors detected. + * + *----------------------------------------------------------------*/ + +int tt_call5_suffix_to_text (const char *buttons, int quiet, char *text) +{ + const char *b; + char *t; + char c; + int packed; /* from last 4 digits */ + int row, col; + int errors = 0; + int k; + + t = text; + *t = '\0'; /* result */ + +/* Validity check. */ + + if (strlen(buttons) != 5) { + + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Callsign 3+2 suffix to text: Encoded Callsign \"%s\" must be exactly 5 digits.\n", buttons); + } + errors++; + return (errors); + } + + for (b = buttons; *b != '\0'; b++) { + + if (! isdigit(*b)) { + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Callsign 3+2 suffix to text: Encoded Callsign \"%s\" can contain only digits.\n", buttons); + } + errors++; + return (errors); + } + } + + packed = atoi(buttons+3); + + for (k = 0; k < 3; k++) { + c = buttons[k]; + + row = c - '0'; + col = (packed >> ((2 - k) * 2)) & 3; + + if (row < 0 || row > 9 || col < 0 || col > 3) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Callsign 3+2 suffix to text: INTERNAL ERROR %d %d. Should not be here.\n", row, col); + errors++; + row = 0; + col = 1; + } + + if (call10encoding[row][col] != 0) { + *t++ = call10encoding[row][col]; + *t = '\0'; + } + else { + errors++; + if (! quiet) { + text_color_set (DW_COLOR_ERROR); + dw_printf ("Callsign 3+2 suffix to text: Invalid combination: button %d, position %d.\n", row, col); + } + } + } + + if (errors > 0) { + strcpy (text, ""); + return (errors); + } + + return (errors); + +} /* end tt_call5_suffix_to_text */ + + + /*------------------------------------------------------------------ * * Name: tt_mhead_to_text diff --git a/tt_text.h b/tt_text.h index 40b4f06..7cab3b8 100644 --- a/tt_text.h +++ b/tt_text.h @@ -25,6 +25,8 @@ int tt_two_key_to_text (const char *buttons, int quiet, char *text); int tt_call10_to_text (const char *buttons, int quiet, char *text); +int tt_call5_suffix_to_text (const char *buttons, int quiet, char *text); + int tt_mhead_to_text (const char *buttons, int quiet, char *text, size_t textsiz); int tt_satsq_to_text (const char *buttons, int quiet, char *text); diff --git a/tt_user.c b/tt_user.c index da7e0dc..46aff3c 100644 --- a/tt_user.c +++ b/tt_user.c @@ -141,6 +141,9 @@ static struct tt_user_s { char freq[12]; /* Frequency in format 999.999MHz */ + char ctcss[5]; /* CTCSS tone. Exactly 3 digits for integer part. */ + /* For example 74.4 Hz becomes "074". */ + char comment[MAX_COMMENT_LEN+1]; /* Free form comment from user. */ /* Comment sent in final object report includes */ /* other information besides this. */ @@ -215,18 +218,19 @@ void tt_user_init (struct audio_s *p_audio_config, struct tt_config_s *p_tt_conf } } + /*------------------------------------------------------------------ * * Name: tt_user_search * * Purpose: Search for user in recent history. * - * Inputs: callsign - full or a suffix abbreviation + * Inputs: callsign - full or a old style 3 DIGIT suffix abbreviation * overlay * * Returns: Handle for refering to table position or -1 if not found. * This happens to be an index into an array but - * the implementation could change so the caller should + * the implementation could change so the caller should * not make any assumptions. * *----------------------------------------------------------------*/ @@ -273,6 +277,51 @@ int tt_user_search (char *callsign, char overlay) } /* end tt_user_search */ + +/*------------------------------------------------------------------ + * + * Name: tt_3char_suffix_search + * + * Purpose: Search for new style 3 CHARACTER (vs. 3 digit) suffix in recent history. + * + * Inputs: suffix - full or a old style 3 DIGIT suffix abbreviation + * + * Outputs: callsign - corresponding full callsign or empty string. + * + * Returns: Handle for refering to table position (>= 0) or -1 if not found. + * This happens to be an index into an array but + * the implementation could change so the caller should + * not make any assumptions. + * + *----------------------------------------------------------------*/ + +int tt_3char_suffix_search (char *suffix, char *callsign) +{ + int i; + + +/* + * Look for suffix in list of known calls. + */ + for (i=0; i= 3 && len <= 6 && strcmp(tt_user[i].callsign + len - 3, suffix) == 0) { + strlcpy (callsign, tt_user[i].callsign, MAX_CALLSIGN_LEN+1); + return (i); + } + } + +/* + * Not found. + */ + strlcpy (callsign, "", MAX_CALLSIGN_LEN+1); + return (-1); + +} /* end tt_3char_suffix_search */ + + + /*------------------------------------------------------------------ * * Name: clear_user @@ -413,6 +462,7 @@ static void digit_suffix (char *callsign, char *suffix) * longitude * ambiguity * freq + * ctcss * comment * mic_e * dao @@ -426,7 +476,7 @@ static void digit_suffix (char *callsign, char *suffix) *----------------------------------------------------------------*/ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *loc_text, double latitude, - double longitude, int ambiguity, char *freq, char *comment, char mic_e, char *dao) + double longitude, int ambiguity, char *freq, char *ctcss, char *comment, char mic_e, char *dao) { int i; @@ -458,8 +508,7 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *lo i = find_avail (); assert (i >= 0 && i < MAX_TT_USERS); - strncpy (tt_user[i].callsign, callsign, MAX_CALLSIGN_LEN); - tt_user[i].callsign[MAX_CALLSIGN_LEN] = '\0'; + strlcpy (tt_user[i].callsign, callsign, sizeof(tt_user[i].callsign)); tt_user[i].count = 1; tt_user[i].ssid = ssid; tt_user[i].overlay = overlay; @@ -481,14 +530,15 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *lo tt_user[i].ambiguity = ambiguity; strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq)); - strncpy (tt_user[i].comment, comment, MAX_COMMENT_LEN); - tt_user[i].comment[MAX_COMMENT_LEN] = '\0'; + strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss)); + strlcpy (tt_user[i].comment, comment, sizeof(tt_user[i].comment)); tt_user[i].mic_e = mic_e; - strncpy(tt_user[i].dao, dao, 6); + strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao)); } else { /* * Known user. Update with any new information. + * Keep any old values where not being updated. */ assert (i >= 0 && i < MAX_TT_USERS); @@ -496,6 +546,13 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *lo /* Any reason to look at ssid here? */ + /* Update the symbol if not the default. */ + + if (overlay != APRSTT_DEFAULT_SYMTAB || symbol != APRSTT_DEFAULT_SYMBOL) { + tt_user[i].overlay = overlay; + tt_user[i].symbol = symbol; + } + if (strlen(loc_text) > 0) { strlcpy (tt_user[i].loc_text, loc_text, sizeof(tt_user[i].loc_text)); } @@ -515,17 +572,21 @@ int tt_user_heard (char *callsign, int ssid, char overlay, char symbol, char *lo strlcpy (tt_user[i].freq, freq, sizeof(tt_user[i].freq)); } + if (ctcss[0] != '\0') { + strlcpy (tt_user[i].ctcss, ctcss, sizeof(tt_user[i].ctcss)); + } + if (comment[0] != '\0') { - strncpy (tt_user[i].comment, comment, MAX_COMMENT_LEN); + strlcpy (tt_user[i].comment, comment, MAX_COMMENT_LEN); tt_user[i].comment[MAX_COMMENT_LEN] = '\0'; } if (mic_e != ' ') { tt_user[i].mic_e = mic_e; } + if (strlen(dao) > 0) { - strncpy(tt_user[i].dao, dao, 6); - tt_user[i].dao[5] = '\0'; + strlcpy(tt_user[i].dao, dao, sizeof(tt_user[i].dao)); } } @@ -782,7 +843,10 @@ static void xmit_object_report (int i, int first_time) encode_object (object_name, 0, tt_user[i].last_heard, olat, olong, oambig, tt_user[i].overlay, tt_user[i].symbol, 0,0,0,NULL, G_UNKNOWN, G_UNKNOWN, /* PHGD, Course/Speed */ - atof(tt_user[i].freq), 0, 0, info_comment, object_info, sizeof(object_info)); + strlen(tt_user[i].freq) > 0 ? atof(tt_user[i].freq) : G_UNKNOWN, + strlen(tt_user[i].ctcss) > 0 ? atof(tt_user[i].ctcss) : G_UNKNOWN, + G_UNKNOWN, /* CTCSS */ + info_comment, object_info, sizeof(object_info)); strlcat (stemp, object_info, sizeof(stemp)); @@ -972,6 +1036,13 @@ static void tt_setenv (int i) setenv ("TTFREQ", tt_user[i].freq, 1); + // TODO: Should convert to actual frequency. e.g. 074 becomes 74.4 + // There is some code for this in decode_aprs.c but not broken out + // into a function that we could use from here. + // TODO: Document this environment variable after converting. + + setenv ("TTCTCSS", tt_user[i].ctcss, 1); + setenv ("TTCOMMENT", tt_user[i].comment, 1); setenv ("TTLOC", tt_user[i].loc_text, 1); @@ -1006,10 +1077,10 @@ void tt_user_dump (void) int i; time_t now = time(NULL); - printf ("call ov suf lsthrd xmit nxt cor lat long freq m comment\n"); + printf ("call ov suf lsthrd xmit nxt cor lat long freq ctcss m comment\n"); for (i=0; i