#include <sys/param.h>
#include <sys/systm.h>
#include <sys/malloc.h>
#include <sys/sensors.h>
#include <sys/tty.h>
#include <sys/conf.h>
#include <sys/time.h>
#ifdef NMEA_DEBUG
#define DPRINTFN(n, x) do { if (nmeadebug > (n)) printf x; } while (0)
int nmeadebug = 0;
#else
#define DPRINTFN(n, x)
#endif
#define DPRINTF(x) DPRINTFN(0, x)
void nmeaattach(int);
#define NMEAMAX 82
#define MAXFLDS 32
#define KNOTTOMS (51444 / 100)
#ifdef NMEA_DEBUG
#define TRUSTTIME 30
#else
#define TRUSTTIME (10 * 60)
#endif
int nmea_count, nmea_nxid;
struct nmea {
char cbuf[NMEAMAX];
struct ksensor time;
struct ksensor signal;
struct ksensor latitude;
struct ksensor longitude;
struct ksensor altitude;
struct ksensor speed;
struct ksensordev timedev;
struct timespec ts;
struct timespec lts;
struct timeout nmea_tout;
int64_t gap;
#ifdef NMEA_DEBUG
int gapno;
#endif
int64_t last;
int sync;
int pos;
int no_pps;
char mode;
};
void nmea_scan(struct nmea *, struct tty *);
void nmea_gprmc(struct nmea *, struct tty *, char *fld[], int fldcnt);
void nmea_decode_gga(struct nmea *, struct tty *, char *fld[], int fldcnt);
int nmea_date_to_nano(char *s, int64_t *nano);
int nmea_time_to_nano(char *s, int64_t *nano);
int nmea_degrees(int64_t *dst, char *src, int neg);
int nmea_atoi(int64_t *dst, char *src);
void nmea_timeout(void *);
void
nmeaattach(int dummy)
{
}
int
nmeaopen(dev_t dev, struct tty *tp, struct proc *p)
{
struct nmea *np;
int error;
if (tp->t_line == NMEADISC)
return (ENODEV);
if ((error = suser(p)) != 0)
return (error);
np = malloc(sizeof(struct nmea), M_DEVBUF, M_WAITOK | M_ZERO);
snprintf(np->timedev.xname, sizeof(np->timedev.xname), "nmea%d",
nmea_nxid++);
nmea_count++;
np->time.status = SENSOR_S_UNKNOWN;
np->time.type = SENSOR_TIMEDELTA;
np->time.flags = SENSOR_FINVALID;
sensor_attach(&np->timedev, &np->time);
np->signal.type = SENSOR_INDICATOR;
np->signal.status = SENSOR_S_UNKNOWN;
np->signal.value = 0;
strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc));
sensor_attach(&np->timedev, &np->signal);
np->latitude.type = SENSOR_ANGLE;
np->latitude.status = SENSOR_S_UNKNOWN;
np->latitude.flags = SENSOR_FINVALID;
np->latitude.value = 0;
strlcpy(np->latitude.desc, "Latitude", sizeof(np->latitude.desc));
sensor_attach(&np->timedev, &np->latitude);
np->longitude.type = SENSOR_ANGLE;
np->longitude.status = SENSOR_S_UNKNOWN;
np->longitude.flags = SENSOR_FINVALID;
np->longitude.value = 0;
strlcpy(np->longitude.desc, "Longitude", sizeof(np->longitude.desc));
sensor_attach(&np->timedev, &np->longitude);
np->altitude.type = SENSOR_DISTANCE;
np->altitude.status = SENSOR_S_UNKNOWN;
np->altitude.flags = SENSOR_FINVALID;
np->altitude.value = 0;
strlcpy(np->altitude.desc, "Altitude", sizeof(np->altitude.desc));
sensor_attach(&np->timedev, &np->altitude);
np->speed.type = SENSOR_VELOCITY;
np->speed.status = SENSOR_S_UNKNOWN;
np->speed.flags = SENSOR_FINVALID;
np->speed.value = 0;
strlcpy(np->speed.desc, "Ground speed", sizeof(np->speed.desc));
sensor_attach(&np->timedev, &np->speed);
np->sync = 1;
tp->t_sc = (caddr_t)np;
error = linesw[TTYDISC].l_open(dev, tp, p);
if (error) {
free(np, M_DEVBUF, sizeof(*np));
tp->t_sc = NULL;
} else {
sensordev_install(&np->timedev);
timeout_set(&np->nmea_tout, nmea_timeout, np);
}
return (error);
}
int
nmeaclose(struct tty *tp, int flags, struct proc *p)
{
struct nmea *np = (struct nmea *)tp->t_sc;
tp->t_line = TTYDISC;
timeout_del(&np->nmea_tout);
sensordev_deinstall(&np->timedev);
free(np, M_DEVBUF, sizeof(*np));
tp->t_sc = NULL;
nmea_count--;
if (nmea_count == 0)
nmea_nxid = 0;
return (linesw[TTYDISC].l_close(tp, flags, p));
}
int
nmeainput(int c, struct tty *tp)
{
struct nmea *np = (struct nmea *)tp->t_sc;
struct timespec ts;
int64_t gap;
long tmin, tmax;
switch (c) {
case '$':
nanotime(&ts);
np->pos = np->sync = 0;
gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) -
(np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec);
np->lts.tv_sec = ts.tv_sec;
np->lts.tv_nsec = ts.tv_nsec;
if (gap <= np->gap)
break;
np->ts.tv_sec = ts.tv_sec;
np->ts.tv_nsec = ts.tv_nsec;
#ifdef NMEA_DEBUG
if (nmeadebug > 0) {
linesw[TTYDISC].l_rint('[', tp);
linesw[TTYDISC].l_rint('0' + np->gapno++, tp);
linesw[TTYDISC].l_rint(']', tp);
}
#endif
np->gap = gap;
if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR |
TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) {
tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec);
tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec);
if (tmax - tmin > 1)
np->no_pps = 1;
else {
np->ts.tv_sec = tp->t_tv.tv_sec;
np->ts.tv_nsec = tp->t_tv.tv_usec *
1000L;
np->no_pps = 0;
}
}
break;
case '\r':
case '\n':
if (!np->sync) {
np->cbuf[np->pos] = '\0';
nmea_scan(np, tp);
np->sync = 1;
}
break;
default:
if (!np->sync && np->pos < (NMEAMAX - 1))
np->cbuf[np->pos++] = c;
break;
}
return (linesw[TTYDISC].l_rint(c, tp));
}
void
nmea_scan(struct nmea *np, struct tty *tp)
{
int fldcnt = 0, cksum = 0, msgcksum, n;
char *fld[MAXFLDS], *cs;
fld[fldcnt++] = &np->cbuf[0];
for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) {
switch (np->cbuf[n]) {
case '*':
np->cbuf[n] = '\0';
cs = &np->cbuf[n + 1];
break;
case ',':
if (fldcnt < MAXFLDS) {
cksum ^= np->cbuf[n];
np->cbuf[n] = '\0';
fld[fldcnt++] = &np->cbuf[n + 1];
} else {
DPRINTF(("nr of fields in %s sentence exceeds "
"maximum of %d\n", fld[0], MAXFLDS));
return;
}
break;
default:
cksum ^= np->cbuf[n];
}
}
if (strncmp(fld[0], "BD", 2) &&
strncmp(fld[0], "GA", 2) &&
strncmp(fld[0], "GL", 2) &&
strncmp(fld[0], "GN", 2) &&
strncmp(fld[0], "GP", 2))
return;
if (strncmp(fld[0] + 2, "RMC", 3) &&
strncmp(fld[0] + 2, "GGA", 3))
return;
if (cs != NULL) {
msgcksum = 0;
while (*cs) {
if ((*cs >= '0' && *cs <= '9') ||
(*cs >= 'A' && *cs <= 'F')) {
if (msgcksum)
msgcksum <<= 4;
if (*cs >= '0' && *cs<= '9')
msgcksum += *cs - '0';
else if (*cs >= 'A' && *cs <= 'F')
msgcksum += 10 + *cs - 'A';
cs++;
} else {
DPRINTF(("bad char %c in checksum\n", *cs));
return;
}
}
if (msgcksum != cksum) {
DPRINTF(("checksum mismatch\n"));
return;
}
}
if (strncmp(fld[0] + 2, "RMC", 3) == 0)
nmea_gprmc(np, tp, fld, fldcnt);
if (strncmp(fld[0] + 2, "GGA", 3) == 0)
nmea_decode_gga(np, tp, fld, fldcnt);
}
void
nmea_gprmc(struct nmea *np, struct tty *tp, char *fld[], int fldcnt)
{
int64_t date_nano, time_nano, nmea_now;
int jumped = 0;
if (fldcnt < 12 || fldcnt > 14) {
DPRINTF(("gprmc: field count mismatch, %d\n", fldcnt));
return;
}
if (nmea_time_to_nano(fld[1], &time_nano)) {
DPRINTF(("gprmc: illegal time, %s\n", fld[1]));
return;
}
if (nmea_date_to_nano(fld[9], &date_nano)) {
DPRINTF(("gprmc: illegal date, %s\n", fld[9]));
return;
}
nmea_now = date_nano + time_nano;
if (nmea_now <= np->last) {
DPRINTF(("gprmc: time not monotonically increasing\n"));
jumped = 1;
}
np->last = nmea_now;
np->gap = 0LL;
#ifdef NMEA_DEBUG
if (np->time.status == SENSOR_S_UNKNOWN) {
np->time.status = SENSOR_S_OK;
timeout_add_sec(&np->nmea_tout, TRUSTTIME);
}
np->gapno = 0;
if (nmeadebug > 0) {
linesw[TTYDISC].l_rint('[', tp);
linesw[TTYDISC].l_rint('C', tp);
linesw[TTYDISC].l_rint(']', tp);
}
#endif
np->time.value = np->ts.tv_sec * 1000000000LL +
np->ts.tv_nsec - nmea_now;
np->time.tv.tv_sec = np->ts.tv_sec;
np->time.tv.tv_usec = np->ts.tv_nsec / 1000L;
if (fldcnt < 13)
strlcpy(np->time.desc, "GPS", sizeof(np->time.desc));
else if (*fld[12] != np->mode) {
np->mode = *fld[12];
switch (np->mode) {
case 'S':
strlcpy(np->time.desc, "GPS simulated",
sizeof(np->time.desc));
break;
case 'E':
strlcpy(np->time.desc, "GPS estimated",
sizeof(np->time.desc));
break;
case 'A':
strlcpy(np->time.desc, "GPS autonomous",
sizeof(np->time.desc));
break;
case 'D':
strlcpy(np->time.desc, "GPS differential",
sizeof(np->time.desc));
break;
case 'N':
strlcpy(np->time.desc, "GPS invalid",
sizeof(np->time.desc));
break;
default:
strlcpy(np->time.desc, "GPS unknown",
sizeof(np->time.desc));
DPRINTF(("gprmc: unknown mode '%c'\n", np->mode));
}
}
switch (*fld[2]) {
case 'A':
np->time.status = SENSOR_S_OK;
np->signal.value = 1;
np->signal.status = SENSOR_S_OK;
np->latitude.status = SENSOR_S_OK;
np->longitude.status = SENSOR_S_OK;
np->speed.status = SENSOR_S_OK;
np->time.flags &= ~SENSOR_FINVALID;
np->latitude.flags &= ~SENSOR_FINVALID;
np->longitude.flags &= ~SENSOR_FINVALID;
np->speed.flags &= ~SENSOR_FINVALID;
break;
case 'V':
np->signal.value = 0;
np->signal.status = SENSOR_S_CRIT;
np->latitude.status = SENSOR_S_WARN;
np->longitude.status = SENSOR_S_WARN;
np->speed.status = SENSOR_S_WARN;
break;
}
if (nmea_degrees(&np->latitude.value, fld[3], *fld[4] == 'S' ? 1 : 0))
np->latitude.status = SENSOR_S_WARN;
if (nmea_degrees(&np->longitude.value,fld[5], *fld[6] == 'W' ? 1 : 0))
np->longitude.status = SENSOR_S_WARN;
if (nmea_atoi(&np->speed.value, fld[7]))
np->speed.status = SENSOR_S_WARN;
np->speed.value *= KNOTTOMS;
if (jumped)
np->time.status = SENSOR_S_WARN;
if (np->time.status == SENSOR_S_OK)
timeout_add_sec(&np->nmea_tout, TRUSTTIME);
if (np->no_pps)
np->time.status = SENSOR_S_CRIT;
}
void
nmea_decode_gga(struct nmea *np, struct tty *tp, char *fld[], int fldcnt)
{
if (fldcnt != 15) {
DPRINTF(("GGA: field count mismatch, %d\n", fldcnt));
return;
}
#ifdef NMEA_DEBUG
if (nmeadebug > 0) {
linesw[TTYDISC].l_rint('[', tp);
linesw[TTYDISC].l_rint('C', tp);
linesw[TTYDISC].l_rint(']', tp);
}
#endif
np->altitude.status = SENSOR_S_OK;
if (nmea_atoi(&np->altitude.value, fld[9]))
np->altitude.status = SENSOR_S_WARN;
np->altitude.value *= 1000;
np->altitude.flags &= ~SENSOR_FINVALID;
}
int
nmea_atoi(int64_t *dst, char *src)
{
char *p;
int i = 3;
*dst = 0;
for (p = src; *p && *p != '.' && *p >= '0' && *p <= '9' ; )
*dst = *dst * 10 + (*p++ - '0');
if (*p != '.')
return -1;
p++;
for (; *p && i > 0 && *p >= '0' && *p <= '9' ; i--)
*dst = *dst * 10 + (*p++ - '0');
for (; i > 0 ; i--)
*dst *= 10;
DPRINTFN(2,("%s -> %lld\n", src, *dst));
return 0;
}
int
nmea_degrees(int64_t *dst, char *src, int neg)
{
size_t ppos;
int i, n;
int64_t deg = 0, min = 0;
char *p;
while (*src == '0')
++src;
for (p = src, ppos = 0; *p; ppos++)
if (*p++ == '.')
break;
if (*p == '\0')
return (-1);
for (n = 0; *src && n + 2 < ppos; n++)
deg = deg * 10 + (*src++ - '0');
for (; *src && n < ppos; n++)
min = min * 10 + (*src++ - '0');
src++;
for (; *src && n < (ppos + 4); n++)
min = min * 10 + (*src++ - '0');
for (i=0; i < 6 + ppos - n; i++)
min *= 10;
deg = deg * 1000000 + (min/60);
*dst = neg ? -deg : deg;
return (0);
}
int
nmea_date_to_nano(char *s, int64_t *nano)
{
struct clock_ymdhms ymd;
time_t secs;
char *p;
int n;
for (n = 0, p = s; n < 6 && *p && *p >= '0' && *p <= '9'; n++, p++)
;
if (n != 6 || (*p != '\0'))
return (-1);
ymd.dt_year = 2000 + (s[4] - '0') * 10 + (s[5] - '0');
ymd.dt_mon = (s[2] - '0') * 10 + (s[3] - '0');
ymd.dt_day = (s[0] - '0') * 10 + (s[1] - '0');
ymd.dt_hour = ymd.dt_min = ymd.dt_sec = 0;
secs = clock_ymdhms_to_secs(&ymd);
*nano = secs * 1000000000LL;
return (0);
}
int
nmea_time_to_nano(char *s, int64_t *nano)
{
long fac = 36000L, div = 6L, secs = 0L, frac = 0L;
char ul = '2';
int n;
for (n = 0, secs = 0; fac && *s && *s >= '0' && *s <= ul; s++, n++) {
secs += (*s - '0') * fac;
div = 16 - div;
fac /= div;
switch (n) {
case 0:
if (*s <= '1')
ul = '9';
else
ul = '3';
break;
case 1:
case 3:
ul = '5';
break;
case 2:
case 4:
ul = '9';
break;
}
}
if (fac)
return (-1);
div = 1L;
if (*s == '.') {
for (++s; div < 1000000 && *s && *s >= '0' && *s <= '9'; s++) {
frac *= 10;
frac += (*s - '0');
div *= 10;
}
}
if (*s != '\0')
return (-1);
*nano = secs * 1000000000LL + (int64_t)frac * (1000000000 / div);
return (0);
}
void
nmea_timeout(void *xnp)
{
struct nmea *np = xnp;
np->signal.value = 0;
np->signal.status = SENSOR_S_CRIT;
if (np->time.status == SENSOR_S_OK) {
np->time.status = SENSOR_S_WARN;
np->latitude.status = SENSOR_S_WARN;
np->longitude.status = SENSOR_S_WARN;
np->altitude.status = SENSOR_S_WARN;
np->speed.status = SENSOR_S_WARN;
timeout_add_sec(&np->nmea_tout, TRUSTTIME);
} else {
np->time.status = SENSOR_S_CRIT;
np->latitude.status = SENSOR_S_CRIT;
np->longitude.status = SENSOR_S_CRIT;
np->altitude.status = SENSOR_S_CRIT;
np->speed.status = SENSOR_S_CRIT;
}
}