Developer Relations Engineer, PubNub
IN THIS ARTICLE

    It’s hard to imagine living in a world without GPS. From handheld devices that globally navigate us, to satellite atomic clocks synchronizing our time, we rely heavily on GPS in modern civilization.

    What was once a tool developed by the US Navy to locate submarines, today’s abundance of satellites in orbit and advancements in technology have granted GPS access to anyone willing to wield it. With over thirty GPS satellites in orbit, a person standing anywhere on the planet will have at least four satellites “visible” to them at all times. Each satellite transmits information about its exact position at light speed, which can be received instantly by anyone with a GPS receiver.

    However, knowing the position of one satellite is not too helpful for determining a person’s own location, which is why having four satellites are! Though the process of triangulation, a person can calculate their own position by only knowing the positions of at least three other satellites. You can read more about how this works here!

    Satellites

    Tutorial Overview

    From tracking automobiles in a rideshare app to geologging the life’s journey of a bald eagle, GPS is an incredibly powerful tool and we’re going to show you just how easy it is to implement a GPS receiver of your own.

    In this article, we’re going to show you how to add GPS capabilities to a Raspberry Pi project with a few pieces of hardware and PubNub. Not only will you learn some basics of IoT programming, but also learn how IoT data can easily be handled with PubNub’s robust Data Stream Network and APIs!

    What You’ll Need

    Setup

    Soldering

    You’ll notice that none of the pins are attached on your GPS module.

    In order to easily connect wires to our module and RPI, we’ll need to solder the pins on with our trusty soldering iron. Break off 9 of the pins you see on the left and insert them (short side first) into the top of the board (the side with the pin labels) and solder the pins to the board.

    Above is what two pins look like properly soldered and below is all the pins soldered on.

    NOTE: If you don’t know how to solder, watch this video and proceed carefully. You can severely burn yourself soldering.

    Wiring

    Since we are using a Raspberry Pi with this GPS module, we’ll need to have access to the Raspberry Pi’s UART (Universal Asynchronous Receiver Transmitter) interface.

    The details of why we’re using this protocol extend far beyond the scope of this project. In essence, this is the best way to send and receive data serially from our module to the RPI. The TX (transmitter) pin of the GPS module sends bits to the RX (receiver) pin of the RPI and vice versa.

    You can read more about it here and even more about it here.

    Specifics aside, wire up your hardware as shown:

    Quick Disclaimer: The next few sections require setting up the Raspberry Pi with various libraries and drivers so that we can properly establish communication with the GPS module. This may get confusing for some who are not familiar with IoT or embedded system programming, but don’t give up! I guarantee you’ll make it out alive!

    NOTE: Be sure to have SSH enabled on your RPI before you jump into the mess. You can learn how to do so here.

    NOTE: Also make sure you sign up for a free PubNub account before you begin! You’ll need your own pub/sub keys to make it work.

    Step 0: Setting up 3G/4G LTE (Optional)

    If you want to be able to have your Raspberry Pi have GPS capabilities without WiFi dependance, then you’ll need to use an LTE shield. The setup for this is fairly simple and you’ll need to go to this page for the setup.

    Once you’re on the page, carefully follow the “Hardware Setup” as well as the “Software Setup” instruction sections. Here you will essentially download the necessary libraries, packages, and GitHub repositories to run SIXFAB’s software on the hardware.

    Lastly, follow the “Autoconnect on Reboot” section to make sure you’re RPI connects to LTE on startup.

    Step 1: Enabling UART on the Raspberry Pi

    SSH into your RPI and type in:

    sudo raspi-config

    You should then get a graphical interface in your terminal. Select the menu options highlighted in red.

    Then:

    sudo reboot

    Step 2: Configure I2C on the RPI

    This step is required to use the CircuitPython Library in Step 4 as many of the I2C drivers are used in that library. This is essentially a 2-wire bus protocol that allows one chip to talk with another. Please reference this page if you’re interested in learning more about bus protocols.

    First, ssh into your RPI. Next, install the I2C-tools utility:

    sudo apt-get install -y python-smbus
    sudo apt-get install -y i2c-tools
    

    Next, we need to install Kernal Support. Like before, in your terminal type in

    sudo raspi-config

    Then: 

    sudo reboot

    Step 3: Configure SPI on the RPI

    SPI (Serial Peripheral Interface) is another bus protocol that synchronizes serial communication between chips. Again, for those interested in learning more, check out this Wiki page.

    Just like before, ssh into your RPI and do a:

    sudo raspi-config

    Navigate through the menu interface by selecting the options highlighted in red.

    Then: 

    sudo reboot

    Step 4: Test and Verify

    To make sure we did the previous steps correctly, we are going to run a test script on our RPI to verify we have all the necessary hardware protocols enabled.

    NOTE: Be sure you have python3 and pip3 commands installed on your device before you continue!

    SSH into your RPI and create a working directory for your project:

    mkdir gps
    cd gps
    

    Install the Raspberry Pi GPIO Library.

    pip3 install RPI.GPIO

    Now install the library for our test script.

    pip3 install adafruit-blinka

    Now create the test script named blinkatest.py with nano.

    nano blinkatest.py

    Copy and paste this code into the test script and save.

    import board
    import digitalio
    import busio
     
    print("Hello blinka!")
     
    # Try to great a Digital input
    pin = digitalio.DigitalInOut(board.D4)
    print("Digital IO ok!")
     
    # Try to create an I2C device
    i2c = busio.I2C(board.SCL, board.SDA)
    print("I2C ok!")
     
    # Try to create an SPI device
    spi = busio.SPI(board.SCLK, board.MOSI, board.MISO)
    print("SPI ok!")
     
    print("done!")
    

    Run the test script.

    python3 blinkatest.py

    You should get an output like this. If not, go back and re-do the corresponding step to your failed tests.

    Step 5: Install CircuitPython

    CircuitPython is Adafruit Technology’s programming language that aims to “simplify experimenting and learning to program on low-cost microcontroller boards.” Therefore, this is the best tool to quickly and easily run example programs of their various products so we’re simplifying this page’s installation instructions.

    If you haven’t done so already, ssh into your RPI and cd into your project’s directory.

    In your project directory, clone this repository.

    To be safe, cd into the library you just cloned and then clone the GPS python library.

    Then install any dependencies.

    sudo pip3 install adafruit-circuitpython-gps
    

    Step 6: Run an Example Script to Test your Module

    In order to make sure we have done everything correctly (in both hardware and software setup), let’s run an example script in the circuitpython-gps library you cloned.

    cd Adafruit_CircuitPython_GPS
    cd examples
    Nano gps_simpletest.py 
    

    Locate and comment out the following lines to make sure the program runs smoothly with our RPI.

    RX = board.RX
    TX = board.TX
    

    Then:

    uart = busio.UART(TX, RX, baudrate=9600, timeout=3000)

    Then locate and UNcomment these lines.

    #import serial
    #uart = serial.Serial("/dev/ttyUSB0", baudrate=9600, timeout=3000)

    Then change the 2nd line you just uncommented to this.

    #uart = serial.Serial("/dev/ttyS0", baudrate=9600, timeout=3000)

    Now you should be all set to run the code. Make sure your GPS module has a clear view of the sky and run the code!

    python3 gps_simpletest.py

    While your module searches for the GPS satellites in the sky, you should get a terminal output like so:

    Once it’s got a fix, you should get the GPS data readings that looks like this:

    Step 7: Stream Data Over PubNub

    If you haven’t already done so, sign up for a free PubNub account before you begin this step.

    Instead of creating a whole new file from scratch, we’re going to ease the coding process by making just a few modifications to the gps_simpletest.py file we tested earlier. You can pick any of the example tests (or write your own from scratch), but it’s always better to start from a template.  

    cd into the examples directory containing the gps_simpletest.py file and install the PubNub Python SDK.

    pip3 install pubnub

    Import PubNub packages.

    import pubnub
    from pubnub.pnconfiguration import PNConfiguration
    from pubnub.pubnub import PubNub
    from pubnub.callbacks import SubscribeCallback
    from pubnub.enums import PNOperationType, PNStatusCategory

    Configure a PubNub instance with your publish/subscribe keys.

    pnconfig = PNConfiguration()
    pnconfig.subscribe_key = "YOUR SUBSCRIBE KEY"
    pnconfig.publish_key = "YOUR PUBLISH KEY"
    pnconfig.ssl = False
    pubnub = PubNub(pnconfig)

    Then to publish, place a publishing callback somewhere near the beginning of your code. You can write whatever you want for the callback, but we’ll leave it blank as we don’t really need it for now.

    def publish_callback(result, status):
        pass
        # Handle PNPublishResult and PNStatus

    Here is where you decide what data you want to publish. Since we are building just a simple GPS tracking device, we’re just going to be dealing with the latitude and longitude coordinates.

    When you want to publish multiple variables in one JSON, you must create a dictionary like so:

    dictionary = {"DATA 1 NAME": gps.DATA1, "DATA 2 NAME": gps.DATA2}

    So in our case we would write:

    dictionary = {"latitude": gps.latitude, "longitude": gps.longitude}

    And then to publish that data, you would format the dictionary like this:

    pubnub.publish().channel("CHANNEL").message(dictionary).pn_async(publish_callback)

    It is best to place the dictionary and publishing lines within the “if gps.DATA is not none” to avoid any program failures.

    Step 8: Visualize your GPS Data with Google Maps

    I hope we can all appreciate how far we’ve come so far because we’re about to see it all come together. The last step of this journey is to visualize our GPS data in a way that humans can understand.

    We’re just going to create a small HTML page that will grab GPS data from our PubNub channel and graph the data with a mapping API.

    Google Maps API

    The Google Maps API is a universal tool that is not only one of the cheaper APIs for a greater amount of API calls but also has a rich and expansive toolset for developers. The GPS data is not only more accurate than most other APIs, but also has extensive tools such as “ETA” that uses Google’s geographical terrain data.

    So if you ever want to build a serious GPS tracking app with PubNub, Google Maps is the way to go.

    Image result for google maps api with marker

    You’ll first need to get a Google Maps API Key

    Once that’s done, create an .html file and copy-paste the code below (explanation of the code is below as well).

    <!DOCTYPE html>
    <html>
      <head>
        <title>Simple Map</title>
        <meta name="viewport" content="initial-scale=1.0">
        <meta charset="utf-8">
        <style>
          /* Always set the map height explicitly to define the size of the div
           * element that contains the map. */
          #map {
            height: 100%;
          }
          /* Optional: Makes the sample page fill the window. */
          html, body {
            height: 100%;
            margin: 0;
            padding: 0;
          }
        </style>
        <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.23.0.js"></script>
      </head>
      <body>
        <div id="map"></div>
        <script>
      // the smooth zoom function
      function smoothZoom (map, max, cnt) {
          if (cnt >= max) {
              return;
          }
          else {
              z = google.maps.event.addListener(map, 'zoom_changed', function(event){
                  google.maps.event.removeListener(z);
                  smoothZoom(map, max, cnt + 1);
              });
              setTimeout(function(){map.setZoom(cnt)}, 80); // 80ms is what I found to work well on my system -- it might not work well on all systems
          }
      } 
        var pubnub = new PubNub({
        subscribeKey: "YOUR SUBSCRIBE KEY",
        ssl: true
      });	
      var longitude = 30.5;
      var latitude = 50.5;
      pubnub.addListener({
          message: function(m) {
              // handle message
              var channelName = m.channel; // The channel for which the message belongs
              var channelGroup = m.subscription; // The channel group or wildcard subscription match (if exists)
              var pubTT = m.timetoken; // Publish timetoken
              var msg = m.message; // The Payload
              longitude = msg.longitude;
              latitude = msg.latitude;
              var publisher = m.publisher; //The Publisher
        var myLatlng = new google.maps.LatLng(latitude, longitude);
        var marker = new google.maps.Marker({
            position: myLatlng,
            title:"PubNub GPS"
        });
        // To add the marker to the map, call setMap();
        map.setCenter(marker.position);
        smoothZoom(map, 14, map.getZoom());
        marker.setMap(map);
          },
          presence: function(p) {
              // handle presence
              var action = p.action; // Can be join, leave, state-change or timeout
              var channelName = p.channel; // The channel for which the message belongs
              var occupancy = p.occupancy; // No. of users connected with the channel
              var state = p.state; // User State
              var channelGroup = p.subscription; //  The channel group or wildcard subscription match (if exists)
              var publishTime = p.timestamp; // Publish timetoken
              var timetoken = p.timetoken;  // Current timetoken
              var uuid = p.uuid; // UUIDs of users who are connected with the channel
          },
          status: function(s) {
              var affectedChannelGroups = s.affectedChannelGroups;
              var affectedChannels = s.affectedChannels;
              var category = s.category;
              var operation = s.operation;
          }
      });
      pubnub.subscribe({
          channels: ['ch1'],
      });
          var map;
          function initMap() {
            map = new google.maps.Map(document.getElementById('map'), {
              center: {lat: latitude, lng: longitude},
              zoom: 8
            });
          }
        </script>
        <script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyBLuWQHjBa9SMVVDyyqxqTpR2ZwnxwcbGE&callback=initMap"
        async defer></script>
      </body>
    </html>

    This part of the code is responsible for rendering our map on the HTML page.

    <style>
      /* Always set the map height explicitly to define the size of the div
       * element that contains the map. */
      #map {
        height: 100%;
      }
      /* Optional: Makes the sample page fill the window. */
      html, body {
        height: 100%;
        margin: 0;
        padding: 0;
      }
    </style>
    

    Just a little below it, we enter a div id tag to tell where we want the map to render:

    	//div tag for map id
      	 <div id="map"></div>

    Here we simply import the PubNub JS SDK to enable PubNub data streaming for our GPS data:

    <script src="https://cdn.pubnub.com/sdk/javascript/pubnub.4.23.0.js"></script>

    We must also import the Google Maps API with this script tag:

    <script src="https://maps.googleapis.com/maps/api/js?key=YOURAPIKEY&callback=initMap"async defer></script>

    NOTE: The rest of the code is encapsulated within one script tag, so don’t be alarmed if we jump around in explaining this final part of the code.

    In order to stream our data, instantiate a PubNub instance:

    var pubnub = new PubNub({
        subscribeKey: "YOUR SUBSCRIBE KEY",
        ssl: true
      });
    

    Then we instantiate a PubNub listener with the following code.

    pubnub.addListener({
          message: function(m) {
              // handle message
              var channelName = m.channel; // The channel for which the message belongs
              var channelGroup = m.subscription; // The channel group or wildcard subscription match (if exists)
              var pubTT = m.timetoken; // Publish timetoken
              var publisher = m.publisher; //The Publisher
              
              var msg = m.message; // The Payload
              //extract and save the longitude and latitude data from your incomming PubNub message
              longitude = msg.longitude;
              latitude = msg.latitude;
              
            //Create a new Google Maps instance with updated GPS coordinates
          var myLatlng = new google.maps.LatLng(latitude, longitude);
          //Create a marker instance with the coordinates
          var marker = new google.maps.Marker({
              position: myLatlng,
              title:"PubNub GPS"
          });
          
          //center the map with the maker position
          map.setCenter(marker.position);
          //Optional: create a zooming annimation when the gps changes coordinates
          smoothZoom(map, 14, map.getZoom());
          // To add the marker to the map, call setMap();
          marker.setMap(map);
          },
          presence: function(p) {
              // handle presence
              var action = p.action; // Can be join, leave, state-change or timeout
              var channelName = p.channel; // The channel for which the message belongs
              var occupancy = p.occupancy; // No. of users connected with the channel
              var state = p.state; // User State
              var channelGroup = p.subscription; //  The channel group or wildcard subscription match (if exists)
              var publishTime = p.timestamp; // Publish timetoken
              var timetoken = p.timetoken;  // Current timetoken
              var uuid = p.uuid; // UUIDs of users who are connected with the channel
          },
          status: function(s) {
              var affectedChannelGroups = s.affectedChannelGroups;
              var affectedChannels = s.affectedChannels;
              var category = s.category;
              var operation = s.operation;
          }
      });
    

    In order to avoid syntax errors, place a subscriber instance right below the listener.

    pubnub.subscribe({
          channels: ['YOUR CHANNEL NAME'],
      });
    

    As you can see, we open up incoming messages with the following line of code.

    var msg = m.message; // The Payload

    And then extract the variables we desire based on the sent JSON.

    longitude = msg.longitude;
    latitude = msg.latitude;
    

    We then format the data variables in accordance to a Google Maps object.

    var myLatlng = new google.maps.LatLng(latitude, longitude);

    To set a Google marker on our GPS coordinates we create a Google Maps marker object.

    var marker = new google.maps.Marker({
              position: myLatlng,
              title:"Title of Marker"
          });

    Then add the marker to your Google Maps object by calling setMap().

    marker.setMap(map);

    Of course, it would be nice to center our map on the marker so we can actually see it so we center it on the markers position.

    map.setCenter(marker.position);

    This is optional, but if you want to add a smooth zooming animation every time you locate a marker, call a smoothZoom function like so.

    smoothZoom(map, 14, map.getZoom());

    And implement the smoothZoom function somewhere.

    function smoothZoom (map, max, cnt) {
          if (cnt >= max) {
              return;
          }
          else {
              z = google.maps.event.addListener(map, 'zoom_changed', function(event){
                  google.maps.event.removeListener(z);
                  smoothZoom(map, max, cnt + 1);
              });
              setTimeout(function(){map.setZoom(cnt)}, 80); // 80ms is what I found to work well on my system -- it might not work well on all systems
          }
      } 
    

    Lastly we’ll need to initialize the map so we write:

    var map;
         function initMap() {
           map = new google.maps.Map(document.getElementById('map'), {
             center: {lat: latitude, lng: longitude},
             zoom: 8
           });
         }
    

    And set the initial values of your latitude and longitude variables to wherever you want.

    var longitude = 30.5;
    var latitude = 50.5;
    

    And that’s it! 

    Try PubNub today!

    Build realtime applications that perform reliably and securely, at global scale.
    Try Our APIs
    Try PubNub today!
    More From PubNub