refclock_nmea.c   [plain text]


/*
 * refclock_nmea.c - clock driver for an NMEA GPS CLOCK
 *		Michael Petry Jun 20, 1994
 *		 based on refclock_heathn.c
 */
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#if defined(SYS_WINNT)
#undef close
#define close closesocket
#endif

#if defined(REFCLOCK) && defined(CLOCK_NMEA)

#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
 *
 * Protype 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
 * 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
 */

/*
 * Definitions
 */
#ifdef SYS_WINNT
# define DEVICE "COM%d:" 	/* COM 1 - 3 supported */
#else
# define DEVICE	"/dev/gps%d"	/* name of radio device */
#endif
#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) */

#define LENNMEA		75	/* min timecode length */

/*
 * Tables to compute the ddd of year form icky dd/mm timecode. Viva la
 * leap.
 */
static int day1tab[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
static int day2tab[] = {31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

/*
 * Unit control structure
 */
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 */
};

/*
 * Function prototypes
 */
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 *));
static	char	*field_parse	P((char *, int));

/*
 * Transfer vector
 */
struct	refclock refclock_nmea = {
	nmea_start,		/* start up 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 */
	noentry,		/* initialize driver */
	noentry,		/* buginfo */
	NOFLAGS			/* not used */
};

/*
 * nmea_start - open the GPS devices and initialize data for processing
 */
static int
nmea_start(
	int unit,
	struct peer *peer
	)
{
	register struct nmeaunit *up;
	struct refclockproc *pp;
	int fd;
	char device[20];

	/*
	 * Open serial port. Use CLK line discipline, if available.
	 */
	(void)sprintf(device, DEVICE, unit);

	fd = refclock_open(device, SPEED232, LDISC_CLK);
	if (fd < 0)
	    return (0);

	/*
	 * 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;
	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);
		free(up);
		return (0);
	}
	pp->unitptr = (caddr_t)up;

	/*
	 * Initialize miscellaneous variables
	 */
	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 */
}

/*
 * nmea_shutdown - shut down a GPS clock
 */
static void
nmea_shutdown(
	int unit,
	struct peer *peer
	)
{
	register struct nmeaunit *up;
	struct refclockproc *pp;

	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
 */
static void
nmea_receive(
	struct recvbuf *rbufp
	)
{
	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	rd_lastcode[BMAX];
	l_fp	rd_tmp;
	u_short	rd_lencode;

	/*
	 * Initialize pointers and read the timecode and timestamp
	 */
	peer = (struct peer *)rbufp->recv_srcclock;
	pp = peer->procptr;
	up = (struct nmeaunit *)pp->unitptr;
	rd_lencode = (u_short)refclock_gtlin(rbufp, rd_lastcode, BMAX, &rd_tmp);

	/*
	 * There is a case that a <CR><LF> gives back a "blank" line
	 */
	if (rd_lencode == 0)
	    return;

#ifdef DEBUG
	if (debug)
	    printf("nmea: gpsread %d %s\n", rd_lencode,
		   rd_lastcode);
#endif

	/*
	 * 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
	 * $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 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;


	/* See if I want to process this message type */
	if ( ((peer->ttl == 0) && (cmdtype != GPRMC))
           || ((peer->ttl != 0) && !(cmdtype & peer->ttl)) )
		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:
		/*
		 * 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]) ||
		    !isdigit((int)dp[3]) ||
		    !isdigit((int)dp[4]) ||
		    !isdigit((int)dp[5])	
		    ) {
			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->nsec = 0; 
	if (dp[6] == '.') {
		if (isdigit((int)dp[7])) {
			pp->nsec = (dp[7] - '0') * 100000000;
			if (isdigit((int)dp[8])) {
				pp->nsec += (dp[8] - '0') * 10000000;
				if (isdigit((int)dp[9])) {
					pp->nsec += (dp[9] - '0') * 1000000;
				}
			}
		}
	}

	if (pp->hour > 23 || pp->minute > 59 || pp->second > 59
	  || pp->nsec > 1000000000) {
		refclock_report(peer, CEVNT_BADTIME);
		return;
	}


	/*
	 * 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);
		return;
	}

        /* Hmmmm this will be a nono for 2100,2200,2300 but I don't think I'll be here */
        /* good thing that 2000 is a leap year */
	/* pp->year will be 00-99 if read from GPS, 00->  (years since 1900) from tm_year */
	if (pp->year % 4) {
		if (day > day1tab[month - 1]) {
			refclock_report(peer, CEVNT_BADTIME);
			return;
		}
		for (i = 0; i < month - 1; i++)
		    day += day1tab[i];
	} else {
		if (day > day2tab[month - 1]) {
			refclock_report(peer, CEVNT_BADTIME);
			return;
		}
		for (i = 0; i < month - 1; i++)
		    day += day2tab[i];
	}
	pp->day = day;


#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.
	 */

	if (!refclock_process(pp)) {
		refclock_report(peer, CEVNT_BADTIME);
		return;
	}



	/*
	 * Only go on if we had been polled.
	 */
	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);

	record_clock_stats(&peer->srcadr, pp->a_lastcode);

}

/*
 * nmea_poll - called by the transmit procedure
 *
 * We go to great pains to avoid changing state here, since there may be
 * more than one eavesdropper receiving the same timecode.
 */
static void
nmea_poll(
	int unit,
	struct peer *peer
	)
{
	register struct nmeaunit *up;
	struct refclockproc *pp;

	pp = peer->procptr;
	up = (struct nmeaunit *)pp->unitptr;
	if (up->pollcnt == 0)
	    refclock_report(peer, CEVNT_TIMEOUT);
	else
	    up->pollcnt--;
	pp->polls++;
	up->polled = 1;

	/*
	 * usually nmea_receive can get a timestamp every second
	 */

	gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
}

/*
 *
 *	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
 *	RTCM SC104 messages for differential positioning. It should
 *	also give us better time. Without a PPS output, we're
 *	Just fooling ourselves because of the serial code paths
 *
 */
static void
gps_send(
	int fd,
	const char *cmd,
	struct peer *peer
	)
{

	if (write(fd, cmd, strlen(cmd)) == -1) {
		refclock_report(peer, CEVNT_FAULT);
	}
}

static char *
field_parse(
	char *cp,
	int fn
	)
{
	char *tp;
	int i = fn;

	for (tp = cp; *tp != '\0'; tp++) {
		if (*tp == ',')
		    i--;
		if (i == 0)
		    break;
	}
	return (++tp);
}
#else
int refclock_nmea_bs;
#endif /* REFCLOCK */