1 /* $OpenBSD: tty_nmea.c,v 1.49 2019/08/15 15:08:14 otto Exp $ */ 2 3 /* 4 * Copyright (c) 2006, 2007, 2008 Marc Balmer <mbalmer@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 /* A tty line discipline to decode NMEA 0183 data to get the time. */ 20 21 #include <sys/param.h> 22 #include <sys/systm.h> 23 #include <sys/malloc.h> 24 #include <sys/sensors.h> 25 #include <sys/tty.h> 26 #include <sys/conf.h> 27 #include <sys/time.h> 28 29 #ifdef NMEA_DEBUG 30 #define DPRINTFN(n, x) do { if (nmeadebug > (n)) printf x; } while (0) 31 int nmeadebug = 0; 32 #else 33 #define DPRINTFN(n, x) 34 #endif 35 #define DPRINTF(x) DPRINTFN(0, x) 36 37 void nmeaattach(int); 38 39 #define NMEAMAX 82 40 #define MAXFLDS 32 41 #define KNOTTOMS (51444 / 100) 42 #ifdef NMEA_DEBUG 43 #define TRUSTTIME 30 44 #else 45 #define TRUSTTIME (10 * 60) /* 10 minutes */ 46 #endif 47 48 int nmea_count, nmea_nxid; 49 50 struct nmea { 51 char cbuf[NMEAMAX]; /* receive buffer */ 52 struct ksensor time; /* the timedelta sensor */ 53 struct ksensor signal; /* signal status */ 54 struct ksensor latitude; 55 struct ksensor longitude; 56 struct ksensor altitude; 57 struct ksensor speed; 58 struct ksensordev timedev; 59 struct timespec ts; /* current timestamp */ 60 struct timespec lts; /* timestamp of last '$' seen */ 61 struct timeout nmea_tout; /* invalidate sensor */ 62 int64_t gap; /* gap between two sentences */ 63 #ifdef NMEA_DEBUG 64 int gapno; 65 #endif 66 int64_t last; /* last time rcvd */ 67 int sync; /* if 1, waiting for '$' */ 68 int pos; /* position in rcv buffer */ 69 int no_pps; /* no PPS although requested */ 70 char mode; /* GPS mode */ 71 }; 72 73 /* NMEA decoding */ 74 void nmea_scan(struct nmea *, struct tty *); 75 void nmea_gprmc(struct nmea *, struct tty *, char *fld[], int fldcnt); 76 void nmea_decode_gga(struct nmea *, struct tty *, char *fld[], int fldcnt); 77 78 /* date and time conversion */ 79 int nmea_date_to_nano(char *s, int64_t *nano); 80 int nmea_time_to_nano(char *s, int64_t *nano); 81 82 /* longitude and latitude conversion */ 83 int nmea_degrees(int64_t *dst, char *src, int neg); 84 int nmea_atoi(int64_t *dst, char *src); 85 86 /* degrade the timedelta sensor */ 87 void nmea_timeout(void *); 88 89 void 90 nmeaattach(int dummy) 91 { 92 /* noop */ 93 } 94 95 int 96 nmeaopen(dev_t dev, struct tty *tp, struct proc *p) 97 { 98 struct nmea *np; 99 int error; 100 101 if (tp->t_line == NMEADISC) 102 return (ENODEV); 103 if ((error = suser(p)) != 0) 104 return (error); 105 np = malloc(sizeof(struct nmea), M_DEVBUF, M_WAITOK | M_ZERO); 106 snprintf(np->timedev.xname, sizeof(np->timedev.xname), "nmea%d", 107 nmea_nxid++); 108 nmea_count++; 109 np->time.status = SENSOR_S_UNKNOWN; 110 np->time.type = SENSOR_TIMEDELTA; 111 np->time.flags = SENSOR_FINVALID; 112 sensor_attach(&np->timedev, &np->time); 113 114 np->signal.type = SENSOR_INDICATOR; 115 np->signal.status = SENSOR_S_UNKNOWN; 116 np->signal.value = 0; 117 strlcpy(np->signal.desc, "Signal", sizeof(np->signal.desc)); 118 sensor_attach(&np->timedev, &np->signal); 119 120 np->latitude.type = SENSOR_ANGLE; 121 np->latitude.status = SENSOR_S_UNKNOWN; 122 np->latitude.flags = SENSOR_FINVALID; 123 np->latitude.value = 0; 124 strlcpy(np->latitude.desc, "Latitude", sizeof(np->latitude.desc)); 125 sensor_attach(&np->timedev, &np->latitude); 126 127 np->longitude.type = SENSOR_ANGLE; 128 np->longitude.status = SENSOR_S_UNKNOWN; 129 np->longitude.flags = SENSOR_FINVALID; 130 np->longitude.value = 0; 131 strlcpy(np->longitude.desc, "Longitude", sizeof(np->longitude.desc)); 132 sensor_attach(&np->timedev, &np->longitude); 133 134 np->altitude.type = SENSOR_DISTANCE; 135 np->altitude.status = SENSOR_S_UNKNOWN; 136 np->altitude.flags = SENSOR_FINVALID; 137 np->altitude.value = 0; 138 strlcpy(np->altitude.desc, "Altitude", sizeof(np->altitude.desc)); 139 sensor_attach(&np->timedev, &np->altitude); 140 141 np->speed.type = SENSOR_VELOCITY; 142 np->speed.status = SENSOR_S_UNKNOWN; 143 np->speed.flags = SENSOR_FINVALID; 144 np->speed.value = 0; 145 strlcpy(np->speed.desc, "Ground speed", sizeof(np->speed.desc)); 146 sensor_attach(&np->timedev, &np->speed); 147 148 np->sync = 1; 149 tp->t_sc = (caddr_t)np; 150 151 error = linesw[TTYDISC].l_open(dev, tp, p); 152 if (error) { 153 free(np, M_DEVBUF, sizeof(*np)); 154 tp->t_sc = NULL; 155 } else { 156 sensordev_install(&np->timedev); 157 timeout_set(&np->nmea_tout, nmea_timeout, np); 158 } 159 return (error); 160 } 161 162 int 163 nmeaclose(struct tty *tp, int flags, struct proc *p) 164 { 165 struct nmea *np = (struct nmea *)tp->t_sc; 166 167 tp->t_line = TTYDISC; /* switch back to termios */ 168 timeout_del(&np->nmea_tout); 169 sensordev_deinstall(&np->timedev); 170 free(np, M_DEVBUF, sizeof(*np)); 171 tp->t_sc = NULL; 172 nmea_count--; 173 if (nmea_count == 0) 174 nmea_nxid = 0; 175 return (linesw[TTYDISC].l_close(tp, flags, p)); 176 } 177 178 /* Collect NMEA sentences from the tty. */ 179 int 180 nmeainput(int c, struct tty *tp) 181 { 182 struct nmea *np = (struct nmea *)tp->t_sc; 183 struct timespec ts; 184 int64_t gap; 185 long tmin, tmax; 186 187 switch (c) { 188 case '$': 189 nanotime(&ts); 190 np->pos = np->sync = 0; 191 gap = (ts.tv_sec * 1000000000LL + ts.tv_nsec) - 192 (np->lts.tv_sec * 1000000000LL + np->lts.tv_nsec); 193 194 np->lts.tv_sec = ts.tv_sec; 195 np->lts.tv_nsec = ts.tv_nsec; 196 197 if (gap <= np->gap) 198 break; 199 200 np->ts.tv_sec = ts.tv_sec; 201 np->ts.tv_nsec = ts.tv_nsec; 202 203 #ifdef NMEA_DEBUG 204 if (nmeadebug > 0) { 205 linesw[TTYDISC].l_rint('[', tp); 206 linesw[TTYDISC].l_rint('0' + np->gapno++, tp); 207 linesw[TTYDISC].l_rint(']', tp); 208 } 209 #endif 210 np->gap = gap; 211 212 /* 213 * If a tty timestamp is available, make sure its value is 214 * reasonable by comparing against the timestamp just taken. 215 * If they differ by more than 2 seconds, assume no PPS signal 216 * is present, note the fact, and keep using the timestamp 217 * value. When this happens, the sensor state is set to 218 * CRITICAL later when the GPRMC sentence is decoded. 219 */ 220 if (tp->t_flags & (TS_TSTAMPDCDSET | TS_TSTAMPDCDCLR | 221 TS_TSTAMPCTSSET | TS_TSTAMPCTSCLR)) { 222 tmax = lmax(np->ts.tv_sec, tp->t_tv.tv_sec); 223 tmin = lmin(np->ts.tv_sec, tp->t_tv.tv_sec); 224 if (tmax - tmin > 1) 225 np->no_pps = 1; 226 else { 227 np->ts.tv_sec = tp->t_tv.tv_sec; 228 np->ts.tv_nsec = tp->t_tv.tv_usec * 229 1000L; 230 np->no_pps = 0; 231 } 232 } 233 break; 234 case '\r': 235 case '\n': 236 if (!np->sync) { 237 np->cbuf[np->pos] = '\0'; 238 nmea_scan(np, tp); 239 np->sync = 1; 240 } 241 break; 242 default: 243 if (!np->sync && np->pos < (NMEAMAX - 1)) 244 np->cbuf[np->pos++] = c; 245 break; 246 } 247 /* pass data to termios */ 248 return (linesw[TTYDISC].l_rint(c, tp)); 249 } 250 251 /* Scan the NMEA sentence just received. */ 252 void 253 nmea_scan(struct nmea *np, struct tty *tp) 254 { 255 int fldcnt = 0, cksum = 0, msgcksum, n; 256 char *fld[MAXFLDS], *cs; 257 258 /* split into fields and calculate the checksum */ 259 fld[fldcnt++] = &np->cbuf[0]; /* message type */ 260 for (cs = NULL, n = 0; n < np->pos && cs == NULL; n++) { 261 switch (np->cbuf[n]) { 262 case '*': 263 np->cbuf[n] = '\0'; 264 cs = &np->cbuf[n + 1]; 265 break; 266 case ',': 267 if (fldcnt < MAXFLDS) { 268 cksum ^= np->cbuf[n]; 269 np->cbuf[n] = '\0'; 270 fld[fldcnt++] = &np->cbuf[n + 1]; 271 } else { 272 DPRINTF(("nr of fields in %s sentence exceeds " 273 "maximum of %d\n", fld[0], MAXFLDS)); 274 return; 275 } 276 break; 277 default: 278 cksum ^= np->cbuf[n]; 279 } 280 } 281 282 /* 283 * we only look at the messages coming from well-known sources or 'talkers', 284 * distinguished by the two-chars prefix, the most common being: 285 * GPS (GP) 286 * Glonass (GL) 287 * BeiDou (BD) 288 * Galileo (GA) 289 * 'Any kind/a mix of GNSS systems' (GN) 290 */ 291 if (strncmp(fld[0], "BD", 2) && 292 strncmp(fld[0], "GA", 2) && 293 strncmp(fld[0], "GL", 2) && 294 strncmp(fld[0], "GN", 2) && 295 strncmp(fld[0], "GP", 2)) 296 return; 297 298 /* we look for the RMC & GGA messages */ 299 if (strncmp(fld[0] + 2, "RMC", 3) && 300 strncmp(fld[0] + 2, "GGA", 3)) 301 return; 302 303 /* if we have a checksum, verify it */ 304 if (cs != NULL) { 305 msgcksum = 0; 306 while (*cs) { 307 if ((*cs >= '0' && *cs <= '9') || 308 (*cs >= 'A' && *cs <= 'F')) { 309 if (msgcksum) 310 msgcksum <<= 4; 311 if (*cs >= '0' && *cs<= '9') 312 msgcksum += *cs - '0'; 313 else if (*cs >= 'A' && *cs <= 'F') 314 msgcksum += 10 + *cs - 'A'; 315 cs++; 316 } else { 317 DPRINTF(("bad char %c in checksum\n", *cs)); 318 return; 319 } 320 } 321 if (msgcksum != cksum) { 322 DPRINTF(("checksum mismatch\n")); 323 return; 324 } 325 } 326 if (strncmp(fld[0] + 2, "RMC", 3) == 0) 327 nmea_gprmc(np, tp, fld, fldcnt); 328 if (strncmp(fld[0] + 2, "GGA", 3) == 0) 329 nmea_decode_gga(np, tp, fld, fldcnt); 330 } 331 332 /* Decode the recommended minimum specific GPS/TRANSIT data. */ 333 void 334 nmea_gprmc(struct nmea *np, struct tty *tp, char *fld[], int fldcnt) 335 { 336 int64_t date_nano, time_nano, nmea_now; 337 int jumped = 0; 338 339 if (fldcnt < 12 || fldcnt > 14) { 340 DPRINTF(("gprmc: field count mismatch, %d\n", fldcnt)); 341 return; 342 } 343 if (nmea_time_to_nano(fld[1], &time_nano)) { 344 DPRINTF(("gprmc: illegal time, %s\n", fld[1])); 345 return; 346 } 347 if (nmea_date_to_nano(fld[9], &date_nano)) { 348 DPRINTF(("gprmc: illegal date, %s\n", fld[9])); 349 return; 350 } 351 nmea_now = date_nano + time_nano; 352 if (nmea_now <= np->last) { 353 DPRINTF(("gprmc: time not monotonically increasing\n")); 354 jumped = 1; 355 } 356 np->last = nmea_now; 357 np->gap = 0LL; 358 #ifdef NMEA_DEBUG 359 if (np->time.status == SENSOR_S_UNKNOWN) { 360 np->time.status = SENSOR_S_OK; 361 timeout_add_sec(&np->nmea_tout, TRUSTTIME); 362 } 363 np->gapno = 0; 364 if (nmeadebug > 0) { 365 linesw[TTYDISC].l_rint('[', tp); 366 linesw[TTYDISC].l_rint('C', tp); 367 linesw[TTYDISC].l_rint(']', tp); 368 } 369 #endif 370 371 np->time.value = np->ts.tv_sec * 1000000000LL + 372 np->ts.tv_nsec - nmea_now; 373 np->time.tv.tv_sec = np->ts.tv_sec; 374 np->time.tv.tv_usec = np->ts.tv_nsec / 1000L; 375 376 if (fldcnt < 13) 377 strlcpy(np->time.desc, "GPS", sizeof(np->time.desc)); 378 else if (*fld[12] != np->mode) { 379 np->mode = *fld[12]; 380 switch (np->mode) { 381 case 'S': 382 strlcpy(np->time.desc, "GPS simulated", 383 sizeof(np->time.desc)); 384 break; 385 case 'E': 386 strlcpy(np->time.desc, "GPS estimated", 387 sizeof(np->time.desc)); 388 break; 389 case 'A': 390 strlcpy(np->time.desc, "GPS autonomous", 391 sizeof(np->time.desc)); 392 break; 393 case 'D': 394 strlcpy(np->time.desc, "GPS differential", 395 sizeof(np->time.desc)); 396 break; 397 case 'N': 398 strlcpy(np->time.desc, "GPS invalid", 399 sizeof(np->time.desc)); 400 break; 401 default: 402 strlcpy(np->time.desc, "GPS unknown", 403 sizeof(np->time.desc)); 404 DPRINTF(("gprmc: unknown mode '%c'\n", np->mode)); 405 } 406 } 407 switch (*fld[2]) { 408 case 'A': /* The GPS has a fix, (re)arm the timeout. */ 409 /* XXX is 'D' also a valid state? */ 410 np->time.status = SENSOR_S_OK; 411 np->signal.value = 1; 412 np->signal.status = SENSOR_S_OK; 413 np->latitude.status = SENSOR_S_OK; 414 np->longitude.status = SENSOR_S_OK; 415 np->speed.status = SENSOR_S_OK; 416 np->time.flags &= ~SENSOR_FINVALID; 417 np->latitude.flags &= ~SENSOR_FINVALID; 418 np->longitude.flags &= ~SENSOR_FINVALID; 419 np->speed.flags &= ~SENSOR_FINVALID; 420 break; 421 case 'V': /* 422 * The GPS indicates a warning status, do not add to 423 * the timeout, if the condition persist, the sensor 424 * will be degraded. Signal the condition through 425 * the signal sensor. 426 */ 427 np->signal.value = 0; 428 np->signal.status = SENSOR_S_CRIT; 429 np->latitude.status = SENSOR_S_WARN; 430 np->longitude.status = SENSOR_S_WARN; 431 np->speed.status = SENSOR_S_WARN; 432 break; 433 } 434 if (nmea_degrees(&np->latitude.value, fld[3], *fld[4] == 'S' ? 1 : 0)) 435 np->latitude.status = SENSOR_S_WARN; 436 if (nmea_degrees(&np->longitude.value,fld[5], *fld[6] == 'W' ? 1 : 0)) 437 np->longitude.status = SENSOR_S_WARN; 438 439 if (nmea_atoi(&np->speed.value, fld[7])) 440 np->speed.status = SENSOR_S_WARN; 441 /* convert from knot to um/s */ 442 np->speed.value *= KNOTTOMS; 443 444 if (jumped) 445 np->time.status = SENSOR_S_WARN; 446 if (np->time.status == SENSOR_S_OK) 447 timeout_add_sec(&np->nmea_tout, TRUSTTIME); 448 /* 449 * If tty timestamping is requested, but no PPS signal is present, set 450 * the sensor state to CRITICAL. 451 */ 452 if (np->no_pps) 453 np->time.status = SENSOR_S_CRIT; 454 } 455 456 /* Decode the GPS fix data for altitude. 457 * - field 9 is the altitude in meters 458 * $GNGGA,085901.00,1234.5678,N,00987.12345,E,1,12,0.84,1040.9,M,47.4,M,,*4B 459 */ 460 void 461 nmea_decode_gga(struct nmea *np, struct tty *tp, char *fld[], int fldcnt) 462 { 463 if (fldcnt != 15) { 464 DPRINTF(("GGA: field count mismatch, %d\n", fldcnt)); 465 return; 466 } 467 #ifdef NMEA_DEBUG 468 if (nmeadebug > 0) { 469 linesw[TTYDISC].l_rint('[', tp); 470 linesw[TTYDISC].l_rint('C', tp); 471 linesw[TTYDISC].l_rint(']', tp); 472 } 473 #endif 474 475 np->altitude.status = SENSOR_S_OK; 476 if (nmea_atoi(&np->altitude.value, fld[9])) 477 np->altitude.status = SENSOR_S_WARN; 478 479 /* convert to uMeter */ 480 np->altitude.value *= 1000; 481 np->altitude.flags &= ~SENSOR_FINVALID; 482 } 483 484 /* 485 * Convert nmea integer/decimal values in the form of XXXX.Y to an integer value 486 * if it's a meter/altitude value, will be returned as mm 487 */ 488 int 489 nmea_atoi(int64_t *dst, char *src) 490 { 491 char *p; 492 int i = 3; /* take 3 digits */ 493 *dst = 0; 494 495 for (p = src; *p && *p != '.' && *p >= '0' && *p <= '9' ; ) 496 *dst = *dst * 10 + (*p++ - '0'); 497 498 /* *p should be '.' at that point */ 499 if (*p != '.') 500 return -1; /* no decimal point, or bogus value ? */ 501 p++; 502 503 /* read digits after decimal point, stop at first non-digit */ 504 for (; *p && i > 0 && *p >= '0' && *p <= '9' ; i--) 505 *dst = *dst * 10 + (*p++ - '0'); 506 507 for (; i > 0 ; i--) 508 *dst *= 10; 509 510 DPRINTFN(2,("%s -> %lld\n", src, *dst)); 511 return 0; 512 } 513 514 /* 515 * Convert a nmea position in the form DDDMM.MMMM to an 516 * angle sensor value (degrees*1000000) 517 */ 518 int 519 nmea_degrees(int64_t *dst, char *src, int neg) 520 { 521 size_t ppos; 522 int i, n; 523 int64_t deg = 0, min = 0; 524 char *p; 525 526 while (*src == '0') 527 ++src; /* skip leading zeroes */ 528 529 for (p = src, ppos = 0; *p; ppos++) 530 if (*p++ == '.') 531 break; 532 533 if (*p == '\0') 534 return (-1); /* no decimal point */ 535 536 for (n = 0; *src && n + 2 < ppos; n++) 537 deg = deg * 10 + (*src++ - '0'); 538 539 for (; *src && n < ppos; n++) 540 min = min * 10 + (*src++ - '0'); 541 542 src++; /* skip decimal point */ 543 544 for (; *src && n < (ppos + 4); n++) 545 min = min * 10 + (*src++ - '0'); 546 547 for (i=0; i < 6 + ppos - n; i++) 548 min *= 10; 549 550 deg = deg * 1000000 + (min/60); 551 552 *dst = neg ? -deg : deg; 553 return (0); 554 } 555 556 /* 557 * Convert a NMEA 0183 formatted date string to seconds since the epoch. 558 * The string must be of the form DDMMYY. 559 * Return 0 on success, -1 if illegal characters are encountered. 560 */ 561 int 562 nmea_date_to_nano(char *s, int64_t *nano) 563 { 564 struct clock_ymdhms ymd; 565 time_t secs; 566 char *p; 567 int n; 568 569 /* make sure the input contains only numbers and is six digits long */ 570 for (n = 0, p = s; n < 6 && *p && *p >= '0' && *p <= '9'; n++, p++) 571 ; 572 if (n != 6 || (*p != '\0')) 573 return (-1); 574 575 ymd.dt_year = 2000 + (s[4] - '0') * 10 + (s[5] - '0'); 576 ymd.dt_mon = (s[2] - '0') * 10 + (s[3] - '0'); 577 ymd.dt_day = (s[0] - '0') * 10 + (s[1] - '0'); 578 ymd.dt_hour = ymd.dt_min = ymd.dt_sec = 0; 579 580 secs = clock_ymdhms_to_secs(&ymd); 581 *nano = secs * 1000000000LL; 582 return (0); 583 } 584 585 /* 586 * Convert NMEA 0183 formatted time string to nanoseconds since midnight. 587 * The string must be of the form HHMMSS[.[sss]] (e.g. 143724 or 143723.615). 588 * Return 0 on success, -1 if illegal characters are encountered. 589 */ 590 int 591 nmea_time_to_nano(char *s, int64_t *nano) 592 { 593 long fac = 36000L, div = 6L, secs = 0L, frac = 0L; 594 char ul = '2'; 595 int n; 596 597 for (n = 0, secs = 0; fac && *s && *s >= '0' && *s <= ul; s++, n++) { 598 secs += (*s - '0') * fac; 599 div = 16 - div; 600 fac /= div; 601 switch (n) { 602 case 0: 603 if (*s <= '1') 604 ul = '9'; 605 else 606 ul = '3'; 607 break; 608 case 1: 609 case 3: 610 ul = '5'; 611 break; 612 case 2: 613 case 4: 614 ul = '9'; 615 break; 616 } 617 } 618 if (fac) 619 return (-1); 620 621 /* Handle the fractions of a second, up to a maximum of 6 digits. */ 622 div = 1L; 623 if (*s == '.') { 624 for (++s; div < 1000000 && *s && *s >= '0' && *s <= '9'; s++) { 625 frac *= 10; 626 frac += (*s - '0'); 627 div *= 10; 628 } 629 } 630 631 if (*s != '\0') 632 return (-1); 633 634 *nano = secs * 1000000000LL + (int64_t)frac * (1000000000 / div); 635 return (0); 636 } 637 638 /* 639 * Degrade the sensor state if we received no NMEA sentences for more than 640 * TRUSTTIME seconds. 641 */ 642 void 643 nmea_timeout(void *xnp) 644 { 645 struct nmea *np = xnp; 646 647 np->signal.value = 0; 648 np->signal.status = SENSOR_S_CRIT; 649 if (np->time.status == SENSOR_S_OK) { 650 np->time.status = SENSOR_S_WARN; 651 np->latitude.status = SENSOR_S_WARN; 652 np->longitude.status = SENSOR_S_WARN; 653 np->altitude.status = SENSOR_S_WARN; 654 np->speed.status = SENSOR_S_WARN; 655 /* 656 * further degrade in TRUSTTIME seconds if no new valid NMEA 657 * sentences are received. 658 */ 659 timeout_add_sec(&np->nmea_tout, TRUSTTIME); 660 } else { 661 np->time.status = SENSOR_S_CRIT; 662 np->latitude.status = SENSOR_S_CRIT; 663 np->longitude.status = SENSOR_S_CRIT; 664 np->altitude.status = SENSOR_S_CRIT; 665 np->speed.status = SENSOR_S_CRIT; 666 } 667 } 668