ABSTRACT

This document supplies some code examples that show how to write good gpsd clients. It is an attempt to supplant the myriad bad Stack Overflow examples of gpsd clients.

PREAMBLE

You need to already have recent gpsd installed, with its libraries, and running, on your host for these examples to run. Do not install the gps Python module from pip, it is old and broken.

EXAMPLE 1 — C

Example 1 is a simple gpsd client, written in C, that connects to the already running gpsd on the localhost running on the default port 2947, using TCP.

Grab a copy of example1.c, rename it to not have the .txt extension (an scons bug workaround), compile it, and run it. The example builds and runs fine as a normal user as it does not require any special permissions. Use "^C" to exit. Like this:

$ wget -o example1.c https://gpsd.io/example1.c.txt
$ gcc example1.c -o example1 -lgps -lm
$ ./example1
Fix mode: 3D (3) Time: 1615325173.000000000 Lat 44.068861 Lon -121.314085
Fix mode: 3D (3) Time: 1615325174.000000000 Lat 44.068861 Lon -121.314085
Fix mode: 3D (3) Time: 1615325175.000000000 Lat 44.068861 Lon -121.314085
^C

The paranoid reader will have read example1.c before running it so as not to lose all their Bitcoin.

As you can see above, this client prints, if they are available, the "Fix Mode" ("mode"), Time ("time"), "latitude" and "longitude". These are the Time and Position parts of the acronym TPV. Otherwise it prints nothing.

Adding Velocity output is left as an exercise to the reader.

Note: The "Time" is the time of the fix, not the current time.

The complete example1.c:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// example  gpsd client
// compile this way:
//    gcc example1.c -o example1 -lgps -lm
#include <gps.h>         .. for gps_*()
#include <math.h>        // for isfinite()

#define MODE_STR_NUM 4
static char *mode_str[MODE_STR_NUM] = {
    "n/a",
    "None",
    "2D",
    "3D"
};

int main(int argc, char *argv[])
{
    struct gps_data_t gps_data;

    if (0 != gps_open("localhost", "2947", &gps_data)) {
        printf("Open error.  Bye, bye\n");
        return 1;
    }

    (void)gps_stream(&gps_data, WATCH_ENABLE | WATCH_JSON, NULL);

    while (gps_waiting(&gps_data, 5000000)) {
        if (-1 == gps_read(&gps_data, NULL, 0)) {
            printf("Read error.  Bye, bye\n");
            break;
        }
        if (MODE_SET != (MODE_SET & gps_data.set)) {
            // did not even get mode, nothing to see here
            continue;
        }
        if (0 > gps_data.fix.mode ||
            MODE_STR_NUM <= gps_data.fix.mode) {
            gps_data.fix.mode = 0;
        }
        printf("Fix mode: %s (%d) Time: ",
               mode_str[gps_data.fix.mode],
               gps_data.fix.mode);
        if (TIME_SET == (TIME_SET & gps_data.set)) {
            // not 32 bit safe
            printf("%ld.%09ld ", gps_data.fix.time.tv_sec,
                   gps_data.fix.time.tv_nsec);
        } else {
            puts("n/a ");
        }
        if (isfinite(gps_data.fix.latitude) &&
            isfinite(gps_data.fix.longitude)) {
            // Display data from the GPS receiver if valid.
            printf("Lat %.6f Lon %.6f\n",
                   gps_data.fix.latitude, gps_data.fix.longitude);
        } else {
            printf("Lat n/a Lon n/a\n");
        }
    }

    // When you are done...
    (void)gps_stream(&gps_data, WATCH_DISABLE, NULL);
    (void)gps_close(&gps_data);
    return 0;
}

COMMENTARY

Line by line commentary:

Lines 1 to 4
1
2
3
4
// example  gpsd client
// compile this way:
//    gcc example1.c -o example1 -lgps -lm
#include <gps.h>         .. for gps_*()

All you need to compile this example is libgps, and gps.h, installed on your host. Those two files should have been installed when you installed gpsd on your system. The gcc option -lgps links in libgps and -lm links in libm. Some systems will automatically link in libm.

Line 5
5
#include <math.h>        // for isfinite()

Include math.h to get the prototype for isfinite(). You should always check all floating point numbers from gpsd with isfinite() before using them.

Lines 7 to 12
 7
 8
 9
10
11
12
#define MODE_STR_NUM 4
static char *mode_str[MODE_STR_NUM] = {
    "n/a",
    "None",
    "2D",
    "3D"

An array of strings used to convert gps_data.fix.mode integer to a nice Fix Type string.

Lines 15 to 16
15
16
int main(int argc, char *argv[])
{

All we need is a simple main(). For clarity no options handling is done in this example. Real programs will implement options and arguments: -h; -V; [server[;port[;device]]'z; etc.

Line 17
17
    struct gps_data_t gps_data;

Every variable we care about, all variables, are contained in struct gps_data_t gps_data which is defined, and documented, in gps.h. gps_data contains a struct gps_fix_t fix which is also defined in gps.h. The TPV data we will use is in gps_data.fix.

Line 19 to 22
19
20
21
22
    if (0 != gps_open("localhost", "2947", &gps_data)) {
        printf("Open error.  Bye, bye\n");
        return 1;
    }

Connect to the already running gpsd on the localhost running on the default port 2947. Or exit loudly. See the gpsd(3) man page for details on starting gpsd. There may be significant delays opening the connection if gpsd is not running with the "-n" option. See the libgps man page for details on gps_open() and the other gps_XXX() function calls.

Note the use of [Yoda] conditions. These prevent many hard to spot code errors.

Line 24
24
    (void)gps_stream(&gps_data, WATCH_ENABLE | WATCH_JSON, NULL);

Tell gpsd to send us reports using JSON. Later on gpsd_read() will decode those JSON messsages for us. See the [gpsd_json] man page for details on the JSON messages.

Line 26
26
    while (gps_waiting(&gps_data, 5000000)) {

The main loop. Wait, using gps_waiting() until data from the gpsd connection is available, then run the body of the loop. Exit if no data seen in 5 seconds (5000000 micro seconds).

Lines 27 to 30
27
28
29
30
        if (-1 == gps_read(&gps_data, NULL, 0)) {
            printf("Read error.  Bye, bye\n");
            break;
        }

Read the waiting data using gpsd_read() and parse it into gps_data. Exit loudly on errors. No telling, yet, what the data is. It could be from TPV, SKY, AIS, or other message classes.

Lines 31 to 34
31
32
33
34
        if (MODE_SET != (MODE_SET & gps_data.set)) {
            // did not even get mode, nothing to see here
            continue;
        }

Here is a part that most programmers miss. Check that TPV data was received, not some other data, like SKY. The flag MODE_SET is set IFF a TPV JSON sentence was received. If no MODE_SET then do not bother to look at the rest of the data in gpsdata.fix.

Lines 35 to 38
35
36
37
38
        if (0 > gps_data.fix.mode ||
            MODE_STR_NUM <= gps_data.fix.mode) {
            gps_data.fix.mode = 0;
        }

Range check gpsdata.fix.mode so we can use it as an index into mode_str. New versions of gpsd often extend the range of unenumerated types, so protect yourself from an array overrun. Array overruns are bad.

Lines 39 to 41
39
40
41
        printf("Fix mode: %s (%d) Time: ",
               mode_str[gps_data.fix.mode],
               gps_data.fix.mode);

Print the Fix mode as an integer, and a string.

Lines 42 to 47
42
43
44
45
46
47
        if (TIME_SET == (TIME_SET & gps_data.set)) {
            // not 32 bit safe
            printf("%ld.%09ld ", gps_data.fix.time.tv_sec,
                   gps_data.fix.time.tv_nsec);
        } else {
            puts("n/a ");

Print the gps_data.fix.time as seconds and nano seconds into the UNIX epoch, if we have it, else "n/a". fix.time is a struct timespec. An explanation of struct timespec can be found on the clock_gettime() man page.

Just because we have a "valid" time does not mean it bears any relation to UTC. Many GPS/GNSS receivers output random time when they do not have a fix. Worse, some continue to do so for minutes after reporting that they have a valid fix.

Lines 49 to 56
49
50
51
52
53
54
55
56
        if (isfinite(gps_data.fix.latitude) &&
            isfinite(gps_data.fix.longitude)) {
            // Display data from the GPS receiver if valid.
            printf("Lat %.6f Lon %.6f\n",
                   gps_data.fix.latitude, gps_data.fix.longitude);
        } else {
            printf("Lat n/a Lon n/a\n");
        }

Just because we have a "3D" fix does not mean we have latitude and longitude. The receiver may not have sent that data yet. Conversely, some receivers will send them, without a fix, based on some best guess. This example prints them if we get them regardless of fix "mode" or "status".

When gpsd does not know the value of a floating point variable, it sets that variable to a NaN (Not a Number). So the example checks if latitude and longitude are set by seeing if they are finite numbers by using isfinite() from libm. Do not use isnan()! See [NUMBERS] for a more detailed explanation about this issue.

Lines 59 to 62
59
60
61
62
    // When you are done...
    (void)gps_stream(&gps_data, WATCH_DISABLE, NULL);
    (void)gps_close(&gps_data);
    return 0;

When falling out of the loop, close the TCP connection nicely and return success. Mother always said to clean up after myself.

EXAMPLE 2 — Python

Example 2 is a simple gpsd client, written in Python, that connects to the already running gpsd on the localhost running on the default port 2947, using TCP.

Grab a copy of example2.py, rename it to not have the .txt extension (an scons bug workaround), and run it. The example builds and runs fine as a normal user as it does not require any special permissions. Use "^C" to exit. Like this:

$ wget -o example2.py https://gpsd.io/example2.py.txt
$ python example2.py
Mode: 3D(3) Time: 2022-07-26T00:40:35.000Z Lat 44.068934 Lon -121.314026
Mode: 3D(3) Time: 2022-07-26T00:40:35.000Z Lat 44.068934 Lon -121.314026
Mode: 3D(3) Time: 2022-07-26T00:40:36.000Z Lat 44.068935 Lon -121.314026
Mode: 3D(3) Time: 2022-07-26T00:40:36.000Z Lat 44.068935 Lon -121.314026

^C

The paranoid reader will have read example2.py before running it so as not to lose all their Bitcoin.

As you can see above, this client prints the "Time", "Mode" "Lat" and "Lon" if available. These are the Time and Position parts of the acronym TPV. Otherwise it prints nothing.

The complete example2.py:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#! /usr/bin/env python3
"""
example  Python gpsd client
run this way: python3 example1.py.txt
"""

import gps               # the gpsd interface module

session = gps.gps(mode=gps.WATCH_ENABLE)

try:
    while 0 == session.read():
        if not (gps.MODE_SET & session.valid):
            # not useful, probably not a TPV message
            continue

        print('Mode: %s(%d) Time: ' %
              (("Invalid", "NO_FIX", "2D", "3D")[session.fix.mode],
               session.fix.mode), end="")
        # print time, if we have it
        if gps.TIME_SET & session.valid:
            print(session.fix.time, end="")
        else:
            print('n/a', end="")

        if ((gps.isfinite(session.fix.latitude) and
             gps.isfinite(session.fix.longitude))):
            print(" Lat %.6f Lon %.6f" %
                  (session.fix.latitude, session.fix.longitude))
        else:
            print(" Lat n/a Lon n/a")

except KeyboardInterrupt:
    # got a ^C.  Say bye, bye
    print('')

# Got ^C, or fell out of the loop.  Cleanup, and leave.
session.close()
exit(0)

COMMENTARY

Line by line commentary.

Start with a standard Python file header, and import the gps modile. No other imports are required.

Lines 1 to 7
1
2
3
4
5
6
7
#! /usr/bin/env python3
"""
example  Python gpsd client
run this way: python3 example1.py.txt
"""

import gps               # the gpsd interface module

Connect to the local gpsd daemon, and request streaming updates.

Line 9
9
session = gps.gps(mode=gps.WATCH_ENABLE)

Use a while loop to handle incoming packets. Place it in a try block to cleanly catch "^C". The function session.read() returns 0, for OK, when a packet is received, or on a timeout. It will return less than zero on an error condition, such as a disconnect from the gpsd daemon.

Lines 11 to 12
11
12
try:
    while 0 == session.read():

The read() can return many different packet types. Ensure that we have a TPV packet by looking for the fix mode to be set. Otherwise, skip this message.

Lines 13 to 15
13
14
15
        if not (gps.MODE_SET & session.valid):
            # not useful, probably not a TPV message
            continue

Now that we know we have a good mode, print it. Also print the time, if is is good. The TIME_SET flag bit in session.valid is set if the time is good.

Lines 17 to 24
17
18
19
20
21
22
23
24
        print('Mode: %s(%d) Time: ' %
              (("Invalid", "NO_FIX", "2D", "3D")[session.fix.mode],
               session.fix.mode), end="")
        # print time, if we have it
        if gps.TIME_SET & session.valid:
            print(session.fix.time, end="")
        else:
            print('n/a', end="")

Check that the Latitude and Longitude are finite before printing them. Always check all floating point numbers are finite before using them.

Lines 26 to 31
26
27
28
29
30
31
        if ((gps.isfinite(session.fix.latitude) and
             gps.isfinite(session.fix.longitude))):
            print(" Lat %.6f Lon %.6f" %
                  (session.fix.latitude, session.fix.longitude))
        else:
            print(" Lat n/a Lon n/a")

Code execution has left the while loop. Maybe the except: was triggered on a "^C". Otherwise we fell out of the loop because session.read() returned an end of file, or error. Either way, cleanup and exit.

Lines 33 to 39
33
34
35
36
37
38
39
except KeyboardInterrupt:
    # got a ^C.  Say bye, bye
    print('')

# Got ^C, or fell out of the loop.  Cleanup, and leave.
session.close()
exit(0)

REFERENCES

COPYING

This file is Copyright 2021 by the GPSD project
SPDX-License-Identifier: BSD-2-clause