= gpsd Client Example Code
Gary E. Miller <gem@rellim.com>
24 July 2022
: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.

== 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 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.  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.txt[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 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]
----
include::example1.c.txt[]
----

=== COMMENTARY

Line by line commentary:

Lines 1 to 4::
[source%nowrap,c,numbered]
----
include::example1.c.txt[lines=1..4]
----

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::
[source%nowrap,c,numbered,start=5]
----
include::example1.c.txt[lines=5]
----

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::
[source%nowrap,c,numbered,start=7]
----
include::example1.c.txt[lines=7..12]
----

An array of strings used to convert _gps_data.fix.mode_ integer to a
nice Fix Type string.

Lines 15 to 16::
[source%nowrap,c,numbered,start=15]
----
include::example1.c.txt[lines=15..16]
----

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::
[source%nowrap,c,numbered,start=17]
----
include::example1.c.txt[lines=17]
----

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::
[source%nowrap,c,numbered,start=19]
----
include::example1.c.txt[lines=19..22]
----

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::
[source%nowrap,c,numbered,start=24]
----
include::example1.c.txt[lines=24]
----

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::
[source%nowrap,c,numbered,start=26]
----
include::example1.c.txt[lines=26]
----

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::
[source%nowrap,c,numbered,start=27]
----
include::example1.c.txt[lines=27..30]
----

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::
[source%nowrap,c,numbered,start=31]
----
include::example1.c.txt[lines=31..34]
----

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::
[source%nowrap,c,numbered,start=35]
----
include::example1.c.txt[lines=35..38]
----

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::
[source%nowrap,c,numbered,start=39]
----
include::example1.c.txt[lines=39..41]
----

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

Lines 42 to 47::
[source%nowrap,c,numbered,start=42]
----
include::example1.c.txt[lines=42..47]
----

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::
[source%nowrap,c,numbered,start=49]
----
include::example1.c.txt[lines=49..56]
----

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::
[source%nowrap,c,numbered,start=59]
----
include::example1.c.txt[lines=59..62]
----

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 https://gpsd.io/example2.py.txt[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
https://gpsd.io/example2.py.txt[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:

[source%nowrap,python,numbered]
----
include::example2.py.txt[]
----

=== 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::
[source%nowrap,python,numbered]
----
include::example2.py.txt[lines=1..7]
----

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

Line 9::
[source%nowrap,python,numbered,start=9]
----
include::example2.py.txt[lines=9]
----


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::
[source%nowrap,python,numbered,start=11]
----
include::example2.py.txt[lines=11..12]
----

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::
[source%nowrap,python,numbered,start=13]
----
include::example2.py.txt[lines=13..15]
----

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::
[source%nowrap,python,numbered,start=17]
----
include::example2.py.txt[lines=17..24]
----

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::
[source%nowrap,python,numbered,start=26]
----
include::example2.py.txt[lines=26..31]
----

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::
[source%nowrap,python,numbered,start=33]
----
include::example2.py.txt[lines=33..]
----


== 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)*

* *GPSD Project web site:* {gpsdweb}

== COPYING

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