diff options
Diffstat (limited to 'contrib/ntp/ntpd/refclock_nmea.c')
-rw-r--r-- | contrib/ntp/ntpd/refclock_nmea.c | 427 |
1 files changed, 364 insertions, 63 deletions
diff --git a/contrib/ntp/ntpd/refclock_nmea.c b/contrib/ntp/ntpd/refclock_nmea.c index d90f71c..e9304ee 100644 --- a/contrib/ntp/ntpd/refclock_nmea.c +++ b/contrib/ntp/ntpd/refclock_nmea.c @@ -9,16 +9,25 @@ #if defined(REFCLOCK) && defined(CLOCK_NMEA) -#include <stdio.h> -#include <ctype.h> -#include <sys/time.h> -#include <time.h> - #include "ntpd.h" #include "ntp_io.h" +#include "ntp_unixtime.h" #include "ntp_refclock.h" #include "ntp_stdlib.h" +#include <stdio.h> +#include <ctype.h> + +#ifdef HAVE_PPSAPI +# ifdef HAVE_TIMEPPS_H +# include <timepps.h> +# else +# ifdef HAVE_SYS_TIMEPPS_H +# include <sys/timepps.h> +# endif +# endif +#endif /* HAVE_PPSAPI */ + /* * This driver supports the NMEA GPS Receiver with * @@ -27,6 +36,23 @@ * The receiver used spits out the NMEA sentences for boat navigation. * 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. + * On startup if initialization of the PPSAPI fails, it will fall back + * to the "normal" timestamps. + * + * The PPSAPI part of the driver understands fudge flag2 and flag3. If + * flag2 is set, it will use the clear edge of the pulse. If flag3 is + * set, kernel hardpps is enabled. + * + * GPS sentences other than RMC (the default) may be enabled by setting + * the relevent bits of 'mode' in the server configuration line + * server 127.127.20.x mode X + * + * bit 0 - enables RMC (1) + * bit 1 - enables GGA (2) + * bit 2 - enables GLL (4) + * multiple sentences may be selected */ /* @@ -39,9 +65,11 @@ #endif #define SPEED232 B4800 /* uart speed (4800 bps) */ #define PRECISION (-9) /* precision assumed (about 2 ms) */ -#define DCD_PRECISION (-20) /* precision assumed (about 1 us) */ +#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) */ #define LENNMEA 75 /* min timecode length */ @@ -59,6 +87,12 @@ 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 */ +#endif /* HAVE_PPSAPI */ }; /* @@ -66,6 +100,12 @@ struct nmeaunit { */ static int nmea_start P((int, struct peer *)); static void nmea_shutdown P((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 *)); +#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 *)); @@ -78,7 +118,11 @@ struct refclock refclock_nmea = { nmea_start, /* start up driver */ nmea_shutdown, /* shut down driver */ nmea_poll, /* transmit poll message */ - noentry, /* handle control */ +#ifdef HAVE_PPSAPI + nmea_control, /* fudge control */ +#else + noentry, /* fudge control */ +#endif /* HAVE_PPSAPI */ noentry, /* initialize driver */ noentry, /* buginfo */ NOFLAGS /* not used */ @@ -130,13 +174,27 @@ nmea_start( /* * Initialize miscellaneous variables */ - peer->precision = DCD_PRECISION; + 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); +#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 */ } /* @@ -153,10 +211,149 @@ nmea_shutdown( pp = peer->procptr; up = (struct nmeaunit *)pp->unitptr; +#ifdef HAVE_PPSAPI + if (up->handle != 0) + time_pps_destroy(up->handle); +#endif /* HAVE_PPSAPI */ io_closeclock(&pp->io); free(up); } +#ifdef HAVE_PPSAPI +/* + * nmea_control - fudge control + */ +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 */ + ) +{ + struct refclockproc *pp; + + pp = peer->procptr; + nmea_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2, + pp->sloppyclockflag & CLK_FLAG3); +} + + +/* + * Initialize PPSAPI + */ +int +nmea_ppsapi( + struct peer *peer, /* peer structure pointer */ + int enb_clear, /* clear enable */ + int enb_hardpps /* hardpps enable */ + ) +{ + struct refclockproc *pp; + struct nmeaunit *up; + int capability; + + 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); +} + +/* + * Get PPSAPI timestamps. + * + * Return 0 on failure and 1 on success. + */ +static int +nmea_pps( + struct nmeaunit *up, + l_fp *tsptr + ) +{ + 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); +} +#endif /* HAVE_PPSAPI */ + /* * nmea_receive - receive data from the serial interface */ @@ -168,11 +365,14 @@ nmea_receive( register struct nmeaunit *up; struct refclockproc *pp; struct peer *peer; - l_fp trtmp; int month, day; int i; char *cp, *dp; int cmdtype; + /* Use these variables to hold data until we decide its worth keeping */ + char rd_lastcode[BMAX]; + l_fp rd_tmp; + u_short rd_lencode; /* * Initialize pointers and read the timecode and timestamp @@ -180,23 +380,18 @@ nmea_receive( peer = (struct peer *)rbufp->recv_srcclock; pp = peer->procptr; up = (struct nmeaunit *)pp->unitptr; - pp->lencode = refclock_gtlin(rbufp, pp->a_lastcode, BMAX, &trtmp); + rd_lencode = refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp); /* * There is a case that a <CR><LF> gives back a "blank" line */ - if (pp->lencode == 0) + if (rd_lencode == 0) return; - /* - * We get a buffer and timestamp for each <cr>. - */ - pp->lastrec = up->tstamp = trtmp; - up->pollcnt = 2; #ifdef DEBUG if (debug) - printf("nmea: timecode %d %s\n", pp->lencode, - pp->a_lastcode); + printf("nmea: gpsread %d %s\n", rd_lencode, + rd_lastcode); #endif /* @@ -204,18 +399,31 @@ nmea_receive( * 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 - * $GPGGA,162617.0,4548.339,N,00837.719,E,1,07,0.97,00262,M,048,M,,*5D + * For Magellan (ColorTrak) GLL probably datum (order of sentences) + * also mode (0,1,2,3) select sentence ANY/ALL, RMC, GGA, GLL + * $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,... + * $GPRMC,232418.19,A,3513.8386,S,14900.7853,E,00.0,000.0,121199,12.,E*77 + * $GPAPB,... + * $GPGSA,... + * $GPGSV,... + * $GPGSV,... */ -#define GPRMC 0 -#define GPXXX 1 -#define GPGCA 2 - cp = pp->a_lastcode; +#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=GPGCA; + cmdtype=GPGGA; + } + else if(strncmp(cp,"$GPGLL",6)==0) { + cmdtype=GPGLL; } else if(strncmp(cp,"$GPXXX",6)==0) { cmdtype=GPXXX; @@ -223,14 +431,84 @@ nmea_receive( else return; + + /* See if I want to process this message type */ + if ( ((peer->ttlmax == 0) && (cmdtype != GPRMC)) + || ((peer->ttlmax != 0) && !(cmdtype & peer->ttlmax)) ) + return; + + pp->lencode = rd_lencode; + strcpy(pp->a_lastcode,rd_lastcode); + cp = pp->a_lastcode; + + pp->lastrec = up->tstamp = rd_tmp; + up->pollcnt = 2; + +#ifdef DEBUG + if (debug) + printf("nmea: timecode %d %s\n", pp->lencode, + pp->a_lastcode); +#endif + + + /* Grab field depending on clock string type */ switch( cmdtype ) { case GPRMC: - case GPGCA: /* - * Check time code format of NMEA + * Test for synchronization. Check for quality byte. + */ + 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); + break; + + + case GPGGA: + /* + * Test for synchronization. Check for quality byte. */ + 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); + break; + + + case GPGLL: + /* + * Test for synchronization. Check for quality byte. + */ + 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); + break; + + + case GPXXX: + return; + default: + return; + + } + + /* + * Check time code format of NMEA + */ + if( !isdigit((int)dp[0]) || !isdigit((int)dp[1]) || !isdigit((int)dp[2]) || @@ -242,40 +520,55 @@ nmea_receive( return; } - /* - * Test for synchronization. Check for quality byte. - */ - dp = field_parse(cp,2); - if( dp[0] != 'A') { - refclock_report(peer, CEVNT_BADREPLY); - 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 + */ + pp->msec = 0; + if (dp[6] == '.') { + if (isdigit((int)dp[7])) { + pp->msec = (dp[7] - '0') * 100; + if (isdigit((int)dp[8])) { + pp->msec += (dp[8] - '0') * 10; + if (isdigit((int)dp[9])) { + pp->msec += (dp[9] - '0'); + } + } } - break; - case GPXXX: - return; - default: - return; + } + if (pp->hour > 23 || pp->minute > 59 || pp->second > 59 + || pp->msec > 1000) { + refclock_report(peer, CEVNT_BADTIME); + return; } - if (cmdtype ==GPGCA) { - /* 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; - } else { - dp = field_parse(cp,9); + /* * Convert date and check values. */ - 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'; + 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) { @@ -283,6 +576,9 @@ nmea_receive( 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); @@ -300,19 +596,17 @@ nmea_receive( } pp->day = day; - dp = field_parse(cp,1); + +#ifdef HAVE_PPSAPI /* - * Convert time and check values. + * If the PPSAPI is working, rather use its timestamps. + * assume that the PPS occurs on the second so blow any msec */ - 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'; - pp->msec = 0; - - if (pp->hour > 23 || pp->minute > 59 || pp->second > 59) { - refclock_report(peer, CEVNT_BADTIME); - return; + if (nmea_pps(up, &rd_tmp) == 1) { + pp->lastrec = up->tstamp = rd_tmp; + pp->msec = 0; } +#endif /* HAVE_PPSAPI */ /* * Process the new sample in the median filter and determine the @@ -322,11 +616,14 @@ nmea_receive( * time, which may cause a paranoid protocol module to chuck out * the data. */ + if (!refclock_process(pp)) { refclock_report(peer, CEVNT_BADTIME); return; } + + /* * Only go on if we had been polled. */ @@ -336,7 +633,11 @@ nmea_receive( refclock_receive(peer); + /* 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); + } /* |