Location, Location, Location

Tutorial – gpsd

Author(s):

Even though your computer does not come with its own GPS, you can hack one onto it using a mobile phone. Although, it does take quite a bit of fiddling.

There is plenty of literature online on how to connect a GPS device to a computer. But normally the devices covered are exclusively GPSs, that is, something you would probably have to go out of your way to buy.

What you don't find so much information about is how to use what you have at hand, namely, your regular phone. However; it can be done. It is a bit hacky, but it works.

On Your Laptop

Linux comes with a daemon called gpsd [1] that can process the information delivered by a GPS device. Although gpsd is usually started and stopped with systemd (and is usually launched at boot), you'll be running it by hand. The reason is again the hackiness of the solution, due to processing data coming from a GPS device not on board or that is not connected over a serial or USB port, which is what gpsd usually expects. (See the "NMEA" box.)

NMEA

GPS devices deliver information in the shape of National Marine Electronics Association (NMEA) packages. These packages that conform to the NMEA's 0183 or 2000 standards contain all the data you need to position a GPS device on the Earth's surface.

NMEA packages contain "sentences" that look like this:

GPRMC,092751.000,A,5321.6802,N,00630.3371,W,0.06,31.66,280511,,,A

An NMEA sentence can contain data on the device's altitude, latitude, longitude, and speed. It also indicates the time of the reading at the beginning of the sentence and a checksum at the end. This helps identify broken or partially received sentences.

Most programs and programming libraries used for positioning parse NMEA packages and sentences automatically. This means you usually won't need to write any code to turn the raw NMEA data into something usable.

gpsd is available in most, if not all, distributions. On Debian and its derivatives, it is installed with:

apt-get install gpsd

If you are on Ubuntu, Raspbian, or something similar, you may have to add sudo to the preceding instruction.

Be that as it may, this command will install (and run) the GPS daemon and install several utilities such as gpsmon and gpspipe.

The first thing you want to do is stop the systemd gpsd services. As explained previously, you want to run gpsd with your own parameters. You can later work your special configuration into the systemd version, but for now, run these two instructions to free up the gpsd service and the port on which it listens.

systemctl stop gpsd
systemctl stop gpsd.socket

If this doesn't do the trick, check what systemd is running with:

systemctl | grep gpsd

and stop whichever service shows up there.

You may have to remove the /var/run/gpsd.sock file as well (you will have to do that with sudo) if you are going to run gpsd as a regular user.

On Your Phone

On your phone, you need an app that shares your GPS data over a network. There are some quite fancy proprietary programs out there, but, unless you go for the "premium" version, they are usually addled with ads and bloat.

Fortunately, there is a very simple option created by Tiago Koji Castro Shibata that you can get from the Google Play app store [2] or download and compile yourself from the author's GitHub page [3]. When you run it, it looks like Figure 1.

Figure 1: A simple GPS client that allows you to share data with your computer.

Configuring the app is simplicity itself: Input your laptop's IP. Choose a port to which to forward the information. Press the Start button. The end.

Connecting Both

Back on your laptop, check that your IP corresponds to what you input on the mobile app and run the gpsd daemon from a terminal window:

gpsd -N upd://*:<port>

What you are doing here is polling information from a phone, any phone, on the port you established on the app. To all practical effects, your phone is acting like a server.

For example, if you are connecting to an app configured as shown in Figure 1, you would run:

gpsd -N upd://*:29998

The -N parameter lets you run gpsd without it going into the background. Once you are happy that everything works as it should, you can run gpsd without it.

As for why "any" phone and not your own specific phone, that's because, in theory you could select a specific source on the network by passing your phone's IP to gpsd, like so:

gpsd -N udp://<phone's IP>:<port>

However this did not work for me. The only way gpsd managed to pick up the server on our phone was by using the catch-all *. This shouldn't be a big problem: Pick an atypical and unique port, and it is unlikely that gpsd will poll anything that isn't the device you want it to poll.

Now move around with your phone, preferably near a window, to try and capture some coordinates. Then open another terminal window and run gpsmon.

The gpsmon utility does what it says on the box: It monitors GPS input. Without any parameters, it monitors what gpsd is receiving, but it can also monitor a stream of GPS data from other sources, including the Internet. Its text-based interface shows information on the satellites in view (the GSV section in Figure 2), data which provides 3D location and accuracy (the GGA section), and so on.

Figure 2: The gpsmon utility shows you GPS in action.

A healthy GPS connection will look like Figure 2. If you see something different, wait a while to see if the stream starts, or move your phone to a location where it can "see" satellites, near or outside a window. If that doesn't work, restart gpsd, gpsmon, the app on your phone, or all three. GPS is a fickle beast and breaks a lot. If you have followed the instructions above, at some point you will get a signal, and the stream of data will begin.

This is all very well if you are at home and running stuff over the local network, but you would usually want to use your GPS-enabled system when you are out and about. What then? Use your phone as a WiFi hotspot (Figure 3)! Just because your phone is the hotspot, it doesn't mean it cannot stream GPS data to your laptop.

Figure 3: Activate the Mobile Wi-Fi hotspot feature on your phone and take your GPS rig on the road.

Activate Mobile Wi-Fi hotspot from Settings | Wireless & networks | More | Tethering & mobile hotspot on your phone, connect to your phone's network from your laptop, and your computer will be assigned an IP that is something like 192.168.43.X (for the record, phones usually adopt 192.168.43.1 as their IP when acting as a hotspot). Use that with your Android GPS client, and you will be able to take your GPS experiment out and about.

A final gpsd tool to be aware of is gpspipe. At its simplest, gpspipe takes a parameter that tells it which format to use and just dumps the stream from gpsd to the standard output:

gpspipe -r

will stream raw NMEA sentences to the terminal, while

gpspipe -w

sends native gpsd sentences (see Listing 1).

Listing 1

gpspipe -w output

{"class":"TPV","device":"udp://*:29998","mode":3,"time":"2018-03-11T09:21:21.000Z","ept":0.005,"lat":36.736763683,"lon":-4.381267583,"alt":149.000,"epv":20.700,"track":0.0000,"speed":0.000}
{"class":"SKY","device":"udp://*:29998","vdop":0.90,"hdop":1.50,"pdop":1.70,"satellites":[{"PRN":7,"el":26,"az":45,"ss":45,"used":false},{"PRN":13,"el":45,"az":298,"ss":24,"used":false},{"PRN":28,"el":35,"az":113,"ss":18,"used":false},{"PRN":30,"el":51,"az":46,"ss":38,"used":false},
...
]}

gpspipe can also output to a file using the -o parameter, be sent to the background as a daemon (-d), or send data to a serial device (-s).

You could use gpspipe to send all the information to a file and keep track of a trip or a run. In the case of a run, you could also see the terrain's profile and figure out how much you climbed, since the NMEA sentences also include the altitude at any given point in your route.

I could end the article right here. You have connected your phone; you can use its GPS on your laptop or on a single board computer such as the Raspberry Pi; and you have learned how to store the data. With that alone, you can carry out a bunch of cool projects. None of which, useful as they may be, will be very visually appealing.

So let's make a graphical map-tracking application instead.

Mapping Out

The objective is to show a map of the GPS device's location, and, as it moves, have the map move with it (see Figure 4). A circle indicates where the device is located on the map.

Figure 4: Your app will show the position of your phone on a map. As it moves, the red circle will follow it around.

Your first reaction would probably be to base your app on some open source map app that already exists. Marble [4] seems like a good candidate, and if you run it like so

marble --latlon <lat>,<lon> --distance 2 --map "earth/openstreetmap/openstreetmap.dgml"

where lat and lon are the latitude and longitude, Marble will open at the desired location, which is already promising.

Then, if you think that

gpspipe -w | sed -n s/.*lat:(.*),/1/p | cut -d, -f1

will give you the latitude of your phone, and

gpspipe -w | sed -n s/.*lon:(.*),/1/p | cut -d, -f1

will give you the longitude, things definitely seem to be shaping up.

The preceding instructions will give you an ongoing stream of latitudes and longitudes. It would be possible to pipe the output to a file and then run through the rows and use them with Marble somehow. But this is an inelegant and convoluted way of doing things. It is much smarter to break out QML [5].

QML App

QML is a relatively simple declarative language that uses the Qt libraries to build graphical applications. How QML works is a bit like how you'd go about programming an interface using a visual IDE, but with text: You "design" the layout describing it in text form, and then you do the same with all the items contained in the layout. To execute actions (say, when a button is pressed), you associate events with JavaScript modules to each object.

Getting back to the original idea, the Marble documentation says that it is easy to develop apps based on the Marble code in QML. It refers the would-be programmer to a tutorial and examples available at [6]. Unfortunately, the code examples and snippets are outdated, and it is quite hard to figure out how to do things. According to one developer, the problem is that Marble's code is in continuous flux, changing nearly daily.

Instead, he suggests using QML's own Location library. To get started, install all the bits and pieces you'll need:

apt install qmlscene qml-module-qtpositioning qml-module-qtlocation

The modules will install positioning and location libraries for your app and drag in all the Qt QML common files. The qmlscene utility is what you use to run QML apps.

The code for your app might look like Listing 2.

Listing 2

map.qml

01 import QtQuick 2.1
02 import QtQuick.Window 2.0
03 import QtPositioning 5.5
04 import QtLocation 5.6
05
06 Window {
07   id:page
08   width: 800
09   height: 800
10   visible: true
11
12   Map {
13     id:myMap
14     anchors.fill: parent
15     plugin: mapPlugin
16     zoomLevel: 17
17
18     property MapCircle circle
19
20     function update(pos) {
21       removeMapItem(circle);
22
23       circle = Qt.createQmlObject('import QtLocation 5.3; MapCircle {}', page);
24       circle.radius = 30;
25       circle.color = "transparent";
26       circle.border.color = "red"
27       circle.border.width = 3;
28       myMap.addMapItem(circle);
29
30       circle.center = pos.coordinate;
31       myMap.center = pos.coordinate;
32
33       //console.log("Coordinates: ", pos.coordinate.latitude, pos.coordinate.longitude);
34     }
35   }
36
37   Plugin {
38     id: mapPlugin
39     name: "osm"
40   }
41
42   PositionSource {
43     id: gpsPos
44     updateInterval: 500
45     active: true
46     nmeaSource: "socket://localhost:29999"
47
48     onPositionChanged: {
49       myMap.update(position);
50     }
51   }
52 }

After loading in all the modules you need (lines 1-4), you create an all-encompassing Window object (line 6). You give it a name (page), set its size, and make it visible.

Next you create a Map widget. Again, you give it a name (myMap), you set its size to automatically fit in Window (the parent object), and you set the plugin it will use. In this case, the plugin is declared on lines 37 to 40, where you can see you will be using the OpenStreetMap [7] data for your maps. Finally, you set the zoom level of the map, in this case, 17 meters up in the air.

Map objects allow you to draw shapes on the map. If you want a circle, you create a circle object on line 18. You then manipulate it using a JavaScript function (lines 20-34) and moving it (line 30) when the GPS position changes. You also move the view of the map so that the circle is always in the center (line 31) .

Line 33 is for debugging and testing only. Uncomment it to see coordinates scroll by on the terminal window as you move around.

As mentioned previously, QML is declarative and event-based and you can see how that works in the PositionsSource object (lines 42-49). This is an object provided by the QtPositioning module, and it is in charge of retrieving and processing the GPS information.

As usual, you give your object a name (gpsPos) to refer to it later, you set how often it will poll the GPS source (once every 500 milliseconds), and you set from where it will get the NMEA information.

A slight detour here, because it turns out that, unless you have GPS on the laptop or a GPS-only device connected via USB (that is, not a phone), PositionSource does a poor job of determining where it should get its data.

However, you can force it to look for data by setting the nmeaSource attribute. You would usually nmeaSource to point to a file with static NMEA data to "simulate" a GPS positioning (nmeaSource is primarily used when testing), but you can also point to a server. This got me thinking: First I tried to pipe the output from gpspipe to a file and read from that:

gpspipe -r > gps.dat

Line 46 then looked like this:

nmeaSource: "gps.dat"

But then PositionSource was never updated – it just took the last value from the file and never went back to check if there were any more.

A bit more research revealed that PositionSource can also be read from a socket URL. And know what makes sockets super easy? Netcat [8] does. Netcat (nc) is preinstalled in nearly every Linux installation on the planet, so it was simply a matter of doing this:

gpspipe -r | nc -l 29999

Then, create a stream of GPS data on localhost using port 29999, and get PositionSource to read from it, as shown on line 46.

Finally, you set up an event that fires when the position you are polling changes (line 48). When it does fire, you visit the update(pos) function in the Map object (lines 20-34) and redraw the circle and the map with the new position data, pos.coordinate, that you pass in the function call.

To summarize, here's what you do to make everything work together:

1. Run gpsd udp://*.29998.

2. Connect your phone's GPS client to your laptop using your laptop's IP and port 29998.

3. Run gpsmon to check you are receiving GPS data correctly.

4. Run gpspipe -r | nc -l 29999 to set up a data stream through port 29999 on your computer.

5. Run your application with qmlscene map.qml.

Conclusion

There you have it: A full GPS app on a laptop (or Raspberry Pi) in 50 lines of code [9]. This is definitely a fun project, and it combines hardware you probably already have hanging around at home in an interesting way. Expanding to build an actually useful application should be a piece of cake.

Famous last words.