A pure node.js library to decode and encode packets for LoRa/LoRaWANTM radio communication, based on the specification from the LoRa Alliance (based on V1.0.2 final), and as used by The Things Network.
Packet decoding is also wrapped in a simple command-line tool that accepts input in hex and base-64
I'm happy to fix or add functionality, but I can only do this if I have example packets.
(nodejs>=10)
npm install lora-packet
(nodejs<=9)
npm install lora-packet@~0.7.14
$ lora-packet-decode --hex 40F17DBE4900020001954378762B11FF0D
$ lora-packet-decode --base64 QPF9vkkAAgABlUN4disR/w0=
$ lora-packet-decode \
--appkey ec925802ae430ca77fd3dd73cb2cc588 \
--nwkkey 44024241ed4ce9a68c6a8bc055233fd3 \
--hex 40F17DBE4900020001954378762B11FF0D
Parse & create packet structure from wire-format buffer (i.e. "radio PHYPayload")
returns an object containing the decoded packet fields, named as per LoRa spec, e.g. MHDR, MACPayload etc
Note: DevAddr and FCnt are stored big-endian, i.e. the way round that you'd expect to see them, not how they're sent down the wire.
returns the packet MType as a string (e.g. "Unconfirmed Data Up")
returns the direction (Dir) as a string ('up' or 'down')
returns the frame count (FCnt) as a number
returns true if packet is confirmed, else returns false
returns the port (FPort) as a number (or null if FPort is absent)
returns the flag (ACK) of field FCtrl as a boolean
returns the flag (FPending) of field FCtrl as a boolean
returns the flag (ADR) of field FCtrl as a boolean
returns the flag (ADRACKReq) of field FCtrl as a boolean
returns an object containing the encrypted FOpts field. If SNwkSIntKey is provided, the mic is recalculated and modifies the packet.
alias for encryptFOpts, just for sake of clarification
returns a boolean; true if the MIC is correct (i.e. the value at the end of the packet data matches the calculation over the packet contents)
NB AppKey is used for Join Request/Accept, otherwise NwkSkey is used
Optionally, if using 32-byt FCnts, supply the upper 2 bytes as a Buffer.
returns the MIC, as a buffer
NB AppKey is used for Join Request/Accept, otherwise NwkSkey is used
Optionally, if using 32-byt FCnts, supply the upper 2 bytes as a Buffer.
calculates the MIC & updates the packet (no return value)
NB AppKey is used for Join Request/Accept, otherwise NwkSkey is used
Optionally, if using 32-byt FCnts, supply the upper 2 bytes as a Buffer.
decrypts and returns the payload as a buffer: The library cannot know whether this is an ASCII string or binary data, so you will need to interpret it appropriately.
NB the relevant key is chosen depending on the value of FPort, and NB key order is different than MIC APIs
decrypts and returns the Join Accept Message as a buffer:
const packet = lora_packet.fromWire(inputData);
const DecryptedPacket = lora_packet.fromWire(lora_packet.decryptJoinAccept(packet, appKey));
takes an object with properties representing fields in the packet - see example below
The wire-format payload can be obtained by calling getPHYPayload() (or getBuffers().PHYPayload)
in Lorawan 1.1 optional fields after data
are:
For usage Refer to Lorawan 1.1 Spec.
const lora_packet = require("lora-packet");
//-----------------
// packet decoding
// decode a packet
const packet = lora_packet.fromWire(Buffer.from("40F17DBE4900020001954378762B11FF0D", "hex"));
// debug: prints out contents
// - contents depend on packet type
// - contents are named based on LoRa spec
console.log("packet.toString()=\n" + packet);
// e.g. retrieve payload elements
console.log("packet MIC=" + packet.MIC.toString("hex"));
console.log("FRMPayload=" + packet.FRMPayload.toString("hex"));
// check MIC
const NwkSKey = Buffer.from("44024241ed4ce9a68c6a8bc055233fd3", "hex");
console.log("MIC check=" + (lora_packet.verifyMIC(packet, NwkSKey) ? "OK" : "fail"));
// calculate MIC based on contents
console.log("calculated MIC=" + lora_packet.calculateMIC(packet, NwkSKey).toString("hex"));
// decrypt payload
const AppSKey = Buffer.from("ec925802ae430ca77fd3dd73cb2cc588", "hex");
console.log("Decrypted (ASCII)='" + lora_packet.decrypt(packet, AppSKey, NwkSKey).toString() + "'");
console.log("Decrypted (hex)='0x" + lora_packet.decrypt(packet, AppSKey, NwkSKey).toString("hex") + "'");
//-----------------
// packet creation
// create a packet
const constructedPacket = lora_packet.fromFields(
{
MType: "Unconfirmed Data Up", // (default)
DevAddr: Buffer.from("01020304", "hex"), // big-endian
FCtrl: {
ADR: false, // default = false
ACK: true, // default = false
ADRACKReq: false, // default = false
FPending: false, // default = false
},
FCnt: Buffer.from("0003", "hex"), // can supply a buffer or a number
payload: "test",
},
Buffer.from("ec925802ae430ca77fd3dd73cb2cc588", "hex"), // AppSKey
Buffer.from("44024241ed4ce9a68c6a8bc055233fd3", "hex") // NwkSKey
);
console.log("constructedPacket.toString()=\n" + constructedPacket);
const wireFormatPacket = constructedPacket.getPHYPayload();
console.log("wireFormatPacket.toString()=\n" + wireFormatPacket.toString("hex"));
There's a nice online decoder that uses this library.
NB this is created & maintained by a third party & I can't support or answer questions about it.
lora-packet
fails to parse,
or incorrectly decodes / decrypts etc, please let me know!It took me longer than expected to understand the various IDs & key names. Different terminology is used by LoRaWAN / TTN / Multitech, & there's both OTA & manual personalisation options. This is a quick summary which I hope you'll find helpful.
(TODO!)