diff --git a/connectivity/lorawan/lorastack/mac/LoRaMac.cpp b/connectivity/lorawan/lorastack/mac/LoRaMac.cpp index fca30bad90b..6b8f7b6f1c0 100644 --- a/connectivity/lorawan/lorastack/mac/LoRaMac.cpp +++ b/connectivity/lorawan/lorastack/mac/LoRaMac.cpp @@ -1134,14 +1134,19 @@ lorawan_status_t LoRaMac::schedule_tx() _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR; return status; case LORAWAN_STATUS_DUTYCYCLE_RESTRICTED: - if (backoff_time != 0) { - tr_debug("DC enforced: Transmitting in %lu ms", backoff_time); - _can_cancel_tx = true; - if (_device_class != CLASS_C) { - _lora_phy->put_radio_to_sleep(); - } - _lora_time.start(_params.timers.backoff_timer, backoff_time); + // Enforce a minimum backoff of 1ms so the timer always fires. + // If backoff_time is 0, no timer would be started, leaving + // tx_ongoing=true permanently and all future sends returning + // LORAWAN_STATUS_WOULD_BLOCK with no recovery path. + if (backoff_time == 0) { + backoff_time = 1; + } + tr_debug("DC enforced: Transmitting in %lu ms", backoff_time); + _can_cancel_tx = true; + if (_device_class != CLASS_C) { + _lora_phy->put_radio_to_sleep(); } + _lora_time.start(_params.timers.backoff_timer, backoff_time); return LORAWAN_STATUS_OK; default: break; @@ -1872,6 +1877,13 @@ void LoRaMac::disconnect() reset_mcps_confirmation(); reset_mlme_confirmation(); reset_mcps_indication(); + + // Clear any in-progress TX so that reconnecting after disconnect does not + // permanently return LORAWAN_STATUS_WOULD_BLOCK. All timers that would + // normally drive the state machine to call reset_ongoing_tx() (backoff, + // RX windows, ACK timeout) have already been stopped above, so without + // this explicit reset the tx_ongoing flag would be stuck at true. + reset_ongoing_tx(true); } uint8_t LoRaMac::get_max_possible_tx_size(uint8_t fopts_len) diff --git a/connectivity/lorawan/source/LoRaWANStack.cpp b/connectivity/lorawan/source/LoRaWANStack.cpp index 22d45454739..c44f7f65fd6 100644 --- a/connectivity/lorawan/source/LoRaWANStack.cpp +++ b/connectivity/lorawan/source/LoRaWANStack.cpp @@ -1170,6 +1170,14 @@ void LoRaWANStack::process_scheduling_state(lorawan_status_t &op_status) _ctrl_flags &= ~TX_DONE_FLAG; _loramac.set_tx_ongoing(true); _device_current_state = DEVICE_STATE_SENDING; + } else if (_loramac.tx_ongoing()) { + // tx_ongoing was already true from a previous successful send (e.g. a + // QoS nb_trans retry queued via post_process_tx_no_reception). The + // re-send failed with a non-recoverable error and the return value of + // the queued _queue->call() is ignored, so no failure handler would + // otherwise run. Explicitly clean up so tx_ongoing does not get stuck. + _loramac.set_tx_ongoing(false); + _loramac.reset_ongoing_tx(); } }