Skip to content

Payload Format

Overview

Payload Types

Port Type Description
100 Welcome Device type, firmware version, hardware ID. Sent once after reboot
101 Status Battery level, buffer status information and sensor data
103 Location GPS location information and time
150-200 Proprietary Refer to application-specific documentation
212 Git Git revision. Sent once after reboot.
220 Configuration Device 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.

Byte Size Description Format
0 1 Device type (Tracker=1) enum
1 1 Device sub-type (miro Nomad=1, miro Cargo=3) enum
2-5 4 Firmware version hash uint32
6 1 Reset source (1=WU, 2=PIN, 3=LPW, 4=SW, 5=POR, 6=IWDG, 7=WWDG) enum
7-14 8 Hardware ID uint64_t

Status Message

Contains general status information and environmental sensor data.

Byte Size Description Format
0-7 8 System time (ms since reset) uint64_t, ms
8-11 4 UTC Date uint32, DDMMYY
12-15 4 UTC Time uint32, HHMMSS
16-17 2 Buffer level (STA) uint16
18-19 2 Buffer level (GPS) uint16
20-21 2 Buffer level (ACC) uint16
22-23 2 Buffer level (LOG) uint16
24-25 2 Temperature int16, 0.1 °C
26-27 2 Pressure uint16, 0.1 hPa
28-29 2 Orientation X int16, mG
30-31 2 Orientation Y int16, mG
32-33 2 Orientation Z int16, mG
34-35 2 Battery voltage uint16, mV
36 1 LoRaWAN battery level (1 to 254) uint8
37 1 Last TTF (time to fix) uint8, s
38-39 2 NMEA sentences checksum OK uint16
40-41 2 NMEA sentences checksum fail uint16
42-43 2 Total GPS signal to noise (0-99 for each satellite) uint16, C/n0 [dBHz]
44 1 GPS satellite count Navstar uint8
45 1 GPS satellite count Glonass uint8
46 1 GPS satellite count Galileo uint8
47 1 GPS satellite count Beidou uint8
48-49 2 GPS dilution of precision uint16, cm

Location Message

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

Byte Size Description Format
0-3 4 UTC Date uint32, DDMMYY
4-7 4 UTC Time uint32, HHMMSS
8-11 4 Latitude int32, 1/100'000 deg
12-15 4 Longitude int32, 1/100'000 deg
16-19 4 Altitude int32, 1/100 m

Git Revision

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

Byte Size Description Format
0-19 20 Git Revision binary/hex

Configuration commands and responses

Configuration downlink commands and responses are sent as plain text. Note that commands need to be zero-terminated. Refer to settings documentation for more information.

Byte Size Description Format
0-(n-1) n AT command ASCII
n 1 zero-termination (0x00) char
Byte Size Description Format
0-(n-1) n Reply to previous AT command ASCII
n 1 zero-termination (0x00) char

JavaScript Decoder

For an easy start using miro Cargo 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 = 'miro Nomad';
          break;
      case 3:
          decoded.deviceType = 'miro Cargo';
          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
  };
}