From 42314b7219ec266697396c7a4b61afe91be9b92c Mon Sep 17 00:00:00 2001 From: wb2osz Date: Sun, 2 Jan 2022 02:55:11 +0000 Subject: [PATCH] gpsd 3.23 (API 12) compatibility and cleanups. --- CHANGES.md | 4 ++ src/ax25_link.c | 14 +++++++ src/decode_aprs.c | 54 ++++++++++++++++++++------- src/dwgpsd.c | 12 +++++- src/dwgpsnmea.c | 93 +++++++++++++++++++---------------------------- 5 files changed, 105 insertions(+), 72 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b2ffa01..589cd5a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,7 +17,11 @@ - The BEACON configuration now recognizes the SOURCE= option. This replaces the AX.25 source address rather than using the MYCALL value for the channel. This is useful for sending more than 5 analog telemetry channels. Use two, or more, source addresses with up to 5 analog channels each. +- For more flexibility, the FX.25 transmit property can now be set individually by channel, rather than having a global setting for all channels. The -X on the command line applies only to channel 0. For other channels you need to add a new line to the configuration file. + > After: "CHANNEL 1" (or other channel) + > + > Add: "FX25TX 1" (or 16 or 32 or 64) ## Version 1.6 -- October 2020 ## diff --git a/src/ax25_link.c b/src/ax25_link.c index 988385a..09e7135 100644 --- a/src/ax25_link.c +++ b/src/ax25_link.c @@ -26,6 +26,10 @@ * Establish connections and transfer data in the proper * order with retries. * + * Using the term "data link" is rather unfortunate because it causes + * confusion to someone familiar with the OSI networking model. + * This corresponds to the layer 4 transport, not layer 2 data link. + * * Description: * * Typical sequence for establishing a connection @@ -824,6 +828,11 @@ static ax25_dlsm_t *get_link_handle (char addrs[AX25_MAX_ADDRS][AX25_MAX_ADDR_LE // Create new data link state machine. p = calloc (sizeof(ax25_dlsm_t), 1); + if (p == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FATAL ERROR: Out of memory.\n"); + exit (EXIT_FAILURE); + } p->magic1 = MAGIC1; p->start_time = dtime_now(); p->stream_id = next_stream_id++; @@ -1493,6 +1502,11 @@ void dl_register_callsign (dlq_item_t *E) } r = calloc(sizeof(reg_callsign_t),1); + if (r == NULL) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("FATAL ERROR: Out of memory.\n"); + exit (EXIT_FAILURE); + } strlcpy (r->callsign, E->addrs[0], sizeof(r->callsign)); r->chan = E->chan; r->client = E->client; diff --git a/src/decode_aprs.c b/src/decode_aprs.c index ba29e2b..d00d8ff 100644 --- a/src/decode_aprs.c +++ b/src/decode_aprs.c @@ -1377,9 +1377,14 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int #define isT(c) ((c) == ' ' || (c) == '>' || (c) == ']' || (c) == '`' || (c) == '\'') -// Last updated Sept. 2016 for TH-D74A +// Last Updated Dec. 2021 + +// This does not change very often but I'm wondering if we could simply parse +// http://www.aprs.org/aprs12/mic-e-types.txt similar to how we use tocalls.txt. if (isT(*pfirst)) { + +// "legacy" formats. if (*pfirst == ' ' ) { strlcpy (A->g_mfr, "Original MIC-E", sizeof(A->g_mfr)); pfirst++; } @@ -1390,6 +1395,8 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int else if (*pfirst == ']' && *plast == '=') { strlcpy (A->g_mfr, "Kenwood TM-D710", sizeof(A->g_mfr)); pfirst++; plast--; } else if (*pfirst == ']' ) { strlcpy (A->g_mfr, "Kenwood TM-D700", sizeof(A->g_mfr)); pfirst++; } +// ` should be used for message capable devices. + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ' ') { strlcpy (A->g_mfr, "Yaesu VX-8", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '"') { strlcpy (A->g_mfr, "Yaesu FTM-350", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '#') { strlcpy (A->g_mfr, "Yaesu VX-8G", sizeof(A->g_mfr)); pfirst++; plast-=2; } @@ -1398,11 +1405,15 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int else if (*pfirst == '`' && *(plast-1) == '_' && *plast == ')') { strlcpy (A->g_mfr, "Yaesu FTM-100D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '(') { strlcpy (A->g_mfr, "Yaesu FT2D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '0') { strlcpy (A->g_mfr, "Yaesu FT3D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '3') { strlcpy (A->g_mfr, "Yaesu FT5D", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '_' && *plast == '1') { strlcpy (A->g_mfr, "Yaesu FTM-300D", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '`' && *(plast-1) == ' ' && *plast == 'X') { strlcpy (A->g_mfr, "AP510", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' && *(plast-1) == '(' && *plast == '5') { strlcpy (A->g_mfr, "Anytone D578UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Generic Mic-Emsg", sizeof(A->g_mfr)); pfirst++; } - else if (*pfirst == '`' ) { strlcpy (A->g_mfr, "Mic-Emsg", sizeof(A->g_mfr)); pfirst++; } +// ' should be used for trackers (not message capable). else if (*pfirst == '\'' && *(plast-1) == '(' && *plast == '8') { strlcpy (A->g_mfr, "Anytone D878UV", sizeof(A->g_mfr)); pfirst++; plast-=2; } @@ -1412,13 +1423,13 @@ static void aprs_mic_e (decode_aprs_t *A, packet_t pp, unsigned char *info, int else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '4') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7400 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if (*pfirst == '\'' && *(plast-1) == ':' && *plast == '8') { strlcpy (A->g_mfr, "SCS GmbH & Co. P4dragon DR-7800 modems", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "McTrackr", sizeof(A->g_mfr)); pfirst++; } + else if (*pfirst == '\'' ) { strlcpy (A->g_mfr, "Generic McTrackr", sizeof(A->g_mfr)); pfirst++; } else if ( *(plast-1) == '\\' ) { strlcpy (A->g_mfr, "Hamhud ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '/' ) { strlcpy (A->g_mfr, "Argent ?", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '^' ) { strlcpy (A->g_mfr, "HinzTec anyfrog", sizeof(A->g_mfr)); pfirst++; plast-=2; } else if ( *(plast-1) == '*' ) { strlcpy (A->g_mfr, "APOZxx www.KissOZ.dk Tracker. OZ1EKD and OZ7HVO", sizeof(A->g_mfr)); pfirst++; plast-=2; } - else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; } + else if ( *(plast-1) == '~' ) { strlcpy (A->g_mfr, "Unknown OTHER", sizeof(A->g_mfr)); pfirst++; plast-=2; } } /* @@ -3486,6 +3497,8 @@ time_t get_timestamp (decode_aprs_t *A, char *p) time_t ts; ts = time(NULL); + // FIXME: use gmtime_r instead. + // Besides not being thread safe, gmtime could possibly return null. ptm = gmtime(&ts); pdhm = (void *)p; @@ -4211,9 +4224,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); } -// TODO: Would like to restrict to even length something like this: ([!-{][!-{]){2,7} - - e = regcomp (&base91_tel_re, "\\|([!-{]{4,14})\\|", REG_EXTENDED); + e = regcomp (&base91_tel_re, "\\|(([!-{][!-{]){2,7})\\|", REG_EXTENDED); if (e) { regerror (e, &base91_tel_re, emsg, sizeof(emsg)); dw_printf("%s:%d: %s\n", __FILE__, __LINE__, emsg); @@ -4411,20 +4422,20 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) /* * Telemetry data, in base 91 compressed format appears as 2 to 7 pairs - * of base 91 digits, surrounded by | at start and end. + * of base 91 digits, ! thru {, surrounded by | at start and end. */ if (regexec (&base91_tel_re, A->g_comment, MAXMATCH, match, 0) == 0) { - char tdata[30]; /* Should be 4 to 14 characters. */ + char tdata[30]; /* Should be even number of 4 to 14 characters. */ - //dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); + //dw_printf("compressed telemetry start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); substr_se (tdata, A->g_comment, match[1].rm_so, match[1].rm_eo); - //dw_printf("compressed telemetry data = \"%s\"\n", tdata); + //dw_printf("compressed telemetry data = \"%s\"\n", tdata); telemetry_data_base91 (A->g_src, tdata, A->g_telemetry, sizeof(A->g_telemetry)); @@ -4442,6 +4453,8 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) * * It surprised me to see this in a MIC-E message. * MIC-E has resolution of .01 minute so it would make sense to have it as an option. + * We also find an example in http://www.aprs.org/aprs12/mic-e-examples.txt + * 'abc123R/'123}FFF.FFFMHztext.../A=123456...!DAO! Mv */ if (regexec (&dao_re, A->g_comment, MAXMATCH, match, 0) == 0) @@ -4451,7 +4464,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) int a = A->g_comment[match[0].rm_so+2]; int o = A->g_comment[match[0].rm_so+3]; - //dw_printf("start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); + dw_printf("DAO start=%d, end=%d\n", (int)(match[0].rm_so), (int)(match[0].rm_eo)); /* @@ -4503,7 +4516,7 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) */ /* - * Here is an interesting case. + * Here are a couple situations where it is seen. * * W8SAT-1>T2UV0P:`qC<0x1f>l!Xu\'"69}WMNI EDS Response Unit #1|+/%0'n|!w:X!|3 * @@ -4520,13 +4533,26 @@ static void process_comment (decode_aprs_t *A, char *pstart, int clen) * Comment earlier points out that MIC-E format has resolution of 0.01 minute, * same as non-compressed format, so the DAO does work out, after thinking * about it for a while. + * We also find a MIC-E example with !DAO! here: http://www.aprs.org/aprs12/mic-e-examples.txt + * + * Another one: + * + * KS4FUN-12>3X0PRU,W6CX-3,BKELEY,WIDE2*:`2^=l!<0x1c>+/'"48}MT-RTG|%B%p'a|!wqR!|3 + * + * MIC-E, Red Cross, Special + * N 38 00.2588, W 122 06.3354 + * 0 MPH, course 100, alt 108 ft + * MT-RTG comment + * |%B%p'a| Seq=397, A1=443, A2=610 + * !wqR! DAO + * |3 Byonics TinyTrack3 + * */ /* * The spec appears to be wrong. It says '}' is the maximum value when it should be '{'. */ - if (isdigit91(a)) { A->g_lat += (a - B91_MIN) * 1.1 / 600000.0 * sign(A->g_lat); } diff --git a/src/dwgpsd.c b/src/dwgpsd.c index 8378288..47df809 100644 --- a/src/dwgpsd.c +++ b/src/dwgpsd.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2013, 2014, 2015, 2020 John Langner, WB2OSZ +// Copyright (C) 2013, 2014, 2015, 2020, 2022 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 @@ -60,7 +60,11 @@ // An incompatibility was introduced with version 7 // and again with 9 and again with 10. -#if GPSD_API_MAJOR_VERSION < 5 || GPSD_API_MAJOR_VERSION > 11 +// release lib version API Raspberry Pi OS +// 3.22 28 11 bullseye +// 3.23 29 12 + +#if GPSD_API_MAJOR_VERSION < 5 || GPSD_API_MAJOR_VERSION > 12 #error libgps API version might be incompatible. #endif @@ -364,6 +368,10 @@ static void * read_gpsd_thread (void *arg) default: case MODE_NOT_SEEN: case MODE_NO_FIX: + if (info.fix <= DWFIX_NOT_SEEN) { + text_color_set(DW_COLOR_INFO); + dw_printf ("GPSD: No location fix.\n"); + } if (info.fix >= DWFIX_2D) { text_color_set(DW_COLOR_INFO); dw_printf ("GPSD: Lost location fix.\n"); diff --git a/src/dwgpsnmea.c b/src/dwgpsnmea.c index 89f494c..14cda77 100644 --- a/src/dwgpsnmea.c +++ b/src/dwgpsnmea.c @@ -144,8 +144,6 @@ int dwgpsnmea_init (struct misc_config_s *pconfig, int debug) /* * Open serial port connection. - * 4800 baud is standard for GPS. - * Should add an option to allow changing someday. */ s_gpsnmea_port_fd = serial_port_open (pconfig->gpsnmea_port, pconfig->gpsnmea_speed); @@ -264,9 +262,12 @@ static void * read_gpsnmea_thread (void *arg) } dwgps_set_data (&info); - // TODO: doesn't exist yet - serial_port_close(fd); + serial_port_close(s_gpsnmea_port_fd); s_gpsnmea_port_fd = MYFDERROR; + // TODO: If the open() was in this thread, we could wait a while and + // try to open again. That would allow recovery if the USB GPS device + // is unplugged and plugged in again. break; /* terminate thread. */ } @@ -287,74 +288,53 @@ static void * read_gpsnmea_thread (void *arg) } /* Process sentence. */ -// FIXME: Ignore the second letter rather than recognizing only GP... and GN... +// TODO: More general: Ignore the second letter rather than recognizing only GP... and GN... if (strncmp(gps_msg, "$GPRMC", 6) == 0 || strncmp(gps_msg, "$GNRMC", 6) == 0) { - f = dwgpsnmea_gprmc (gps_msg, 0, &info.dlat, &info.dlon, &info.speed_knots, &info.track); + // Here we just tuck away the course and speed. + // Fix and location will be updated by GxGGA. - if (f == DWFIX_ERROR) { + double ignore_dlat; + double ignore_dlon; + + f = dwgpsnmea_gprmc (gps_msg, 0, &ignore_dlat, &ignore_dlon, &info.speed_knots, &info.track); - /* Parse error. Shouldn't happen. Better luck next time. */ - text_color_set(DW_COLOR_INFO); + if (f == DWFIX_ERROR) { + /* Parse error. Shouldn't happen. Better luck next time. */ + text_color_set(DW_COLOR_ERROR); dw_printf ("GPSNMEA: Error parsing $GPRMC sentence.\n"); dw_printf ("%s\n", gps_msg); } - else if (f == DWFIX_2D) { - - if (info.fix != DWFIX_2D && info.fix != DWFIX_3D) { - - text_color_set(DW_COLOR_INFO); - dw_printf ("GPSNMEA: Location fix is now available.\n"); - - info.fix = DWFIX_2D; // Don't know if 2D or 3D. Take minimum. - } - info.timestamp = time(NULL); - if (s_debug >= 2) { - text_color_set(DW_COLOR_DEBUG); - dwgps_print ("GPSNMEA: ", &info); - } - dwgps_set_data (&info); - } - else { - - if (info.fix == DWFIX_2D || info.fix == DWFIX_3D) { - - text_color_set(DW_COLOR_INFO); - dw_printf ("GPSNMEA: Lost location fix.\n"); - } - info.fix = f; /* lost it. */ - info.timestamp = time(NULL); - if (s_debug >= 2) { - text_color_set(DW_COLOR_DEBUG); - dwgps_print ("GPSNMEA: ", &info); - } - dwgps_set_data (&info); - } - } + else if (strncmp(gps_msg, "$GPGGA", 6) == 0 || strncmp(gps_msg, "$GNGGA", 6) == 0) { int nsat; f = dwgpsnmea_gpgga (gps_msg, 0, &info.dlat, &info.dlon, &info.altitude, &nsat); - /* Only switch between 2D & 3D. */ - /* Let GPRMC handle other changes in fix state and data transfer. */ - if (f == DWFIX_ERROR) { - /* Parse error. Shouldn't happen. Better luck next time. */ - text_color_set(DW_COLOR_INFO); + text_color_set(DW_COLOR_ERROR); dw_printf ("GPSNMEA: Error parsing $GPGGA sentence.\n"); dw_printf ("%s\n", gps_msg); } - else if ((f == DWFIX_3D && info.fix == DWFIX_2D) || - (f == DWFIX_2D && info.fix == DWFIX_3D)) { - text_color_set(DW_COLOR_INFO); - dw_printf ("GPSNMEA: Location fix is now %dD.\n", (int)f); - info.fix = f; + else { + if (f != info.fix) { // Print change in location fix. + text_color_set(DW_COLOR_INFO); + if (f == DWFIX_NO_FIX) dw_printf ("GPSNMEA: Location fix has been lost.\n"); + if (f == DWFIX_2D) dw_printf ("GPSNMEA: Location fix is now 2D.\n"); + if (f == DWFIX_3D) dw_printf ("GPSNMEA: Location fix is now 3D.\n"); + info.fix = f; + } + info.timestamp = time(NULL); + if (s_debug >= 2) { + text_color_set(DW_COLOR_DEBUG); + dwgps_print ("GPSNMEA: ", &info); + } + dwgps_set_data (&info); } } } @@ -445,9 +425,11 @@ static int remove_checksum (char *sent, int quiet) * odlon longitude * oknots speed * ocourse direction of travel. - * + * * Left undefined if not valid. * + * Note: RMC does not contain altitude. + * * Returns: DWFIX_ERROR Parse error. * DWFIX_NO_FIX GPS is there but Position unknown. Could be temporary. * DWFIX_2D Valid position. We don't know if it is really 2D or 3D. @@ -597,10 +579,13 @@ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon * * Left undefined if not valid. * + * Note: GGA has altitude but not course and speed so we need to use both. + * * Returns: DWFIX_ERROR Parse error. * DWFIX_NO_FIX GPS is there but Position unknown. Could be temporary. * DWFIX_2D Valid position. We don't know if it is really 2D or 3D. * Take more cautious value so we don't try using altitude. + * DWFIX_3D Valid 3D position. * * Examples: $GPGGA,001429.00,,,,,0,00,99.99,,,,,,*68 * $GPGGA,212407.000,4237.1505,N,07120.8602,W,0,00,,,M,,M,,*58 @@ -609,9 +594,6 @@ dwfix_t dwgpsnmea_gprmc (char *sentence, int quiet, double *odlat, double *odlon * *--------------------------------------------------------------------*/ - -// TODO: in progress... - dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon, float *oalt, int *onsat) { char stemp[NMEA_MAX_LEN]; /* Make copy because parsing is destructive. */ @@ -708,8 +690,7 @@ dwfix_t dwgpsnmea_gpgga (char *sentence, int quiet, double *odlat, double *odlon return (DWFIX_ERROR); } - - // TODO: num sat... + // TODO: num sat... Why would we care? /* * We can distinguish between 2D & 3D fix by presence