From 45136a91ebd00ab0312b58e1b760ee99280940b9 Mon Sep 17 00:00:00 2001 From: wb2osz Date: Tue, 26 May 2020 21:20:37 -0400 Subject: [PATCH] AIS Reception enhancements. --- src/CMakeLists.txt | 1 + src/ais.c | 192 ++++++++++++++++++++++++++++++++++++++------- src/config.c | 38 +++++++-- src/config.h | 8 +- src/direwolf.c | 86 ++++++++++++++++++-- src/gen_tone.c | 2 + src/waypoint.c | 161 ++++++++++++++++++++++++++++--------- src/waypoint.h | 2 + 8 files changed, 409 insertions(+), 81 deletions(-) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 4e02a13..46d3ac7 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -41,6 +41,7 @@ list(APPEND direwolf_SOURCES dtime_now.c dtmf.c dwgps.c + dwsock.c encode_aprs.c encode_aprs.c fcs_calc.c diff --git a/src/ais.c b/src/ais.c index 8352599..cadf648 100644 --- a/src/ais.c +++ b/src/ais.c @@ -87,6 +87,10 @@ static const struct { { 96, 168 } // 27 96 or 168, not range }; +static void save_ship_data(char *mssi, char *shipname, char *callsign, char *destination); +static void get_ship_data(char *mssi, char *comment, int comment_size); + + /*------------------------------------------------------------------- * * Functions to get and set element of a bit vector. @@ -144,14 +148,34 @@ static int get_field_signed (unsigned char *base, unsigned int start, unsigned i return (result); } -static double get_field_latlon (unsigned char *base, unsigned int start, unsigned int len) +static double get_field_lat (unsigned char *base, unsigned int start, unsigned int len) { // Latitude of 0x3412140 (91 deg) means not available. - // Longitude of 0x6791AC0 (181 deg) means not available. - return ((double)get_field_signed(base, start, len) / 600000.0); - - // Message type 27 uses lower resolution, 17 & 18 bits rather than 27 & 28. + // Message type 27 uses lower resolution, 17 bits rather than 27. // It encodes minutes/10 rather than normal minutes/10000. + + int n = get_field_signed(base, start, len); + if (len == 17) { + return ((n == 91*600) ? G_UNKNOWN : (double)n / 600.0); + } + else { + return ((n == 91*600000) ? G_UNKNOWN : (double)n / 600000.0); + } +} + +static double get_field_lon (unsigned char *base, unsigned int start, unsigned int len) +{ + // Longitude of 0x6791AC0 (181 deg) means not available. + // Message type 27 uses lower resolution, 18 bits rather than 28. + // It encodes minutes/10 rather than normal minutes/10000. + + int n = get_field_signed(base, start, len); + if (len == 18) { + return ((n == 181*600) ? G_UNKNOWN : (double)n / 600.0); + } + else { + return ((n == 181*600000) ? G_UNKNOWN : (double)n / 600000.0); + } } static float get_field_speed (unsigned char *base, unsigned int start, unsigned int len) @@ -159,14 +183,33 @@ static float get_field_speed (unsigned char *base, unsigned int start, unsigned // Raw 1023 means not available. // Multiply by 0.1 to get knots. // For aircraft it is knots, not deciknots. - return ((float)get_field(base, start, len) * 0.1); + + // Message type 27 uses lower resolution, 6 bits rather than 10. + // It encodes minutes/10 rather than normal minutes/10000. + + int n = get_field(base, start, len); + if (len == 6) { + return ((n == 63) ? G_UNKNOWN : (float)n); + } + else { + return ((n == 1023) ? G_UNKNOWN : (float)n * 0.1); + } } static float get_field_course (unsigned char *base, unsigned int start, unsigned int len) { // Raw 3600 means not available. // Multiply by 0.1 to get degrees - return ((float)get_field(base, start, len) * 0.1); + // Message type 27 uses lower resolution, 9 bits rather than 12. + // It encodes degrees rather than normal degrees/10. + + int n = get_field(base, start, len); + if (len == 9) { + return ((n == 360) ? G_UNKNOWN : (float)n); + } + else { + return ((n == 3600) ? G_UNKNOWN : (float)n * 0.1); + } } static int get_field_ascii (unsigned char *base, unsigned int start, unsigned int len) @@ -428,7 +471,7 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss int type = get_field(ais, 0, 6); if (type >= 1 && type <= 27) { - snprintf (mssi, mssi_size, "%d", get_field(ais, 8, 30)); + snprintf (mssi, mssi_size, "%09d", get_field(ais, 8, 30)); } switch (type) { @@ -439,10 +482,11 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss snprintf (descr, descr_size, "AIS %d: Position Report Class A", type); *symtab = '/'; *symbol = 's'; // Power boat (ship) side view - *odlon = get_field_latlon(ais, 61, 28); - *odlat = get_field_latlon(ais, 89, 27); + *odlon = get_field_lon(ais, 61, 28); + *odlat = get_field_lat(ais, 89, 27); *ofknots = get_field_speed(ais, 50, 10); *ofcourse = get_field_course(ais, 116, 12); + get_ship_data(mssi, comment, comment_size); break; case 4: // Base Station Report @@ -456,8 +500,10 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss //hour = get_field(ais, 61, 5); //minute = get_field(ais, 66, 6); //second = get_field(ais, 72, 6); - *odlon = get_field_latlon(ais, 79, 28); - *odlat = get_field_latlon(ais, 107, 27); + *odlon = get_field_lon(ais, 79, 28); + *odlat = get_field_lat(ais, 107, 27); + // Is this suitable or not? Doesn't hurt, I suppose. + get_ship_data(mssi, comment, comment_size); break; case 5: // Static and Voyage Related Data @@ -472,13 +518,8 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss get_field_string(ais, 70, 42, callsign); get_field_string(ais, 112, 120, shipname); get_field_string(ais, 302, 120, destination); - - if (strlen(destination) > 0) { - snprintf (comment, comment_size, "%s, %s, dest. %s", shipname, callsign, destination); - } - else { - snprintf (comment, comment_size, "%s, %s", shipname, callsign); - } + save_ship_data(mssi, shipname, callsign, destination); + get_ship_data(mssi, comment, comment_size); } break; @@ -489,10 +530,12 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss *symtab = '/'; *symbol = '\''; // Small AIRCRAFT *ofalt_m = get_field(ais, 38, 12); // meters, 4095 means not available - *odlon = get_field_latlon(ais, 61, 28); - *odlat = get_field_latlon(ais, 89, 27); - *ofknots = get_field_speed(ais, 50, 10) * 10.0; // plane is knots, not knots/10 + *odlon = get_field_lon(ais, 61, 28); + *odlat = get_field_lat(ais, 89, 27); + *ofknots = get_field_speed(ais, 50, 10); // plane is knots, not knots/10 + if (*ofknots != G_UNKNOWN) *ofknots = *ofknots * 10.0; *ofcourse = get_field_course(ais, 116, 12); + get_ship_data(mssi, comment, comment_size); break; case 18: // Standard Class B CS Position Report @@ -501,8 +544,9 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss snprintf (descr, descr_size, "AIS %d: Standard Class B CS Position Report", type); *symtab = '/'; *symbol = 'Y'; // YACHT (sail) - *odlon = get_field_latlon(ais, 57, 28); - *odlat = get_field_latlon(ais, 85, 27); + *odlon = get_field_lon(ais, 57, 28); + *odlat = get_field_lat(ais, 85, 27); + get_ship_data(mssi, comment, comment_size); break; case 19: // Extended Class B CS Position Report @@ -510,8 +554,9 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss snprintf (descr, descr_size, "AIS %d: Extended Class B CS Position Report", type); *symtab = '/'; *symbol = 'Y'; // YACHT (sail) - *odlon = get_field_latlon(ais, 57, 28); - *odlat = get_field_latlon(ais, 85, 27); + *odlon = get_field_lon(ais, 57, 28); + *odlat = get_field_lat(ais, 85, 27); + get_ship_data(mssi, comment, comment_size); break; case 27: // Long Range AIS Broadcast message @@ -519,10 +564,11 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss snprintf (descr, descr_size, "AIS %d: Long Range AIS Broadcast message", type); *symtab = '\\'; *symbol = 's'; // OVERLAY SHIP/boat (top view) - *odlon = get_field_latlon(ais, 44, 18) * 1000; // Note: minutes/10 rather than usual /10000. - *odlat = get_field_latlon(ais, 62, 17) * 1000; - *ofknots = get_field_speed(ais, 79, 6) * 10; // Note: knots, not deciknots. - *ofcourse = get_field_course(ais, 85, 9) * 10; // Note: degrees, not decidegrees. + *odlon = get_field_lon(ais, 44, 18); // Note: minutes/10 rather than usual /10000. + *odlat = get_field_lat(ais, 62, 17); + *ofknots = get_field_speed(ais, 79, 6); // Note: knots, not deciknots. + *ofcourse = get_field_course(ais, 85, 9); // Note: degrees, not decidegrees. + get_ship_data(mssi, comment, comment_size); break; default: @@ -575,4 +621,90 @@ int ais_check_length (int type, int length) } // end ais_check_length + +/*------------------------------------------------------------------- + * + * Name: save_ship_data + * + * Purpose: Save shipname, etc., from "Static and Voyage Related Data" + * so it can be combined later with the position reports. + * + * Inputs: mssi + * shipname + * callsign + * destination + * + *--------------------------------------------------------------------*/ + +struct ship_data_s { + struct ship_data_s *pnext; + char mssi[9+1]; + char shipname[20+1]; + char callsign[7+1]; + char destination[20+1]; +}; + +// Just use a single linked list for now. +// If I get ambitious, I might use a hash table. +// I don't think we need a critical region because all channels +// should be serialized thru the receive queue. + +static struct ship_data_s *ships = NULL; + + +static void save_ship_data(char *mssi, char *shipname, char *callsign, char *destination) +{ + // Get list node, either existing or new. + struct ship_data_s *p = ships; + while (p != NULL) { + if (strcmp(mssi, p->mssi) == 0) { + break; + } + p = p->pnext; + } + if (p == NULL) { + p = calloc(sizeof(struct ship_data_s),1); + p->pnext = ships; + ships = p; + } + + strlcpy (p->mssi, mssi, sizeof(p->mssi)); + strlcpy (p->shipname, shipname, sizeof(p->shipname)); + strlcpy (p->callsign, callsign, sizeof(p->callsign)); + strlcpy (p->destination, destination, sizeof(p->destination)); +} + +/*------------------------------------------------------------------- + * + * Name: save_ship_data + * + * Purpose: Get ship data for specified mssi. + * + * Inputs: mssi + * + * Outputs: comment - If mssi is found, return in single string here, + * suitable for the comment field. + * + *--------------------------------------------------------------------*/ + +static void get_ship_data(char *mssi, char *comment, int comment_size) +{ + struct ship_data_s *p = ships; + while (p != NULL) { + if (strcmp(mssi, p->mssi) == 0) { + break; + } + p = p->pnext; + } + if (p != NULL) { + if (strlen(p->destination) > 0) { + snprintf (comment, comment_size, "%s, %s, dest. %s", p->shipname, p->callsign, p->destination); + } + else { + snprintf (comment, comment_size, "%s, %s", p->shipname, p->callsign); + } + } +} + + // end ais.c diff --git a/src/config.c b/src/config.c index 5b9dc34..1c4f64f 100644 --- a/src/config.c +++ b/src/config.c @@ -885,7 +885,7 @@ void config_init (char *fname, struct audio_s *p_audio_config, p_misc_config->kiss_serial_poll = 0; strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port)); - strlcpy (p_misc_config->waypoint_port, "", sizeof(p_misc_config->waypoint_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)); @@ -4602,21 +4602,46 @@ void config_init (char *fname, struct audio_s *p_audio_config, } /* - * WAYPOINT - Generate WPL NMEA sentences for display on map. + * 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 device name for WAYPOINT on line %d.\n", line); + dw_printf ("Config file: Missing output device for WAYPOINT on line %d.\n", line); continue; } - else { - strlcpy (p_misc_config->waypoint_port, t, sizeof(p_misc_config->waypoint_port)); + + /* 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++ ) { @@ -4633,6 +4658,9 @@ void config_init (char *fname, struct audio_s *p_audio_config, 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; diff --git a/src/config.h b/src/config.h index baf6cb4..562d307 100644 --- a/src/config.h +++ b/src/config.h @@ -65,10 +65,15 @@ struct misc_config_s { /* Default is 2947. */ - char waypoint_port[20]; /* Serial port name for sending NMEA waypoint sentences */ + char waypoint_serial_port[20]; /* Serial port name for sending NMEA waypoint sentences */ /* to a GPS map display or other mapping application. */ /* e.g. COM22, /dev/ttyACM0 */ /* Currently no option for setting non-standard speed. */ + /* This was done in 2014 and no one has complained yet. */ + + char waypoint_udp_hostname[80]; /* Destination host when using UDP. */ + + int waypoint_udp_portnum; /* UDP port. */ int waypoint_formats; /* Which sentence formats should be generated? */ @@ -76,6 +81,7 @@ struct misc_config_s { #define WPT_FORMAT_GARMIN 0x02 /* G $PGRMW */ #define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */ #define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */ +#define WPT_FORMAT_AIS 0x10 /* A !AIVDM */ int log_daily_names; /* True to generate new log file each day. */ diff --git a/src/direwolf.c b/src/direwolf.c index 5412ae5..b59bfb3 100644 --- a/src/direwolf.c +++ b/src/direwolf.c @@ -94,6 +94,7 @@ #include "ax25_pad.h" #include "xid.h" #include "decode_aprs.h" +#include "encode_aprs.h" #include "textcolor.h" #include "server.h" #include "kiss.h" @@ -123,6 +124,7 @@ #include "ax25_link.h" #include "dtime_now.h" #include "fx25.h" +#include "dwsock.h" //static int idx_decoded = 0; @@ -182,6 +184,8 @@ static int d_p_opt = 0; /* "-d p" option for dumping packets over radio. */ static int q_h_opt = 0; /* "-q h" Quiet, suppress the "heard" line with audio level. */ static int q_d_opt = 0; /* "-q d" Quiet, suppress the printing of decoded of APRS packets. */ +static int A_opt_ais_to_obj = 0; /* "-A" Convert received AIS to APRS "Object Report." */ + int main (int argc, char *argv[]) { @@ -284,7 +288,7 @@ int main (int argc, char *argv[]) text_color_init(t_opt); text_color_set(DW_COLOR_INFO); //dw_printf ("Dire Wolf version %d.%d (%s) Beta Test 4\n", MAJOR_VERSION, MINOR_VERSION, __DATE__); - dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "E", __DATE__); + dw_printf ("Dire Wolf DEVELOPMENT version %d.%d %s (%s)\n", MAJOR_VERSION, MINOR_VERSION, "F", __DATE__); //dw_printf ("Dire Wolf version %d.%d\n", MAJOR_VERSION, MINOR_VERSION); @@ -321,22 +325,32 @@ int main (int argc, char *argv[]) * Apple computers with Intel processors started with P6. Since the * cpu test code was giving Clang compiler grief it has been excluded. * - * Now, where can I find a Pentium 2 or earlier to test this? + * Version 1.6: Newer compiler with i686, rather than i386 target. + * This is running about 10% faster for the same hardware so it would + * appear the compiler is using newer, more efficient, instructions. + * + * According to https://en.wikipedia.org/wiki/P6_(microarchitecture) + * and https://en.wikipedia.org/wiki/Streaming_SIMD_Extensions + * the Pentium III still seems to be the minimum required because + * it has the P6 microarchitecture and SSE instructions. + * + * I've never heard any complaints about anyone getting the message below. */ #if defined(__SSE__) && !defined(__APPLE__) - int cpuinfo[4]; + int cpuinfo[4]; // EAX, EBX, ECX, EDX __cpuid (cpuinfo, 0); if (cpuinfo[0] >= 1) { __cpuid (cpuinfo, 1); //dw_printf ("debug: cpuinfo = %x, %x, %x, %x\n", cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); + // https://en.wikipedia.org/wiki/CPUID if ( ! ( cpuinfo[3] & (1 << 25))) { text_color_set(DW_COLOR_ERROR); dw_printf ("------------------------------------------------------------------\n"); dw_printf ("This version requires a minimum of a Pentium 3 or equivalent.\n"); dw_printf ("If you are seeing this message, you are probably using a computer\n"); - dw_printf ("from the previous century. See comments in Makefile.win for\n"); - dw_printf ("information on how you can recompile it for use with your antique.\n"); + dw_printf ("from the previous Century. See instructions in User Guide for\n"); + dw_printf ("information on how you can compile it for use with your antique.\n"); dw_printf ("------------------------------------------------------------------\n"); } } @@ -373,7 +387,7 @@ int main (int argc, char *argv[]) /* ':' following option character means arg is required. */ - c = getopt_long(argc, argv, "hP:B:gjJD:U:c:pxr:b:n:d:q:t:ul:L:Sa:E:T:e:X:", + c = getopt_long(argc, argv, "hP:B:gjJD:U:c:pxr:b:n:d:q:t:ul:L:Sa:E:T:e:X:A", long_options, &option_index); if (c == -1) break; @@ -636,6 +650,11 @@ int main (int argc, char *argv[]) X_fx25_xmit_enable = atoi(optarg); break; + case 'A': // -A convert AIS to APRS object + + A_opt_ais_to_obj = 1; + break; + default: /* Should not be here. */ @@ -670,6 +689,8 @@ int main (int argc, char *argv[]) symbols_init (); + (void)dwsock_init(); + config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config); if (r_opt != 0) { @@ -1205,6 +1226,8 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev * * Suppress printed decoding if "-q d" option used. */ + char ais_obj_packet[300]; + strcpy (ais_obj_packet, ""); if (ax25_is_aprs(pp)) { @@ -1239,6 +1262,37 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev mheard_save_rf (chan, &A, pp, alevel, retries); +// For AIS, we have an option to convert the NMEA format, in User Defined data, +// into an APRS "Object Report" and send that to the clients as well. + +// FIXME: partial implementation. + + static const char user_def_da[4] = { '{', USER_DEF_USER_ID, USER_DEF_TYPE_AIS, '\0' }; + + if (strncmp((char*)pinfo, user_def_da, 3) == 0) { + + waypoint_send_ais((char*)pinfo + 3); + + if (A_opt_ais_to_obj && A.g_lat != G_UNKNOWN && A.g_lon != G_UNKNOWN) { + + char ais_obj_info[256]; + (void)encode_object (A.g_name, 0, time(NULL), + A.g_lat, A.g_lon, 0, // no ambiguity + A.g_symbol_table, A.g_symbol_code, + 0, 0, 0, "", // power, height, gain, direction. + // Unknown not handled properly. + // Should encode_object take floating point here? + (int)(A.g_course+0.5), (int)(DW_MPH_TO_KNOTS(A.g_speed_mph)+0.5), + 0, 0, 0, A.g_comment, // freq, tone, offset + ais_obj_info, sizeof(ais_obj_info)); + + snprintf (ais_obj_packet, sizeof(ais_obj_packet), "%s>%s%1d%1d:%s", A.g_src, APP_TOCALL, MAJOR_VERSION, MINOR_VERSION, ais_obj_info); + + dw_printf ("[%d.AIS] %s\n", chan, ais_obj_packet); + + // This will be sent to client apps after the User Defined Data representation. + } + } // Convert to NMEA waypoint sentence if we have a location. @@ -1265,6 +1319,20 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS serial port kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, fbuf, flen, -1); // KISS pseudo terminal + if (A_opt_ais_to_obj && strlen(ais_obj_packet) != 0) { + packet_t ao_pp = ax25_from_text (ais_obj_packet, 1); + if (ao_pp != NULL) { + unsigned char ao_fbuf[AX25_MAX_PACKET_LEN]; + int ao_flen = ax25_pack(ao_pp, ao_fbuf); + + server_send_rec_packet (chan, ao_pp, ao_fbuf, ao_flen); + kissnet_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, -1); + kissserial_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, -1); + kisspt_send_rec_packet (chan, KISS_CMD_DATA_FRAME, ao_fbuf, ao_flen, -1); + ax25_delete (ao_pp); + } + } + /* * If it came from DTMF decoder, send it to APRStt gateway. * Otherwise, it is a candidate for IGate and digipeater. @@ -1302,6 +1370,9 @@ void app_process_rec_packet (int chan, int subchan, int slice, packet_t pp, alev * Use only those with correct CRC; We don't want to spread corrupted data! */ +// TODO: Should also use anything received with FX.25 because it is known to be good. +// Our earlier "fix bits" hack could allow corrupted information to get thru. + if (ax25_is_aprs(pp) && retries == RETRY_NONE) { digipeater (chan, pp); @@ -1384,8 +1455,9 @@ static void usage (char **argv) dw_printf (" -j 2400 bps QPSK compatible with direwolf <= 1.5.\n"); dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n"); dw_printf (" -P xxx Modem Profiles.\n"); + dw_printf (" -A Convert AIS positions to APRS Object Reports.\n"); dw_printf (" -D n Divide audio sample rate by n for channel 0.\n"); - dw_printf (" -X n Enable FX.25 transmit. Specify number of check bytes: 16, 32, or 64.\n"); + dw_printf (" -X n 1 to enable FX.25 transmit.\n"); dw_printf (" -d Debug options:\n"); dw_printf (" a a = AGWPE network protocol client.\n"); dw_printf (" k k = KISS serial port or pseudo terminal client.\n"); diff --git a/src/gen_tone.c b/src/gen_tone.c index 18afa4a..68f72bc 100644 --- a/src/gen_tone.c +++ b/src/gen_tone.c @@ -215,6 +215,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets) case MODEM_BASEBAND: case MODEM_SCRAMBLE: + case MODEM_AIS: // Tone is half baud. ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); @@ -398,6 +399,7 @@ void tone_gen_put_bit (int chan, int dat) case MODEM_BASEBAND: case MODEM_SCRAMBLE: + case MODEM_AIS: if (dat != prev_dat[chan]) { tone_phase[chan] += f1_change_per_sample[chan]; diff --git a/src/waypoint.c b/src/waypoint.c index 0f6d1dd..c06b362 100644 --- a/src/waypoint.c +++ b/src/waypoint.c @@ -1,7 +1,7 @@ // // This file is part of Dire Wolf, an amateur radio packet TNC. // -// Copyright (C) 2014, 2015, 2016 John Langner, WB2OSZ +// Copyright (C) 2014, 2015, 2016, 2020 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 @@ -32,16 +32,21 @@ #include "direwolf.h" // should be first - #include #include +#include #if __WIN32__ -#include +#include +#include // _WIN32_WINNT must be set to 0x0501 before including this #else -#include #include #include +#include +#include +#include +//#include +#include // gethostbyname #endif #include @@ -57,13 +62,16 @@ #include "mgn_icon.h" /* Magellan icons */ #include "dwgpsnmea.h" #include "serial_port.h" +#include "dwsock.h" -static MYFDTYPE s_waypoint_port_fd = MYFDERROR; +static MYFDTYPE s_waypoint_serial_port_fd = MYFDERROR; +static int s_waypoint_udp_sock_fd = -1; // ideally INVALID_SOCKET for Windows. +static struct sockaddr_in s_udp_dest_addr; static int s_waypoint_formats = 0; /* which formats should we generate? */ -static int s_waypoint_debug = 0; /* Print information flowing to attached device. */ +static int s_waypoint_debug = 0; /* Print information flowing to attached device. */ @@ -86,19 +94,24 @@ void waypoint_set_debug (int n) * * Inputs: mc - Pointer to configuration options. * - * ->waypoint_port - Name of serial port. COM1, /dev/ttyS0, etc. + * ->waypoint_serial_port - Name of serial port. COM1, /dev/ttyS0, etc. * + * ->waypoint_udp_hostname - Destination host when using UDP. + * + * ->waypoint_udp_portnum - UDP port number. * * (currently none) - speed, baud. Default 4800 if not set * * * ->waypoint_formats - Set of formats enabled. - * If none set, default to generic & Kenwood. + * If none set, default to generic & Kenwood here. * - * Global output: s_waypoint_port_fd + * Global output: s_waypoint_serial_port_fd + * s_waypoint_udp_sock_fd * * Description: First to see if this is shared with GPS input. * If not, open serial port. + * In version 1.6 UDP is added. It is possible to use both. * * Restriction: MUST be done after GPS init because we might be sharing the * same serial port device. @@ -111,46 +124,79 @@ void waypoint_init (struct misc_config_s *mc) #if DEBUG text_color_set (DW_COLOR_DEBUG); - dw_printf ("waypoint_init() device=%s formats=%d\n", mc->waypoint_port, mc->waypoint_formats); + dw_printf ("waypoint_init() serial device=%s formats=%02x\n", mc->waypoint_serial_port, mc->waypoint_formats); + dw_printf ("waypoint_init() destination hostname=%s UDP port=%d\n", mc->waypoint_udp_hostname, mc->waypoint_udp_portnum); #endif - + + s_waypoint_udp_sock_fd = -1; + + if (mc->waypoint_udp_portnum > 0) { + + s_waypoint_udp_sock_fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); + if (s_waypoint_udp_sock_fd != -1) { + + // Not thread-safe. Should use getaddrinfo instead. + struct hostent *hp = gethostbyname(mc->waypoint_udp_hostname); + + if (hp != NULL) { + memset ((char *)&s_udp_dest_addr, 0, sizeof(s_udp_dest_addr)); + s_udp_dest_addr.sin_family = AF_INET; + memcpy ((char *)&s_udp_dest_addr.sin_addr, (char *)hp->h_addr, hp->h_length); + s_udp_dest_addr.sin_port = htons(mc->waypoint_udp_portnum); + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Waypoint: Couldn't get address for %s\n", mc->waypoint_udp_hostname); + close (s_waypoint_udp_sock_fd); + s_waypoint_udp_sock_fd = -1; + } + } + else { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Couldn't create socket for waypoint send to %s\n", mc->waypoint_udp_hostname); + } + } + /* * TODO: * Are we sharing with GPS input? * First try to get fd if they have same device name. * If that fails, do own serial port open. */ - if (strlen(mc->waypoint_port) > 0) { + s_waypoint_serial_port_fd = MYFDERROR; - s_waypoint_port_fd = dwgpsnmea_get_fd (mc->waypoint_port, 4800); + if (strlen(mc->waypoint_serial_port) > 0) { - if (s_waypoint_port_fd == MYFDERROR) { - s_waypoint_port_fd = serial_port_open (mc->waypoint_port, 4800); + s_waypoint_serial_port_fd = dwgpsnmea_get_fd (mc->waypoint_serial_port, 4800); + + if (s_waypoint_serial_port_fd == MYFDERROR) { + s_waypoint_serial_port_fd = serial_port_open (mc->waypoint_serial_port, 4800); } else { text_color_set (DW_COLOR_INFO); dw_printf ("Note: Sharing same port for GPS input and waypoint output.\n"); } - if (s_waypoint_port_fd == MYFDERROR) { + if (s_waypoint_serial_port_fd == MYFDERROR) { text_color_set (DW_COLOR_ERROR); - dw_printf ("Unable to open %s for waypoint output.\n", mc->waypoint_port); - return; - } - - s_waypoint_formats = mc->waypoint_formats; - if (s_waypoint_formats == 0) { - s_waypoint_formats = WPT_FORMAT_NMEA_GENERIC | WPT_FORMAT_KENWOOD; - } - if (s_waypoint_formats & WPT_FORMAT_GARMIN) { - s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */ + dw_printf ("Unable to open serial port %s for waypoint output.\n", mc->waypoint_serial_port); } } +// Set default formats if user did not specify any. + + s_waypoint_formats = mc->waypoint_formats; + if (s_waypoint_formats == 0) { + s_waypoint_formats = WPT_FORMAT_NMEA_GENERIC | WPT_FORMAT_KENWOOD; + } + if (s_waypoint_formats & WPT_FORMAT_GARMIN) { + s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */ + } #if DEBUG text_color_set (DW_COLOR_DEBUG); - dw_printf ("end of waypoint_init: s_waypoint_port_fd = %d\n", s_waypoint_port_fd); + dw_printf ("end of waypoint_init: s_waypoint_serial_port_fd = %d\n", s_waypoint_serial_port_fd); + dw_printf ("end of waypoint_init: s_waypoint_udp_sock_fd = %d\n", s_waypoint_udp_sock_fd); #endif } @@ -252,6 +298,12 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol); #endif +// Don't waste time if no destintations specified. + + if (s_waypoint_serial_port_fd == MYFDERROR && + s_waypoint_udp_sock_fd == -1) { + return; + } /* * We need to remove any , or * from name, symbol, or comment because they are field delimiters. @@ -583,29 +635,59 @@ void waypoint_send_sentence (char *name_in, double dlat, double dlong, char symt } /* end waypoint_send_sentence */ +/*------------------------------------------------------------------- + * + * Name: nema_send_ais + * + * Purpose: Send NMEA AIS sentence to GPS display or other mapping application. + * + * Inputs: sentence - should look something like this, with checksum, and no CR LF. + * + * !AIVDM,1,1,,A,35NO=dPOiAJriVDH@94E84AJ0000,0*4B + * + *--------------------------------------------------------------------*/ + +void waypoint_send_ais (char *sentence) +{ + if (s_waypoint_serial_port_fd == MYFDERROR && + s_waypoint_udp_sock_fd == -1) { + return; + } + + if (s_waypoint_formats & WPT_FORMAT_AIS) { + send_sentence (sentence); + } +} + + /* * Append CR LF and send it. */ static void send_sentence (char *sent) { - char final[256]; - - if (s_waypoint_port_fd == MYFDERROR) { - return; - } - if (s_waypoint_debug) { text_color_set(DW_COLOR_XMIT); - dw_printf ("%s\n", sent); + dw_printf ("waypoint send sentence: \"%s\"\n", sent); } strlcpy (final, sent, sizeof(final)); strlcat (final, "\r\n", sizeof(final)); + int final_len = strlen(final); - serial_port_write (s_waypoint_port_fd, final, strlen(final)); + if (s_waypoint_serial_port_fd != MYFDERROR) { + serial_port_write (s_waypoint_serial_port_fd, final, final_len); + } + + if (s_waypoint_udp_sock_fd != -1) { + int n = sendto(s_waypoint_udp_sock_fd, final, final_len, 0, (struct sockaddr*)(&s_udp_dest_addr), sizeof(struct sockaddr_in)); + if (n != final_len) { + text_color_set(DW_COLOR_ERROR); + dw_printf ("Failed to send waypoint via UDP, errno=%d\n", errno); + } + } } /* send_sentence */ @@ -613,10 +695,13 @@ static void send_sentence (char *sent) void waypoint_term (void) { - - if (s_waypoint_port_fd != MYFDERROR) { + if (s_waypoint_serial_port_fd != MYFDERROR) { //serial_port_close (s_waypoint_port_fd); - s_waypoint_port_fd = MYFDERROR; + s_waypoint_serial_port_fd = MYFDERROR; + } + if (s_waypoint_udp_sock_fd != -1) { + close (s_waypoint_udp_sock_fd); + s_waypoint_udp_sock_fd = -1; } } diff --git a/src/waypoint.h b/src/waypoint.h index 0f5ef3a..3ba6f1c 100644 --- a/src/waypoint.h +++ b/src/waypoint.h @@ -16,6 +16,8 @@ void waypoint_set_debug (int n); void waypoint_send_sentence (char *wname_in, double dlat, double dlong, char symtab, char symbol, float alt, float course, float speed, char *comment_in); +void waypoint_send_ais (char *sentence); + void waypoint_term ();