summaryrefslogtreecommitdiff
path: root/ntpd/refclock_nmea.c
diff options
context:
space:
mode:
Diffstat (limited to 'ntpd/refclock_nmea.c')
-rw-r--r--ntpd/refclock_nmea.c1317
1 files changed, 915 insertions, 402 deletions
diff --git a/ntpd/refclock_nmea.c b/ntpd/refclock_nmea.c
index a176ee86c139..22ec19d9015f 100644
--- a/ntpd/refclock_nmea.c
+++ b/ntpd/refclock_nmea.c
@@ -2,13 +2,27 @@
* refclock_nmea.c - clock driver for an NMEA GPS CLOCK
* Michael Petry Jun 20, 1994
* based on refclock_heathn.c
+ *
+ * Updated to add support for Accord GPS Clock
+ * Venu Gopal Dec 05, 2007
+ * neo.venu@gmail.com, venugopal_d@pgad.gov.in
+ *
+ * Updated to process 'time1' fudge factor
+ * Venu Gopal May 05, 2008
+ *
+ * Converted to common PPSAPI code, separate PPS fudge time1
+ * from serial timecode fudge time2.
+ * Dave Hart July 1, 2009
+ * hart@ntp.org, davehart@davehart.com
*/
+
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if defined(REFCLOCK) && defined(CLOCK_NMEA)
+#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
@@ -17,24 +31,35 @@
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"
+#include "ntp_calendar.h"
#ifdef HAVE_PPSAPI
# include "ppsapi_timepps.h"
+# include "refclock_atom.h"
#endif /* HAVE_PPSAPI */
#ifdef SYS_WINNT
+#undef write /* ports/winnt/include/config.h: #define write _write */
extern int async_write(int, const void *, unsigned int);
-#undef write
#define write(fd, data, octets) async_write(fd, data, octets)
#endif
+#ifndef TIMESPECTOTS
+#define TIMESPECTOTS(ptspec, pts) \
+ do { \
+ DTOLFP((ptspec)->tv_nsec * 1.0e-9, pts); \
+ (pts)->l_ui += (u_int32)((ptspec)->tv_sec) + JAN_1970; \
+ } while (0)
+#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
+ * And you thought it was an information superhighway. Try a raging river
* filled with rapids and whirlpools that rip away your data and warp time.
*
* If HAVE_PPSAPI is defined code to use the PPSAPI will be compiled in.
@@ -52,80 +77,139 @@ extern int async_write(int, const void *, unsigned int);
* bit 0 - enables RMC (1)
* bit 1 - enables GGA (2)
* bit 2 - enables GLL (4)
- * multiple sentences may be selected
+ * 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/ZDA is selected.
+ *
+ * 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
*/
-#ifdef SYS_WINNT
-# define DEVICE "COM%d:" /* COM 1 - 3 supported */
-#else
-# define DEVICE "/dev/gps%d" /* name of radio device */
-#endif
+#define DEVICE "/dev/gps%d" /* GPS serial device */
+#define PPSDEV "/dev/gpspps%d" /* PPSAPI device override */
#define SPEED232 B4800 /* uart speed (4800 bps) */
#define PRECISION (-9) /* precision assumed (about 2 ms) */
#define PPS_PRECISION (-20) /* precision assumed (about 1 us) */
#define REFID "GPS\0" /* reference id */
#define DESCRIPTION "NMEA GPS Clock" /* who we are */
-#define NANOSECOND 1000000000 /* one second (ns) */
-#define RANGEGATE 500000 /* range gate (ns) */
+#ifndef O_NOCTTY
+#define M_NOCTTY 0
+#else
+#define M_NOCTTY O_NOCTTY
+#endif
+#ifndef O_NONBLOCK
+#define M_NONBLOCK 0
+#else
+#define M_NONBLOCK O_NONBLOCK
+#endif
+#define PPSOPENMODE (O_RDWR | M_NOCTTY | M_NONBLOCK)
-#define LENNMEA 75 /* min timecode length */
+/* NMEA sentence array indexes for those we use */
+#define NMEA_GPRMC 0 /* recommended min. nav. */
+#define NMEA_GPGGA 1 /* fix and quality */
+#define NMEA_GPGLL 2 /* geo. lat/long */
+#define NMEA_GPZDA 3 /* date/time */
+/*
+ * $GPZDG is a proprietary sentence that violates the spec, by not
+ * using $P and an assigned company identifier to prefix the sentence
+ * identifier. When used with this driver, the system needs to be
+ * isolated from other NTP networks, as it operates in GPS time, not
+ * UTC as is much more common. GPS time is >15 seconds different from
+ * UTC due to not respecting leap seconds since 1970 or so. Other
+ * than the different timebase, $GPZDG is similar to $GPZDA.
+ */
+#define NMEA_GPZDG 4
+#define NMEA_ARRAY_SIZE (NMEA_GPZDG + 1)
/*
- * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
- * leap.
+ * Sentence selection mode bits
*/
-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};
+#define USE_ALL 0 /* any/all */
+#define USE_GPRMC 1
+#define USE_GPGGA 2
+#define USE_GPGLL 4
+#define USE_GPZDA_ZDG 8 /* affects both */
+
+/* mapping from sentence index to controlling mode bit */
+u_char sentence_mode[NMEA_ARRAY_SIZE] =
+{
+ USE_GPRMC,
+ USE_GPGGA,
+ USE_GPGLL,
+ USE_GPZDA_ZDG,
+ USE_GPZDA_ZDG
+};
/*
* Unit control structure
*/
struct nmeaunit {
- int pollcnt; /* poll message counter */
- int polled; /* Hand in a sample? */
- l_fp tstamp; /* timestamp of last poll */
#ifdef HAVE_PPSAPI
- struct timespec ts; /* last timestamp */
- pps_params_t pps_params; /* pps parameters */
- pps_info_t pps_info; /* last pps data */
- pps_handle_t handle; /* pps handlebars */
+ struct refclock_atom atom; /* PPSAPI structure */
+ int ppsapi_tried; /* attempt PPSAPI once */
+ int ppsapi_lit; /* time_pps_create() worked */
+ int ppsapi_fd; /* fd used with PPSAPI */
+ int ppsapi_gate; /* allow edge detection processing */
+ int tcount; /* timecode sample counter */
+ int pcount; /* PPS sample counter */
#endif /* HAVE_PPSAPI */
+ l_fp tstamp; /* timestamp of last poll */
+ int gps_time; /* 0 UTC, 1 GPS time */
+ /* per sentence checksum seen flag */
+ struct calendar used; /* hh:mm:ss of used sentence */
+ u_char cksum_seen[NMEA_ARRAY_SIZE];
};
/*
* Function prototypes
*/
-static int nmea_start P((int, struct peer *));
-static void nmea_shutdown P((int, struct peer *));
+static int nmea_start (int, struct peer *);
+static void nmea_shutdown (int, struct peer *);
+static void nmea_receive (struct recvbuf *);
+static void nmea_poll (int, struct peer *);
#ifdef HAVE_PPSAPI
-static void nmea_control P((int, struct refclockstat *, struct
- refclockstat *, struct peer *));
-static int nmea_ppsapi P((struct peer *, int, int));
-static int nmea_pps P((struct nmeaunit *, l_fp *));
+static void nmea_control (int, struct refclockstat *,
+ struct refclockstat *, struct peer *);
+static void nmea_timer (int, struct peer *);
+#define NMEA_CONTROL nmea_control
+#define NMEA_TIMER nmea_timer
+#else
+#define NMEA_CONTROL noentry
+#define NMEA_TIMER noentry
#endif /* HAVE_PPSAPI */
-static void nmea_receive P((struct recvbuf *));
-static void nmea_poll P((int, struct peer *));
-static void gps_send P((int, const char *, struct peer *));
-static char *field_parse P((char *, int));
+static void gps_send (int, const char *, struct peer *);
+static char * field_parse (char *, int);
+static int nmea_checksum_ok(const char *);
+static void nmea_day_unfold(struct calendar*);
+static void nmea_century_unfold(struct calendar*);
/*
* Transfer vector
*/
struct refclock refclock_nmea = {
nmea_start, /* start up driver */
- nmea_shutdown, /* shut down driver */
+ nmea_shutdown, /* shut down driver */
nmea_poll, /* transmit poll message */
-#ifdef HAVE_PPSAPI
- nmea_control, /* fudge control */
-#else
- noentry, /* fudge control */
-#endif /* HAVE_PPSAPI */
+ NMEA_CONTROL, /* fudge control */
noentry, /* initialize driver */
noentry, /* buginfo */
- NOFLAGS /* not used */
+ NMEA_TIMER /* called once per second */
};
/*
@@ -141,77 +225,124 @@ nmea_start(
struct refclockproc *pp;
int fd;
char device[20];
+ int baudrate;
+ char *baudtext;
+
+ pp = peer->procptr;
/*
* Open serial port. Use CLK line discipline, if available.
*/
- (void)sprintf(device, DEVICE, unit);
+ snprintf(device, sizeof(device), DEVICE, unit);
+
+ /*
+ * Opening the serial port with appropriate baudrate
+ * based on the value of bit 4/5/6
+ */
+ 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, SPEED232, LDISC_CLK);
+ fd = refclock_open(device, baudrate, LDISC_CLK);
+
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, *nmea_tail;
+ 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);
+ if ((nmea_tail = strtok(NULL,":")) == NULL)
+ return(0);
+
+ nmea_port = atoi(nmea_tail);
+
+ if ((he = gethostbyname(nmea_host)) == NULL)
+ return(0);
+ if ((p = getprotobyname("tcp")) == NULL)
+ return(0);
+ memset(&so_addr, 0, sizeof(so_addr));
+ 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, sizeof(so_addr)) == -1) {
+ close(fd);
+ return (0);
+ }
#else
- return (0);
+ pp->io.fd = -1;
+ return (0);
#endif
- }
+ }
+
+ msyslog(LOG_NOTICE, "%s serial %s open at %s bps",
+ refnumtoa(&peer->srcadr), device, baudtext);
/*
* Allocate and initialize unit structure
*/
- up = (struct nmeaunit *)emalloc(sizeof(struct nmeaunit));
- if (up == NULL) {
- (void) close(fd);
- return (0);
- }
- memset((char *)up, 0, sizeof(struct nmeaunit));
- pp = peer->procptr;
+ up = emalloc(sizeof(*up));
+ memset(up, 0, sizeof(*up));
pp->io.clock_recv = nmea_receive;
pp->io.srcclock = (caddr_t)peer;
pp->io.datalen = 0;
pp->io.fd = fd;
if (!io_addclock(&pp->io)) {
- (void) close(fd);
+ pp->io.fd = -1;
+ close(fd);
free(up);
return (0);
}
@@ -222,29 +353,19 @@ nmea_start(
*/
peer->precision = PRECISION;
pp->clockdesc = DESCRIPTION;
- memcpy((char *)&pp->refid, REFID, 4);
- up->pollcnt = 2;
- gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
+ memcpy(&pp->refid, REFID, 4);
+
+ gps_send(fd,"$PMOTG,RMC,0000*1D\r\n", peer);
-#ifdef HAVE_PPSAPI
- /*
- * Start the PPSAPI interface if it is there. Default to use
- * the assert edge and do not enable the kernel hardpps.
- */
- if (time_pps_create(fd, &up->handle) < 0) {
- up->handle = 0;
- msyslog(LOG_ERR,
- "refclock_nmea: time_pps_create failed: %m");
- return (1);
- }
- return(nmea_ppsapi(peer, 0, 0));
-#else
return (1);
-#endif /* HAVE_PPSAPI */
}
+
/*
* nmea_shutdown - shut down a GPS clock
+ *
+ * NOTE this routine is called after nmea_start() returns failure,
+ * as well as during a normal shutdown due to ntpq :config unpeer.
*/
static void
nmea_shutdown(
@@ -255,150 +376,234 @@ nmea_shutdown(
register struct nmeaunit *up;
struct refclockproc *pp;
+ UNUSED_ARG(unit);
+
pp = peer->procptr;
up = (struct nmeaunit *)pp->unitptr;
+ if (up != NULL) {
#ifdef HAVE_PPSAPI
- if (up->handle != 0)
- time_pps_destroy(up->handle);
-#endif /* HAVE_PPSAPI */
- io_closeclock(&pp->io);
- free(up);
+ if (up->ppsapi_lit) {
+ time_pps_destroy(up->atom.handle);
+ if (up->ppsapi_fd != pp->io.fd)
+ close(up->ppsapi_fd);
+ }
+#endif
+ free(up);
+ }
+ if (-1 != pp->io.fd)
+ io_closeclock(&pp->io);
}
-#ifdef HAVE_PPSAPI
/*
- * nmea_control - fudge control
+ * nmea_control - configure fudge params
*/
+#ifdef HAVE_PPSAPI
static void
nmea_control(
- int unit, /* unit (not used */
- struct refclockstat *in, /* input parameters (not uded) */
- struct refclockstat *out, /* output parameters (not used) */
- struct peer *peer /* peer structure pointer */
+ int unit,
+ struct refclockstat *in_st,
+ struct refclockstat *out_st,
+ struct peer *peer
)
{
+ char device[32];
+ register struct nmeaunit *up;
struct refclockproc *pp;
+ int pps_fd;
+
+ UNUSED_ARG(in_st);
+ UNUSED_ARG(out_st);
pp = peer->procptr;
- nmea_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2,
- pp->sloppyclockflag & CLK_FLAG3);
+ up = (struct nmeaunit *)pp->unitptr;
+
+ if (!(CLK_FLAG1 & pp->sloppyclockflag)) {
+ if (!up->ppsapi_tried)
+ return;
+ up->ppsapi_tried = 0;
+ if (!up->ppsapi_lit)
+ return;
+ peer->flags &= ~FLAG_PPS;
+ peer->precision = PRECISION;
+ time_pps_destroy(up->atom.handle);
+ if (up->ppsapi_fd != pp->io.fd)
+ close(up->ppsapi_fd);
+ up->atom.handle = 0;
+ up->ppsapi_lit = 0;
+ up->ppsapi_fd = -1;
+ return;
+ }
+
+ if (up->ppsapi_tried)
+ return;
+ /*
+ * Light up the PPSAPI interface.
+ */
+ up->ppsapi_tried = 1;
+
+ /*
+ * if /dev/gpspps$UNIT can be opened that will be used for
+ * PPSAPI. Otherwise, the GPS serial device /dev/gps$UNIT
+ * already opened is used for PPSAPI as well.
+ */
+ snprintf(device, sizeof(device), PPSDEV, unit);
+
+ pps_fd = open(device, PPSOPENMODE, S_IRUSR | S_IWUSR);
+
+ if (-1 == pps_fd)
+ pps_fd = pp->io.fd;
+
+ if (refclock_ppsapi(pps_fd, &up->atom)) {
+ up->ppsapi_lit = 1;
+ up->ppsapi_fd = pps_fd;
+ /* prepare to use the PPS API for our own purposes now. */
+ refclock_params(pp->sloppyclockflag, &up->atom);
+ return;
+ }
+
+ NLOG(NLOG_CLOCKINFO)
+ msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails",
+ refnumtoa(&peer->srcadr));
}
+#endif /* HAVE_PPSAPI */
/*
- * Initialize PPSAPI
+ * nmea_timer - called once per second, fetches PPS
+ * timestamp and stuffs in median filter.
*/
-int
-nmea_ppsapi(
- struct peer *peer, /* peer structure pointer */
- int enb_clear, /* clear enable */
- int enb_hardpps /* hardpps enable */
+#ifdef HAVE_PPSAPI
+static void
+nmea_timer(
+ int unit,
+ struct peer * peer
)
{
- struct refclockproc *pp;
struct nmeaunit *up;
- int capability;
+ struct refclockproc *pp;
+
+ UNUSED_ARG(unit);
pp = peer->procptr;
up = (struct nmeaunit *)pp->unitptr;
- if (time_pps_getcap(up->handle, &capability) < 0) {
- msyslog(LOG_ERR,
- "refclock_nmea: time_pps_getcap failed: %m");
- return (0);
- }
- memset(&up->pps_params, 0, sizeof(pps_params_t));
- if (enb_clear)
- up->pps_params.mode = capability & PPS_CAPTURECLEAR;
- else
- up->pps_params.mode = capability & PPS_CAPTUREASSERT;
- if (!up->pps_params.mode) {
- msyslog(LOG_ERR,
- "refclock_nmea: invalid capture edge %d",
- !enb_clear);
- return (0);
- }
- up->pps_params.mode |= PPS_TSFMT_TSPEC;
- if (time_pps_setparams(up->handle, &up->pps_params) < 0) {
- msyslog(LOG_ERR,
- "refclock_nmea: time_pps_setparams failed: %m");
- return (0);
- }
- if (enb_hardpps) {
- if (time_pps_kcbind(up->handle, PPS_KC_HARDPPS,
- up->pps_params.mode & ~PPS_TSFMT_TSPEC,
- PPS_TSFMT_TSPEC) < 0) {
- msyslog(LOG_ERR,
- "refclock_nmea: time_pps_kcbind failed: %m");
- return (0);
- }
- pps_enable = 1;
- }
- peer->precision = PPS_PRECISION;
-
-#if DEBUG
- if (debug) {
- time_pps_getparams(up->handle, &up->pps_params);
- printf(
- "refclock_ppsapi: capability 0x%x version %d mode 0x%x kern %d\n",
- capability, up->pps_params.api_version,
- up->pps_params.mode, enb_hardpps);
- }
-#endif
- return (1);
+ if (up->ppsapi_lit && up->ppsapi_gate &&
+ refclock_pps(peer, &up->atom, pp->sloppyclockflag) > 0) {
+ up->pcount++,
+ peer->flags |= FLAG_PPS;
+ peer->precision = PPS_PRECISION;
+ }
}
+#endif /* HAVE_PPSAPI */
+#ifdef HAVE_PPSAPI
/*
- * Get PPSAPI timestamps.
+ * This function is used to correlate a receive time stamp and a
+ * reference time with a PPS edge time stamp. It applies the necessary
+ * fudges (fudge1 for PPS, fudge2 for receive time) and then tries to
+ * move the receive time stamp to the corresponding edge. This can
+ * warp into future, if a transmission delay of more than 500ms is not
+ * compensated with a corresponding fudge time2 value, because then
+ * the next PPS edge is nearer than the last. (Similiar to what the
+ * PPS ATOM driver does, but we deal with full time stamps here, not
+ * just phase shift information.) Likewise, a negative fudge time2
+ * value must be used if the reference time stamp correlates with the
+ * *following* PPS pulse.
*
- * Return 0 on failure and 1 on success.
+ * Note that the receive time fudge value only needs to move the receive
+ * stamp near a PPS edge but that close proximity is not required;
+ * +/-100ms precision should be enough. But since the fudge value will
+ * probably also be used to compensate the transmission delay when no PPS
+ * edge can be related to the time stamp, it's best to get it as close
+ * as possible.
+ *
+ * It should also be noted that the typical use case is matching to
+ * the preceeding edge, as most units relate their sentences to the
+ * current second.
+ *
+ * The function returns PPS_RELATE_NONE (0) if no PPS edge correlation
+ * can be fixed; PPS_RELATE_EDGE (1) when a PPS edge could be fixed, but
+ * the distance to the reference time stamp is too big (exceeds +/-400ms)
+ * and the ATOM driver PLL cannot be used to fix the phase; and
+ * PPS_RELATE_PHASE (2) when the ATOM driver PLL code can be used.
+ *
+ * On output, the receive time stamp is replaced with the
+ * corresponding PPS edge time if a fix could be made; the PPS fudge
+ * is updated to reflect the proper fudge time to apply. (This implies
+ * that 'refclock_process_f()' must be used!)
*/
+#define PPS_RELATE_NONE 0 /* no pps correlation possible */
+#define PPS_RELATE_EDGE 1 /* recv time fixed, no phase lock */
+#define PPS_RELATE_PHASE 2 /* recv time fixed, phase lock ok */
+
static int
-nmea_pps(
- struct nmeaunit *up,
- l_fp *tsptr
- )
+refclock_ppsrelate(
+ const struct refclockproc *pp , /* for sanity */
+ const struct refclock_atom *ap , /* for PPS io */
+ const l_fp *reftime ,
+ l_fp *rd_stamp, /* i/o read stamp */
+ double pp_fudge, /* pps fudge */
+ double *rd_fudge) /* i/o read fudge */
{
- pps_info_t pps_info;
- struct timespec timeout, ts;
- double dtemp;
- l_fp tstmp;
-
- /*
- * Convert the timespec nanoseconds field to ntp l_fp units.
- */
- if (up->handle == 0)
- return (0);
- timeout.tv_sec = 0;
- timeout.tv_nsec = 0;
- memcpy(&pps_info, &up->pps_info, sizeof(pps_info_t));
- if (time_pps_fetch(up->handle, PPS_TSFMT_TSPEC, &up->pps_info,
- &timeout) < 0)
- return (0);
- if (up->pps_params.mode & PPS_CAPTUREASSERT) {
- if (pps_info.assert_sequence ==
- up->pps_info.assert_sequence)
- return (0);
- ts = up->pps_info.assert_timestamp;
- } else if (up->pps_params.mode & PPS_CAPTURECLEAR) {
- if (pps_info.clear_sequence ==
- up->pps_info.clear_sequence)
- return (0);
- ts = up->pps_info.clear_timestamp;
- } else {
- return (0);
- }
- if ((up->ts.tv_sec == ts.tv_sec) && (up->ts.tv_nsec == ts.tv_nsec))
- return (0);
- up->ts = ts;
-
- tstmp.l_ui = ts.tv_sec + JAN_1970;
- dtemp = ts.tv_nsec * FRAC / 1e9;
- tstmp.l_uf = (u_int32)dtemp;
- *tsptr = tstmp;
- return (1);
+ pps_info_t pps_info;
+ struct timespec timeout;
+ l_fp pp_stamp, pp_delta;
+ double delta, idelta;
+
+ if (pp->leap == LEAP_NOTINSYNC)
+ return PPS_RELATE_NONE; /* clock is insane, no chance */
+
+ memset(&timeout, 0, sizeof(timeout));
+ memset(&pps_info, 0, sizeof(pps_info_t));
+
+ if (time_pps_fetch(ap->handle, PPS_TSFMT_TSPEC,
+ &pps_info, &timeout) < 0)
+ return PPS_RELATE_NONE;
+
+ /* get last active PPS edge before receive */
+ if (ap->pps_params.mode & PPS_CAPTUREASSERT)
+ timeout = pps_info.assert_timestamp;
+ else if (ap->pps_params.mode & PPS_CAPTURECLEAR)
+ timeout = pps_info.clear_timestamp;
+ else
+ return PPS_RELATE_NONE;
+
+ /* get delta between receive time and PPS time */
+ TIMESPECTOTS(&timeout, &pp_stamp);
+ pp_delta = *rd_stamp;
+ L_SUB(&pp_delta, &pp_stamp);
+ LFPTOD(&pp_delta, delta);
+ delta += pp_fudge - *rd_fudge;
+ if (fabs(delta) > 1.5)
+ return PPS_RELATE_NONE; /* PPS timeout control */
+
+ /* eventually warp edges, check phase */
+ idelta = floor(delta + 0.5);
+ pp_fudge -= idelta;
+ delta -= idelta;
+ if (fabs(delta) > 0.45)
+ return PPS_RELATE_NONE; /* dead band control */
+
+ /* we actually have a PPS edge to relate with! */
+ *rd_stamp = pp_stamp;
+ *rd_fudge = pp_fudge;
+
+ /* if whole system out-of-sync, do not try to PLL */
+ if (sys_leap == LEAP_NOTINSYNC)
+ return PPS_RELATE_EDGE; /* cannot PLL with atom code */
+
+ /* check against reftime if ATOM PLL can be used */
+ pp_delta = *reftime;
+ L_SUB(&pp_delta, &pp_stamp);
+ LFPTOD(&pp_delta, delta);
+ delta += pp_fudge;
+ if (fabs(delta) > 0.45)
+ return PPS_RELATE_EDGE; /* cannot PLL with atom code */
+
+ /* all checks passed, gets an AAA rating here! */
+ return PPS_RELATE_PHASE; /* can PLL with atom code */
}
-#endif /* HAVE_PPSAPI */
+#endif /* HAVE_PPSAPI */
/*
* nmea_receive - receive data from the serial interface
@@ -411,171 +616,223 @@ nmea_receive(
register struct nmeaunit *up;
struct refclockproc *pp;
struct peer *peer;
- int month, day;
- int i;
- char *cp, *dp;
- int cmdtype;
- /* Use these variables to hold data until we decide its worth keeping */
+ char *cp, *dp, *msg;
+ u_char sentence;
+ /* 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, reftime;
+ int rd_lencode;
+ double rd_fudge;
+ struct calendar date;
/*
* Initialize pointers and read the timecode and timestamp
*/
- peer = (struct peer *)rbufp->recv_srcclock;
+ peer = rbufp->recv_peer;
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
+ * There is a case that a <CR><LF> gives back a "blank" line.
+ * We can't have a well-formed sentence with less than 8 chars.
*/
- if (rd_lencode == 0)
- return;
+ if (0 == rd_lencode)
+ return;
-#ifdef DEBUG
- if (debug)
- printf("nmea: gpsread %d %s\n", rd_lencode,
- rd_lastcode);
-#endif
+ if (rd_lencode < 8) {
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ }
+
+ 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,...
+ *
+ * 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
+ *
+ * $GPZDG,HHMMSS.S,DD,MM,YYYY,AA.BB,V*CS<CR><LF>
+ *
+ * It contains the GPS timestamp valid for next PPS pulse.
+ * 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 GPXXX 0
-#define GPRMC 1
-#define GPGGA 2
-#define GPGLL 4
- 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,"$GPXXX",6)==0) {
- cmdtype=GPXXX;
- }
- else
- return;
+ cp = rd_lastcode;
+ if (cp[0] == '$') {
+ /* Allow for GLGGA and GPGGA etc. */
+ msg = cp + 3;
+
+ if (strncmp(msg, "RMC", 3) == 0)
+ sentence = NMEA_GPRMC;
+ else if (strncmp(msg, "GGA", 3) == 0)
+ sentence = NMEA_GPGGA;
+ else if (strncmp(msg, "GLL", 3) == 0)
+ sentence = NMEA_GPGLL;
+ else if (strncmp(msg, "ZDG", 3) == 0)
+ sentence = NMEA_GPZDG;
+ else if (strncmp(msg, "ZDA", 3) == 0)
+ sentence = NMEA_GPZDA;
+ 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)) )
+ if ((peer->ttl & NMEA_MESSAGE_MASK) &&
+ !(peer->ttl & sentence_mode[sentence]))
return;
- pp->lencode = rd_lencode;
- strcpy(pp->a_lastcode,rd_lastcode);
- cp = pp->a_lastcode;
-
- pp->lastrec = up->tstamp = rd_tmp;
- up->pollcnt = 2;
+ /*
+ * $GPZDG provides GPS time not UTC, and the two mix poorly.
+ * Once have processed a $GPZDG, do not process any further
+ * UTC sentences (all but $GPZDG currently).
+ */
+ if (up->gps_time && NMEA_GPZDG != sentence)
+ return;
-#ifdef DEBUG
- if (debug)
- printf("nmea: timecode %d %s\n", pp->lencode,
- pp->a_lastcode);
-#endif
+ /*
+ * Apparently, older NMEA specifications (which are expensive)
+ * did not require the checksum for all sentences. $GPMRC is
+ * the only one so far identified which has always been required
+ * to include a checksum.
+ *
+ * Today, most NMEA GPS receivers checksum every sentence. To
+ * preserve its error-detection capabilities with modern GPSes
+ * while allowing operation without checksums on all but $GPMRC,
+ * we keep track of whether we've ever seen a checksum on a
+ * given sentence, and if so, reject future checksum failures.
+ */
+ if (nmea_checksum_ok(rd_lastcode)) {
+ up->cksum_seen[sentence] = TRUE;
+ } else if (NMEA_GPRMC == sentence || up->cksum_seen[sentence]) {
+ refclock_report(peer, CEVNT_BADREPLY);
+ return;
+ }
+ cp = rd_lastcode;
/* Grab field depending on clock string type */
- switch( cmdtype ) {
- case GPRMC:
+ memset(&date, 0, sizeof(date));
+ switch (sentence) {
+
+ case NMEA_GPRMC:
/*
* Test for synchronization. Check for quality byte.
*/
- dp = field_parse(cp,2);
- if( dp[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:
+ case NMEA_GPGGA:
/*
* Test for synchronization. Check for quality byte.
*/
- dp = field_parse(cp,6);
- if( dp[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:
+ case NMEA_GPGLL:
/*
* Test for synchronization. Check for quality byte.
*/
- dp = field_parse(cp,6);
- if( dp[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 NMEA_GPZDG:
+ /* For $GPZDG check for validity of GPS time. */
+ dp = field_parse(cp, 6);
+ if (dp[0] == '0')
+ pp->leap = LEAP_NOTINSYNC;
+ else
+ pp->leap = LEAP_NOWARNING;
+ /* fall through to NMEA_GPZDA */
+ case NMEA_GPZDA:
+ if (NMEA_GPZDA == sentence)
+ pp->leap = LEAP_NOWARNING;
- case GPXXX:
- return;
- default:
- return;
+ /* Now point at the time field */
+ dp = field_parse(cp, 1);
+ break;
+ default:
+ return;
}
- /*
- * Check time code format of NMEA
- */
-
- 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);
- return;
- }
-
+ /*
+ * Check time code format of NMEA
+ */
+ 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])) {
+
+ 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;
+ }
/*
* Convert time and check values.
*/
- 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
- */
+ date.hour = ((dp[0] - '0') * 10) + dp[1] - '0';
+ date.minute = ((dp[2] - '0') * 10) + dp[3] - '0';
+ date.second = ((dp[4] - '0') * 10) + dp[5] - '0';
+ /*
+ * 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])) {
@@ -589,103 +846,190 @@ nmea_receive(
}
}
- if (pp->hour > 23 || pp->minute > 59 || pp->second > 59
- || pp->nsec > 1000000000) {
+ if (date.hour > 23 || date.minute > 59 ||
+ date.second > 59 || pp->nsec > 1000000000) {
+
+ DPRINTF(1, ("NMEA hour/min/sec/nsec range %02d:%02d:%02d.%09ld\n",
+ pp->hour, pp->minute, pp->second, pp->nsec));
refclock_report(peer, CEVNT_BADTIME);
return;
}
+ /*
+ * Used only the first recognized sentence each second.
+ */
+ if (date.hour == up->used.hour &&
+ date.minute == up->used.minute &&
+ date.second == up->used.second)
+ return;
+
+ pp->lencode = (u_short)rd_lencode;
+ memcpy(pp->a_lastcode, rd_lastcode, pp->lencode + 1);
+ up->tstamp = rd_timestamp;
+ pp->lastrec = up->tstamp;
+ DPRINTF(1, ("nmea: timecode %d %s\n", pp->lencode, pp->a_lastcode));
/*
* 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;
- }
-
- if (month < 1 || month > 12 || day < 1) {
- refclock_report(peer, CEVNT_BADTIME);
+ if (NMEA_GPRMC == sentence) {
+
+ dp = field_parse(cp,9);
+ date.monthday = 10 * (dp[0] - '0') + (dp[1] - '0');
+ date.month = 10 * (dp[2] - '0') + (dp[3] - '0');
+ date.year = 10 * (dp[4] - '0') + (dp[5] - '0');
+ nmea_century_unfold(&date);
+
+ } else if (NMEA_GPZDA == sentence || NMEA_GPZDG == sentence) {
+
+ dp = field_parse(cp, 2);
+ date.monthday = 10 * (dp[0] - '0') + (dp[1] - '0');
+ dp = field_parse(cp, 3);
+ date.month = 10 * (dp[0] - '0') + (dp[1] - '0');
+ dp = field_parse(cp, 4);
+ date.year = 1000 * (dp[0] - '0') + 100 * (dp[1] - '0')
+ + 10 * (dp[2] - '0') + (dp[3] - '0');
+
+ } else
+ nmea_day_unfold(&date);
+
+ if (date.month < 1 || date.month > 12 ||
+ date.monthday < 1 || date.monthday > 31) {
+ 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;
+ up->used.hour = date.hour;
+ up->used.minute = date.minute;
+ up->used.second = date.second;
+
+ /*
+ * If "fudge 127.127.20.__ flag4 1" is configured in ntp.conf,
+ * remove the location and checksum from the NMEA sentence
+ * recorded as the last timecode and visible to remote users
+ * with:
+ *
+ * ntpq -c clockvar <server>
+ *
+ * Note that this also removes the location from the clockstats
+ * log (if it is enabled). Some NTP operators monitor their
+ * NMEA GPS using the change in location in clockstats over
+ * time as as a proxy for the quality of GPS reception and
+ * thereby time reported.
+ */
+ if (CLK_FLAG4 & pp->sloppyclockflag) {
+ /*
+ * Start by pointing cp and dp at the fields with
+ * longitude and latitude in the last timecode.
+ */
+ switch (sentence) {
+
+ case NMEA_GPGLL:
+ cp = field_parse(pp->a_lastcode, 1);
+ dp = field_parse(cp, 2);
+ break;
+
+ case NMEA_GPGGA:
+ cp = field_parse(pp->a_lastcode, 2);
+ dp = field_parse(cp, 2);
+ break;
+
+ case NMEA_GPRMC:
+ cp = field_parse(pp->a_lastcode, 3);
+ dp = field_parse(cp, 2);
+ break;
+
+ case NMEA_GPZDA:
+ case NMEA_GPZDG:
+ default:
+ cp = dp = NULL;
}
- for (i = 0; i < month - 1; i++)
- day += day1tab[i];
- } else {
- if (day > day2tab[month - 1]) {
- refclock_report(peer, CEVNT_BADTIME);
- return;
+
+ /* Blank the entire latitude & longitude. */
+ while (cp) {
+ while (',' != *cp) {
+ if ('.' != *cp)
+ *cp = '_';
+ cp++;
+ }
+
+ /* Longitude at cp then latitude at dp */
+ if (cp < dp)
+ cp = dp;
+ else
+ cp = NULL;
}
- for (i = 0; i < month - 1; i++)
- day += day2tab[i];
- }
- pp->day = day;
+ /* Blank the checksum, the last two characters */
+ if (dp) {
+ cp = pp->a_lastcode + pp->lencode - 2;
+ if (0 == cp[2])
+ cp[0] = cp[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;
- pp->nsec = 0;
}
-#endif /* HAVE_PPSAPI */
/*
- * Process the new sample in the median filter and determine the
- * reference clock offset and dispersion. We use lastrec as both
- * the reference time and receive time, in order to avoid being
- * cute, like setting the reference time later than the receive
- * time, which may cause a paranoid protocol module to chuck out
- * the data.
+ * Get the reference time stamp from the calendar buffer.
+ * Process the new sample in the median filter and determine
+ * the timecode timestamp, but only if the PPS is not in
+ * control.
*/
+ rd_fudge = pp->fudgetime2;
+ date.yearday = 0; /* make sure it's not used */
+ DTOLFP(pp->nsec * 1.0e-9, &reftime);
+ reftime.l_ui += caltontp(&date);
- if (!refclock_process(pp)) {
- refclock_report(peer, CEVNT_BADTIME);
- return;
+ /* $GPZDG postprocessing first... */
+ if (NMEA_GPZDG == sentence) {
+ /*
+ * Note if we're only using GPS timescale from now on.
+ */
+ if (!up->gps_time) {
+ up->gps_time = 1;
+ NLOG(NLOG_CLOCKINFO)
+ msyslog(LOG_INFO, "%s using only $GPZDG",
+ refnumtoa(&peer->srcadr));
+ }
+ /*
+ * $GPZDG indicates the second after the *next* PPS
+ * pulse. So we remove 1 second from the reference
+ * time now.
+ */
+ reftime.l_ui--;
}
-
-
+#ifdef HAVE_PPSAPI
+ up->tcount++;
/*
- * Only go on if we had been polled.
+ * If we have PPS running, we try to associate the sentence with
+ * the last active edge of the PPS signal.
*/
- if (!up->polled)
- 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 (up->ppsapi_lit)
+ switch (refclock_ppsrelate(pp, &up->atom, &reftime,
+ &rd_timestamp, pp->fudgetime1,
+ &rd_fudge))
+ {
+ case PPS_RELATE_EDGE:
+ up->ppsapi_gate = 0;
+ break;
+ case PPS_RELATE_PHASE:
+ up->ppsapi_gate = 1;
+ break;
+ default:
+ break;
+ }
+ else
+ up->ppsapi_gate = 0;
- record_clock_stats(&peer->srcadr, pp->a_lastcode);
+ if (up->ppsapi_gate && (peer->flags & FLAG_PPS))
+ return;
+#endif /* HAVE_PPSAPI */
+ refclock_process_offset(pp, reftime, rd_timestamp, rd_fudge);
}
+
/*
* nmea_poll - called by the transmit procedure
*
@@ -703,23 +1047,47 @@ nmea_poll(
pp = peer->procptr;
up = (struct nmeaunit *)pp->unitptr;
- if (up->pollcnt == 0)
- refclock_report(peer, CEVNT_TIMEOUT);
- else
- up->pollcnt--;
+
+ /*
+ * Process median filter samples. If none received, declare a
+ * timeout and keep going.
+ */
+#ifdef HAVE_PPSAPI
+ if (up->pcount == 0) {
+ peer->flags &= ~FLAG_PPS;
+ peer->precision = PRECISION;
+ }
+ if (up->tcount == 0) {
+ pp->coderecv = pp->codeproc;
+ refclock_report(peer, CEVNT_TIMEOUT);
+ return;
+ }
+ up->pcount = up->tcount = 0;
+#else /* HAVE_PPSAPI */
+ if (pp->coderecv == pp->codeproc) {
+ refclock_report(peer, CEVNT_TIMEOUT);
+ return;
+ }
+#endif /* HAVE_PPSAPI */
+
pp->polls++;
- up->polled = 1;
+ pp->lastref = pp->lastrec;
+ refclock_receive(peer);
+ record_clock_stats(&peer->srcadr, pp->a_lastcode);
/*
- * 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.
+ * gps_send(fd,cmd, peer) Sends a command to the GPS receiver.
* as gps_send(fd,"rqts,u\r", peer);
*
* We don't currently send any data, but would like to send
@@ -735,12 +1103,12 @@ gps_send(
struct peer *peer
)
{
-
if (write(fd, cmd, strlen(cmd)) == -1) {
refclock_report(peer, CEVNT_FAULT);
}
}
+
static char *
field_parse(
char *cp,
@@ -750,14 +1118,159 @@ 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;
+}
+
+/*
+ * -------------------------------------------------------------------
+ * funny calendar-oriented stuff -- a bit hard to grok.
+ * -------------------------------------------------------------------
+ */
+/*
+ * Do a periodic unfolding of a truncated value around a given pivot
+ * value.
+ * The result r will hold to pivot <= r < pivot+period (period>0) or
+ * pivot+period < r <= pivot (period < 0) and value % period == r % period,
+ * using floor division convention.
+ */
+static time_t
+nmea_periodic_unfold(
+ time_t pivot,
+ time_t value,
+ time_t period)
+{
+ /*
+ * This will only work as long as 'value - pivot%period' does
+ * not create a signed overflow condition.
+ */
+ value = (value - (pivot % period)) % period;
+ if (value && (value ^ period) < 0)
+ value += period;
+ return pivot + value;
}
+
+/*
+ * Unfold a time-of-day (seconds since midnight) around the current
+ * system time in a manner that guarantees an absolute difference of
+ * less than 12hrs.
+ *
+ * This function is used for NMEA sentences that contain no date
+ * information. This requires the system clock to be in +/-12hrs
+ * around the true time, or the clock will synchronize the system 1day
+ * off if not augmented with a time sources that also provide the
+ * necessary date information.
+ *
+ * The function updates the refclockproc structure is also uses as
+ * input to fetch the time from.
+ */
+static void
+nmea_day_unfold(
+ struct calendar *jd)
+{
+ time_t value, pivot;
+ struct tm *tdate;
+
+ value = ((time_t)jd->hour * MINSPERHR
+ + (time_t)jd->minute) * SECSPERMIN
+ + (time_t)jd->second;
+ pivot = time(NULL) - SECSPERDAY/2;
+
+ value = nmea_periodic_unfold(pivot, value, SECSPERDAY);
+ tdate = gmtime(&value);
+ if (tdate) {
+ jd->year = tdate->tm_year + 1900;
+ jd->yearday = tdate->tm_yday + 1;
+ jd->month = tdate->tm_mon + 1;
+ jd->monthday = tdate->tm_mday;
+ jd->hour = tdate->tm_hour;
+ jd->minute = tdate->tm_min;
+ jd->second = tdate->tm_sec;
+ } else {
+ jd->year = 0;
+ jd->yearday = 0;
+ jd->month = 0;
+ jd->monthday = 0;
+ }
+}
+
+/*
+ * Unfold a 2-digit year into full year spec around the current year
+ * of the system time. This requires the system clock to be in -79/+19
+ * years around the true time, or the result will be off by
+ * 100years. The assymetric behaviour was chosen to enable inital sync
+ * for systems that do not have a battery-backup-clock and start with
+ * a date that is typically years in the past.
+ *
+ * The function updates the calendar structure that is also used as
+ * input to fetch the year from.
+ */
+static void
+nmea_century_unfold(
+ struct calendar *jd)
+{
+ time_t pivot_time;
+ struct tm *pivot_date;
+ time_t pivot_year;
+
+ /* get warp limit and century start of pivot from system time */
+ pivot_time = time(NULL);
+ pivot_date = gmtime(&pivot_time);
+ pivot_year = pivot_date->tm_year + 1900 - 20;
+ jd->year = nmea_periodic_unfold(pivot_year, jd->year, 100);
+}
+
#else
int refclock_nmea_bs;
-#endif /* REFCLOCK */
+#endif /* REFCLOCK && CLOCK_NMEA */