Skip to content

P1Q LoRaWAN packet format

For P1QP and P1QT devices from Quorum Precision

Measurement packet FPort == 1 WITHOUT alarm functionality

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)

Measurement packet FPort == 1 WITH alarm functionality

Full packet example (HEX) (22 bytes): 0x03 E5B8B441 0000C741 E5B8A441 0000C841 3EA3D843 02
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)
17-203EA3D843Meta data - please ignore
2102Alarm status - see table below

Alarm byte interpretation

Packet value Alarm state
0x00 no alarm
0x01 measured value too low
0x02 measured value too high

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 PacketParser {
  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("P1AP pressure sensor 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