How to read downlink MAC data?



I’m trying to make sense of MAC data in downlinks from The Things Network.
The reason is that I’m interested to see what MAC commands are arriving.

From LMIC-v3.3.0.pdf paragraph 2.5.18:

If port is zero and nMessage is zero, piggybacked MAC data can be detected and inspected by checking the value of LMIC.dataBeg. If non-zero, there are LMIC.dataBeg bytes of piggybacked data, and the data can be found at LMIC.frame[0] through LMIC.frame[LMIC.dataBeg-1].

From the LoRaWAN 1.0.3 Specification (chapter 5 MAC commands):

A single data frame can contain any sequence of MAC commands, either piggybacked in the FOpts field or, when sent as separate data frame, in the FRMPayload field with the FPort field being set to 0.

A MAC command consists of a command identifier (CID) of 1 octet followed by a possibly empty command-specific sequence of octets.

The CID is a single byte and most common MAC commands. Valid CID’s have a value in the range 0x02 to 0x0A or 0x0D.

My test results:

LMIC version: v3.3.0
Network: The Things Network V3

My code is an enhanced version of the standard ttn-otaa.ino example.

Output was printed in the default onEvent() event handler on EV_TXCOMPLETE.

Sample output:

Port: 0
MAC data length: 13
MAC data: 60 34 13 0B 26 85 21 00 03 51 FF 00 01

I expected the first byte to be a valid CID so I could recognize the MAC commands, but 0x60 is not a valid CID however.


  • How should I read/interprete the MAC data stored in LMIC.frame described in the LMIC docs?

  • According to the LoRaWAN specification there are two ways a data frame can contain ‘any sequence of MAC commands’:

    1. Piggybacked in the FOpts field.
    2. When sent as a separate data frame, in the FRMPayload field with the FPort field being set to 0.

    Which of these does MCCI LMIC store in LMIC.frame? Can it handle both and does it store whichever is present in LMIC.frame?

The printout starts at the first byte of the message. I always refer to this:

So the 60 is the MHDR byte (unconfirmed downlink)

The next 4 bytes are the dev addr.

The next byte (85) is the FCtrl byte and says “5 bytes of ooptions”

21 00 is the frame count.

The options start (therefore) 5 bytes back from the end, and are:

03 51 FF 00 01.

This is a link ADR request.

Best regards,

1 Like

Thanks. Very helpful.

I wasn’t aware that I had to start looking at the PHYPayload level.
That didn’t become clear while reading the LMIC-v3.0.0 document.
I either overlooked or that information is missing.

I read the LoRaWAN-at-a-glance document before. It is a nice overview/summary but what confuses is that it shows the MHDR details at MACPayload level (on page 1 and twice on page 4) while MHDR is part of PHYPayload.

No doubt it’s missing. The technical justification here is that it’s really a hack to minimize code footprint. You can get a pointer to the entire datagram, and you are given a pointer to where the user payload begins. That’s how the info is printed out. If I’m counting correctly, the first mac option byte is at offset 8 (always) from the start of the entire datagram; the client could ignore that (and we could add a zero-cost API to let clients query this).

This is because of the way the spec works; the MIC is calculated differently, based on the situation. Layering is therefore not 100% clean, and that’s hard to represent in this kind of breakdown. Repetition seems to be the best way to handle it, but I’m open to suggestions for improvement. I think it’s important to follow the spec’s language, in any case; otherwise hard to cross reference.

Best regards,

Thanks for this explanation how to decode downlink MAC commands. But how to decode uplink MAC commands?

And is there somewhere a.working code example for decoding up- and downlink MAC commands with MCCI LMIC?

That part is exactly the same. (Though you have to have access to see them; at the moment there’s no hook to let you monitor the uplink MAC commands on the device.) I just now filed a ticket for this, #721.

1 Like