Skip to main content

Payload Format of nomad XL



Payload Types

PortTypeDescription
100WelcomeDevice type, firmware version, hardware ID. Sent once after reboot
101StatusBattery level, buffer status information and sensor data
103LocationGPS location information and time
150-200ProprietaryRefer to application-specific documentation
212GitGit revision. Sent once after reboot.
220ConfigurationDevice configuration using AT command downlinks

Number Encoding

Signed integers use two’s complement for encoding.

important

Unless otherwise noted, payloads will use big endian data encoding.

Payload Description

Welcome Message

Contains information about the device and firmware. The welcome message is sent only once after reboot.

ByteSizeDescriptionFormat
01Device type (Tracker=1)enum
11Device sub-type (nomad XS=1, nomad XL=3)enum
2-54Firmware version hashuint32
61Reset source (1=WU, 2=PIN, 3=LPW, 4=SW, 5=POR, 6=IWDG, 7=WWDG)enum
7-148Hardware IDuint64_t

Status Message

Contains general status information and environmental sensor data.

ByteSizeDescriptionFormat
0-78System time (ms since reset)uint64_t, ms
8-114UTC Dateuint32, DDMMYY
12-154UTC Timeuint32, HHMMSS
16-172Buffer level (STA)uint16
18-192Buffer level (GPS)uint16
20-212Buffer level (ACC)uint16
22-232Buffer level (LOG)uint16
24-252Temperatureint16, 0.1 °C
26-272Pressureuint16, 0.1 hPa
28-292Orientation Xint16, mG
30-312Orientation Yint16, mG
32-332Orientation Zint16, mG
34-352Battery voltageuint16, mV
361LoRaWAN battery level (1 to 254)uint8
371Last TTF (time to fix)uint8, s
38-392NMEA sentences checksum OKuint16
40-412NMEA sentences checksum failuint16
42-432Total GPS signal to noise (0-99 for each satellite)uint16, C/n0 [dBHz]
441GPS satellite count Navstaruint8
451GPS satellite count Glonassuint8
461GPS satellite count Galileouint8
471GPS satellite count Beidouuint8
48-492GPS dilution of precisionuint16, cm

Location Message

Contains GPS time and location information. If the payload is all zeros, the nomad XL could not acquire a GPS fix.

ByteSizeDescriptionFormat
0-34UTC Dateuint32, DDMMYY
4-74UTC Timeuint32, HHMMSS
8-114Latitudeint32, 1/100'000 deg
12-154Longitudeint32, 1/100'000 deg
16-194Altitudeint32, 1/100 m

Git Revision

Contains the Git revision of the firmware build. The Git message is sent only once after reboot.

ByteSizeDescriptionFormat
0-1920Git Revisionbinary/hex


nomad XL is configured with LoRaWAN® downlinks which are transmitted to port 220. The payload of a configuration downlink corresponds to a so-called AT command. After one or more configuration downlinks are received, a reset command needs to be transmitted to the nomad XL such that the configuration is stored in non-volatile memory and a reset is triggered. After this reset, the nomad XL uses the new configuration.

Configuration commands and responses

Configuration downlink commands and responses are sent as plain text. Note that commands need to be zero-terminated.

ByteSizeDescriptionFormat
0-(n-1)nAT commandASCII
n1zero-termination (0x00)char
ByteSizeDescriptionFormat
0-(n-1)nReply to previous AT commandASCII
n1zero-termination (0x00)char

Overview

The following configuration downlands are available:

CommandDescriptionDefault valueMin valueMax valueUnit
ATZReset the MCU----
AT+GPSHOLD=Moving interval
(GNSS hold time)
120'000
(2 min)
60'000
(1 min)
4'294'967'295
(49.7 days)
ms
AT+GPSCYC=Steady interval
(GNSS cycle time )
21'600'000
(6 hours)
60'000
(1 min)
4'294'967'295
(49.7 days)
ms
AT+STACYC=Status message interval21'600'000
(6 hours)
600'000
(10 min)
4'294'967'295
(49.7 days)
ms

ATZ

Resets the MCU. If the configuration has been changed, the new configuration is stored in non-volatile memory and applied after the reset.

AT+GPSHOLD

Sets the GNSS hold time, also known as a moving interval. The GNSS hold time inhibits further GNSS fix acquisition for a certain time period. This setting is used when GNSS fixes are triggered by the accelerometer. A first accelerometer event will trigger GNSS fix acquisition immediately. Further accelerometer events will be ignored until after the hold time interval. However, a flag will be set in this case, so that the next GNSS fix acquisition will immediately start right after the hold time interval.

example

Setting the moving interval to 5 minutes (300 seconds)

AT+GPSHOLD=300000

AT+GPSCYC

Sets the regular GNSS fix cycle time, also known as a steady interval. The GNSS cycle time is used when GNSS fixes are triggered by the timer. The GNSS cycle time is the time between two consecutive GNSS fix acquisitions. The GNSS cycle time is running independently of accelerometer events.

example

Setting the steady interval to 1 hour (3600 seconds)

AT+GPSCYC=3600000

AT+STACYC

Set the regular status interval. Status messages are enabled by default, all sensors are read out when the regular status interval expires.

example

Setting the status interval to 12 hours (43200 seconds)

AT+GPSCYC=3600000

For every downlink packet, an uplink packet is scheduled containing the corresponding AT response code. The following response codes are available:

Response codeHexadecimalDescription
AT_OK41545F4F4BCommand executed successfully
AT_PARAM_ERROR41545F504152414D5F4552524F52Command was outside of the valid range
AT_ERROR41545F4552524F52Command execution failed

The payload of an AT command downlink corresponds to an ASCII encoded AT command with zero-termination. Zero-termination means that 00 needs to be added at the very end of the hexadecimal representation of the AT command. The last line of the following examples corresponds to the payload of the respective downlink.

example

Changing the moving interval to 3 minutes (180 seconds) send the following command

RepresentationData
ASCII stringAT+GPSHOLD=180000
Hex41542B475053484F4C443D313830303030
Hex with zero termination:41542B475053484F4C443D31383030303000
example

Resetting the device:

RepresentationData
ASCII stringATZ
Hex41545A
Hex with zero termination:41545A00
important

After changing a configuration parameter via downlink, the nomad XL needs to be reset such that the configuration is loaded. This is done by sending the downlink command ATZ on port 220.

For every downlink packet, an uplink packet is scheduled containing the corresponding AT response code (usually AT_OK or 41545F4F4B in hexadecimal representation). If both the configuration change and the reset are queued simultaneously on the LoRaWAN® network server, the configuration is applied the fastest way possible. The reason for this being that the configuration change triggers an uplink message (usually AT_OK) which opens another downlink slot for the reset command to be received immediately.

JavaScript Decoder

For an easy start using nomad XL on TTN or TTI you can make use of the following JavaScript decoder template.

function decodeUplink(input) {

var bytes = input.bytes;
var port = input.fPort;
var decoded = {};
var warnings = [];
var errors = [];

if (port === 100) {
// WELCOME MESSAGE
// Returns device type and firmware version

switch (bytes[1]) {
case 1:
decoded.deviceType = 'nomad XS';
break;
case 3:
decoded.deviceType = 'nomad XL';
break;
default:
decoded.deviceType = 'unknown';
break;
}

decoded.firmware = 'v' + bytes[2].toString() + '.' + bytes[3].toString() + '.' + (bytes[4] * 256 + bytes[5]).toString();
}

if (port === 212) {
// GIT REVISION
// Returns base64-encoded HEX string of firmware GIT revision

decoded.git_revision = toHexString(bytes);
}

if (port === 220) {
// AT COMMAND RESPONSE
// Returns base64-encoded ASCII string of AT command response

decoded.at_reply = bin2String(bytes);
}

if (port === 101) {
// STATUS MESSAGE
// Returns status information
// Note: Not all fields are taken into account here

var system_time_ms = (bytes[0] << 56 | bytes[1] << 48 | bytes[2] << 40 | bytes[3] << 32 | bytes[4] << 24 | bytes[5] << 16 | bytes[6] << 8 | bytes[7]) >>> 0;
var temperature = (bytes[24] << 8 | bytes[25]) >>> 0;
var pressure = (bytes[26] << 8 | bytes[27]) >>> 0;
var x = (bytes[28] << 8 | bytes[29]) >>> 0;
var y = (bytes[30] << 8 | bytes[31]) >>> 0;
var z = (bytes[32] << 8 | bytes[33]) >>> 0;
var battery_mv = (bytes[34] << 8 | bytes[35]) >>> 0;

var dat = (bytes[8] << 24 | bytes[9] << 16 | bytes[10] << 8 | bytes[11]) >>> 0;
var tim = (bytes[12] << 24 | bytes[13] << 16 | bytes[14] << 8 | bytes[15]) >>> 0;

// Conversion to signed integer (2's complement)
if (temperature > 0x7FFF) {
temperature = -(0xFFFF - temperature + 1);
}
if (x > 0x7FFF) {
x = -(0xFFFF - x + 1);
}
if (y > 0x7FFF) {
y = -(0xFFFF - y + 1);
}
if (z > 0x7FFF) {
z = -(0xFFFF - z + 1);
}

decoded.battery_lorawan = bytes[36];
decoded.gps_ttf_s = bytes[37];
decoded.gps_signal = bytes[42];

decoded.battery_v = battery_mv / 1000.0;
decoded.system_time_s = system_time_ms / 1000.0;
decoded.temperature_deg = temperature / 10.0;
decoded.pressure_hpa = pressure / 1.0;
decoded.orientation_x_g = x / 1000.0;
decoded.orientation_y_g = y / 1000.0;
decoded.orientation_z_g = z / 1000.0;

decoded.date_yymmdd = dat;
decoded.time_hhmmss = tim;
}

if (port === 103) {
// LOCATION MESSAGE
// Returns date and time as DDMMYY and HHMMSS
// Returns latitude, longitude and altitude as float

var dat = (bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3]) >>> 0;
var tim = (bytes[4] << 24 | bytes[5] << 16 | bytes[6] << 8 | bytes[7]) >>> 0;
var lat = (bytes[8] << 24 | bytes[9] << 16 | bytes[10] << 8 | bytes[11]) >>> 0;
var lon = (bytes[12] << 24 | bytes[13] << 16 | bytes[14] << 8 | bytes[15]) >>> 0;
var alt = (bytes[16] << 24 | bytes[17] << 16 | bytes[18] << 8 | bytes[19]) >>> 0;

// Conversion to signed integer (2's complement)
if (lat > 0x7FFFFFFF) {
lat = -(0xFFFFFFFF - lat + 1);
}
if (lon > 0x7FFFFFFF) {
lon = -(0xFFFFFFFF - lon + 1);
}
if (alt > 0x7FFFFFFF) {
alt = -(0xFFFFFFFF - alt + 1);
}

if ((lat != 0) && (lon != 0)) {
decoded.gps_dat = dat;
decoded.gps_tim = tim;
decoded.gps_lat = (lat / 100000.0);
decoded.gps_lon = (lon / 100000.0);
decoded.gps_alt = (alt / 100.0);
decoded.unixtime_ms = getTimestamp(dat, tim);
} else {
warnings.push('No GPS fix, empty location message, ');
}
}

function toHexString(byteArray) {
return Array.from(byteArray, function(byte) {
return ('0' + (byte & 0xFF).toString(16)).slice(-2);
}).join('')
}

function bin2String(array) {
return String.fromCharCode.apply(String, array);
}

function getTimestamp(ddmmyy, hhmmss) {
day = parseInt(ddmmyy / 10000, 10);
month = parseInt(ddmmyy / 100 - day * 100, 10);
year = parseInt(ddmmyy - month * 100 - day * 10000 + 2000, 10);
hour = parseInt(hhmmss / 10000, 10);
minute = parseInt(hhmmss / 100 - hour * 100, 10);
second = parseInt(hhmmss - minute * 100 - hour * 10000, 10);

var date = new Date(year, month - 1, day, hour, minute, second);
return date.valueOf();
}

return {
data: decoded,
warnings: warnings,
errors: errors
};
}