AIS Reception enhancements.

This commit is contained in:
wb2osz 2020-05-26 21:20:37 -04:00
parent 0edb44efc3
commit 45136a91eb
8 changed files with 409 additions and 81 deletions

View File

@ -41,6 +41,7 @@ list(APPEND direwolf_SOURCES
dtime_now.c dtime_now.c
dtmf.c dtmf.c
dwgps.c dwgps.c
dwsock.c
encode_aprs.c encode_aprs.c
encode_aprs.c encode_aprs.c
fcs_calc.c fcs_calc.c

192
src/ais.c
View File

@ -87,6 +87,10 @@ static const struct {
{ 96, 168 } // 27 96 or 168, not range { 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. * 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); 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. // Latitude of 0x3412140 (91 deg) means not available.
// Longitude of 0x6791AC0 (181 deg) means not available. // Message type 27 uses lower resolution, 17 bits rather than 27.
return ((double)get_field_signed(base, start, len) / 600000.0);
// Message type 27 uses lower resolution, 17 & 18 bits rather than 27 & 28.
// It encodes minutes/10 rather than normal minutes/10000. // 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) 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. // Raw 1023 means not available.
// Multiply by 0.1 to get knots. // Multiply by 0.1 to get knots.
// For aircraft it is knots, not deciknots. // 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) static float get_field_course (unsigned char *base, unsigned int start, unsigned int len)
{ {
// Raw 3600 means not available. // Raw 3600 means not available.
// Multiply by 0.1 to get degrees // 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) 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); int type = get_field(ais, 0, 6);
if (type >= 1 && type <= 27) { 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) { 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); snprintf (descr, descr_size, "AIS %d: Position Report Class A", type);
*symtab = '/'; *symtab = '/';
*symbol = 's'; // Power boat (ship) side view *symbol = 's'; // Power boat (ship) side view
*odlon = get_field_latlon(ais, 61, 28); *odlon = get_field_lon(ais, 61, 28);
*odlat = get_field_latlon(ais, 89, 27); *odlat = get_field_lat(ais, 89, 27);
*ofknots = get_field_speed(ais, 50, 10); *ofknots = get_field_speed(ais, 50, 10);
*ofcourse = get_field_course(ais, 116, 12); *ofcourse = get_field_course(ais, 116, 12);
get_ship_data(mssi, comment, comment_size);
break; break;
case 4: // Base Station Report 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); //hour = get_field(ais, 61, 5);
//minute = get_field(ais, 66, 6); //minute = get_field(ais, 66, 6);
//second = get_field(ais, 72, 6); //second = get_field(ais, 72, 6);
*odlon = get_field_latlon(ais, 79, 28); *odlon = get_field_lon(ais, 79, 28);
*odlat = get_field_latlon(ais, 107, 27); *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; break;
case 5: // Static and Voyage Related Data 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, 70, 42, callsign);
get_field_string(ais, 112, 120, shipname); get_field_string(ais, 112, 120, shipname);
get_field_string(ais, 302, 120, destination); get_field_string(ais, 302, 120, destination);
save_ship_data(mssi, shipname, callsign, destination);
if (strlen(destination) > 0) { get_ship_data(mssi, comment, comment_size);
snprintf (comment, comment_size, "%s, %s, dest. %s", shipname, callsign, destination);
}
else {
snprintf (comment, comment_size, "%s, %s", shipname, callsign);
}
} }
break; break;
@ -489,10 +530,12 @@ int ais_parse (char *sentence, int quiet, char *descr, int descr_size, char *mss
*symtab = '/'; *symtab = '/';
*symbol = '\''; // Small AIRCRAFT *symbol = '\''; // Small AIRCRAFT
*ofalt_m = get_field(ais, 38, 12); // meters, 4095 means not available *ofalt_m = get_field(ais, 38, 12); // meters, 4095 means not available
*odlon = get_field_latlon(ais, 61, 28); *odlon = get_field_lon(ais, 61, 28);
*odlat = get_field_latlon(ais, 89, 27); *odlat = get_field_lat(ais, 89, 27);
*ofknots = get_field_speed(ais, 50, 10) * 10.0; // plane is knots, not knots/10 *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); *ofcourse = get_field_course(ais, 116, 12);
get_ship_data(mssi, comment, comment_size);
break; break;
case 18: // Standard Class B CS Position Report 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); snprintf (descr, descr_size, "AIS %d: Standard Class B CS Position Report", type);
*symtab = '/'; *symtab = '/';
*symbol = 'Y'; // YACHT (sail) *symbol = 'Y'; // YACHT (sail)
*odlon = get_field_latlon(ais, 57, 28); *odlon = get_field_lon(ais, 57, 28);
*odlat = get_field_latlon(ais, 85, 27); *odlat = get_field_lat(ais, 85, 27);
get_ship_data(mssi, comment, comment_size);
break; break;
case 19: // Extended Class B CS Position Report 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); snprintf (descr, descr_size, "AIS %d: Extended Class B CS Position Report", type);
*symtab = '/'; *symtab = '/';
*symbol = 'Y'; // YACHT (sail) *symbol = 'Y'; // YACHT (sail)
*odlon = get_field_latlon(ais, 57, 28); *odlon = get_field_lon(ais, 57, 28);
*odlat = get_field_latlon(ais, 85, 27); *odlat = get_field_lat(ais, 85, 27);
get_ship_data(mssi, comment, comment_size);
break; break;
case 27: // Long Range AIS Broadcast message 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); snprintf (descr, descr_size, "AIS %d: Long Range AIS Broadcast message", type);
*symtab = '\\'; *symtab = '\\';
*symbol = 's'; // OVERLAY SHIP/boat (top view) *symbol = 's'; // OVERLAY SHIP/boat (top view)
*odlon = get_field_latlon(ais, 44, 18) * 1000; // Note: minutes/10 rather than usual /10000. *odlon = get_field_lon(ais, 44, 18); // Note: minutes/10 rather than usual /10000.
*odlat = get_field_latlon(ais, 62, 17) * 1000; *odlat = get_field_lat(ais, 62, 17);
*ofknots = get_field_speed(ais, 79, 6) * 10; // Note: knots, not deciknots. *ofknots = get_field_speed(ais, 79, 6); // Note: knots, not deciknots.
*ofcourse = get_field_course(ais, 85, 9) * 10; // Note: degrees, not decidegrees. *ofcourse = get_field_course(ais, 85, 9); // Note: degrees, not decidegrees.
get_ship_data(mssi, comment, comment_size);
break; break;
default: default:
@ -575,4 +621,90 @@ int ais_check_length (int type, int length)
} // end ais_check_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 // end ais.c

View File

@ -885,7 +885,7 @@ void config_init (char *fname, struct audio_s *p_audio_config,
p_misc_config->kiss_serial_poll = 0; p_misc_config->kiss_serial_poll = 0;
strlcpy (p_misc_config->gpsnmea_port, "", sizeof(p_misc_config->gpsnmea_port)); 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; p_misc_config->log_daily_names = 0;
strlcpy (p_misc_config->log_path, "", sizeof(p_misc_config->log_path)); 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 serial-device [ formats ]
* WAYPOINT host:udpport [ formats ]
* *
*/ */
else if (strcasecmp(t, "waypoint") == 0) { else if (strcasecmp(t, "waypoint") == 0) {
t = split(NULL,0); t = split(NULL,0);
if (t == NULL) { if (t == NULL) {
text_color_set(DW_COLOR_ERROR); 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; 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); t = split(NULL,1);
if (t != NULL) { if (t != NULL) {
for ( ; *t != '\0' ; t++ ) { for ( ; *t != '\0' ; t++ ) {
@ -4633,6 +4658,9 @@ void config_init (char *fname, struct audio_s *p_audio_config,
case 'K': case 'K':
p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD; p_misc_config->waypoint_formats |= WPT_FORMAT_KENWOOD;
break; break;
case 'A':
p_misc_config->waypoint_formats |= WPT_FORMAT_AIS;
break;
case ' ': case ' ':
case ',': case ',':
break; break;

View File

@ -65,10 +65,15 @@ struct misc_config_s {
/* Default is 2947. */ /* 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. */ /* to a GPS map display or other mapping application. */
/* e.g. COM22, /dev/ttyACM0 */ /* e.g. COM22, /dev/ttyACM0 */
/* Currently no option for setting non-standard speed. */ /* 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? */ 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_GARMIN 0x02 /* G $PGRMW */
#define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */ #define WPT_FORMAT_MAGELLAN 0x04 /* M $PMGNWPL */
#define WPT_FORMAT_KENWOOD 0x08 /* K $PKWDWPL */ #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. */ int log_daily_names; /* True to generate new log file each day. */

View File

@ -94,6 +94,7 @@
#include "ax25_pad.h" #include "ax25_pad.h"
#include "xid.h" #include "xid.h"
#include "decode_aprs.h" #include "decode_aprs.h"
#include "encode_aprs.h"
#include "textcolor.h" #include "textcolor.h"
#include "server.h" #include "server.h"
#include "kiss.h" #include "kiss.h"
@ -123,6 +124,7 @@
#include "ax25_link.h" #include "ax25_link.h"
#include "dtime_now.h" #include "dtime_now.h"
#include "fx25.h" #include "fx25.h"
#include "dwsock.h"
//static int idx_decoded = 0; //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_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 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[]) int main (int argc, char *argv[])
{ {
@ -284,7 +288,7 @@ int main (int argc, char *argv[])
text_color_init(t_opt); text_color_init(t_opt);
text_color_set(DW_COLOR_INFO); 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 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); //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 * Apple computers with Intel processors started with P6. Since the
* cpu test code was giving Clang compiler grief it has been excluded. * 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__) #if defined(__SSE__) && !defined(__APPLE__)
int cpuinfo[4]; int cpuinfo[4]; // EAX, EBX, ECX, EDX
__cpuid (cpuinfo, 0); __cpuid (cpuinfo, 0);
if (cpuinfo[0] >= 1) { if (cpuinfo[0] >= 1) {
__cpuid (cpuinfo, 1); __cpuid (cpuinfo, 1);
//dw_printf ("debug: cpuinfo = %x, %x, %x, %x\n", cpuinfo[0], cpuinfo[1], cpuinfo[2], cpuinfo[3]); //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))) { if ( ! ( cpuinfo[3] & (1 << 25))) {
text_color_set(DW_COLOR_ERROR); text_color_set(DW_COLOR_ERROR);
dw_printf ("------------------------------------------------------------------\n"); dw_printf ("------------------------------------------------------------------\n");
dw_printf ("This version requires a minimum of a Pentium 3 or equivalent.\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 ("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 ("from the previous Century. See instructions in User Guide for\n");
dw_printf ("information on how you can recompile it for use with your antique.\n"); dw_printf ("information on how you can compile it for use with your antique.\n");
dw_printf ("------------------------------------------------------------------\n"); dw_printf ("------------------------------------------------------------------\n");
} }
} }
@ -373,7 +387,7 @@ int main (int argc, char *argv[])
/* ':' following option character means arg is required. */ /* ':' 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); long_options, &option_index);
if (c == -1) if (c == -1)
break; break;
@ -636,6 +650,11 @@ int main (int argc, char *argv[])
X_fx25_xmit_enable = atoi(optarg); X_fx25_xmit_enable = atoi(optarg);
break; break;
case 'A': // -A convert AIS to APRS object
A_opt_ais_to_obj = 1;
break;
default: default:
/* Should not be here. */ /* Should not be here. */
@ -670,6 +689,8 @@ int main (int argc, char *argv[])
symbols_init (); symbols_init ();
(void)dwsock_init();
config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config); config_init (config_file, &audio_config, &digi_config, &cdigi_config, &tt_config, &igate_config, &misc_config);
if (r_opt != 0) { 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. * Suppress printed decoding if "-q d" option used.
*/ */
char ais_obj_packet[300];
strcpy (ais_obj_packet, "");
if (ax25_is_aprs(pp)) { 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); 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. // 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 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 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. * If it came from DTMF decoder, send it to APRStt gateway.
* Otherwise, it is a candidate for IGate and digipeater. * 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! * 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) { if (ax25_is_aprs(pp) && retries == RETRY_NONE) {
digipeater (chan, pp); 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 direwolf <= 1.5.\n");
dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n"); dw_printf (" -J 2400 bps QPSK compatible with MFJ-2400.\n");
dw_printf (" -P xxx Modem Profiles.\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 (" -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 (" -d Debug options:\n");
dw_printf (" a a = AGWPE network protocol client.\n"); dw_printf (" a a = AGWPE network protocol client.\n");
dw_printf (" k k = KISS serial port or pseudo terminal client.\n"); dw_printf (" k k = KISS serial port or pseudo terminal client.\n");

View File

@ -215,6 +215,7 @@ int gen_tone_init (struct audio_s *audio_config_p, int amp, int gen_packets)
case MODEM_BASEBAND: case MODEM_BASEBAND:
case MODEM_SCRAMBLE: case MODEM_SCRAMBLE:
case MODEM_AIS:
// Tone is half baud. // Tone is half baud.
ticks_per_bit[chan] = (int) ((TICKS_PER_CYCLE / (double)audio_config_p->achan[chan].baud ) + 0.5); 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_BASEBAND:
case MODEM_SCRAMBLE: case MODEM_SCRAMBLE:
case MODEM_AIS:
if (dat != prev_dat[chan]) { if (dat != prev_dat[chan]) {
tone_phase[chan] += f1_change_per_sample[chan]; tone_phase[chan] += f1_change_per_sample[chan];

View File

@ -1,7 +1,7 @@
// //
// This file is part of Dire Wolf, an amateur radio packet TNC. // 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 // 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 // it under the terms of the GNU General Public License as published by
@ -32,16 +32,21 @@
#include "direwolf.h" // should be first #include "direwolf.h" // should be first
#include <stdio.h> #include <stdio.h>
#include <unistd.h> #include <unistd.h>
#include <stdlib.h>
#if __WIN32__ #if __WIN32__
#include <stdlib.h> #include <winsock2.h>
#include <ws2tcpip.h> // _WIN32_WINNT must be set to 0x0501 before including this
#else #else
#include <stdlib.h>
#include <ctype.h> #include <ctype.h>
#include <errno.h> #include <errno.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
//#include <arpa/inet.h>
#include <netdb.h> // gethostbyname
#endif #endif
#include <assert.h> #include <assert.h>
@ -57,9 +62,12 @@
#include "mgn_icon.h" /* Magellan icons */ #include "mgn_icon.h" /* Magellan icons */
#include "dwgpsnmea.h" #include "dwgpsnmea.h"
#include "serial_port.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_formats = 0; /* which formats should we generate? */
@ -86,19 +94,24 @@ void waypoint_set_debug (int n)
* *
* Inputs: mc - Pointer to configuration options. * 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 * (currently none) - speed, baud. Default 4800 if not set
* *
* *
* ->waypoint_formats - Set of formats enabled. * ->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. * Description: First to see if this is shared with GPS input.
* If not, open serial port. * 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 * Restriction: MUST be done after GPS init because we might be sharing the
* same serial port device. * same serial port device.
@ -111,32 +124,66 @@ void waypoint_init (struct misc_config_s *mc)
#if DEBUG #if DEBUG
text_color_set (DW_COLOR_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 #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: * TODO:
* Are we sharing with GPS input? * Are we sharing with GPS input?
* First try to get fd if they have same device name. * First try to get fd if they have same device name.
* If that fails, do own serial port open. * 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_serial_port_fd = dwgpsnmea_get_fd (mc->waypoint_serial_port, 4800);
s_waypoint_port_fd = serial_port_open (mc->waypoint_port, 4800);
if (s_waypoint_serial_port_fd == MYFDERROR) {
s_waypoint_serial_port_fd = serial_port_open (mc->waypoint_serial_port, 4800);
} }
else { else {
text_color_set (DW_COLOR_INFO); text_color_set (DW_COLOR_INFO);
dw_printf ("Note: Sharing same port for GPS input and waypoint output.\n"); 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); text_color_set (DW_COLOR_ERROR);
dw_printf ("Unable to open %s for waypoint output.\n", mc->waypoint_port); dw_printf ("Unable to open serial port %s for waypoint output.\n", mc->waypoint_serial_port);
return;
} }
}
// Set default formats if user did not specify any.
s_waypoint_formats = mc->waypoint_formats; s_waypoint_formats = mc->waypoint_formats;
if (s_waypoint_formats == 0) { if (s_waypoint_formats == 0) {
@ -145,12 +192,11 @@ void waypoint_init (struct misc_config_s *mc)
if (s_waypoint_formats & WPT_FORMAT_GARMIN) { if (s_waypoint_formats & WPT_FORMAT_GARMIN) {
s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */ s_waypoint_formats |= WPT_FORMAT_NMEA_GENERIC; /* See explanation below. */
} }
}
#if DEBUG #if DEBUG
text_color_set (DW_COLOR_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 #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); dw_printf ("waypoint_send_sentence (\"%s\", \"%c%c\")\n", name_in, symtab, symbol);
#endif #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. * 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 */ } /* 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. * Append CR LF and send it.
*/ */
static void send_sentence (char *sent) static void send_sentence (char *sent)
{ {
char final[256]; char final[256];
if (s_waypoint_port_fd == MYFDERROR) {
return;
}
if (s_waypoint_debug) { if (s_waypoint_debug) {
text_color_set(DW_COLOR_XMIT); text_color_set(DW_COLOR_XMIT);
dw_printf ("%s\n", sent); dw_printf ("waypoint send sentence: \"%s\"\n", sent);
} }
strlcpy (final, sent, sizeof(final)); strlcpy (final, sent, sizeof(final));
strlcat (final, "\r\n", 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 */ } /* send_sentence */
@ -613,10 +695,13 @@ static void send_sentence (char *sent)
void waypoint_term (void) void waypoint_term (void)
{ {
if (s_waypoint_serial_port_fd != MYFDERROR) {
if (s_waypoint_port_fd != MYFDERROR) {
//serial_port_close (s_waypoint_port_fd); //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;
} }
} }

View File

@ -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, void waypoint_send_sentence (char *wname_in, double dlat, double dlong, char symtab, char symbol,
float alt, float course, float speed, char *comment_in); float alt, float course, float speed, char *comment_in);
void waypoint_send_ais (char *sentence);
void waypoint_term (); void waypoint_term ();