Skip to main content

How To Use GNSS With The imp006

Take Advantage Of A Cell Modem’s Satellite-driven Location Sensing

The imp006 in combination with the Quectel BG96 cellular modem and impOS 43 is capable of using the BG96’s integrated GNSS (Global Navigation Satellite System) functionality to determine its geographical location. This is made possible using a new extension to the imp API.

However, for many customers whose cellular application may benefit from precise location data, it is sufficient to use Electric Imp’s BG96 GPS library, which significantly simplifies GNSS setup and usage. For this reason, this guide will focus on implementing the library. Developers who require more control over the GNSS sub-system than the library provides should consult the API documentation.

Note Using GNSS with the imp006 requires impOS™ 43.

Fix An Active GNSS Antenna

The BG96 will require an external antenna to pick up signals from navigation satellites. There are many GNSS antennas available. For simplicity and performance, an active patch antenna with integrated ground plane is preferable. We have had good results with the Molex 206640, but there are many alternatives, such as those made by Taoglas.

A Molex active GNSS antenna

If you are testing impOS’ GNSS functionality with the imp006 Breakout Board, there is a µ.FL connector ready to take the antenna. Please note that µ.FL connectors are generally rated only for 30 insertions.

The imp006 Breakout Board GNSS antenna connector

Add The Library To Your Code

The BG96 GPS library 1.0.0 makes use of the new GNSS API and therefore requires impOS 43 or above. It will throw an exception if used on an earlier version of impOS.

The library code to use is available in our BG96_GPS repo. To include it in your application, add #require "BG96_GPS.device.lib.nut:1.0.0" at the top of your device code.

Library Usage

The library is not implemented as a class but as a Squirrel table called BG96_GPS. It therefore has no constructor — you simply call BG96_GPSenableGNSS() function, passing in settings as a table. Here’s an example:

// Set the library to show debug output
BG96_GPS.debug = true;

// Attempt to enable GNSS and take readings
BG96_GPS.enableGNSS({
    "maxPosTime": 120,
    "checkFreq":  20,
    "onEvent":  onEvent,
    "onLocation": onLocation
});

The setup options table has specific keys. Those used in the example above set, respectively:

  • How long the modem should be allowed to determine the device’s location before an error is reported. The time is in seconds, in the range 1-255.
  • How often fix data is returned, in seconds.
  • A callback function to notify your application that GNSS is enabled (or could not be enabled) and other events and errors.
  • A callback function to which location data (or an error) is posted.

When code calls BG96_GPS.enableGNSS() the library attempts to enable the modem’s GNSS sub-system, and then to establish a GNSS session. The GNSS Session is represented by an imp API object through which the library, or your code if it is working directly with the GNSS API, communicates with the GNSS sub-system.

The Library’s Callbacks

Assuming the setup process proceeds smoothly, the library triggers the onEvent callback, if set. If the onLocation callback has been sett too, the library then begins periodically polling the modem for the device’s location. Let’s look at these callbacks in more detail. First, onEvent — here’s a basic example:

function onEvent(notification) {
    if ("error" in notification) {
        server.error(notification.error + " (code: " + notification.errcode + ")");
    } else {
        server.log(notification.event);
    }
}

This function just reports success or failure, but your application might use this callback to determine whether it needs to perform additional set-up or clean-up tasks. The callback itself receives a table which will contain the key error or event. In an error took place, it also includes the key errcode, a numeric error code relevant to the human-readable value of error.

The onLocation callback can be straightforward too:

function onLocation(result) {
    if ("fix" in result) {
        server.log("Got fix:");
        foreach (key, value in result) {
            if (typeof value == "table") {
                foreach (k, v in value) {
                    server.log(" " + k + ": " + v);
                }
            } else {
                server.log(key + ": " + value);
            }
        }
    } else {
        server.error(result.error);
    }
}

Here the code dumps out the fix data to the log, or displays any error reported by the library. This will be GPS fix not available if the library has not yet determined the device’s location.

A cold fix can take up to 12.5 minutes if new almanacs and ephemerides need to be fetched, so you may see the GPS fix not available error posted frequently until the code reports that it has valid fix data. When a fix is achieved, the code above will output fix data like this:

2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device] Got fix:
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  cog: 215.05
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  alt: 133.0
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  fixType: 3
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  time: 2020-12-30 11:27:08.100Z
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  numSats: 04
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  lat: 51.56292
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  spkm: 3.1
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  spkn: 1.7
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  lon: -0.14084
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  utc: 112708.100
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  date: 301220
2020-12-30 11:27:15.012 +00:00 "imp006-BK": [Device]  hdop: 2.2
2020-12-30 11:27:33.015 +00:00 "imp006-BK": [Device] [BG96_GPS] Parsing location data

The meaning of most of the data points is clear from the keys by which they are read from the table passed into the onLocation callback. To learn the meanings of all the keys, please see the API documentation.

The format of the latitude (lat) and longitude (lon) readings is (-)dd.ddddd by default. You can change the format to either ddmm.mmmm N/S and ddmm.mmmm E/W, or ddmm.mmmmmm N/S and ddmm.mmmmmm E/W by including the key locMode in your setup options table and provide the value BG96_GNSS_LOCATION_MODE.ZERO or BG96_GNSS_LOCATION_MODE.ONE, respectively. The default is BG96_GNSS_LOCATION_MODE.TWO.

Request location data directly

The onEvent and onLocation callbacks are optional. If you don’t provide the latter to BG96_GPS.enableGNSS(), the library will not poll for location data; instead you will need to call the library’s BG96_GPS.getLocation() function. This also takes a set of options provided as a table. One of these options is also an onLocation callback to relay location data. By default, BG96_GPS.getLocation() only performs one location look-up, but you can force it to poll periodically by including the key poll into the function’s options table.

Use the mode key and one of the BG96_GNSS_LOCATION_MODE constants listed earlier to set the format of the location data. Include the checkFreq key and a period in seconds to tell the function how frequently to return fresh location data.

Load GNSS Location Assist Data

The BG96 supports Quectel’s gpsOneXTRA feature, which allows you to pre-load the modem’s GNSS sub-system with satellite ephemeris data in order to accelerate the location process. This data is stored in a binary format and is available from Quectel at the following URLs:

The xtra2.bin data supports GPS and Glonass; the xtra3grc.bin data supports GPS, Glonass and BeiDou. Choose the data package that best meets your needs. Both are well under 50KB, so can be easily acquired by your agent:

// Wait for a request from the device
device.on("get.assist.data", function(ignored) {
    // Set up an HTTP request to get the assist data
    local assistDataURL = "http://xtrapath4.izatcloud.net/xtra3grc.bin";
    local request = http.get(assistDataURL);

    // Send the request asynchronously
    request.sendasync(function(response) {
        // We've got a response -- it is good?
        if (response.statuscode == 200) {
            // Yes! Relay the data to the device as a blob
            local adb = blob(response.body.len());
            adb.writestring(response.body);
            device.send("set.assist.data", adb);
        } else {
            // No! 
            server.error("Could not get assist data (Error " + response.statuscode + ")");
        }
    });
});

The device code shown earlier can be used largely as is, but we need to update the last part of the code so that the operation to set up GNSS and begin taking readings occurs in response to assist data being sent through by the agent. The code also needs to tell the agent to request the data:

// Handle assist data received from the agent
agent.on("set.assist.data", function(assistData) {
    server.log("Enabling GNSS and getting fix...");

    BG96_GPS.enableGNSS({
        "maxPosTime" : 120,
        "checkFreq" : 20,
        "onLocation" : onLocation,
        "onEvent": onEvent,
        "assistData" : assistData
    });
}.bindenv(this));

// Get the agent to request the ephemerides
agent.send("get.assist.data", true);

How to disable GNSS

Depending on your application’s power requirements and how often it needs to determine its current location, you may prefer to power down GNSS once you have location data. You can do this by calling BG96_GPS.disableGNSS(). It returns true or false to indicate whether it was successful or not.

If you need to re-enable GNSS to get a subsequent fix, just call BG96_GPS.enableGNSS() (and BG96_GPS.getLocation(), if used) once more. You can flag the installation of assist data so that you don’t re-request it next time. In this case, add the useAssist key with the value true to the options table passed into BG96_GPS.enableGNSS() in order to make use of the already loaded data.

Assist Data Validity

Ephemeris data is not permanent, so it’s important to check whether the current data is valid. Quectel data downloaded from the URLs listed above has a validity of seven days. At any time, you can call BG96_GPS.isAssistDataValid() to determine assist data validity. This function returns a boolean value: true or false, depending on whether the data is valid or not (or could not be determined).

Additionally, your onEvent callback will be triggered (if you have set one) with validity information. The table passed into the callback will contain the key data, in addition to event. The value of data is itself a table. It contains the key valid; its value is a boolean as above. If valid is true, data also contains time; its value is the data validity duration in minutes.