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.

EXAMPLE 1

A simple gpsd client that connects to the already running gpsd on the localhost running on the default port 2947, using TCP. You need to already have gpsd installed, with its libraries, and running, on your host for this example to run.

Grab a copy of example1.c, rename it to not have the .txt extension (an scons bug workaround), compile it, and run it. i 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 the "Fix Mode" ("mode"), Time ("time"), "latitude" and "longitude" if available. 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
// example  gpsd client
// compile this way:
//    gcc example1.c -o example1 -lgps -lm
#include <gps.h>
#include <math.h>        // for isfinite()
#include <unistd.h>      // for sleep()

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

    // 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>

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.

Lines 5 to 6
5
6
#include <math.h>        // for isfinite()
#include <unistd.h>      // for sleep()

The system includes are to get prototypes for isfinite() and sleep().

Lines 8 to 14
 8
 9
10
11
12
13
14
#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 16 to 17
16
17
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]]; etc.

Line 18
18
    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 20 to 23
20
21
22
23
    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 25
25
    (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 27
27
    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 28 to 31
28
29
30
31
        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 32 to 35
32
33
34
35
        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 36 to 39
36
37
38
39
        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 40 to 42
40
41
42
        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 43 to 48
43
44
45
46
47
48
49
        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 50 to 55
50
51
52
53
54
55
        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);
        }

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 58 to 61
58
59
60
61
    // 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.

REFERENCES

COPYING

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