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 |
|---|---|---|
| 0 | 03 | unit of measure (megapascal - see table below) |
| 1-4 | E5B8B441 | MIN value: float - little endian (~22.590) |
| 5-8 | 0000C741 | MAX value: float - little endian (24.875) |
| 9-12 | E5B8A441 | Average value: float - little endian (~20.590) |
| 13-16 | 0000C841 | Last 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 |
|---|---|---|
| 0 | 03 | unit of measure (megapascal - see table below) |
| 1-4 | E5B8B441 | MIN value: float - little endian (~22.590) |
| 5-8 | 0000C741 | MAX value: float - little endian (24.875) |
| 9-12 | E5B8A441 | Average value: float - little endian (~20.590) |
| 13-16 | 0000C841 | Last value: float - little endian (25) |
| 17-20 | 3EA3D843 | Meta data - please ignore |
| 21 | 02 | Alarm 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 |
|---|---|---|
| 0 | 0a | raw 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