Hi, here's a diff to add 5 new sensor values to nmea: altitude, quality, hdop, vdop & pdop. altitude and quality are provided by GGA messages: http://aprs.gids.nl/nmea/#gga, quality is either 0 (no fix), 1 (gps fix) or 2 (dgps fix).
The last 3 are 'Dilution of precision' values, respectively horizontal, vertical & positional (ie 3d), cf https://en.wikipedia.org/wiki/Dilution_of_precision_(navigation) & http://www.trakgps.com/en/index.php/information/gps-articles-information/65-gps-accuracy and are provided by GSA messages: http://aprs.gids.nl/nmea/#gsa it is generally considered good precision when the DOP is below 2, so i've set the sensor status warning accordingly. this provides for example (angle values hidden for 'privacy', just after the gps got a fix): nmea0.indicator0 On OK Signal nmea0.raw0 1 raw OK GPS fix nmea0.raw1 1920 raw OK HDOP nmea0.raw2 2520 raw WARNING VDOP nmea0.raw3 3170 raw WARNING PDOP nmea0.timedelta0 24.867 ms OK GPS autonomous nmea0.angle0 xx.yyyy degrees OK Latitude nmea0.angle1 z.wwww degrees OK Longitude nmea0.distance0 371.50 mm OK Altitude and after a while, when precision improved: nmea0.indicator0 On OK Signal nmea0.raw0 1 raw OK GPS fix nmea0.raw1 800 raw OK HDOP nmea0.raw2 1300 raw OK VDOP nmea0.raw3 1530 raw OK PDOP nmea0.timedelta0 -296.499 ms OK GPS autonomous nmea0.angle0 xx.yyyy degrees OK Latitude nmea0.angle1 z.wwww degrees OK Longitude nmea0.distance0 355.50 mm OK Altitude two problems with this display: - DOPs are usually decimal values, ie 1.3, 1.53.. but raw sensors only support integers, hence *1000. - GGA messages provide altitude as '355.5' in meters (i *think* the unit is fixed in the protocol, because altitude in feets is provided by PGRMZ) - and we only have a SENSOR_DISTANCE type in mm (which doesnt seem used by any driver..). Thus altitude shown in milimeters.. This sensor types were added in https://github.com/openbsd/src/commit/d95200b806051887b8c69294833e22dad6302828 but no driver apparently makes use of it. >From that point, questions: - should i add more 'interesting' sensor values, like amount of satellites seen/used ? - i want to add speed value (as RMC has it in knots?, and VTG in various units, per http://aprs.gids.nl/nmea/#vtg), but we only have SENSOR_ACCEL type in m.s-2 (which is only used by asmc(4)). Should we add a 'speed' sensor type ? in m/s ? in km/h ? knots ? - should i add a SENSOR_ALTITUDE type in meters ? - is there any interest in all this, from the sensors framework POV ? Otherwise i can leave it as is and just use gpsd in userspace, which works 100% fine... All this done with an umodem0 at uhub1 port 1 configuration 1 interface 0 "u-blox AG - www.u-blox.com u-blox GNSS receiver" rev 1.10/2.01 addr 3 which works fine with ldattach & gpsd, ie i run doas gpsd -N -D2 $(doas ldattach -p nmea /dev/cuaU0) and gpsmon shows me the msgs received by the device, sent to the kernel via ldattach, and forwarded to gpsd. comments welcome, diff not to be commited as is of course (nmea_atoi is *horrible*, i know..) Landry
Index: tty_nmea.c =================================================================== RCS file: /cvs/src/sys/kern/tty_nmea.c,v retrieving revision 1.47 diff -u -r1.47 tty_nmea.c --- tty_nmea.c 1 Sep 2018 06:09:26 -0000 1.47 +++ tty_nmea.c 3 Nov 2018 15:28:11 -0000 @@ -29,6 +29,11 @@ #ifdef NMEA_DEBUG #define DPRINTFN(n, x) do { if (nmeadebug > (n)) printf x; } while (0) int nmeadebug = 0; +/* + * 1 = print interesting messages + * 2 = print all messages +int nmeadebug = 2; + */ #else #define DPRINTFN(n, x) #endif @@ -52,6 +57,11 @@ struct ksensor signal; /* signal status */ struct ksensor latitude; struct ksensor longitude; + struct ksensor altitude; + struct ksensor quality; + struct ksensor hdop; + struct ksensor vdop; + struct ksensor pdop; struct ksensordev timedev; struct timespec ts; /* current timestamp */ struct timespec lts; /* timestamp of last '$' seen */ @@ -70,6 +80,8 @@ /* NMEA decoding */ void nmea_scan(struct nmea *, struct tty *); void nmea_gprmc(struct nmea *, struct tty *, char *fld[], int fldcnt); +void nmea_decode_gsa(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); @@ -77,6 +89,7 @@ /* 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 *); @@ -126,6 +139,41 @@ 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->quality.type = SENSOR_INTEGER; + np->quality.status = SENSOR_S_UNKNOWN; + np->quality.flags = SENSOR_FINVALID; + np->quality.value = 0; + strlcpy(np->quality.desc, "Fix Quality", sizeof(np->quality.desc)); + sensor_attach(&np->timedev, &np->quality); + + np->hdop.type = SENSOR_INTEGER; + np->hdop.status = SENSOR_S_UNKNOWN; + np->hdop.flags = SENSOR_FINVALID; + np->hdop.value = 0; + strlcpy(np->hdop.desc, "HDOP", sizeof(np->hdop.desc)); + sensor_attach(&np->timedev, &np->hdop); + + np->vdop.type = SENSOR_INTEGER; + np->vdop.status = SENSOR_S_UNKNOWN; + np->vdop.flags = SENSOR_FINVALID; + np->vdop.value = 0; + strlcpy(np->vdop.desc, "VDOP", sizeof(np->vdop.desc)); + sensor_attach(&np->timedev, &np->vdop); + + np->pdop.type = SENSOR_INTEGER; + np->pdop.status = SENSOR_S_UNKNOWN; + np->pdop.flags = SENSOR_FINVALID; + np->pdop.value = 0; + strlcpy(np->pdop.desc, "PDOP", sizeof(np->pdop.desc)); + sensor_attach(&np->timedev, &np->pdop); + np->sync = 1; tp->t_sc = (caddr_t)np; @@ -182,7 +230,7 @@ np->ts.tv_nsec = ts.tv_nsec; #ifdef NMEA_DEBUG - if (nmeadebug > 0) { + if (nmeadebug > 1) { linesw[TTYDISC].l_rint('[', tp); linesw[TTYDISC].l_rint('0' + np->gapno++, tp); linesw[TTYDISC].l_rint(']', tp); @@ -261,7 +309,7 @@ } /* - * we only look at the RMC message, which can come from different 'talkers', + * 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) @@ -269,11 +317,17 @@ * Galileo (GA) * 'Any kind/a mix of GNSS systems' (GN) */ - if (strcmp(fld[0], "BDRMC") && - strcmp(fld[0], "GARMC") && - strcmp(fld[0], "GLRMC") && - strcmp(fld[0], "GNRMC") && - strcmp(fld[0], "GPRMC")) + 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, GSA & GGA messages */ + if (strncmp(fld[0] + 2, "RMC", 3) && + strncmp(fld[0] + 2, "GSA", 3) && + strncmp(fld[0] + 2, "GGA", 3)) return; /* if we have a checksum, verify it */ @@ -299,7 +353,22 @@ return; } } - nmea_gprmc(np, tp, fld, fldcnt); +/* + if (strncmp(fld[0] + 2, "RMC", 3) == 0) + nmea_gprmc(np, tp, fld, fldcnt); + if (strncmp(fld[0] + 2, "GSA", 3) == 0) + nmea_decode_gsa(np, tp, fld, fldcnt); + if (strncmp(fld[0] + 2, "GGA", 3) == 0) + nmea_decode_gga(np, tp, fld, fldcnt); +*/ + if (strcmp(fld[0], "GNRMC") == 0) + nmea_gprmc(np, tp, fld, fldcnt); + if (strcmp(fld[0], "GNGSA") == 0) + nmea_decode_gsa(np, tp, fld, fldcnt); + if (strcmp(fld[0], "GNGGA") == 0) + nmea_decode_gga(np, tp, fld, fldcnt); +/* +*/ } /* Decode the recommended minimum specific GPS/TRANSIT data. */ @@ -418,6 +487,107 @@ np->time.status = SENSOR_S_CRIT; } +/* Decode the GPS fix data for altitude and fix quality. */ +void +nmea_decode_gga(struct nmea *np, struct tty *tp, char *fld[], int fldcnt) +{ + if (fldcnt != 14 && 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; + np->altitude.flags &= ~SENSOR_FINVALID; + if (nmea_atoi(&np->altitude.value, fld[9])) + np->altitude.status = SENSOR_S_WARN; + + np->quality.status = SENSOR_S_OK; + np->quality.flags &= ~SENSOR_FINVALID; + np->quality.value = *fld[6] - '0'; + + switch (np->quality.value) { + case 2: + strlcpy(np->quality.desc, "DGPS fix", + sizeof(np->quality.desc)); + break; + case 1: + strlcpy(np->quality.desc, "GPS fix", + sizeof(np->quality.desc)); + break; + case 0: + default : + np->quality.status = SENSOR_S_CRIT; + strlcpy(np->quality.desc, "Invalid", + sizeof(np->quality.desc)); + break; + } +} + +/* Decode the GPS DOP data. */ +void +nmea_decode_gsa(struct nmea *np, struct tty *tp, char *fld[], int fldcnt) +{ + if (fldcnt != 17 && fldcnt != 18) { + DPRINTF(("GSA: 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->hdop.status = SENSOR_S_OK; + np->hdop.flags &= ~SENSOR_FINVALID; + if (nmea_atoi(&np->hdop.value, fld[16]) || np->hdop.value > 2000) + np->hdop.status = SENSOR_S_WARN; + np->vdop.status = SENSOR_S_OK; + np->vdop.flags &= ~SENSOR_FINVALID; + if (nmea_atoi(&np->vdop.value, fld[17]) || np->vdop.value > 2000) + np->vdop.status = SENSOR_S_WARN; + np->pdop.status = SENSOR_S_OK; + np->pdop.flags &= ~SENSOR_FINVALID; + if (nmea_atoi(&np->pdop.value, fld[15]) || np->pdop.value > 2000) + np->pdop.status = SENSOR_S_WARN; +} + +/* + * Convert nmea altitude/dop in the form of XXXX.Y to an integer value + */ +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 */ + for (; *p && i > 0 && *p >= '0' && *p <= '9' ; i--) + *dst = *dst * 10 + (*p++ - '0'); + + for (; i > 0 ; i--) + *dst *= 10; + + DPRINTFN(1,("%s -> %lld\n", src, *dst)); + return 0; +} + /* * Convert a nmea position in the form DDDMM.MMMM to an * angle sensor value (degrees*1000000) @@ -557,6 +727,11 @@ 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->hdop.status = SENSOR_S_WARN; + np->vdop.status = SENSOR_S_WARN; + np->pdop.status = SENSOR_S_WARN; + np->quality.status = SENSOR_S_WARN; /* * further degrade in TRUSTTIME seconds if no new valid NMEA * sentences are received. @@ -566,5 +741,10 @@ 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->hdop.status = SENSOR_S_CRIT; + np->vdop.status = SENSOR_S_CRIT; + np->pdop.status = SENSOR_S_CRIT; + np->quality.status = SENSOR_S_CRIT; } }