LMIC_setLinkCheckMode() questions


From LMIC-V3.3.0.pdf:

2.5.9 void LMIC_setLinkCheckMode (bit_t enabled)

Enable/disable link check validation.Link check mode is enabled by default and is used to periodically verify network connectivity. Must be called only if a session is established.

This is all documentation about link check and LMIC_setLinkCheckMode but it does not provide any useful information.


  • Why is link check mode enabled by default?

  • When is a link check performed?

  • What does periodically mean here?

  • What happens if no LinkCheckAns response is received?
    Is this where EV_LINK_DEAD and EV_LINK_ALIVE come into play?

  • What happens with the Margin and GwCnt values returned by MAC response LinkCheckAns?

  • Is it possible for user code to:

    • initiate a link check?
    • check if a LinkCheckAns response was received or not?
    • read the Margin and GwCnt values returned in the LinkCheckAns response?
  • If yes, how?

  • If not, then what is the practical use of link checks?

  • (Almost) all examples included with the MCCI LMIC library disable link check mode (LMIC_setLinkCheckMode(0)). Why?

LMIC_setLinkCheckMode … Must be called only if a session is established.

  • Why must LMIC_setLinkCheckMode be called only if a session is established?

  • To my understanding ‘enabling link check mode’ only enables use of link checks but does not instantly perform a link check by itself. I expect that the library code which sends the actual LinkCheckReq MAC command(s) is smart enough to know whether a session is established or not (OTAA is used and is joined or ABP is used) and therefore it should not matter if a session is established or not when LMIC_setLinkCheckMode() is called.
    Is this correct?

  • In several OTAA examples LMIC_setLinkCheckMode(0) is called from within setup() when a session has not yet been established. That would confirm my assumption above.
    Is that correct?

  • In several OTAA examples LMIC_setLinkCheckMode(0) is called in response to the EV_JOINED event (see below). Why is link check mode disabled after a join has completed? Do I understand correctly from the comment that link check validation is always automatically enabled during join?

    // Disable link check validation (automatically enabled
    // during join, but because slow data rates change max TX
    // size, we don't use it in this example.
  • In several OTAA examples LMIC_setLinkCheckMode(0) is called both in setup() and in response to the EV_JOINED event. Why? To initially disable it and disable it again after a join has automatically enabled it again?

  • If latter is true wouldn’t it be better to enhance the join process to save link check mode state at the start of the join process and restore the initial link check mode state at the end of the join process (assuming that link check mode is required during the join process)?

Thanks for your questions.

All answers relative to LoRaWAN 1.0.3.

First of all, the term “link check” is historical, and doesn’t correspond to the LinkCheckRequest in section 5. Instead, this is relative to the procedure specified in section “Adaptive Data Rate Control”. That section title is a little misleading, because the section specifies “validating that the network still receives the uplink frames” (line 445).

This is the (optional) behavior that LMIC_setLinkCheckMode() enables or disables.

The procedure is outlined at length in lines 446 to 468.

Non-normative summary:

  1. Keep a counter that is cleared every time you get a valid downlink.
  2. On each uplink, if counter is in the range 64…127, set the linkADR request bit in the uplink frame. (The lower bound comes from the LoRaWAN spec; the upper bound comes from the compliance tests.)
  3. if the counter reaches 96 (64 + 32), lower the uplink data rate if possible, and reset the counter to 64 if successful.
  4. Otherwise (no lower data rate is possible) keep counting. After some (device-specific) number of uplinks without downlinks, do a JOIN (if OTAA).

The LMIC currently uses (752 + 64) == 816 as the threshold for triggering a JOIN. This should probably be adjustable, but it is not currently adjustable.

This behavior is enabled by default because otherwise things like loss of registration at the NS (e.g., TTN V2 to V3 migration) will never be detected and the device will never rejoin. Experience shows that this happens. For example, a non-trivial loss of registration occurs when the FCntUp in the device gets too far ahead of the network (because only one gateway covers the device, and the gateway is down for a long period of time). This actually happened to a few of our devices during the pandemic, due to access restrictions; we unfortunately did not have link check enabled, and so we had to wait until we were permitted to visit.

See the procedure. It is implicit; a successful downlink is used as evidence that the device’s uplinks are being heard.

Fundamentally based on how often the device does an uplink; and secondarily how often the network deigns to respond.

This is a terminology problem. If I introduced it, apologies. LinkCheckAns is fundamentally not related to “link check mode”.

Indeed. The first time the counter gets to 96, the LMIC report EV_LINK_CHECK_DEAD. When a downlink is received and the LMIC had reported EV_LINK_CHECK_DEAD, it reports EV_LINK_CHECK_ALIVE.

Actually, LinkCheckReq1 and LinkCheckAns` are not implemented (which causes an EU compliance failure; I appear to have forgotten to file that issue, but I discovered it about two weeks ago.

Link checks happen on each uplink. They have nothing to do with LoRaWAN LinkCheckReq.

Because early versions of TTNv2 didn’t support ADR properly. And although there was an implementation in the LMIC when I started the compliance testing / corrections, it was not matching the standard (at all). So there were interop problems.

A code review indicates that this is not true; it may have been true at one time, because I remember adding the code that makes it not true. My memory is that LMIC.adrAckReq was formerly always initialized by a join; but I haven’t taken the time to look at the git history.

In any case, it’s not true; once disabled, it’s disabled through a join (and not re-enabled).

I think this is superseded by other discussions.

The assumption that this API sets state that survives joins is correct.

It was at one time. When I fixed other bugs, I invalidated this. There were a number of problems with data-rate switching, not least being that the LMIC at one point would happily violate length restictions (sending 240 bytes of payload at SF10 in the US, for example; you’re limited to 11 bytes). As of 3.2 or so, that all was fixed. I didn’t go over the examples at all carefully, as I was focused on the compliance sketch.

History. No longer relevant.

This is how the library works today.

Thanks for your answers.

(I’ll probably have some related questions later.)