Skip to content

P1A LoRaWAN packet format

For P1AP and P1AT devices from Moire Labs

Measurement packet FPort == 1

Full packet example (HEX) (17 bytes): 0x03 E5B8B441 0000C741 E5B8A441 0000C841
byte no.example (HEX)description
003unit of measure (megapascal - see table below)
1-4E5B8B441MIN value: float - little endian (~22.590)
5-80000C741MAX value: float - little endian (24.875)
9-12E5B8A441Average value: float - little endian (~20.590)
13-160000C841Last value: float - little endian (25)

Units of measurement

#define UNIT_TYPE_C   0     /**< C = celsius */
#define UNIT_TYPE_PA  1     /**< Pa = pascal */
#define UNIT_TYPE_KPA 2     /**< kPa = kilopascal */
#define UNIT_TYPE_MPA 3     /**< MPa = megapascal */

Boot packet FPort == 2

Boot packet example (HEX) (4 bytes): 0x61 0A DD ED
Boot packet starts with byte of value 0x61. Additional 3 bytes follow, which have no defined value (can be anything and can be safely ignored). This packet is only send once every time the device connects and registers to LoRaWAN network so it can be used as an indicator of boot of the device.

Battery packet FPort == 3

Battery status indicator packet
byte no.example (HEX)description
00abattery level (0-255 non-linear)

Example parsers

Python3

#!/usr/bin/env python3

import struct
import argparse
from pprint import pprint


def parse(packet, fport):
    out = {"packet": "".join("{:02X}".format(x) for x in packet)}

    out["fport"] = fport
    if fport == 1:
        out["info"] = "This is a standard measurement packet."
    elif fport == 2:
        out["info"] = "This is a boot / test packet."
        return out
    elif fport == 3:
        out["info"] = "This is a BATTERY STATUS packet."
        out["battery_level_adc"] = int(packet[0])
        return out
    elif fport == 0x43:
        out[
            "info"
        ] = "This is a START packet. This packet is only sent once after device boots!"
        return out
    else:
        out["info"] = "Packet FPort unknown type -> 0x{:02x}! Aborting parsing.".format(
            fport
        )
        return out

    out["unit of measure"] = packet[0]

    out["MIN (float)"] = struct.unpack("<f", packet[1:5])[0]
    out["MAX (float)"] = struct.unpack("<f", packet[5:9])[0]
    out["AVG (float)"] = struct.unpack("<f", packet[9:13])[0]
    out["LAST (float)"] = struct.unpack("<f", packet[13:17])[0]

    return out


if __name__ == "__main__":

    parser = argparse.ArgumentParser(description="LoRaWAN packet parser")
    parser.add_argument(
        "--pl",
        default="03E5B8B4410000C741E5B8A4410000C841",
        action="store",
        help="Payload in hex",
    )
    parser.add_argument(
        "--fport",
        default=1,
        action="store",
        type=int,
        help="LoRaWAN FPort",
    )
    args = parser.parse_args()

    packet = bytes.fromhex(args.pl)

    print("Packet in HEX: 0x", end="")
    for p in packet:
        print("{:02X} ".format(p), end="")
    print()
    print("Packet in DEC: ", end="")
    for p in packet:
        print("{} ".format(int(p)), end="")
    print()

    data = parse(packet, args.fport)
    pprint(data)

Java

class MoireLabsParser {
  public static float reverse_endianness(float x) {
    return java.nio.ByteBuffer.allocate(8)
      .order(java.nio.ByteOrder.BIG_ENDIAN).putFloat(x)
      .order(java.nio.ByteOrder.LITTLE_ENDIAN).getFloat(0);
  }

  public static void main(String[] args) {
    System.out.println("moirelabs.com P1AP LoRaWAN packet parser");

    String packet = "03E5B8B4410000C741E5B8A4410000C841";

    final int unit_of_measure = Integer.parseInt(packet.substring(0, 2), 16);

    Long i = Long.parseLong(packet.substring(2, 10), 16);
    final Float min_value = reverse_endianness(Float.intBitsToFloat(i.intValue()));

    i = Long.parseLong(packet.substring(10, 18), 16);
    final Float max_value = reverse_endianness(Float.intBitsToFloat(i.intValue()));

    i = Long.parseLong(packet.substring(18, 26), 16);
    final Float average_value = reverse_endianness(Float.intBitsToFloat(i.intValue()));

    i = Long.parseLong(packet.substring(26, 34), 16);
    final Float last_value = reverse_endianness(Float.intBitsToFloat(i.intValue()));

    System.out.println("Example packet in HEX: " + packet);

    System.out.print("Unit of measure index: " + unit_of_measure);

    if (unit_of_measure == 3) {
      System.out.println(" MPa");
    } else {
      System.out.println("");
    }

    System.out.println("MIN value: " + min_value);
    System.out.println("MAX value: " + max_value);
    System.out.println("AVERAGE value: " + average_value);
    System.out.println("LAST value: " + last_value);
  }
}

ChirpStack / JavaScript

var units = ['C', 'Pa', 'kPa', 'MPa'];

function bytesToFloat(bytes) {
    "use strict";
    var bits = bytes[3]<<24 | bytes[2]<<16 | bytes[1]<<8 | bytes[0];
    var sign = (bits>>>31 === 0) ? 1.0 : -1.0;
    var e = bits>>>23 & 0xff;
    var m = (e === 0) ? (bits & 0x7fffff)<<1 : (bits & 0x7fffff) | 0x800000;
    var f = sign * m * Math.pow(2, e - 150);
    return f;
}

function Decode(fPort, bytes, variables) {
    "use strict";
    switch (fPort) {
        case 1:
            return {
                    unit: units[bytes[0]],
                    min_value: bytesToFloat(bytes.slice(1, 5)),
                    max_value: bytesToFloat(bytes.slice(5, 9)),
                    average_value: bytesToFloat(bytes.slice(9, 13)),
                    last_value: bytesToFloat(bytes.slice(13, 17))
            };
        default:
            return {
                errors: ['Unknown FPort - see device manual!'],
            };
    }
}

TTN / JavaScript

var units = ['C', 'Pa', 'kPa', 'MPa'];

function bytesToFloat(bytes) {
    "use strict";
    var bits = bytes[3]<<24 | bytes[2]<<16 | bytes[1]<<8 | bytes[0];
    var sign = (bits>>>31 === 0) ? 1.0 : -1.0;
    var e = bits>>>23 & 0xff;
    var m = (e === 0) ? (bits & 0x7fffff)<<1 : (bits & 0x7fffff) | 0x800000;
    var f = sign * m * Math.pow(2, e - 150);
    return f;
}

function decodeUplink(input) {
    "use strict";
    switch (input.fPort) {
        case 1:
            return {
                data: {
                    unit: units[input.bytes[0]],
                    min_value: +bytesToFloat(input.bytes.slice(1, 5)).toFixed(4),
                    max_value: +bytesToFloat(input.bytes.slice(5, 9)).toFixed(4),
                    average_value: +bytesToFloat(input.bytes.slice(9, 13)).toFixed(4),
                    last_value: +bytesToFloat(input.bytes.slice(13, 17)).toFixed(4)
                }
            };
        default:
            return {
                errors: ['Unknown FPort - see device manual!'],
            };
      }
}

Support

For support please contact your distributor or manufacturer directly via www.moirelabs.com