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;
        }
 }

Reply via email to