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 <stdio.h>
 #include <unistd.h>
+#include <stdlib.h>
 
 #if __WIN32__
-#include <stdlib.h>
+#include <winsock2.h>
+#include <ws2tcpip.h>           // _WIN32_WINNT must be set to 0x0501 before including this
 #else
-#include <stdlib.h>
 #include <ctype.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
 
 #include <assert.h>
@@ -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 ();