diff options
Diffstat (limited to 'ntpd/refclock_nmea.c')
-rw-r--r-- | ntpd/refclock_nmea.c | 1317 |
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 */ |