From 71db8ddea3efbb35fd47b6e579848400179c0cf4 Mon Sep 17 00:00:00 2001 From: Charles Date: Fri, 6 Mar 2026 11:46:40 +0100 Subject: [PATCH] fix(lorawan): NbTrans retransmissions skipped after LinkADRReq nb_trans 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 --- connectivity/lorawan/lorastack/mac/LoRaMac.cpp | 3 +++ connectivity/lorawan/source/LoRaWANStack.cpp | 8 ++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/connectivity/lorawan/lorastack/mac/LoRaMac.cpp b/connectivity/lorawan/lorastack/mac/LoRaMac.cpp index fca30bad90b..82ecc36d6f6 100644 --- a/connectivity/lorawan/lorastack/mac/LoRaMac.cpp +++ b/connectivity/lorawan/lorastack/mac/LoRaMac.cpp @@ -1051,6 +1051,9 @@ lorawan_status_t LoRaMac::send(loramac_mhdr_t *machdr, const uint8_t fport, _mcps_confirmation.nb_retries = 0; _mcps_confirmation.ack_received = false; _mcps_confirmation.ul_frame_counter = _params.ul_frame_counter; + // Reset per-frame repetition counter so nb_retries in the TX confirmation + // reports the count for this frame only, not cumulative across all frames. + _params.ul_nb_rep_counter = 0; status = schedule_tx(); diff --git a/connectivity/lorawan/source/LoRaWANStack.cpp b/connectivity/lorawan/source/LoRaWANStack.cpp index 22d45454739..4f4612eaf2c 100644 --- a/connectivity/lorawan/source/LoRaWANStack.cpp +++ b/connectivity/lorawan/source/LoRaWANStack.cpp @@ -644,10 +644,14 @@ void LoRaWANStack::post_process_tx_no_reception() } else { _ctrl_flags |= TX_DONE_FLAG; - uint8_t prev_QOS_level = _loramac.get_prev_QOS_level(); + // LoRaWAN spec 4.3.1.1: all NbTrans retransmissions use the same FCnt. + // FCnt is incremented only after all retransmissions are done. + // The _qos_cnt counter (reset to 1 per frame in handle_tx) controls + // the number of physical transmissions independently of any LinkADRReq + // transition, so no prev/current QOS comparison is needed here. uint8_t QOS_level = _loramac.get_QOS_level(); - if (QOS_level > LORAWAN_DEFAULT_QOS && (prev_QOS_level == QOS_level)) { + if (QOS_level > LORAWAN_DEFAULT_QOS) { if (_qos_cnt < QOS_level) { const int ret = _queue->call(this, &LoRaWANStack::state_controller, DEVICE_STATE_SCHEDULING);