#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#if defined(REFCLOCK) && defined(CLOCK_NMEA)
#include <sys/stat.h>
#include <stdio.h>
#include <ctype.h>
#include "ntpd.h"
#include "ntp_io.h"
#include "ntp_unixtime.h"
#include "ntp_refclock.h"
#include "ntp_stdlib.h"
#ifdef HAVE_PPSAPI
# include "ppsapi_timepps.h"
#include "refclock_atom.h"
#endif
#ifdef SYS_WINNT
#undef write
extern int async_write(int, const void *, unsigned int);
#define write(fd, data, octets) async_write(fd, data, octets)
#endif
#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
#define DEVICE "/dev/gps%d"
#define PPSDEV "/dev/gpspps%d"
#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
#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)
struct nmeaunit {
#ifdef HAVE_PPSAPI
struct refclock_atom atom;
int ppsapi_tried;
int ppsapi_lit;
int ppsapi_fd;
int tcount;
int pcount;
#endif
l_fp tstamp;
int gps_time;
};
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 (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
static void gps_send (int, const char *, struct peer *);
static char * field_parse (char *, int);
static int nmea_checksum_ok(const char *);
struct refclock refclock_nmea = {
nmea_start,
nmea_shutdown,
nmea_poll,
NMEA_CONTROL,
noentry,
noentry,
NMEA_TIMER
};
static int
nmea_start(
int unit,
struct peer *peer
)
{
register struct nmeaunit *up;
struct refclockproc *pp;
int fd;
char device[20];
int baudrate;
char *baudtext;
pp = peer->procptr;
snprintf(device, sizeof(device), DEVICE, unit);
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, baudrate, LDISC_CLK);
if (fd <= 0) {
#ifdef HAVE_READLINK
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("ip")) == 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
pp->io.fd = -1;
return (0);
#endif
}
msyslog(LOG_NOTICE, "%s serial %s open at %s bps",
refnumtoa(&peer->srcadr), device, baudtext);
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)) {
pp->io.fd = -1;
close(fd);
free(up);
return (0);
}
pp->unitptr = (caddr_t)up;
peer->precision = PRECISION;
pp->clockdesc = DESCRIPTION;
memcpy(&pp->refid, REFID, 4);
gps_send(fd,"$PMOTG,RMC,0000*1D\r\n", peer);
return (1);
}
static void
nmea_shutdown(
int unit,
struct peer *peer
)
{
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->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
static void
nmea_control(
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;
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;
up->ppsapi_tried = 1;
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;
return;
}
NLOG(NLOG_CLOCKINFO)
msyslog(LOG_WARNING, "%s flag1 1 but PPSAPI fails",
refnumtoa(&peer->srcadr));
}
#endif
#ifdef HAVE_PPSAPI
static void
nmea_timer(
int unit,
struct peer * peer
)
{
struct nmeaunit *up;
struct refclockproc *pp;
UNUSED_ARG(unit);
pp = peer->procptr;
up = (struct nmeaunit *)pp->unitptr;
if (up->ppsapi_lit &&
refclock_pps(peer, &up->atom, pp->sloppyclockflag) > 0) {
up->pcount++,
peer->flags |= FLAG_PPS;
peer->precision = PPS_PRECISION;
}
}
#endif
static void
nmea_receive(
struct recvbuf *rbufp
)
{
register struct nmeaunit *up;
struct refclockproc *pp;
struct peer *peer;
int month, day;
char *cp, *dp, *msg;
int cmdtype;
int cmdtypezdg = 0;
char rd_lastcode[BMAX];
l_fp rd_timestamp;
int rd_lencode;
peer = rbufp->recv_peer;
pp = peer->procptr;
up = (struct nmeaunit *)pp->unitptr;
rd_lencode = refclock_gtlin(
rbufp,
rd_lastcode,
sizeof(rd_lastcode),
&rd_timestamp);
if (rd_lencode == 0)
return;
DPRINTF(1, ("nmea: gpsread %d %s\n", rd_lencode, rd_lastcode));
#define GPXXX 0
#define GPRMC 1
#define GPGGA 2
#define GPGLL 4
#define GPZDG_ZDA 8
cp = rd_lastcode;
cmdtype=0;
if (cp[0] == '$') {
msg = cp + 3;
if (strncmp(msg, "RMC", 3) == 0)
cmdtype = GPRMC;
else if (strncmp(msg, "GGA", 3) == 0)
cmdtype = GPGGA;
else if (strncmp(msg, "GLL", 3) == 0)
cmdtype = GPGLL;
else if (strncmp(msg, "ZD", 2) == 0) {
cmdtype = GPZDG_ZDA;
if ('G' == msg[2])
cmdtypezdg = 1;
else if ('A' != msg[2])
return;
} else
return;
} else
return;
if (peer->ttl && !(cmdtype & (peer->ttl & NMEA_MESSAGE_MASK)))
return;
if (up->gps_time && !cmdtypezdg)
return;
if (!nmea_checksum_ok(rd_lastcode)) {
refclock_report(peer, CEVNT_BADREPLY);
return;
}
pp->lencode = (u_short) rd_lencode;
memcpy(pp->a_lastcode, rd_lastcode, pp->lencode + 1);
cp = pp->a_lastcode;
up->tstamp = rd_timestamp;
pp->lastrec = up->tstamp;
DPRINTF(1, ("nmea: timecode %d %s\n", pp->lencode, pp->a_lastcode));
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 GPZDG_ZDA:
if (cmdtypezdg) {
dp = field_parse(cp, 6);
if (dp[0] == '0')
pp->leap = LEAP_NOTINSYNC;
else
pp->leap = LEAP_NOWARNING;
} else
pp->leap = LEAP_NOWARNING;
dp = field_parse(cp, 1);
break;
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])) {
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;
}
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 (cmdtypezdg) {
if (pp->second == 0) {
pp->second = 59;
if (pp->minute == 0) {
pp->minute = 59;
if (pp->hour == 0)
pp->hour = 23;
}
} else
pp->second -= 1;
}
if (pp->hour > 23 || pp->minute > 59 ||
pp->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;
}
if (GPRMC == cmdtype) {
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 if (GPZDG_ZDA == cmdtype) {
dp = field_parse(cp, 2);
day = 10 * (dp[0] - '0') + (dp[1] - '0');
dp = field_parse(cp, 3);
month = 10 * (dp[0] - '0') + (dp[1] - '0');
dp = field_parse(cp, 4);
pp->year = 10 * (dp[2] - '0') + (dp[3] - '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 + 1900;
}
if (month < 1 || month > 12 || day < 1) {
refclock_report(peer, CEVNT_BADDATE);
return;
}
if (pp->year < 100) {
if (pp->year < 9)
pp->year += 2100;
else
pp->year += 2000;
}
day = ymd2yd(pp->year, month, day);
if (-1 == day) {
refclock_report(peer, CEVNT_BADDATE);
return;
}
pp->day = day;
if (CLK_FLAG4 & pp->sloppyclockflag) {
switch (cmdtype) {
case GPGLL:
cp = field_parse(pp->a_lastcode, 1);
dp = field_parse(cp, 2);
break;
case GPGGA:
cp = field_parse(pp->a_lastcode, 2);
dp = field_parse(cp, 2);
break;
case GPRMC:
cp = field_parse(pp->a_lastcode, 3);
dp = field_parse(cp, 2);
break;
case GPZDG_ZDA:
default:
cp = dp = NULL;
}
while (cp) {
while (',' != *cp) {
if ('.' != *cp)
*cp = '_';
cp++;
}
if (cp < dp)
cp = dp;
else
cp = NULL;
}
if (dp) {
cp = pp->a_lastcode + pp->lencode - 2;
if (0 == cp[2])
cp[0] = cp[1] = '_';
}
}
if (cmdtypezdg && !up->gps_time) {
up->gps_time = 1;
NLOG(NLOG_CLOCKINFO)
msyslog(LOG_INFO, "%s using only $GPZDG",
refnumtoa(&peer->srcadr));
}
#ifdef HAVE_PPSAPI
up->tcount++;
if (peer->flags & FLAG_PPS)
return;
#endif
if (!refclock_process_f(pp, pp->fudgetime2))
refclock_report(peer, CEVNT_BADTIME);
}
static void
nmea_poll(
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->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
if (pp->coderecv == pp->codeproc) {
refclock_report(peer, CEVNT_TIMEOUT);
return;
}
#endif
pp->polls++;
pp->lastref = pp->lastrec;
refclock_receive(peer);
record_clock_stats(&peer->srcadr, pp->a_lastcode);
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; i && *tp; tp++)
if (*tp == ',')
i--;
return tp;
}
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;
}
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;
}
#else
int refclock_nmea_bs;
#endif