UPDATE: You can now connect your Sigfox devices to DevicePilot with zero code. Get started >>
---
Among the most exciting of the LPWA providers are Sigfox who invited DevicePilot to attend their bootcamp workshop at FabLab London.
At the workshop, we were given an integrated Arduino board with a Sigfox transceiver, which has proven to be fun and easy to use. In just an hour, we connected the device up and showed how DevicePilot, our IoT Device Management system can be used to manage and visualise Sigfox devices. This white paper will show you how to combine the Sigfox Callback service and a small NodeJS bridge service to manage your Sigfox devices from DevicePilot.
Sigfox LPWA
Sigfox is a new, lightweight LPWA (Low Power Wide Area) network standard that aims to become a major backbone for the Internet of Things.
At its heart is the notion that most IoT devices will be low-power, lightweight sensors that can encode the information they need to send into small messages which are sent at lower rate than that of most IP traffic today. Because of this, power usage is also incredibly low and it allows sensors to last for years in the field without the need for battery changes.
Sigfox uses encrypted, sub-GigaHertz RF on an unlicenced ISM band, which means that it can offer a very cheap subscription (a matter of a few dollars a year) service to customers.
The Arduino Sketch
We're going to simulate a small wind turbine that informs DevicePilot every 30 minutes of its current state. We'll use the SnootLabs Akeru board, an arduino with onboard Sigfox transceiver.
Sigfox has a maximum message length of 12 bytes. This may sound small, but in fact you can cram a lot of data into that. We're (very inefficiently) going to use all 12 bytes to send:
Longitude and latitude, each to 4 decimal places, each using 4 bits to signal negation, and 20 bits to represent position
Power generated in kWh in the last 24 hours, to 3 decimal places, using 3 bytes.
Current wind speed in knots, to 2 decimal places, using 2 bytes
Sensor status, using 1 byte (assuming 0 is OK, >1 is any type of failure: instrument, generator, etc.)
The following is a very simple Arduino sketch which using the serial port to send a series of bytes to the Sigfox transceiver. These bytes represent:
<code><latitudeNegation><latitude><longitudeNegation><longitude><powerGenerated><windspeed><status></code>
as defined above.
#include <SoftwareSerial.h>
#define RX_PIN 4
#define TX_PIN 5
SoftwareSerial sigfox(TX_PIN, RX_PIN);
void setup(){
Serial.begin(9600);
sigfox.begin(9600);
sigfox.write("AT$SF=07FEA2000A88739B450E8D00\r");
}
void loop(){
}
Copy the code into a new sketch in the Arduino IDE. Next we're going to setup a Sigfox Callback to route our messages to somewhere useful.
The Sigfox Callback
Sigfox's website allows you to create an HTTP callback, a webhook, for every device on its network, sending a GET, POSTor PUT to your server.
We'll setup a callback which takes the data sent to us by our wind turbine and forwards it to a friendly machine in the Cloud, running a small HTTP service acting as a bridge to DevicePilot.
To do this, open a browser window and go to Sigfox's backend service:
https://backend.sigfox.com/
After logging in, select 'Device Type' and then choose one of your devices by clicking on its 'Name' column, followed by selecting 'Callbacks' in the menu on the left. Finally, select 'New Default Callback' from the top menu.
You'll see something similar to the following in your browser:
We now want to add some parameters to point to the right host for the callback and send the data we need.
First fill in the Url pattern field. It'll be something like:
<code>http://sf.devicepilot.com:1234/sigfox?id={device}&time={time}</code>
This will send the device ID and time the message was received as query parameters to sf.devicepilot.com host.
Next change the Use HTTP method value to POST .
Now alter the Content type field to application/json , and finally fill in the Body section with the following
{
"deviceId": "{device}",
"basestation": "{station}",
"averageSnr": "{avgSnr}",
"deviceData": "{data}"
}
We're only going to use the deviceData property from the JSON body, but the others are useful information you may want logged by the bridge.
You should now see something very similar to the following:
Once complete, select 'Ok'.
You've just created a callback that will be used every time your device sends a message.
The Sigfox to DevicePilot Bridge
The final piece of the puzzle is to write a bridge that listens for the Sigfox callback and translates that into JSON data for DevicePilot.
The bridge listens for the HTTP POST sent by the Sigfox callback service. Using the Sigfox device ID and data sent in the callback body it constructs a JSON object ready to send to DevicePilot.
This is easy to do with just over 100 lines of code in NodeJS. The only change required to the following will be your own username and password.
First, install the npm modules used.
<code>npm install bluebird request express body-parser</code>
"use strict";
var Promise = require('bluebird'),
express = require('express'),
body = require('body-parser'),
request = require('request');
var bridgeApp = express();
// Convert a string to a float, with a specific fixed point divisor.
function fromFixed (divisor, string, start, length) {
return (parseInt(string.substr(start, length), 16) / divisor);
};
// Decode the 12byte message from the Sigfox device to a structure to send
// to DevicePilot.
function decodeMessageData (data) {
// The Sigfox device sends us a string of 12 bytes, comprising:
// * latitude negation: 4 bits (1 === negation, 0 otherwise)
// * latitude: 20 bits
// * longitude negation: 4 bits (1 === negation, 0 otherwise)
// * longitude: 20 bits
// * kWh produced in 24 hours: 3 bytes
// * windspeed in knots: 2 bytes
// * status: 1 bytes
var latNegate = parseInt(data[0], 16),
longNegate = parseInt(data[6], 16),
decodedData = {
latitude: fromFixed(10000, data, 1, 5),
longitude: fromFixed(10000, data, 7, 5),
kWh: fromFixed(1000, data, 12, 6),
windSpeed: fromFixed(100, data, 18, 4),
status: fromFixed(1, data, 22, 2)
};
// See if we need to negate lat/long.
if (latNegate !== 0) {
decodedData.latitude = -decodedData.latitude;
}
if (longNegate !== 0) {
decodedData.longitude = -decodedData.longitude;
}
return decodedData;
}
// Uses HTTPS to talk to DevicePilot, retrieving or sending information.
function callDevicePilot (args) {
var requestObj = {
url: 'https://devicepilot.com' + args.extension,
method: args.method,
json: true,
body: args.body
};
// If we don't have an API token, use the basic auth details.
if (args.token === undefined) {
requestObj.auth = args.auth;
} else {
requestObj.headers = { 'Authorization': 'Token ' + args.token };
}
// Try and make the request.
return new Promise(function (resolve, reject) {
request(requestObj, function (err, resp, body) {
if (err) {
reject(err);
} else {
resolve(body);
}
});
});
}
// Creates a new Sigfox to DevicePilot bridge using express.
(function () {
var userToken;
// Retrieve the API token for the specified user.
return callDevicePilot({ extension: '/tokenauth',
method: 'GET',
auth: { user: 'heds', pass: 'myverysecretpassword' }
}).then(function (userData) {
// We now have our token for passing to DevicePilot instead of using
// basic auth.
userToken = userData.token;
// Ensure that Express is set up correctly.
bridgeApp.use(body.json());
bridgeApp.use(function(err, req, res, next) {
console.log("Error in express:");
console.log(err);
});
// Create a new POST route for the Sigfox callback to use.
bridgeApp.post('/sigfox', function (req, res) {
var newData,
deviceId = req.query.id,
time = parseInt(req.query.time, 10);
console.log('New message received from Sigfox ' + deviceId +
' @ ' + Date(time).toLocaleString());
if (req.body !== undefined) {
// Decode the data string from the Sigfox device to a DevicePilot
// ready object.
newData = decodeMessageData(req.body.deviceData);
// Add the device description to the data.
newData.label = 'Sigfox Device ' + deviceId;
// Does the device already exist? If so, we need to use its
// current $id.
return callDevicePilot({ extension: '/devices?where=' +
encodeURIComponent('label=="' + newData.label + '"'),
token: userToken,
method: 'GET' }).then(function (results) {
// If the device already exists, this ensures we update
// its data instead of creating a new device.
if (results.length === 1) {
newData.$id = results[0].$id;
}
// Call DevicePilot to send the data for the device.
return callDevicePilot({ extension: '/devices', token: userToken,
method: 'POST', body: newData }).then(function () {
console.log('Posted data for device ' + deviceId +
' to DevicePilot');
});
});
}
// We're just a bridge, so even if we fail sending to DevPilot,
// we still ack receipt from Sigfox.
res.status(200).send();
});
// Start Express listening on port 1234.
bridgeApp.listen(1234, '0.0.0.0', function () {
console.log('Started Sigfox Bridge Daemon');
});
});
}())
Putting It All Together
To verify that DevicePilot has seen it, login and select 'Map view' from the 'View' menu. You will see something like the following:
Congratulations, you now have a fullying working Sigfox to DevicePilot bridge. You're ready to manage your Sigfox devices in style!
Sigfox offers an extremely attractive entry point for deploying low-cost, reliable sensors into the field, and with the addition of DevicePilot allows you to manage and triage your device fleet efficiently as you expand and deploy.
Whilst this blog post has shown how you could carry this out using your own bridge, there is little in the way of security via the bridge. DevicePilot is currently working with Sigfox and other LPWA operators to integrate their services more directly, which will allow a more efficient and secure way of allowing customers to use their devices.
Get in contact with us for more details.