Device Type Decoders

Overview

To help manage devices of the same type e.g Semtech LoRaMote SX1272 RuBAN allows you to create Device Types. A Device Type allows you to define

decoders, used to read metrics from a sensors. Therefore Device Types can be shared across all device of a certain type.

In this document we will take you through setting up a Device Type Decoder. In this example we will use the Semtech LoRaMote SX1272.

Configuration

In RuBAN under Admin there is a Payload Decoder section. This page lists all of the Device Types in the system. A Device Type consists of a decoder, and multiple encoders (available soon) and allows you to send and receive data.

The device type should be named after the manufacture device name to make it easy for other users to find the correct type e.g Semtech LoRaMote SX1272


Decoders

There are two types of decoder Dsl and Raw. We will first walk through the steps to create a Dsl decoder for the Semtech LoRaMote SX1272 as an example.

Dsl Decoder

When writing a Dsl decoder you use a json schema to describe the payload. The schema is split into two sections preprocessing and processing.

  • Preprocessing, is used to prepare the payload e.g convert the string to hex.
  • Processing, allows you define the bytes of the payload and how to interpret them.

The decoder we will build in this section will convert this payload 0026fd0a6001c0b54be236fb6ebe005b from a Semtech LoRaMote SX1272 to the following,

  • pressure, 998.1
  • temperature, 26.56
  • batteryLevel, 71.25984251968504
  • latitude, 53.35568825670341
  • longitude, -6.422924995422363

Step 1. The datasheet

The first point of reference is the payload description in the manufactures datasheet for the mote you want to use.

In this example were using the Semtech LoRaMote SX1272 and here is the section on the payload.

 

The key information we need from this datasheet is,

  • Ignore the first byte it just a header
  • Bytes 1 and 2 are the pressure value stored in big-endian. Value should be divided by 10 to get hPa
  • Bytes 3 and 4 are the temperature value stored in big-endian. Value should be divided by 100 to get temperature in decimals.
  • Ignore bytes 5 and 6
  • Byte 7 is the battery, it has a range 1 being the minimum and 254 maximum. Also a value of 0x00 means its powered externally and 0xFF means it cannot be read.
  • Bytes 8,9 and 10 are the latitude, signed 24 bit word where - corresponds to 90° south (the South Pole) and - 1 corresponds to 90° north (the North Pole)
  • Bytes 11,12 and 13 are the longitude, signed 24 bit word where - corresponds to 180° west and - 1 corresponds to 180° east. The Greenwich meridian corresponds to 0


Step 2. The Dsl json schema

We now need to convert the bullet points of information in to the Dsl json schema using json attributes and Dsl Functions.

Dsl json Schema Attributes

Dsl json attributeDescription
sizeThe number of bytes this second will operate on
nameThe RuBAN metric name that will be used to store the value
typeThis is the value type. This can be metric, latitude or longitude
call

Call a dsl function

executeUse instead of call to execute javascript instead of a function.
formatExecute this javascript to convert the raw value to the correct value

validation

This value should only be used if this validation passes

comment

Text to describe the the action of the current block

isPressent

For variable size payload bytes may not be present.


Dsl Functions

The call attribute is used to call a Dsl function. Dsl functions can be managed form the Dsl Functions section under RuBAN Admin page.


Dsl Function
/* 
Read 2 bytes big-endian
*/
function readInt16BE(context) {
    context.value = context.currentBytes.readInt16BE(0);
}

A function takes a parameter which is the context. This has the following properties,

  • payload, the full preprocessed payload
  • currentBytes, this determined by the offset of the byte definition in bytes and the size attribute.


Note: A function does not return a value it stores the resulting value in the context, see last line context.value = latitude.

Once a function has been created and saved it can be used in a dsl decoder. Functions can be used to share common code across all decoders.


The next diagram shows how we associate the payload body with our Dsl json schema using the attributes and Dsl functions we describe above.

 

Finally this is the Dsl decoder.

Dsl decoder
{
	"preprocessing": {
		"call": "payloadToHex"
	},
	"processing": {
		"bytes": [
			{
				"size": 1,
				"comment": "Ignore first byte"
			},
			{
				"size": 2,
				"name": "pressure",
				"type": "metric",
				"call": "readInt16BE",
				"format": "context.value = context.value/10",
				"comment": "Read 1 byte for pressure"
			},
			{
				"size": 2,
				"name": "temperature",
				"type": "metric",
				"call": "readInt16BE",
				"format": "context.value = context.value/100",
				"comment": "Read 1 byte for temperature"
			},
			{
				"size": 2,
				"comment": "Ignore bytes"
			},
			{
				"size": 1,
				"name": "batteryLevel",
				"type": "metric",
				"call": "readUInt8",
				"format": "context.value = (context.value/254) * 100",
				"validation": "context.value != '0xFF' && context.value !== 0",
				"comment": "Read 1 byte for battery level"
			},
			{
				"size": 3,
				"type": "latitude",
				"call": "decode24BitGpsLatitude",
				"comment": "Read a 3 byte gps latitude"
			},
			{
				"size": 3,
				"type": "longitude",
				"call": "decode24BitGpsLongitude",
				"comment": "Read a 3 byte gps longitude"
			}
		]
	}
}

 

Raw Decoder

If a payload is very complex and cannot be expressed with the dsl schema you can use a raw decoder mode.

Raw Function
var payload = new Buffer(context.payload, "hex");
var metrics = [];
metrics.push({name: "pressure", value: payload.readInt16BE(1)/10});
metrics.push({name: "temperature", value: payload.readInt16BE(3)/100});


var batteryStatus =  payload.readUInt8(7);
if(batteryStatus != 0xFF && batteryStatus !== 0) {
    metrics.push({name: "batteryLevel", value: (batteryStatus/254) * 100});
}

var latitude = (payload.readUInt16BE(8) << 8) | payload.readUInt8(10);
if( latitude & 0x800000 ) {
    latitude = -(latitude/8388608) * 90;
} else {
    latitude = (latitude/8388607) * 90;
}

var longitude = (payload.readUInt16BE(11) << 8) | payload.readUInt8(13);
if( longitude & 0x800000 ) {
    longitude = ((longitude/8388608) * 180) - 360;
} else {
    longitude = (longitude/8388607) * 180;
}
context.value = {
    metrics: metrics,
    gps: {
        latitude: latitude,
        longitude: longitude
    }
};

 

This is the raw decoder version of the Dsl decoder above. Note the context is a variable thats available and has the original raw payload.

The result of this code should be stored in the context.value (silimar to Dsl Functions). The structure of the results also should be an object with the following structure.

Raw mode result
{
	metrics: [
		{
				namme: "pressure",
				value: 998.1
		}
	],
	gps: {
		latitude: 53.35568825670341,
        longitude: -6.422924995422363		
	}
}

 

Testing a Decoder

You can test a decoder at any time by specifying a payload in the "Select Payload" tab and clicking on the Run button on the "Confirm Result" tab.

The result and/or errors will be displayed on the page.

 

Appendix.

  1. DSL Functions for sample decoder.

/* 
This is decode24BitGpsLatitude 
*/
function decode24BitGpsLatitude(context) {
    var latitude = (context.currentBytes.readUInt16BE(0) << 8) | context.currentBytes.readUInt8(2);
	if( latitude & 0x800000 ) {
	    latitude = -(latitude/8388608) * 90;
	} else {
	    latitude = (latitude/8388607) * 90;
	}

	context.value = latitude;
}
/* 
This decode24BitGpsLonditude
*/
function decode24BitGpsLongitude(context) {
    var longitude = (context.currentBytes.readUInt16BE(0) << 8) | context.currentBytes.readUInt8(2);
	if( longitude & 0x800000 ) {
	    longitude = ((longitude/8388608) * 180) - 360;
	} else {
	    longitude = (longitude/8388607) * 180;
	}

	context.value = longitude;
}
/* 
Read unsigned byte
*/
function readUInt8(context) {
    context.value = context.currentBytes.readUInt8(0);
}
/* 
Convert the payload to HEX
*/
function payloadToHex(context) {
    context.payload = new Buffer(context.payload, 'hex');
}
/* 
Read 2 bytes big endian
*/
function readInt16BE(context) {
    context.value = context.currentBytes.readInt16BE(0);
}