Create a GPS tracking system with cell connectivity and minimal bandwidth

In this tutorial, we're going to build a GPS tracking system using a balenaFin board, balenaCloud and a Hologram SIM card.

This post outlines the steps to create a GPS tracking system, a useful project in its own right, but also a great way to experience a few features rolling out soon in the balena ecosystem. Follow along as we go step-by-step through the entire process of setting everything up.

GPS (Global Positioning System) trackers enable people and companies to track the location of people and things. With the power of containers, you can easily add the current location of your devices to any new projects or existing balenaCloud projects.

Besides the balenaFin board, we will also need two extra hardware components. First a Mini PCI-Express GPS and GSM Modem, for which we will use the new Quectel EC25. Second, a SIM card for internet connectivity, where for our project, Hologram is a great solution. Note that we are also using two external antennas to improve the signal strength. For LTE we are using a Pulse Electronics W3907 antenna and for GPS a Taoglas FXP611.

On the software side, you will need accounts for balenaCloud and Hologram. In case you are new to the balena ecosystem, we highly recommend checking the getting started guide.

tl;dr, for the project you will need:

Hardware

First, we need to assemble all of the hardware together. Here you can see a picture of the fully-assembled hardware:

Unlike the standard Raspberry Pi, the balenaFin comes with mPCIe and micro-SIM slots, making assembling the hardware components and incorporating cellular connectivity super easy. We're looking forward to creating many new projects using cellular data and testing different GSM modules as well. For that, keep an eye on the blog for new posts!

Software

After connecting all the hardware together, let’s go ahead and create a new balenaCloud project. From the balenaCloud dashboard, click Create Application and select BalenaFin (CM3) as the Default Device Type. Give the project a name and add a new device to the list.

When adding a new balenaFin device, make sure you select a 2.27.0 or later production version of the operating system.

Once the image is downloaded to your computer, connect the balenaFin board to your computer via the micro-USB connector and use balenaEtcher to install balenaOS on the hardware, which will enable us to later manage the entire application online.

After balenaEtcher is done flashing the device, you can turn it ON and open the dashboard and within minutes it will pop up on the screen. You can check the balenaFin getting started guide for more information here.

The entire code for this project can be downloaded from https://github.com/balena-io-playground/gps-tracker. For more information on how to push code from the git repository to your device, please refer to the getting started guide.

Hologram

Hologram is a great tool if you want to add GSM connectivity to your application. The company has created a platform where you can easily manage multiple SIM cards that can be deployed to 196 different countries around the globe, all using an uncomplicated billing system. For more information about the service, check out hologram.io.

Once balenaEtcher has finished installing the base operating system, it will mount the partition in your computer. Go to the folder resin-boot/system-connections and add the following Hologram connection file to a folder so that our cellular connection will work on first boot:

[connection]
id=hologram-connection  
type=gsm  
autoconnect=true

[gsm]
apn=hologram  
number=*99#

[serial]
baud=115200

[ipv4]
method=auto

[ipv6]
addr-gen-mode=stable-privacy  
method=ignore  

Configuring SSH and connecting to the device

Until recently, you could only SSH into development devices, via port 22222. The main issue there was that the SSH server on these devices have no authentication, leaving it open for anyone to access it. Starting on the OS version 2.26.0, you will also be able to connect securely to production devices via SSH. This is a huge improvement as engineers and fleet managers will be able to get inside the devices without having to rely on balenaCloud. This feature will be extremely important for this project, as we will disable balenaCloud VPN in order to reduce the daily bandwidth usage for each device.

Now, let's go ahead and add your public SSH key into the config.json file so that we can use the hologram spaceBridge to ssh into our device even when the balenaCloud VPN is turned off. This can be done by adding the following to your config.json on the resin-boot partition, replacing KEY1 by the string from your ~/.ssh/id_rsa.pub.

For more info on setting these values, visit: https://github.com/balena-os/meta-balena#os

"os": {
    "sshKeys": [
      "KEY1"
    ]
  }

The Hologram service provides a tool called spaceBridge which allow users to securely ssh into devices through the Hologram network. Since balenaOS devices run a socket activated SSH daemon on port 22222, and we have added our custom SSH key, we can simply set up spaceBridge following the hologram spaceBridge guide.

Now if we set up spaceBridge to create a link between our device’s port 22222 and our computer's port 3000, we should have a secure SSH tunnel into the host of our balenaOS device even with the balenaCloud VPN disabled!

Once you have the spaceBridge running, open a terminal session on your computer and run the following to SSH into your device remotely, make sure you have your key added into your ssh-agent:

ssh root@127.0.0.1 -p3000  

Note: spaceBridge will only work when there is an active data session on the cellular connection, so when your device is running on wifi, spaceBridge will fail to establish a link.

Custom udev rules

Every time your Linux system boots, udev, the device manager for the Linux kernel, checks for external devices connected and tries to initialize them by adding to the /dev system path. For example, when you attach a USB device to your computer, udev detects it and add it to /dev/ttyusb0.

Sometimes, however, udev will give a different name for the same device, for example instead of /dev/ttyusb0 it might be /dev/ttyusb1, which can break the code that tries to interface with or manage the external devices.

With that in mind, we have released an updated 2.27.0 OS that fixes this issue by allowing users to predefine custom udev rules in the hostOS, improving the reliability of our systems and fixing an annoyance for fleet owners. In our current project, we will predefine a device name for our modem so that every time we boot the device, it will have the exact same name.

In order to add the udev rules, we need to edit our config.json file to always map our modem’s GPS serial port to /dev/EC25.NMEA. (You can read more about this here). On the “os” section of the config.json, add:

       "udevRules": {
           "20-quectel": "SUBSYSTEMS==\"usb\", ENV{.LOCAL_ifNum}=\"$attr{bInterfaceNumber}\"\n\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"05c6\", ATTRS{idProduct}==\"9003\", ENV{.LOCAL_ifNum}==\"01\", SYMLINK+=\"UC20.NMEA\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"05c6\", ATTRS{idProduct}==\"9003\", ENV{.LOCAL_ifNum}==\"02\", SYMLINK+=\"UC20.AT\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"05c6\", ATTRS{idProduct}==\"9003\", ENV{.LOCAL_ifNum}==\"03\", SYMLINK+=\"UC20.MODEM\", MODE=\"0660\"\n\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0121\", ENV{.LOCAL_ifNum}==\"02\", SYMLINK+=\"EC21.AT\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0121\", ENV{.LOCAL_ifNum}==\"03\", SYMLINK+=\"EC21.MODEM\", MODE=\"0660\"\n\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0125\", ENV{.LOCAL_ifNum}==\"01\", SYMLINK+=\"EC25.NMEA\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0125\", ENV{.LOCAL_ifNum}==\"02\", SYMLINK+=\"EC25.AT\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0125\", ENV{.LOCAL_ifNum}==\"03\", SYMLINK+=\"EC25.MODEM\", MODE=\"0660\"\n\n"
       }

At the end our complete config.json should look something like the following:

{
   "applicationName": "GPS-tracker",
   "applicationId": 3333390,
   "deviceType": "fincm3",
   "userId": 8888,
   "username": "john_doe",
   "appUpdatePollInterval": 600000,
   "listenPort": 48484,
   "vpnPort": 443,
   "apiEndpoint": "https://api.balena-cloud.com",
   "vpnEndpoint": "vpn.balena-cloud.com",
   "registryEndpoint": "registry2.balena-cloud.com",
   "deltaEndpoint": "https://delta.balena-cloud.com",
   "pubnubSubscribeKey": "sub-c-aaaaaaaaaaaaaaaaaaaaa",
   "pubnubPublishKey": "pub-c-bbbbbbbbbbbbbbbbbbbbbbb",
   "mixpanelToken": "ccccccccccccccccccccccc",
   "apiKey": "deadbeefffffffffffff",
   "os": {
       "sshKeys": [
           "ssh-rsa blablablablabla johndoe@balena.io"
       ],
       "udevRules": {
           "20-quectel": "SUBSYSTEMS==\"usb\", ENV{.LOCAL_ifNum}=\"$attr{bInterfaceNumber}\"\n\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"05c6\", ATTRS{idProduct}==\"9003\", ENV{.LOCAL_ifNum}==\"01\", SYMLINK+=\"UC20.NMEA\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"05c6\", ATTRS{idProduct}==\"9003\", ENV{.LOCAL_ifNum}==\"02\", SYMLINK+=\"UC20.AT\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"05c6\", ATTRS{idProduct}==\"9003\", ENV{.LOCAL_ifNum}==\"03\", SYMLINK+=\"UC20.MODEM\", MODE=\"0660\"\n\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0121\", ENV{.LOCAL_ifNum}==\"02\", SYMLINK+=\"EC21.AT\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0121\", ENV{.LOCAL_ifNum}==\"03\", SYMLINK+=\"EC21.MODEM\", MODE=\"0660\"\n\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0125\", ENV{.LOCAL_ifNum}==\"01\", SYMLINK+=\"EC25.NMEA\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0125\", ENV{.LOCAL_ifNum}==\"02\", SYMLINK+=\"EC25.AT\", MODE=\"0660\"\nSUBSYSTEMS==\"usb\", KERNEL==\"ttyUSB[0-9]*\", ATTRS{idVendor}==\"2c7c\", ATTRS{idProduct}==\"0125\", ENV{.LOCAL_ifNum}==\"03\", SYMLINK+=\"EC25.MODEM\", MODE=\"0660\"\n\n"
       }
   }
}


Configuring the App for minimal bandwidth usage

If your devices are primarily running on cellular data, there are a few things you should do to lower the bandwidth usage.

First, on your Fleet Configuration page in the dashboard, change the following:

  • Disable VPN connectivity check
  • Disable logs from being sent to balena
  • Disable VPN
  • Set BALENA_SUPERVISOR_POLL_INTERVAL = 86400000this ensures that the supervisor only checks for updates once every 24 hours.

Note that disabling all of the above will mean that you no longer get live status feedback of your devices, for example, the online/offline indicator will always show offline and you will not get logs pushed back to the dashboard. With all of the above settings, the regular operation of the device should use less than 2MB per month. For more information on bandwidth savings, check out this blog post on Device Data Usage.

With the device posting to the API every 10 minutes and the minimum daily check-ins for the balena update infrastructure to function properly, the device uses about 3MB per day.

Additionally, to reduce the size and impact of your container changes, you should enable delta updates on your device, a feature that guarantees that on every update, only the code difference will be downloaded. This way, for example, if you only change a few lines of code on your project, the update should only require a few kilobytes. Click here to know more about delta updates.

To enable delta updates on your device, you should set the following two device configuration options:

  • BALENA_SUPERVISOR_DELTA = 1
  • BALENA_SUPERVISOR_DELTA_VERSION = 3

Location API

One cool feature on the balenaCloud dashboard is a section called “Location”, where it parses the device IP address (you can find more information about this feature here) using Geocoding to estimate its location. As the main idea of this project is to be able to track the location change in real time, we can’t use the IP address anymore. The solution will be using the Node SDK to set the latitude and longitude based on the GPS NMEA sentences from the modem.



Conclusion

This was a cool project that we had a lot of fun building, from setting up the balenaFin board and connecting all the hardware to being able to track our device anywhere in the world. Adding GPS tracking to a device is just the beginning and we would love to hear what else you build with it!

If you’ve any further questions, feedback or just want to discuss this or any other project, the balena team always reads our forums at https://forums.balena.io - come and join in the discussion!

comments powered by Disqus
Terms of Service | Privacy Statement | Master agreement | Copyright 2019 Balena | All Rights Reserved