All stories

Building a raspberry pie real-time GPS tracker

Let's create a fancy device that can track your current position and display that data in real-time on your Drupal site.

11 min read

Portrait of Mario Weber
by  Mario Weber

1. The Idea

Since I was a child I felt the urgent need for a device that could track my every move and let everyone know where exactly I am. Just kidding - the idea is actually based on a real-live use-case I encountered a few moths ago. If you are familiar with the "Basler Fasnacht" maybe you can relate. Basically, we are a large group of people celebrating carnival. Every now and then during the day, we move in formation and change location frequently. During that time of displacement, no one is able to use their phone and update other friends on our actual location. Also, the later it gets and the more "mineral water" we drink, people tend to leave the group for a short amount of time, or miss certain prefixed times. Now, how can they find us again, if no one can text them our current position? Yeah, I think you know where this is going....

Lets dive right into this...

2. Device Setup and Architecture

Open Google and keep your credit card ready. Lets go shopping.

2.1 Hardware

Other Stuff

For running the linux image and our code, we will need a micro SD Card. I am using a 32GB (10) (Costs: SFR 30.-). Of course we will also need a SIM Card from our preferred company. Good Guys Sunrise provided a fair prepaid plan with fast internet. Ideal for our needs. Powering will be handled by the Power Bank ’USB Battery Pack 10400mAh - 2 x 5V @ 2A’ (Costs: SFR 40.-). For a badass look I decided to also invest in an awesome mat black casing. (Costs: SFR 15.-). Image: https://www.pi-shop.ch

2.1 Interfaces and Protocols

Before getting started with the configuration, we have to be sure which interfaces are used to connect these parts with each other: The GSM Module is connected over the 40 Pin GPIO ( General Purpose Input Out- put) directly on the Pie. This way, it is automatically connected to the Serial UART Rx/Tx, which allows to communicate with the module directly by using the Hayes command Set (AT Commands). We won’t use these commands in the end, but they are really helpful for testing and debugging functionalities (e.g. show connected ports, send/receive sms, show signal strength, general behaviour). The GSM uses the power supply from the Pie and (if configured correctly) will start automatically whenever the Pie is started. The default serial port will be the '/dev/ttyS0' (for Raspberry Pie 3).

The GPS Stick is plugged into one of the Pie’s USB Slots. The stick is using the ’/dev/ttyACM0’ port for communication with the Pie.

Connection to the internet is established over the SIM card using the PPP protocol. The device is now basically a big, fat mobile-phone without display and keyboard.

3. Raspberry Pie Config

There are basically 6 steps to take care of:

  1. Setup the Operating System on the Raspberry

  2. Get The GSM Module running

  3. Get The GPS running

  4. Provide internet connection

  5. Implement a service worker for collecting and sending coordinates

  6. Run the script on startup

3.1 The Pie Operating System

We are using the special Image ’RaspiBrick-Firmware’, which is an advanced version of the common Raspian OS for Raspberry Pies, using the handy ’NOOBS’ installation (simple operating system installer).

RaspiBrick: http://www.aplu.ch/home/raspibrick_inst.html
NOOBS: https://www.raspberrypi.org/downloads/noobs/

In this image, most of the important software is already pre installed and its setup process is well documented and explained. It contains also almost of the Python libraries we need to write and execute our custom scripts.

3.2 GSM Configuration

The GSM Module should boot automatically. To test its functionality, we can use AT commands over the serial interface using minicom. The following command starts the minicom with a specific baud rate on the ttyS0 interface:

$ ssh pi@192.168.0.129
$ sudo minicom −b 9600 −D /dev/ttyS0 −o

Then, all kind of different things can be tested, e.g.:

// send SMS to +41791234567
AT+CMGF=1
OK
AT+CMGS="+41791234567"
> This is the text message.
+CMGS: 198
OK

// location lat lng calculated from nearest CELL TOWERS
AT+CIPGSMLOC=1,1
+CIPGSMLOC: 0,7.584216,47.546936,2017/11/25,14:48:49
OK

// get IP of some website
AT+CDNSGIP="www.google.com"
OK
+CDNSGIP: 1,"www.google.com","172.217.17.100

3.3 GPS Configuration

I tried calculating the current location by triangulating over the GPRS signal strengths with the locations of the surrounding cell towers. Unfortunately, the results of these calculations were really inaccurate (+- 50m). Therefore, I decided to buy an extra GPS Receiver for more precise results.

Making the GPS packages available for the application requires the following steps:

  1. Plugin the stick.

  2. Install GPS Deamon and Python Integration

$ sudo apt−get install gpsd gpsd−clients python−gps
$ sudo nano /etc/default/gpsd

Then check that the following configurations are set:

# FILE gpsd
# Start the gpsd daemon automatically at boot time
START_DAEMON="true"

# Use USB hotplugging to add new USB devices automatically to the daemon
USBAUTO="true"

# Devices gpsd should collect to at boot time.
DEVICES="/dev/ttyACM0"

# Other options you want to pass to gpsd
GPSD_OPTIONS="−n"
GPSD_SOCKET="/var/run/gpsd.sock"

To test if the GPS deamon is working, type

$ cgps
//or 
$ gpsmon


3.4 Internet Configuration

To get our coordinates to the cloud, we need to setup the PPP connection.

  1. Get the librarires and

  2. create the web interface

  3. make sure the configuration is set propperly

  4. then edit the network interfaces by adding the new ppp interface

$ sudo apt−get install ppp screen elinks
$ cd /etc/ppp/peers/ && nano rnet
# FILE rnet
# ’internet’ is the apn for sunrise connection
connect "/usr/sbin/chat −v −f /etc/chatscripts/gprs −T internet"

# communication port:
/dev/ttyS0

# baudrate
9600

# Assumes that your IP address is allocated dynamically by the ISP.
noipdefault

# Try to get the name server addresses from the ISP.
usepeerdns

# Use this connection as the default route to the internet .
defaultroute

# Makes PPPD "dial again" when the connection is lost.
persist

# Do not ask the remote to authenticate.
noauth
# FILE /etc/network/interfaces
auto rnet
iface rnet inet ppp
    provider rnet

We can now use the PPP protocol to connect to the internet. Piece of cake!

3.5 Python Service Worker

Let's start coding! The most important thing the worker needs to handle is to forward the data received from the GPS Deamon to the cloud database. The simple script is built on the one found on https://gist.github.com/wolfg1969/4653340. I just added the PubNub library and changed some runtime logic. The worker sends data every 15 seconds. Here is a small snipped from the code:

...
# Import PubNub Library and Settings
from pubnub.pnconfiguration import PNConfiguration
from pubnub.pubnub import PubNub
from pubnub.exceptions import PubNubException

pnconfig = PNConfiguration()
pnconfig.subscribe_key = "sub-key"
pnconfig.publish_key = "pub-key"
pnconfig.ssl = False
pnchannel = 'map-channel'
...

pubnub = PubNub(pnconfig)
pubnub.subscribe().channels(pnchannel).execute()

while True:
            # GET GPS DATA

            gpsData['lat'] = gpsd.fix.latitude
            gpsData['lan'] = gpsd.fix.longitude
            gpsData['time'] = gpsd.utc, ' + ', gpsd.fix.time
            gpsData['alt'] = gpsd.fix.altitude
            gpsData['eps'] = gpsd.fix.eps
            gpsData['epx'] = gpsd.fix.epx
            gpsData['epv'] = gpsd.fix.epv
            gpsData['ept'] = gpsd.fix.ept
            gpsData['speed'] = gpsd.fix.ept
            gpsData['climb'] = gpsd.fix.speed
            gpsData['track'] = gpsd.fix.climb
            gpsData['mode'] = gpsd.fix.track
            gpsData['sats'] = gpsd.fix.mode
            
            trigger = float(gpsData['lat'])
            
            if math.isnan(trigger):
                data = {'lat': 0,'lng': 0}
            else:
                data = {'lat': gpsData['lat'],'lng': gpsData['lan']}
                
            try:	
                envelope = pubnub.publish().channel(pnchannel).message(data).sync()
                print("publish timetoken: %d" % envelope.result.timetoken)
            except PubNubException as e:
                print("Error %s" % str(e))

            time.sleep(15) 
...

3.6 Run on Start

Almost there! The last step is to execute the worker on device start. For that we just call our script within the Pies profile configuration. This way, if the script crashes, we can just restart the device and the script will be running again. Add the following lines to the profile:

$ sudo nano /etc/ profile
# ’/home/pi/myscript.py’ is the path to your script .
sudo python /home/pi/myscript.py

4. PubNub Database

https://www.pubnub.com/

For storing and delivering data, I am using PubNub Real-Time Database. Their ’Free Plan’ allows us to use up to 100 devices and 1M messages a month, which is more than enough for this project. After registration, we sign in and create a new project app. They immediately provide us with the two important keys - the ’Publish Key’ and the ’Subscribe Key’. These are used as credentials when we want to push or listen to messages on our app. Within the app, we can have multiple channels which we can publish and/or subscribe to. PubNub provides, along a very nice documentation, many basic examples that help you to understand how everything is working. PubNub does not care what kind of JSON Data you are publishing. For our app, it's latitude and longitude coordinates.

Applications, subscribed to a channel, will automatically receive updates/are triggered whenever new messages are pushed to that same channel. This is very convenient, since we do not have to care about real-time aspects. Everything is done for us automatically.

5. Drupal Integration

Lets create the custom Block called tracking_map. First we have to define the libraries we are gonna use in tracking_map.libraries.yml and place them accordingly in the folder structure.

# tracking_map.libraries.yml
google.maps:
  version: 3
  js:
    https://maps.googleapis.com/maps/api/js?key=[yourkey]: { type: external, minified: true }

pubnub:
  js:
    js/pubnub.min.js: {}

custom_map:
  version: 1.0
  js:
    js/tracking_map.js: {}
  dependencies:
    - core/jquery
    - core/drupal
    - tracking_map/google.maps
    - tracking_map/pubnub

We provide a custom template tracking_map.html.twig with just the div targeted by the Google Map drawing. 

<div id="tracking−map"></div>

Since we are using PubNub free plan, we are limited to 100 devices connected per day. Every page load will count as one device, even it is the same device. Therefore we need to pass a unique ID along the PubNub credentials to identify devices. My Website is 'login-only', so the Drupal USER ID is perfect for this scenario. This way, the page can be loaded/reloaded multiple times on the same device and it will count as only one. For that to work, we are getting the ID in a module preprocess function and passing it directly as variable to the javascript.

// inside tracking_map.module
function tracking_map_preprocess_tracking_map(&$attachments) {
  /** @var \Drupal\user\Entity\User $user */
  $user = \Drupal::currentUser();
  $userID = $user->id();
  $attachments['#attached']['drupalSettings']['uuidpubnub'] = 'pubnubuuid' . $userID;
}

The last important thing to do is to write the simple javascript controller, which will orchestrate the functionality of the tracking map. The script needs to connect to PubNub, get the current coordinates and draw them on the map. Here are the most important parts of the tracking_map.js to show the PuBNub integration.

//snippet from tracking_map.js
...
var pnChannel = "map-channel";
var pubnub = new PubNub({
  publishKey : 'pub-key',
  subscribeKey : 'sub-key',
  uuid: drupalsettings.uuidpubnub
});

var redraw = function(payload) {
  lat = payload.message.lat;
  lng = payload.message.lng;
  
  map.setCenter({lat:lat, lng:lng, alt:0});
  marker.setVisible(true);
  marker.setPosition({lat:lat, lng:lng, alt:0});

  lineCoordinatesPath.setMap(null);
  if (cordinates.length > 20) {
    cordinates.shift();
  }
  cordinates.push({lat: lat, lng: lng});
  lineCoordinatesPath.setPath(cordinates);
  lineCoordinatesPath.setMap(map);
};

pubnub.subscribe({channels: [pnChannel]});
pubnub.addListener({message:redraw});

...

The last thing to do is to add the Block in the block layout and place it on the desired pages.

6. See it in Action

Start up the Pie by connecting it to the power source. The GPS stick starts immediately to scan for satellites. Start the GSM Module by pressing its start button for two seconds. A slowly red blinking light on the GSM Module shows that the connection to the mobile network is established. Once the satellite fix is established, coordinates will be streamed over PubNub to the map visualization.

Overall, the device is working as intended. Of course, there are many things that can be improved, but just for the proof of concept, it is working really well. I haven’t worked with a Raspberry Pie before and was a total beginner. After reading a ton of references and fixing a lot of issues I got better and better. I spent a lot of time reading references, manuals and stackoverflow threads for and backward. And I mean A LOT. In the end, I got everything running and I think I have acquired some basic understanding of how things work inside and outside a Raspberry Pie. This was definitely the most time-consuming part. after that, using PubNub and creating the Drupal integration was straightforward.

If you think about it, I could achieve the same within seconds by just enabling the Whatsapp Chat live position and sending it to a friend. It would have cost me nothing and I would not have to carry around this large black thing. But where would be the fun in that, right?

About the author

This is Mario

It ain’t over till it’s over!

Portrait of Mario Weber