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 |
---|---|---|
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 */
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 |
---|---|---|
0 | 0a | battery 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