#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
#ifdef SYS_WINNT
# define DEVICE "COM%d:"
#else
# define DEVICE "/dev/gps%d"
#endif
#define SPEED232 B4800
#define PRECISION (-9)
#define PPS_PRECISION (-20)
#define REFID "GPS\0"
#define DESCRIPTION "NMEA GPS Clock"
#define NANOSECOND 1000000000
#define RANGEGATE 500000
#define LENNMEA 75
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};
struct nmeaunit {
int pollcnt;
int polled;
l_fp tstamp;
#ifdef HAVE_PPSAPI
struct timespec ts;
pps_params_t pps_params;
pps_info_t pps_info;
pps_handle_t handle;
#endif
};
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
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));
struct refclock refclock_nmea = {
nmea_start,
nmea_shutdown,
nmea_poll,
#ifdef HAVE_PPSAPI
nmea_control,
#else
noentry,
#endif
noentry,
noentry,
NOFLAGS
};
static int
nmea_start(
int unit,
struct peer *peer
)
{
register struct nmeaunit *up;
struct refclockproc *pp;
int fd;
char device[20];
(void)sprintf(device, DEVICE, unit);
fd = refclock_open(device, SPEED232, LDISC_CLK);
if (fd < 0)
return (0);
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;
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
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
}
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
io_closeclock(&pp->io);
free(up);
}
#ifdef HAVE_PPSAPI
static void
nmea_control(
int unit,
struct refclockstat *in,
struct refclockstat *out,
struct peer *peer
)
{
struct refclockproc *pp;
pp = peer->procptr;
nmea_ppsapi(peer, pp->sloppyclockflag & CLK_FLAG2,
pp->sloppyclockflag & CLK_FLAG3);
}
int
nmea_ppsapi(
struct peer *peer,
int enb_clear,
int enb_hardpps
)
{
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);
}
static int
nmea_pps(
struct nmeaunit *up,
l_fp *tsptr
)
{
pps_info_t pps_info;
struct timespec timeout, ts;
double dtemp;
l_fp tstmp;
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
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;
char rd_lastcode[BMAX];
l_fp rd_tmp;
u_short rd_lencode;
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);
if (rd_lencode == 0)
return;
#ifdef DEBUG
if (debug)
printf("nmea: gpsread %d %s\n", rd_lencode,
rd_lastcode);
#endif
#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;
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
switch( cmdtype ) {
case GPRMC:
dp = field_parse(cp,2);
if( dp[0] != 'A')
pp->leap = LEAP_NOTINSYNC;
else
pp->leap = LEAP_NOWARNING;
dp = field_parse(cp,1);
break;
case GPGGA:
dp = field_parse(cp,6);
if( dp[0] == '0')
pp->leap = LEAP_NOTINSYNC;
else
pp->leap = LEAP_NOWARNING;
dp = field_parse(cp,1);
break;
case GPGLL:
dp = field_parse(cp,6);
if( dp[0] != 'A')
pp->leap = LEAP_NOTINSYNC;
else
pp->leap = LEAP_NOWARNING;
dp = field_parse(cp,5);
break;
case GPXXX:
return;
default:
return;
}
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;
}
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->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;
}
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 {
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;
}
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 (nmea_pps(up, &rd_tmp) == 1) {
pp->lastrec = up->tstamp = rd_tmp;
pp->nsec = 0;
}
#endif
if (!refclock_process(pp)) {
refclock_report(peer, CEVNT_BADTIME);
return;
}
if (!up->polled)
return;
up->polled = 0;
pp->lastref = pp->lastrec;
refclock_receive(peer);
refclock_report(peer, CEVNT_NOMINAL);
record_clock_stats(&peer->srcadr, pp->a_lastcode);
}
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;
gps_send(pp->io.fd,"$PMOTG,RMC,0000*1D\r\n", peer);
}
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