= gpsd Client Example Code
Gary E. Miller <gem@rellim.com>
15 July 2021
:author: Gary E. Miller
:description: A line by line code walk-through of a simple gpsd C client.
:email: <gem@rellim.com>
:keywords: gpsd, example
:robots: index,follow
:sectlinks:
:source-highlighter: rouge
:toc: macro

include::inc-menu.adoc[]

== 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 https://gpsd.io/example1.c.txt[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 https://gpsd.io/example1.c[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 source highlighter and line numbers requires rouge to be installed
// Keep the line numbers in sync with the text.

The complete example1.c:

[source%nowrap,c,numbered]
----
// 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::
[source%nowrap,c,numbered]
----
// 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::
[source%nowrap,c,numbered,start=5]
----
#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::
[source%nowrap,c,numbered,start=8]
----
#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::
[source%nowrap,c,numbered,start=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]]*; etc.

Line 18::
[source%nowrap,c,numbered,start=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::
[source%nowrap,c,numbered,start=20]
----
    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::
[source%nowrap,c,numbered,start=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::
[source%nowrap,c,numbered,start=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::
[source%nowrap,c,numbered,start=28]
----
        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::
[source%nowrap,c,numbered,start=32]
----
        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::
[source%nowrap,c,numbered,start=36]
----
        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::
[source%nowrap,c,numbered,start=40]
----
        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::
[source%nowrap,c,numbered,start=43]
----
        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::
[source%nowrap,c,numbered,start=50]
----
        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::
[source%nowrap,c,numbered,start=58]
----
    // 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

[bibliography]
* [[[IEEE754]]] https://standards.ieee.org/standard/754-2019.html[IEEE Standard
for Floating-Point Arithmetic]

* [[[Yoda]]] https://en.wikipedia.org/wiki/Yoda_conditions[Yoda Conditions]

* [[[libgps]]] {gpsdweb}libgps.html[libgps(3)]

* [[[gpsd_json]]] {gpsdweb}gpsd_json.html[gpsd_json(5)]

* [[[gpsd]]] {gpsdweb}gpsd.html[gpsd(8)]

* {gpsdweb}client-howto.html[GPSD Client HOWTO] is a different look at
*gpsd* clients.

* [[[NUMBERS]]] {gpsdweb}gpsd-numbers-matter.html[GPSD Numbers Matter]

* *isfinite(3)*, *sleep(3)*

* *GPSD Project web site:* {gpsdweb}

== COPYING

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