Skip to content

P1Q LoRaWAN packet format

This document describes uplinks produced by the qp-patrol LoRaWAN firmware variant p1q-nrf52811-s112-lorawan (RN2483/RN2903).

Other P1Q/P1QT/P1QP firmware variants may use different FPorts and/or payload formats.

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 */

System / control packets FPort == 2

Boot packet example (HEX) (4 bytes): 0x61 0A DD ED
FPort 2 is used for confirmed system/control uplinks. The first byte identifies the packet type: * `0x61` (4 bytes total): boot / join indicator. Additional 3 bytes follow, which have no defined value and can be ignored. * `0x01` (1 byte total): uplink connectivity check (confirmed). This packet carries no measurement data.

Warning

Devices shipped before Jan 2025 used unconfirmed packet uplink for the boot packet. After this date confirmed packets are used.

Battery packet FPort == 3

Battery status indicator packet
byte no.example (HEX)description
00araw battery indicator (0-255, implementation-defined). Do not interpret as voltage or percentage.

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