718 lines
16 KiB
C
718 lines
16 KiB
C
/*
|
|
* 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(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);
|
|
|
|
if (!(fd = refclock_open(device, SPEED232, LDISC_CLK)))
|
|
return (0);
|
|
|
|
/*
|
|
* Allocate and initialize unit structure
|
|
*/
|
|
if (!(up = (struct nmeaunit *)
|
|
emalloc(sizeof(struct nmeaunit)))) {
|
|
(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 = 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->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:
|
|
/*
|
|
* 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->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');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (pp->hour > 23 || pp->minute > 59 || pp->second > 59
|
|
|| pp->msec > 1000) {
|
|
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->msec = 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;
|
|
|
|
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 */
|