Skip to content

fix(lorawan): NbTrans retransmissions skipped after LinkADRReq nb_trans change#15546

Open
hallard wants to merge 1 commit intoARMmbed:masterfrom
hallard:lorawan/fix_nbtrans_fcnt
Open

fix(lorawan): NbTrans retransmissions skipped after LinkADRReq nb_trans change#15546
hallard wants to merge 1 commit intoARMmbed:masterfrom
hallard:lorawan/fix_nbtrans_fcnt

Conversation

@hallard
Copy link
Contributor

@hallard hallard commented Mar 6, 2026

Summary

Fixes a LoRaWAN spec 4.3.1.1 compliance failure where the first unconfirmed uplink after a LinkADRReq changes nb_trans is sent only once instead of nb_trans times, and FCnt is incremented after that single TX.

Root cause

get_QOS_level() has a state-updating side-effect: it writes _prev_qos_level = nb_trans each time it is called. get_QOS_level() is only called from post_process_tx_no_reception(), which is only entered when no downlink is received.

Typical scenario when NS sends a LinkADRReq:

  1. The downlink carrying the LinkADRReq is received → process_reception() handles it → post_process_tx_no_reception() is never called_prev_qos_level stays at its old value.
  2. nb_trans is now updated to the new value.
  3. Next application frame is sent → no downlink → post_process_tx_no_reception():
    prev_QOS_level = get_prev_QOS_level()  // old value, e.g. 1
    QOS_level      = get_QOS_level()       // new value, e.g. 3 — side-effect: _prev_qos_level = 3
    if (QOS_level > 1 && (prev_QOS_level == QOS_level))  // 1 == 3 → FALSE
    
    → retransmissions skipped → FCnt++ after a single TX. Spec violation.
  4. Every subsequent frame: prev_QOS_level == QOS_level → retransmits correctly.

Fix

  • LoRaWANStack.cpp: Remove the prev_QOS_level == QOS_level guard. The _qos_cnt counter — reset to 1 in handle_tx() for every new application frame — already correctly bounds the number of retransmissions regardless of nb_trans transitions.

  • LoRaMac.cpp: Reset ul_nb_rep_counter to 0 at the start of each new frame send (LoRaMac::send). Without this reset, the counter accumulates across all frames (it is only reset on join/disconnect), so nb_retries in the TX confirmation grows unboundedly and the commented-out MBED_ASSERT(ul_nb_rep_counter <= nb_trans) in on_radio_tx_done would fire on the second application message.

Test plan

  • Set nb_trans = 1 (default): device sends 1 TX per frame, FCnt increments correctly.
  • Set nb_trans = 3 via LinkADRReq in a downlink: verify the first data frame after the change is sent 3 times with the same FCnt, then FCnt increments.
  • Verify nb_retries in the TX confirmation equals the number of physical TXs for that frame only.
  • LoRaWAN certification ADR/Redundancy test (NbTrans > 1).

🤖 Generated with Claude Code

…ns change

The prev_QOS_level == QOS_level guard in post_process_tx_no_reception() caused
the first unconfirmed uplink after a LinkADRReq changed nb_trans to be sent
only once, violating LoRaWAN spec 4.3.1.1 which requires the new NbTrans to
apply immediately to all subsequent frames.

Root cause: get_QOS_level() has a state-updating side-effect that writes
_prev_qos_level = nb_trans each time it is called. If a downlink was received
during the frame that acknowledged the LinkADRReq, post_process_tx_no_reception
was never entered, so _prev_qos_level was never updated. On the next data frame
the condition prev_QOS_level != QOS_level therefore evaluated to false, skipping
all NbTrans retransmissions and incrementing FCnt after a single TX.

Fix: Remove the prev_QOS_level == QOS_level guard. The _qos_cnt counter (reset
to 1 per frame in handle_tx) already correctly bounds retransmissions for every
frame regardless of nb_trans transitions.

Also reset ul_nb_rep_counter at the start of each new frame send (LoRaMac::send)
so that nb_retries in the TX confirmation metadata reflects only the current
frame, not a cumulative count across all previous frames (the original code
never reset this counter between sends, so the commented-out assert at
on_radio_tx_done would have fired on the second application message).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant