Interpreting payload data from the SodaqOne-UniversalTracker

I received my Sodaq One today with the UniversalTracker firmware, and had already a lot of fun playing with it!

I created this code in NodeRed to parse the payload:

var sodaqone_data = new Buffer(msg.payload, 'hex') var epoch_timestamp = sodaqone_data.readUInt32LE(0); var datetime = new Date(epoch_timestamp*1000); var battery_voltage = sodaqone_data.readUInt8(4); var board_temperature = sodaqone_data.readInt8(5); var lat = sodaqone_data.readUInt32LE(6)/10000000; var lon = sodaqone_data.readUInt32LE(10)/10000000; var altitude = sodaqone_data.readUInt16LE(14); var speed = sodaqone_data.readUInt16LE(16); var course = sodaqone_data.readUInt8(18); var number_of_satellites = sodaqone_data.readUInt8(19); var time_to_fix = sodaqone_data.readUInt8(20); var previous_fix = sodaqone_data.readUInt8(21); var previous_lat = sodaqone_data.readUInt32LE(22)/10000000; var previous_lon = sodaqone_data.readUInt32LE(26)/10000000;

Everything looks ok, but the parsed battery_voltage and board_temperature value is unclear to me.

“battery_voltage”: 118
“board_temperature”: -32,

How to interpret these? Battery Voltage is specified as “between 3 and 4.5 V” and Board Temperature is specified as “degrees celcius”).

I’m sure it isn’t -32 degrees celcius here in Hengelo :slight_smile: Is my calculation wrong? or is the minus a plus? And what does a value of 118 mean for voltage?

I’ll answer one of my questions if someone has the same question.
Judging from the source code, my voltage would be:

(3000+10*118)/1000 = 4.18 V

The temperature reading is still a mystery to me. Maybe the temperature sensor is not calibrated and it just shows a relative measurement?

1 Like

Ignore, my previous post. After testing it I found I was incorrect.

The default settings for the magnetometer is 6.25Hz, the same value (set CTRL5) is used for the temperature reading.

Can you try increasing the delay on line 857 of SodaqOneTracker.ino.

Try setting it to 330ms or something (enough time for 2+ reads):


A better fix might be to increase the data rate try this for line 856:

lsm303.writeReg(LSM303::CTRL5, lsm303.readReg(LSM303::CTRL5) | 0b100 010 00);

which should set the output rate to 25Hz.

Just a note, the latitude and longtitude values should be read as signed integers.

In the wonderful The Things Network dashboard, there are a couple of functions to manipulate the payload. They are referred as Payload Functions in the dasboard UI.

These payload functions consists of a decoder, *convertor *and validator. I tried to use the above mentioned Node-RED code snippet for payload interpretation, “rewrote” it and put it in the *decoder *code. Without success. That’s a direct result of lack of my programming skills :frowning:

Is there someone out there willing to share their code for Tracker payload interpreting, using thisTTN function, or through a Node-RED function? The above mentioned code snippet isn’t working right away.

Thanks a lot Sodaq ONE and TTN adepts:smile:

I’m currently using this:

function (bytes) {
  var epoch = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
  var batvolt = bytes[4];
  var boardtemp = bytes[5];
  var lat = (bytes[9] << 24) | (bytes[8] << 16) | (bytes[7] << 8) | bytes[6];
  var long = (bytes[13] << 24) | (bytes[12] << 16) | (bytes[11] << 8) | bytes[10];
  var alt = (bytes[15] << 8) | bytes[14];
  var speed = (bytes[17] << 8) | bytes[16];
  var course = bytes[18];
  var numsat = bytes[19];
  var fix = bytes[20];
  return {
    epoch: epoch,
    batvolt: batvolt,
    boardtemp: boardtemp,
    lat: lat,
    long: long,
    alt: alt,
    speed: speed,
    course: course,
    numsat: numsat,
    fix: fix,

@Kees_Verstoep Thanks a lot for sharing. I couldn’t wait to give it a try. Works like a charm. At least for the information I was looking for right now. Your code subtle differs from the one I tried to produce. It makes clear once again for me, that I’m not destined for software programming :frowning: Luckily I’m capable of doing some other things like finding people willing to share their code. Thanks a lot again. Later on I’ll share the thing I’m trying to build upon the Sodaq ONE and the tracking software combined with some IBM Bluemix and Pubnub functions.

A version of the above, this is how I’m making a “human readable” version of the Sodaq One tracker code, when displayed in a LORIT web interface.

var bytes = v

var epoch = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0]
var batvolt = bytes[4];
var boardtemp = bytes[5];
var lat = (bytes[9] << 24) | (bytes[8] << 16) | (bytes[7] << 8) | bytes[6];
var long = (bytes[13] << 24) | (bytes[12] << 16) | (bytes[11] << 8) | bytes[10];
var alt = (bytes[15] << 8) | bytes[14];
var speed = (bytes[17] << 8) | bytes[16];
var course = bytes[18];
var numsat = bytes[19];
var fix = bytes[20];

"Timestamp:" + new Date(epoch*1000).toTimeString() + ", " + 
"BatteryVoltage:" + (3000+10*batvolt)/1000  + "v, " + 
"BoardTemperature: " + boardtemp + ", " +
"Lat: " + lat + ", " +
"Long: " + long + ", " + 
"Altitude: " + alt + ", " + 
"Speed: " + speed + ", " + 
"Course: " + course + ", " + 
"SatelliteCount: " + numsat + ", " + 
"fix: " + fix + ", "`

Add this JS via the Decode Data pannel

For completeness sake, here is my Python version of the code to parse a Sodaq One Universal Tracker packet. The input is the base64 encoded string as received from TTN via MQTT.

You can run it as: python PAYLOAD_DATA

import binascii
import struct
import datetime
import sys

def format():
  return "sodaq_tracker_2"

def parse(payload):
  data = binascii.a2b_base64(payload)

    return False

  data = data[-21:]
  parsed = struct.unpack("<IBBiiHHBBB", data)
  values = {}
  values["time"] = datetime.datetime.fromtimestamp(parsed[0])
  values["volt"] = parsed[1]
  values["temp"] = parsed[2]
  values["lat"] = parsed[3] / 10000000.0
  values["lon"] = parsed[4] / 10000000.0
  if parsed[5]==0xFFFF:
    values["alt"] = 0
    values["alt"] = parsed[5]
  values["speed"] = parsed[6]
  values["course"] = parsed[7]
  values["sats"] = parsed[8]
  values["ttf"] = parsed[9]

  return values

if __name__ == "__main__":
  arv = sys.argv[1:]
    print parse(arv[0])
    print (format()+" parser")

This is my decoder payload function in the TTN application dashboard:

function (bytes) {
// Decoder
// Here can decode the payload into json.
// bytes is of type Buffer.

// todo: return an object
var epoch = (bytes[3] << 24) | (bytes[2] << 16) | (bytes[1] << 8) | bytes[0];
var batt = (3000+10*bytes[4])/1000;
var temp = bytes[5];
var lat = (bytes[9] << 24) | (bytes[8] << 16) | (bytes[7] << 8) | bytes[6];
var lon = (bytes[13] << 24) | (bytes[12] << 16) | (bytes[11] << 8) | bytes[10];
var alt = (bytes[15] << 8) | bytes[14];
var speed = (bytes[17] << 8) | bytes[16];
var course = bytes[18];
var sats = bytes[19];
var ttf = bytes[20];
return {
course: course,
satellites: sats,
time_to_fix: ttf,
latitude: lat,
longitude: lon,
epoch: epoch,
battery: batt,
speed: speed,
temperature: temp

I use a M2MQTT client application that reads the json data…

Do you know how to interpret the course information, since it is only one byte: how is it encoded…?

I believe there might be a bug with this value. The Ublox reports the heading (labelled course in the above record) as a 32bit signed integer. The value is in degrees scaled by a factor of 100,000 (the data sheet says a scaling factor of “1e-5”).

As far as I can see, that heading value is simply copied into an 8bit field in the record which is sent. I don’t see it being scaled to fit within an 8bit range.

my raspberry PI + IC88A and SodaqOne node is up and running UniversalTracker hurray!
Data is shown on

The goal ist to push UniversalTracker data to a mySQL Datapase.
I followed your examples to decode the data via TTN Payload Functions.
… the data returned makes no sense.

So i set up mqtt on an other Raspberry to give the python script a try.

Data comimg in:

pi@raspberrypi:~ $ mosquitto_sub -h -t ‘+/devices/+/up’ -u ********** -P ‘***********’ -v
70B3D57ED0000E8F/devices/00000000FBD832CF/up {“payload”:“A4oi5dXR+QNCPLeu7E3I6mLfcdEF”,“port”:1,“counter”:370,“dev_eui”:“00000000FBD832CF”,“metadata”:[{“frequency”:868.3,“datarate”:“SF10BW125”,“codingrate”:“4/5”,“gateway_timestamp”:1513154932,“channel”:1,“server_time”:“2016-10-27T13:50:30.801812194Z”,“rssi”:-75,“lsnr”:9.5,“rfchain”:1,“crc”:1,“modulation”:“LORA”,“gateway_eui”:“B827EBFFFE4F0264”,“altitude”:764,“longitude”:11.44142,“latitude”:47.6014}]}
70B3D57ED0000E8F/devices/00000000FBD832CF/up {“payload”:“eph9UXdDy/nfeCyr+2ykIM8MGEsu”,“port”:1,“counter”:371,“dev_eui”:“00000000FBD832CF”,“metadata”:[{“frequency”:868.5,“datarate”:“SF10BW125”,“codingrate”:“4/5”,“gateway_timestamp”:1515897412,“channel”:2,“server_time”:“2016-10-27T13:50:33.586681466Z”,“rssi”:-76,“lsnr”:7.8,“rfchain”:1,“crc”:1,“modulation”:“LORA”,“gateway_eui”:“B827EBFFFE4F0264”,“altitude”:764,“longitude”:11.44142,“latitude”:47.6014}]}

pi@raspberrypi:~ $ payload=‘LYNdn/wjVo54W5/4+EIMyn3USIrX’
pi@raspberrypi:~ $ python ./ $payload
Traceback (most recent call last):
File “./”, line 38, in
print parse(arv[0])
File “./”, line 19, in parse
values[“time”] = datetime.datetime.fromtimestamp(parsed[0])
ValueError: timestamp out of range for platform time_t
pi@raspberrypi:~ $

I am stuck!
Please help!
My python knowledge is too small to debug here


My fault.
I have everything set up from scratch and it works. :slight_smile:
Unfortunately, I do not know.
Best wishes

I am at a loss interpreting the default data I am receiving. I can’t seem to make heads or tails of the payload. Can anyone help me interpret this data? Here’s two packets I received in Base64 on WirelessThings, and the Hex payload I have made of it (maybe I did that wrong?):

Base64, Hex
iOFxOOYSM9CSguD+A3ZMq+TlTUAH, 88E17138E61233D09282E0FE03764CABE4E54D4007

KIrqmMdhrWNMBhEty++6epysgdw3, 288AEA98C761AD634C06112DCBEFBA7A9CAC81DC37


Hello Siem,
i had a similar problem on
I think my problem was that i overwrote the DevAddr by an DevAddr of an other Device that
i broke before.

If you did overwrite the original DevAddr and do not remember the original one,
please rest to factory default by uploading new sketch with Arduino IDE.

->get SodaqOne-UniversalTracker

->compile and upload in Arduino IDE
->Start serial Monitor and press enter
There you will find the default DevAddr
Do not alter this!
I think, this was the trick do get my SodaqOne working.

All the code examples above worked for me.
The python on is the one i am using.
Thank you @jpmeijers

@mateng, thanks for helping out.
Unfortunately, still no luck. I did overwrite the initial DevEUI with a DEVadr to perform a manual ABP activation with WirelessThings. That network gives me the possibility to forward messages to TTN. All worked and messages were coming in on both platforms. However they were these unreadable HEX strings I posted.

Then I followed your steps. I uploaded the UniversalTracker which did reset my DevEUI. I put that adress in TTN. My only option now is OTAA activation. I have been driving around for a couple of days now and nothing is picked up. So either ABP activation and gateways start picking up my signal but I get unreadable messages or OTAA and device/network won’t activate.

I am still very stuck.
Any ideas?


Hello Siem,
i get the same results as you do using your hex in my working decoder.
Your decoder works fine !
But the hex is bad.
Using jpmeijers pyton script your Data decodes to
./ iOFxOOYSM9CSguD+A3ZMq+TlTUAH {'sats': 64, 'temp': 18, 'course': 77, 'lon': 197.9973344, 'volt': 230, 'time': '2000-01-04 13:03:20', 'lat': -210.4307661, 'alt': 43852, 'speed': 58852, 'ttf': 7}
My config of the ONE looks like that
GPS (OFF=0 / ON=1) (gps=): 1
Fix Interval (min) (fi=): 10
Alt. Fix Interval (min) (afi=): 0
Alt. Fix From (HH) (affh=): 0
Alt. Fix From (MM) (affm=): 0
Alt. Fix To (HH) (afth=): 0
Alt. Fix To (MM) (aftm=): 0
GPS Fix Timeout (sec) (gft=): 120
Minimum sat count (sat=): 4
OTAA Mode (OFF=0 / ON=1) (otaa=): 1
Retry conn. (OFF=0 / ON=1) (retry=): 0
ADR (OFF=0 / ON=1) (adr=): 1
ACK (OFF=0 / ON=1) (ack=): 0
Spreading Factor (sf=): 10
Output Index (pwr=): 1
DevAddr / DevEUI (dev=): 0004A************
AppSKey / AppEUI (app=): 70B3D5**************
NWSKey / AppKey (key=): 35E3AA9B1D8640F1EE*************
Num Coords to Upload (num=): 1
Repeat Count (rep=): 0
Status LED (OFF=0 / ON=1) (led=): 0
Debug (OFF=0 / ON=1) (dbg=): 0
Initializing LoRa…
LoRa init failed!
** Boot-up completed successfully!
The USB is going to be disabled now.


At TTN i registered a new OTTA Device.

  • entered the Device EUI i copied from Arduino serial Monitor
    -after that the userinterface of TTN told me the
    AppSKey / AppEUI (app=):
    NWSKey / AppKey (key=):
    -Restarting the One I was able to add this information to the ONE in the serial monitor

The above functions on converting payload to human readable language work quite nicely on the TTN dashboard. Thanks a lot @BelgoTrack @Kees_Verstoep
However, I’m still having difficult converting the epoch to timestamp on the TTN payload functions.
Has anyone managed to do it?