]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
refclock_nmea.c:
authorDave Hart <hart@ntp.org>
Tue, 31 Mar 2009 09:33:53 +0000 (09:33 +0000)
committerDave Hart <hart@ntp.org>
Tue, 31 Mar 2009 09:33:53 +0000 (09:33 +0000)
  [Bug 1148] NMEA reference clock improvements

bk: 49d1e381J59_wMkTRfcuXWkZErHa1g

ChangeLog
ntpd/refclock_nmea.c

index 2400e8d2bfbd717418360f44ddb3a02d4a0a880f..9fb7093b4683ffe6d038eb68aceeb00bea0b1299 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,4 @@
+* [Bug 1148] NMEA reference clock improvements
 * Documentation updates from Dave Mills.
 (4.2.5p160) 2009/03/30 Released by Harlan Stenn <stenn@ntp.org>
 * Include (4.2.4p7)
index ab61f119ad3af83cc326420dbf620e6e93200403..937087ca8098bcc38a5461474e39bdeacae660ef 100644 (file)
@@ -36,9 +36,9 @@ extern int async_write(int, const void *, unsigned int);
 #endif
 
 /*
- * This driver supports the NMEA GPS Receiver with
+ * This driver supports NMEA-compatible GPS receivers
  *
- * Protype was refclock_trak.c, Thanks a lot.
+ * Prototype was refclock_trak.c, Thanks a lot.
  *
  * The receiver used spits out the NMEA sentences for boat navigation.
  * And you thought it was an information superhighway.  Try a raging river
@@ -59,14 +59,26 @@ extern int async_write(int, const void *, unsigned int);
  * bit 0 - enables RMC (1)
  * bit 1 - enables GGA (2)
  * bit 2 - enables GLL (4)
- * bit 3 - enables ZGD (8) - Accord GPS Clock's custom sentence with GPS time
+ * bit 3 - enables ZDA (8) - Standard Time & Date
+ * bit 3 - enables ZDG (8) - Accord GPS Clock's custom sentence with GPS time 
+ *                          very close to standard ZDA
  * 
- * Multiple sentences may be selected except when ZDG is selected.
+ * Multiple sentences may be selected except when ZDG/ZDA is selected.
  *
- * bit 4 - selects the baudrate for serial port :
+ * bit 4/5/6 - selects the baudrate for serial port :
  *         0 for 4800 (default) 
  *         1 for 9600 
+ *         2 for 19200 
+ *         3 for 38400 
+ *         4 for 57600 
+ *         5 for 115200 
  */
+#define NMEA_MESSAGE_MASK_OLD    0x07
+#define NMEA_MESSAGE_MASK_SINGLE 0x08
+#define NMEA_MESSAGE_MASK        (NMEA_MESSAGE_MASK_OLD | NMEA_MESSAGE_MASK_SINGLE)
+
+#define NMEA_BAUDRATE_MASK       0x70
+#define NMEA_BAUDRATE_SHIFT      4
 
 /*
  * Definitions
@@ -86,13 +98,6 @@ extern int async_write(int, const void *, unsigned int);
 
 #define LENNMEA                75      /* min timecode length */
 
-/*
- * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
- * leap.
- */
-static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
-
 /*
  * Unit control structure
  */
@@ -123,6 +128,7 @@ static      void    nmea_receive    (struct recvbuf *);
 static void    nmea_poll       (int, struct peer *);
 static void    gps_send        (int, const char *, struct peer *);
 static char    *field_parse    (char *, int);
+static int     nmea_checksum_ok(const char *);
 
 /*
  * Transfer vector
@@ -154,6 +160,8 @@ nmea_start(
        struct refclockproc *pp;
        int fd;
        char device[20];
+       int baudrate;
+       char *baudtext;
 
        /*
         * Open serial port. Use CLK line discipline, if available.
@@ -162,83 +170,112 @@ nmea_start(
        
        /*
         * Opening the serial port with appropriate baudrate
-        * based on the value of bit 4.
+        * based on the value of bit 4/5/6
         */
-       if ( peer->ttl & 0x10 ) {
-               fd = refclock_open(device, 2*SPEED232, LDISC_CLK);
-               msyslog(LOG_NOTICE, 
-                               "refclock_nmea : serial port baudrate set to 9600");
-       } else {
-               fd = refclock_open(device, SPEED232, LDISC_CLK);
-               msyslog(LOG_NOTICE,
-                               "refclock_nmea : serial port baudrate set to 4800");
+       switch ((peer->ttl & NMEA_BAUDRATE_MASK) >> NMEA_BAUDRATE_SHIFT) {
+           case 0:
+           case 6:
+           case 7:
+           default:
+               baudrate = SPEED232;
+               baudtext = "4800";
+               break;
+           case 1:
+               baudrate = B9600;
+               baudtext = "9600";
+               break;
+           case 2:
+               baudrate = B19200;
+               baudtext = "19200";
+               break;
+           case 3:
+               baudrate = B38400;
+               baudtext = "38400";
+               break;
+#ifdef B57600
+           case 4:
+               baudrate = B57600;
+               baudtext = "57600";
+               break;
+#endif
+#ifdef B115200
+           case 5:
+               baudrate = B115200;
+               baudtext = "115200";
+               break;
+#endif
        }
+
+       fd = refclock_open(device, baudrate, LDISC_CLK);
        
        /*
-        * Checking the validity of mode field if multiple sectences
+        * Checking the validity of mode field if multiple sentences
         * have been selected.
         */
-       if ( (peer->ttl & 0x0F) > 8 ) {
+       if ( (peer->ttl & NMEA_MESSAGE_MASK_SINGLE) && (peer->ttl & NMEA_MESSAGE_MASK_OLD) ) {
                msyslog(LOG_ERR,
                        "refclock_nmea : invaid combination of NMEA sentences" );
                
                msyslog(LOG_ERR,
                        "refclock_nmea : reseting mode to standard NMEA sentences" );
-               peer->ttl = 0x07;
+               peer->ttl &= NMEA_MESSAGE_MASK_OLD;
        }
 
        if (fd <= 0) {
 #ifdef HAVE_READLINK
-          /* nmead support added by Jon Miner (cp_n18@yahoo.com)
-           *
-           * See http://home.hiwaay.net/~taylorc/gps/nmea-server/
-           * for information about nmead
-           *
-           * To use this, you need to create a link from /dev/gpsX to
-           * the server:port where nmead is running.  Something like this:
-           *
-           * ln -s server:port /dev/gps1
-           */
-          char buffer[80];
-          char *nmea_host;
-          int   nmea_port;
-          int   len;
-          struct hostent *he;
-          struct protoent *p;
-          struct sockaddr_in so_addr;
-
-          if ((len = readlink(device,buffer,sizeof(buffer))) == -1)
-            return(0);
-          buffer[len] = 0;
-
-          if ((nmea_host = strtok(buffer,":")) == NULL)
-            return(0);
-         
-          nmea_port = atoi(strtok(NULL,":"));
-
-          if ((he = gethostbyname(nmea_host)) == NULL)
-            return(0);
-          if ((p = getprotobyname("ip")) == NULL)
-            return(0);
-          so_addr.sin_family = AF_INET;
-          so_addr.sin_port = htons(nmea_port);
-          so_addr.sin_addr = *((struct in_addr *) he->h_addr);
-
-          if ((fd = socket(PF_INET,SOCK_STREAM,p->p_proto)) == -1)
-            return(0);
-          if (connect(fd,(struct sockaddr *)&so_addr,SOCKLEN(&so_addr)) == -1) {
-            close(fd);
-            return (0);
-          }
+               /* nmead support added by Jon Miner (cp_n18@yahoo.com)
+                *
+                * See http://home.hiwaay.net/~taylorc/gps/nmea-server/
+                * for information about nmead
+                *
+                * To use this, you need to create a link from /dev/gpsX to
+                * the server:port where nmead is running.  Something like this:
+                *
+                * ln -s server:port /dev/gps1
+                */
+               char buffer[80];
+               char *nmea_host;
+               int   nmea_port;
+               int   len;
+               struct hostent *he;
+               struct protoent *p;
+               struct sockaddr_in so_addr;
+
+               if ((len = readlink(device,buffer,sizeof(buffer))) == -1)
+                       return(0);
+               buffer[len] = 0;
+
+               if ((nmea_host = strtok(buffer,":")) == NULL)
+                       return(0);
+
+               nmea_port = atoi(strtok(NULL,":"));
+
+               if ((he = gethostbyname(nmea_host)) == NULL)
+                       return(0);
+               if ((p = getprotobyname("ip")) == NULL)
+                       return(0);
+               so_addr.sin_family = AF_INET;
+               so_addr.sin_port = htons(nmea_port);
+               so_addr.sin_addr = *((struct in_addr *) he->h_addr);
+
+               if ((fd = socket(PF_INET,SOCK_STREAM,p->p_proto)) == -1)
+                       return(0);
+               if (connect(fd,(struct sockaddr *)&so_addr,SOCKLEN(&so_addr)) == -1) {
+                       close(fd);
+                       return (0);
+               }
 #else
-            return (0);
+               return (0);
 #endif
-        }
+       }
+
+       msyslog(LOG_NOTICE, "refclock_nmea: serial %s open at %s bps",
+               device, baudtext);
 
        /*
         * Allocate and initialize unit structure
         */
-       up = (struct nmeaunit *)emalloc(sizeof(struct nmeaunit));
+       up = (struct nmeaunit *) emalloc(sizeof(*up));
        if (up == NULL) {
                (void) close(fd);
                return (0);
@@ -368,8 +405,8 @@ nmea_ppsapi(
                        up->pps_params.assert_off_tu.tspec.tv_nsec = ((*fudge) - (long)(*fudge))*(-1e9);
                } else if ((enb_clear) && (capability & PPS_OFFSETCLEAR)) {
                        /* Offset on clear */
-                       up->pps_params.mode |= PPS_OFFSETCLEAR; 
-                       up->pps_params.clear_off_tu.tspec.tv_sec = -(*fudge);
+                       up->pps_params.mode |= PPS_OFFSETCLEAR; 
+                       up->pps_params.clear_off_tu.tspec.tv_sec = -(*fudge);
                        up->pps_params.clear_off_tu.tspec.tv_nsec = ((*fudge) - (long)(*fudge))*(-1e9);
                }
        }
@@ -468,13 +505,13 @@ nmea_receive(
        struct refclockproc *pp;
        struct peer *peer;
        int month, day;
-       int i;
-       char *cp, *dp;
+       char *cp, *dp, *bp, *msg;
        int cmdtype;
+       int blanking;
        /* Use these variables to hold data until we decide its worth keeping */
        char    rd_lastcode[BMAX];
-       l_fp    rd_tmp;
-       u_short rd_lencode;
+       l_fp    rd_timestamp;
+       int     rd_lencode;
 
        /*
         * Initialize pointers and read the timecode and timestamp
@@ -482,42 +519,40 @@ nmea_receive(
        peer = (struct peer *)rbufp->recv_srcclock;
        pp = peer->procptr;
        up = (struct nmeaunit *)pp->unitptr;
-       rd_lencode = (u_short)refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp);
+
+       rd_lencode = refclock_gtlin(
+                       rbufp, 
+                       rd_lastcode, 
+                       sizeof(rd_lastcode), 
+                       &rd_timestamp);
 
        /*
         * There is a case that a <CR><LF> gives back a "blank" line
         */
        if (rd_lencode == 0)
-           return;
+               return;
 
-#ifdef DEBUG
-       if (debug)
-           printf("nmea: gpsread %d %s\n", rd_lencode,
-                  rd_lastcode);
-#endif
+       DPRINTF(1, ("nmea: gpsread %d %s\n", rd_lencode, rd_lastcode));
 
        /*
         * We check the timecode format and decode its contents. The
         * we only care about a few of them.  The most important being
         * the $GPRMC format
         * $GPRMC,hhmmss,a,fddmm.xx,n,dddmmm.xx,w,zz.z,yyy.,ddmmyy,dd,v*CC
-        * For Magellan (ColorTrak) GLL probably datum (order of sentences)
-        * also mode (0,1,2,3) select sentence ANY/ALL, RMC, GGA, GLL
+        * mode (0,1,2,3) selects sentence ANY/ALL, RMC, GGA, GLL, ZDA
         * $GPGLL,3513.8385,S,14900.7851,E,232420.594,A*21
-        * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F
-        * $GPRMB,...
+        * $GPGGA,232420.59,3513.8385,S,14900.7851,E,1,05,3.4,00519,M,,,,*3F
         * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77
-        * $GPAPB,...
-        * $GPGSA,...
-        * $GPGSV,...
-        * $GPGSV,...
-        */
-#define GPXXX  0
-#define GPRMC  1
-#define GPGGA  2
-#define GPGLL  4
-       
-       /* 
+        *
+        * Defining GPZDA to support Standard Time & Date
+        * sentence. The sentence has the following format 
+        *  
+        *  $--ZDA,HHMMSS.SS,DD,MM,YYYY,TH,TM,*CS<CR><LF>
+        *
+        *  Apart from the familiar fields, 
+        *  'TH'    Time zone Hours
+        *  'TM'    Time zone Minutes
+        *
         * Defining GPZDG to support Accord GPS Clock's custom NMEA 
         * sentence. The sentence has the following format 
         *  
@@ -527,134 +562,133 @@ nmea_receive(
         *  Apart from the familiar fields, 
         *  'AA.BB' denotes the signal strength( should be < 05.00 ) 
         *  'V'     denotes the GPS sync status : 
-        *         '0' indicates INVALID time, 
-        *         '1' indicates accuracy of +/-20 ms
-        *         '2' indicates accuracy of +/-100 ns
-        */ 
-
-#define GPZDG  8
+        *         '0' indicates INVALID time, 
+        *         '1' indicates accuracy of +/-20 ms
+        *         '2' indicates accuracy of +/-100 ns
+        */
+#define GPXXX          0       /* any/all */
+#define GPRMC          1
+#define GPGGA          2
+#define GPGLL          4
+#define GPZDG_ZDA      8
 
        cp = rd_lastcode;
        cmdtype=0;
-       if(strncmp(cp,"$GPRMC",6)==0) {
-               cmdtype=GPRMC;
-       }
-       else if(strncmp(cp,"$GPGGA",6)==0) {
-               cmdtype=GPGGA;
-       }
-       else if(strncmp(cp,"$GPGLL",6)==0) {
-               cmdtype=GPGLL;
-       }
-       else if(strncmp(cp,"$GPZDG",6)==0) {
-               cmdtype=GPZDG;
-       }
-       else if(strncmp(cp,"$GPXXX",6)==0) {
-               cmdtype=GPXXX;
-       }
-       else
-           return;
-
+       if (cp[0] == '$') {
+               /* Allow for GLGGA and GPGGA etc. */
+               msg = cp + 3;
+
+               if (strncmp(msg, "RMC", 3) == 0)
+                       cmdtype = GPRMC;
+               else if (strncmp(msg, "GGA", 3) == 0)
+                       cmdtype = GPGGA;
+               else if (strncmp(msg, "GLL", 3) == 0)
+                       cmdtype = GPGLL;
+               else if (strncmp(msg, "ZDG", 3) == 0 ||
+                        strncmp(msg, "ZDA", 3) == 0)
+                       cmdtype = GPZDG_ZDA;
+               else
+                       return;
+       } else
+               return;
 
        /* See if I want to process this message type */
-       if ( ((peer->ttl == 0) && (cmdtype != GPRMC))
-           || ((peer->ttl != 0) && !(cmdtype & (peer->ttl & 0x0F)) ))
+       if (peer->ttl && !(cmdtype & (peer->ttl & NMEA_MESSAGE_MASK)))
                return;
 
-       pp->lencode = rd_lencode;
-       strcpy(pp->a_lastcode,rd_lastcode);
+       /* make sure it came in clean */
+       if (0 == nmea_checksum_ok(rd_lastcode)) {
+               refclock_report(peer, CEVNT_BADREPLY);
+               return;
+       }
+
+       pp->lencode = (u_short) rd_lencode;
+       memcpy(pp->a_lastcode, rd_lastcode, pp->lencode + 1);
        cp = pp->a_lastcode;
 
-       pp->lastrec = up->tstamp = rd_tmp;
+       pp->lastrec = up->tstamp = rd_timestamp;
        up->pollcnt = 2;
 
-#ifdef DEBUG
-       if (debug)
-           printf("nmea: timecode %d %s\n", pp->lencode,
-                  pp->a_lastcode);
-#endif
-
+       DPRINTF(1, ("nmea: timecode %d %s\n", pp->lencode, pp->a_lastcode));
 
        /* Grab field depending on clock string type */
-       switch( cmdtype ) {
+       switch (cmdtype) {
+
            case GPRMC:
                /*
                 * Test for synchronization.  Check for quality byte.
                 */
-               dp = field_parse(cp,2);
-               ifdp[0] != 'A')
+               dp = field_parse(cp, 2);
+               if (dp[0] != 'A')
                        pp->leap = LEAP_NOTINSYNC;
                else
                        pp->leap = LEAP_NOWARNING;
 
                /* Now point at the time field */
-               dp = field_parse(cp,1);
+               dp = field_parse(cp, 1);
                break;
 
-
            case GPGGA:
                /*
                 * Test for synchronization.  Check for quality byte.
                 */
-               dp = field_parse(cp,6);
-               ifdp[0] == '0')
+               dp = field_parse(cp, 6);
+               if (dp[0] == '0')
                        pp->leap = LEAP_NOTINSYNC;
                else
                        pp->leap = LEAP_NOWARNING;
 
                /* Now point at the time field */
-               dp = field_parse(cp,1);
+               dp = field_parse(cp, 1);
                break;
 
-
            case GPGLL:
                /*
                 * Test for synchronization.  Check for quality byte.
                 */
-               dp = field_parse(cp,6);
-               ifdp[0] != 'A')
+               dp = field_parse(cp, 6);
+               if (dp[0] != 'A')
                        pp->leap = LEAP_NOTINSYNC;
                else
                        pp->leap = LEAP_NOWARNING;
 
                /* Now point at the time field */
-               dp = field_parse(cp,5);
+               dp = field_parse(cp, 5);
                break;
        
-               case GPZDG:
+           case GPZDG_ZDA:
                /*
-                * Test for synchronization.  Check for validity of GPS time.
+                * Test for synchronization.  For $GPZDG check for validity of GPS time.
                 */
-               dp = field_parse(cp,6);
-               if( dp[0] == '0') {
-                       pp->leap = LEAP_NOTINSYNC;
-                       msyslog(LOG_ERR,"ref_nmea : accord gps clk yet to sync" );
-               } else if ( (dp[0] == '1') || (dp[0] == '2') ) {
-                       pp->leap = LEAP_NOWARNING;
+               if (cp[5] == 'G') {
+                       dp = field_parse(cp, 6);
+                       if (dp[0] == '0') 
+                               pp->leap = LEAP_NOTINSYNC;
+                       else 
+                               pp->leap = LEAP_NOWARNING;
                }
 
                /* Now point at the time field */
-               dp = field_parse(cp,1);
+               dp = field_parse(cp, 1);
                break;
 
-
-           case GPXXX:
-               return;
            default:
                return;
-
        }
 
        /*
         * Check time code format of NMEA
         */
-       if!isdigit((int)dp[0]) ||
+       if (!isdigit((int)dp[0]) ||
            !isdigit((int)dp[1]) ||
            !isdigit((int)dp[2]) ||
            !isdigit((int)dp[3]) ||
            !isdigit((int)dp[4]) ||
-           !isdigit((int)dp[5])        
-           ) {
-               refclock_report(peer, CEVNT_BADREPLY);
+           !isdigit((int)dp[5])) {
+
+               DPRINTF(1, ("NMEA time code %c%c%c%c%c%c non-numeric",
+                           dp[0], dp[1], dp[2], dp[3], dp[4], dp[5]));
+               refclock_report(peer, CEVNT_BADTIME);
                return;
        }
 
@@ -664,9 +698,10 @@ nmea_receive(
        pp->hour = ((dp[0] - '0') * 10) + dp[1] - '0';
        pp->minute = ((dp[2] - '0') * 10) + dp[3] -  '0';
        pp->second = ((dp[4] - '0') * 10) + dp[5] - '0';
-       /* Default to 0 milliseconds, if decimal convert milliseconds in
-          one, two or three digits
-       */
+       /* 
+        * Default to 0 milliseconds, if decimal convert milliseconds in
+        * one, two or three digits
+        */
        pp->nsec = 0; 
        if (dp[6] == '.') {
                if (isdigit((int)dp[7])) {
@@ -680,28 +715,27 @@ nmea_receive(
                }
        }
 
-       
        /*
         * Manipulating GPS timestamp in GPZDG as the seconds field
         * is valid for next PPS tick. Just rolling back the second,
         * minute and hour fields appopriately
         */
-       if ( cmdtype == GPZDG ) {
-               if ( pp->second == 0 ) {
+       if (cmdtype == GPZDG_ZDA && cp[5] == 'G')
+               if (pp->second == 0) {
                        pp->second = 59;        
-                       if ( pp->minute == 0 ) {
+                       if (pp->minute == 0) {
                                pp->minute = 59;
-                               if ( pp->hour == 0 ) {
+                               if (pp->hour == 0)
                                        pp->hour = 23;
-                               }
                        }
-               } else {
+               } else
                        pp->second -= 1;
-               }
-       }
 
-       if (pp->hour > 23 || pp->minute > 59 || pp->second > 59
-         || pp->nsec > 1000000000) {
+       if (pp->hour > 23 || pp->minute > 59 || 
+           pp->second > 59 || pp->nsec > 1000000000) {
+
+               DPRINTF(1, ("NMEA hour/min/sec/nsec range %02d:%02d:%02d.%09d\n",
+                           pp->hour, pp->minute, pp->second, pp->nsec));
                refclock_report(peer, CEVNT_BADTIME);
                return;
        }
@@ -710,56 +744,129 @@ nmea_receive(
         * Convert date and check values.
         */
        if (cmdtype==GPRMC) {
-           dp = field_parse(cp,9);
-           day = dp[0] - '0';
-           day = (day * 10) + dp[1] - '0';
-           month = dp[2] - '0';
-           month = (month * 10) + dp[3] - '0';
-           pp->year = dp[4] - '0';
-           pp->year = (pp->year * 10) + dp[5] - '0';
-       }
-       else {
-       /* only time */
-           time_t tt = time(NULL);
-           struct tm * t = gmtime(&tt);
-           day = t->tm_mday;
-           month = t->tm_mon + 1;
-           pp->year= t->tm_year;
+
+               dp = field_parse(cp,9);
+               day = dp[0] - '0';
+               day = (day * 10) + dp[1] - '0';
+               month = dp[2] - '0';
+               month = (month * 10) + dp[3] - '0';
+               pp->year = dp[4] - '0';
+               pp->year = (pp->year * 10) + dp[5] - '0';
+
+       } else if (cmdtype==GPZDG_ZDA) {
+
+               dp = field_parse(cp, 2);
+               day = 10 * (dp[0] - '0') + (dp[1] - '0');
+               dp = field_parse(cp, 3);
+               month = 10 * (dp[0] - '0') + (dp[1] - '0');
+               dp = field_parse(cp, 4);
+               pp->year = /* 1000 * (dp[0] - '0') + 100 * (dp[1] - '0') + */ 10 * (dp[2] - '0') + (dp[3] - '0');
+
+       } else {
+               /* only time */
+               time_t tt = time(NULL);
+               struct tm * t = gmtime(&tt);
+               day = t->tm_mday;
+               month = t->tm_mon + 1;
+               pp->year= t->tm_year + 1900;
        }
 
        if (month < 1 || month > 12 || day < 1) {
-               refclock_report(peer, CEVNT_BADTIME);
+               refclock_report(peer, CEVNT_BADDATE);
                return;
        }
 
-    /* Hmmmm this will be a nono for 2100,2200,2300 but I don't think I'll be here */
-    /* good thing that 2000 is a leap year */
-       /* pp->year will be 00-99 if read from GPS, 00->  (years since 1900) from tm_year */
-       if (pp->year % 4) {
-               if (day > day1tab[month - 1]) {
-                       refclock_report(peer, CEVNT_BADTIME);
-                       return;
-               }
-               for (i = 0; i < month - 1; i++)
-                   day += day1tab[i];
-       } else {
-               if (day > day2tab[month - 1]) {
-                       refclock_report(peer, CEVNT_BADTIME);
-                       return;
-               }
-               for (i = 0; i < month - 1; i++)
-                   day += day2tab[i];
+       /* pp->year will be 2 or 4 digits if read from GPS, 4 from gmtime */
+       if (pp->year < 100)
+               if (pp->year < 9)       /* year of our line of code is 2009 */
+                       pp->year += 2100;
+               else
+                       pp->year += 2000;
+
+       /* pp->year now 4 digits as ymd2yd requires */
+       day = ymd2yd(pp->year, month, day);
+       if (-1 == day) {
+               refclock_report(peer, CEVNT_BADDATE);
+               return;
        }
        pp->day = day;
 
+       /*
+        * ntpq -c clockvar clients don't really need to know our 
+        * position with perfect accuracy, so for privacy blank
+        * out least significant digits.  To avoid leaking the
+        * omitted info indirectly, also blank checksum.
+        *
+        * Start by pointing cp and dp at the fields with 
+        * longitude and latitude in the last timecode.
+        */
+       switch (cmdtype) {
+
+           case GPGLL:
+               cp = field_parse(pp->a_lastcode, 1);
+               dp = field_parse(cp, 2);
+               break;
+
+           case GPGGA:
+               cp = field_parse(pp->a_lastcode, 2);
+               dp = field_parse(cp, 2);
+               break;
+
+           case GPRMC:
+               cp = field_parse(pp->a_lastcode, 3);
+               dp = field_parse(cp, 2);
+               break;
+
+           case GPZDG_ZDA:
+           default:
+               cp = dp = NULL;
+       }
+
+       /*
+        * Blanking everything after the decimal point '.' is easy and 
+        * gives enough error for at least a few neighbors to be as 
+        * likely as you to be the one with the reflock.  We're keeping
+        * degrees and minutes but tossing the seconds (expressed as
+        * decimal fractions of a minute).  Degrees minutes seconds,
+        * not hours minutes seconds.  :)
+        */
+       bp = cp;
+       while (bp) {
+               blanking = 0;
+               while (',' != *bp) {
+                       if (blanking)
+                               *bp = '_';
+                       else if ('.' == *bp)
+                               blanking = 1;
+                       bp++;
+               }
+
+               /*
+                * blank the longitude at cp then the latitude at dp
+                * then we're done.
+                */
+               if (bp < dp)
+                       bp = dp;
+               else
+                       bp = NULL;
+       }
+
+       /*
+        * blank the checksum, last two characters on the line
+        */
+       if (cp) {
+               bp = pp->a_lastcode + pp->lencode - 2;
+               if (0 == bp[2])
+                       bp[0] = bp[1] = '_';
+       }
 
 #ifdef HAVE_PPSAPI
        /*
         * If the PPSAPI is working, rather use its timestamps.
         * assume that the PPS occurs on the second so blow any msec
         */
-       if (nmea_pps(up, &rd_tmp) == 1) {
-               pp->lastrec = up->tstamp = rd_tmp;
+       if (nmea_pps(up, &rd_timestamp) == 1) {
+               pp->lastrec = up->tstamp = rd_timestamp;
                pp->nsec = 0;
        }
 #endif /* HAVE_PPSAPI */
@@ -779,23 +886,22 @@ nmea_receive(
        }
 
 
-
        /*
         * Only go on if we had been polled.
         */
        if (!up->polled)
-           return;
+               return;
        up->polled = 0;
        pp->lastref = pp->lastrec;
        refclock_receive(peer);
 
-        /* If we get here - what we got from the clock is OK, so say so */
-         refclock_report(peer, CEVNT_NOMINAL);
+       /* If we get here - what we got from the clock is OK, so say so */
+       refclock_report(peer, CEVNT_NOMINAL);
 
        record_clock_stats(&peer->srcadr, pp->a_lastcode);
-
 }
 
+
 /*
  * nmea_poll - called by the transmit procedure
  *
@@ -821,12 +927,15 @@ nmea_poll(
        up->polled = 1;
 
        /*
-        * usually nmea_receive can get a timestamp every second
+        * usually nmea_receive can get a timestamp every second, 
+        * but at least one Motorola unit needs prompting each
+        * time.
         */
 
        gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
 }
 
+
 /*
  *
  *     gps_send(fd,cmd, peer)  Sends a command to the GPS receiver.
@@ -845,12 +954,12 @@ gps_send(
        struct peer *peer
        )
 {
-
        if (write(fd, cmd, strlen(cmd)) == -1) {
                refclock_report(peer, CEVNT_FAULT);
        }
 }
 
+
 static char *
 field_parse(
        char *cp,
@@ -860,13 +969,59 @@ field_parse(
        char *tp;
        int i = fn;
 
-       for (tp = cp; *tp != '\0'; tp++) {
+       for (tp = cp; i && *tp; tp++)
                if (*tp == ',')
-                   i--;
-               if (i == 0)
-                   break;
+                       i--;
+
+       return tp;
+}
+
+
+/*
+ * nmea_checksum_ok verifies 8-bit XOR checksum is correct then returns 1
+ *
+ * format is $XXXXX,1,2,3,4*ML
+ *
+ * 8-bit XOR of characters between $ and * noninclusive is transmitted
+ * in last two chars M and L holding most and least significant nibbles
+ * in hex representation such as:
+ *
+ *   $GPGLL,5057.970,N,00146.110,E,142451,A*27
+ *   $GPVTG,089.0,T,,,15.2,N,,*7F
+ */
+int
+nmea_checksum_ok(
+       const char *sentence
+       )
+{
+       u_char my_cs;
+       u_long input_cs;
+       const char *p;
+
+       my_cs = 0;
+       p = sentence;
+
+       if ('$' != *p++)
+               return 0;
+
+       for ( ; *p && '*' != *p; p++) {
+
+               my_cs ^= *p;
        }
-       return (++tp);
+
+       if ('*' != *p++)
+               return 0;
+
+       if (0 == p[0] || 0 == p[1] || 0 != p[2])
+               return 0;
+
+       if (0 == hextoint(p, &input_cs))
+               return 0;
+
+       if (my_cs != input_cs)
+               return 0;
+
+       return 1;
 }
 #else
 int refclock_nmea_bs;