Edit

IABSD.fr/src/sys/kern/tty_nmea.c

Branch :

  • Show log

    Commit

  • Author : mlarkin
    Date : 2022-04-02 22:45:18
    Hash : 573411a3
    Message : Update an old comment The old comment only mentioned that tty_nmea was used for time, but subsequently position data was added to this line discipline.

  • sys/kern/tty_nmea.c
  • /*	$OpenBSD: tty_nmea.c,v 1.51 2022/04/02 22:45:18 mlarkin Exp $ */
    
    /*
     * Copyright (c) 2006, 2007, 2008 Marc Balmer <mbalmer@openbsd.org>
     *
     * Permission to use, copy, modify, and distribute this software for any
     * purpose with or without fee is hereby granted, provided that the above
     * copyright notice and this permission notice appear in all copies.
     *
     * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     */
    
    /*
     * A tty line discipline to decode NMEA 0183 data to get the time
     * and GPS position data
     */
    
    #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)	/* 10 minutes */
    #endif
    
    int nmea_count, nmea_nxid;
    
    struct nmea {
    	char			cbuf[NMEAMAX];	/* receive buffer */
    	struct ksensor		time;		/* the timedelta sensor */
    	struct ksensor		signal;		/* signal status */
    	struct ksensor		latitude;
    	struct ksensor		longitude;
    	struct ksensor		altitude;
    	struct ksensor		speed;
    	struct ksensordev	timedev;
    	struct timespec		ts;		/* current timestamp */
    	struct timespec		lts;		/* timestamp of last '$' seen */
    	struct timeout		nmea_tout;	/* invalidate sensor */
    	int64_t			gap;		/* gap between two sentences */
    #ifdef NMEA_DEBUG
    	int			gapno;
    #endif
    	int64_t			last;		/* last time rcvd */
    	int			sync;		/* if 1, waiting for '$' */
    	int			pos;		/* position in rcv buffer */
    	int			no_pps;		/* no PPS although requested */
    	char			mode;		/* GPS mode */
    };
    
    /* NMEA decoding */
    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);
    
    /* date and time conversion */
    int	nmea_date_to_nano(char *s, int64_t *nano);
    int	nmea_time_to_nano(char *s, int64_t *nano);
    
    /* longitude and latitude conversion */
    int	nmea_degrees(int64_t *dst, char *src, int neg);
    int	nmea_atoi(int64_t *dst, char *src);
    
    /* degrade the timedelta sensor */
    void	nmea_timeout(void *);
    
    void
    nmeaattach(int dummy)
    {
    	/* noop */
    }
    
    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;	/* switch back to termios */
    	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));
    }
    
    /* Collect NMEA sentences from the tty. */
    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 a tty timestamp is available, make sure its value is
    		 * reasonable by comparing against the timestamp just taken.
    		 * If they differ by more than 2 seconds, assume no PPS signal
    		 * is present, note the fact, and keep using the timestamp
    		 * value.  When this happens, the sensor state is set to
    		 * CRITICAL later when the GPRMC sentence is decoded.
    		 */
    		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;
    	}
    	/* pass data to termios */
    	return (linesw[TTYDISC].l_rint(c, tp));
    }
    
    /* Scan the NMEA sentence just received. */
    void
    nmea_scan(struct nmea *np, struct tty *tp)
    {
    	int fldcnt = 0, cksum = 0, msgcksum, n;
    	char *fld[MAXFLDS], *cs;
    
    	/* split into fields and calculate the checksum */
    	fld[fldcnt++] = &np->cbuf[0];	/* message type */
    	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];
    		}
    	}
    
    	/*
    	 * we only look at the messages coming from well-known sources or 'talkers',
    	 * distinguished by the two-chars prefix, the most common being:
    	 * GPS (GP)
    	 * Glonass (GL)
    	 * BeiDou (BD)
    	 * Galileo (GA)
    	 * 'Any kind/a mix of GNSS systems' (GN)
    	 */
    	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;
    
    	/* we look for the RMC & GGA messages */
    	if (strncmp(fld[0] + 2, "RMC", 3) &&
    	    strncmp(fld[0] + 2, "GGA", 3))
    		return;
    
    	/* if we have a checksum, verify it */
    	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);
    }
    
    /* Decode the recommended minimum specific GPS/TRANSIT data. */
    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':	/* The GPS has a fix, (re)arm the timeout. */
    			/* XXX is 'D' also a valid state? */
    		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':	/*
    			 * The GPS indicates a warning status, do not add to
    			 * the timeout, if the condition persist, the sensor
    			 * will be degraded.  Signal the condition through
    			 * the signal sensor.
    			 */
    		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;
    	/* convert from knot to um/s */
    	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 tty timestamping is requested, but no PPS signal is present, set
    	 * the sensor state to CRITICAL.
    	 */
    	if (np->no_pps)
    		np->time.status = SENSOR_S_CRIT;
    }
    
    /* Decode the GPS fix data for altitude.
     * - field 9 is the altitude in meters
     * $GNGGA,085901.00,1234.5678,N,00987.12345,E,1,12,0.84,1040.9,M,47.4,M,,*4B
     */
    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;
    
    	/* convert to uMeter */
    	np->altitude.value *= 1000;
    	np->altitude.flags &= ~SENSOR_FINVALID;
    }
    
    /*
     * Convert nmea integer/decimal values in the form of XXXX.Y to an integer value
     * if it's a meter/altitude value, will be returned as mm
     */
    int
    nmea_atoi(int64_t *dst, char *src)
    {
    	char *p;
    	int i = 3; /* take 3 digits */
    	*dst = 0;
    
    	for (p = src; *p && *p != '.' && *p >= '0' && *p <= '9' ; )
    		*dst = *dst * 10 + (*p++ - '0');
    
    	/* *p should be '.' at that point */
    	if (*p != '.')
    		return -1;	/* no decimal point, or bogus value ? */
    	p++;
    
    	/* read digits after decimal point, stop at first non-digit */
    	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;
    }
    
    /*
     * Convert a nmea position in the form DDDMM.MMMM to an
     * angle sensor value (degrees*1000000)
     */
    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;	/* skip leading zeroes */
    
    	for (p = src, ppos = 0; *p; ppos++)
    		if (*p++ == '.')
    			break;
    
    	if (*p == '\0')
    		return (-1);	/* no decimal point */
    
    	for (n = 0; *src && n + 2 < ppos; n++)
    		deg = deg * 10 + (*src++ - '0');
    
    	for (; *src && n < ppos; n++)
    		min = min * 10 + (*src++ - '0');
    
    	src++;		/* skip decimal point */
    
    	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);
    }
    
    /*
     * Convert a NMEA 0183 formatted date string to seconds since the epoch.
     * The string must be of the form DDMMYY.
     * Return 0 on success, -1 if illegal characters are encountered.
     */
    int
    nmea_date_to_nano(char *s, int64_t *nano)
    {
    	struct clock_ymdhms ymd;
    	time_t secs;
    	char *p;
    	int n;
    
    	/* make sure the input contains only numbers and is six digits long */
    	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);
    }
    
    /*
     * Convert NMEA 0183 formatted time string to nanoseconds since midnight.
     * The string must be of the form HHMMSS[.[sss]] (e.g. 143724 or 143723.615).
     * Return 0 on success, -1 if illegal characters are encountered.
     */
    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);
    
    	/* Handle the fractions of a second, up to a maximum of 6 digits. */
    	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);
    }
    
    /*
     * Degrade the sensor state if we received no NMEA sentences for more than
     * TRUSTTIME seconds.
     */
    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;
    		/*
    		 * further degrade in TRUSTTIME seconds if no new valid NMEA
    		 * sentences are received.
    		 */
    		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;
    	}
    }