diff --git a/features/lorawan/LoRaWANStack.cpp b/features/lorawan/LoRaWANStack.cpp index 3cdb543dd1a..4fa10a90b31 100644 --- a/features/lorawan/LoRaWANStack.cpp +++ b/features/lorawan/LoRaWANStack.cpp @@ -332,7 +332,7 @@ int16_t LoRaWANStack::handle_tx(const uint8_t port, const uint8_t *data, return status; } - // All the flags mutually exclusive. In addition to that MSG_MULTICAST_FLAG cannot be + // All the flags are mutually exclusive. In addition to that MSG_MULTICAST_FLAG cannot be // used for uplink. switch (flags & MSG_FLAG_MASK) { case MSG_UNCONFIRMED_FLAG: @@ -631,6 +631,13 @@ void LoRaWANStack::handle_ack_expiry_for_class_c(void) state_controller(DEVICE_STATE_STATUS_CHECK); } +void LoRaWANStack::handle_scheduling_failure(void) +{ + tr_error("Failed to schedule transmission"); + state_controller(DEVICE_STATE_STATUS_CHECK); + state_machine_run_to_completion(); +} + void LoRaWANStack::process_reception(const uint8_t *const payload, uint16_t size, int16_t rssi, int8_t snr) { @@ -948,22 +955,26 @@ void LoRaWANStack::mlme_confirm_handler() void LoRaWANStack::mcps_confirm_handler() { - // success case - if (_loramac.get_mcps_confirmation()->status == LORAMAC_EVENT_INFO_STATUS_OK) { - _lw_session.uplink_counter = _loramac.get_mcps_confirmation()->ul_frame_counter; - send_event_to_application(TX_DONE); - return; - } + switch (_loramac.get_mcps_confirmation()->status) { - // failure case - if (_loramac.get_mcps_confirmation()->status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT) { - tr_error("Fatal Error, Radio failed to transmit"); - send_event_to_application(TX_TIMEOUT); - return; - } + case LORAMAC_EVENT_INFO_STATUS_OK: + _lw_session.uplink_counter = _loramac.get_mcps_confirmation()->ul_frame_counter; + send_event_to_application(TX_DONE); + break; + + case LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT: + tr_error("Fatal Error, Radio failed to transmit"); + send_event_to_application(TX_TIMEOUT); + break; - // if no ack was received, send TX_ERROR - send_event_to_application(TX_ERROR); + case LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR: + send_event_to_application(TX_SCHEDULING_ERROR); + break; + + default: + // if no ack was received after enough retries, send TX_ERROR + send_event_to_application(TX_ERROR); + } } void LoRaWANStack::mcps_indication_handler() @@ -1089,11 +1100,13 @@ void LoRaWANStack::process_status_check_state() { if (_device_current_state == DEVICE_STATE_SENDING || _device_current_state == DEVICE_STATE_AWAITING_ACK) { - // this happens after RX2 slot is exhausted - // we may or may not have a successful UNCONFIRMED transmission + // If there was a successful transmission, this block gets a kick after + // RX2 slot is exhausted. We may or may not have a successful UNCONFIRMED transmission // here. In CONFIRMED case this block is invoked only // when the MAX number of retries are exhausted, i.e., only error // case will fall here. Moreover, it will happen for Class A only. + // Another possibility is the case when the stack fails to schedule a + // deferred transmission and a scheduling failure handler is invoked. _ctrl_flags &= ~TX_DONE_FLAG; _ctrl_flags &= ~TX_ONGOING_FLAG; _loramac.set_tx_ongoing(false); @@ -1215,7 +1228,8 @@ void LoRaWANStack::process_idle_state(lorawan_status_t &op_status) void LoRaWANStack::process_uninitialized_state(lorawan_status_t &op_status) { - op_status = _loramac.initialize(_queue); + op_status = _loramac.initialize(_queue, mbed::callback(this, + &LoRaWANStack::handle_scheduling_failure)); if (op_status == LORAWAN_STATUS_OK) { _device_current_state = DEVICE_STATE_IDLE; diff --git a/features/lorawan/LoRaWANStack.h b/features/lorawan/LoRaWANStack.h index 6c57b429671..4f052d9d1cd 100644 --- a/features/lorawan/LoRaWANStack.h +++ b/features/lorawan/LoRaWANStack.h @@ -484,6 +484,7 @@ class LoRaWANStack: private mbed::NonCopyable { void make_rx_metadata_available(void); void handle_ack_expiry_for_class_c(void); + void handle_scheduling_failure(void); private: LoRaMac _loramac; diff --git a/features/lorawan/lorastack/mac/LoRaMac.cpp b/features/lorawan/lorastack/mac/LoRaMac.cpp index 4195b8814ec..6823e7403d0 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.cpp +++ b/features/lorawan/lorastack/mac/LoRaMac.cpp @@ -839,10 +839,12 @@ lorawan_status_t LoRaMac::handle_retransmission() void LoRaMac::on_backoff_timer_expiry(void) { Lock lock(*this); + _lora_time.stop(_params.timers.backoff_timer); - lorawan_status_t status = schedule_tx(); - MBED_ASSERT(status == LORAWAN_STATUS_OK); - (void) status; + + if ((schedule_tx() != LORAWAN_STATUS_OK) && nwk_joined()) { + _scheduling_failure_handler.call(); + } } void LoRaMac::open_rx1_window(void) @@ -927,8 +929,12 @@ void LoRaMac::on_ack_timeout_timer_event(void) _mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter; + // Schedule a retry - if (handle_retransmission() != LORAWAN_STATUS_OK) { + lorawan_status_t status = handle_retransmission(); + + if (status == LORAWAN_STATUS_NO_CHANNEL_FOUND || + status == LORAWAN_STATUS_NO_FREE_CHANNEL_FOUND) { // In a case when enabled channels are not found, PHY layer // resorts to default channels. Next attempt should go forward as the // default channels are always available if there is a base station in the @@ -939,10 +945,24 @@ void LoRaMac::on_ack_timeout_timer_event(void) _mcps_confirmation.ack_received = false; _mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter; - // now that is a critical failure - lorawan_status_t status = handle_retransmission(); + // For the next attempt we need to make sure that we do not incur length error + // which would mean that the datarate changed during retransmissions and + // the original packet doesn't fit into allowed payload buffer anymore. + status = handle_retransmission(); + + if (status == LORAWAN_STATUS_LENGTH_ERROR) { + _scheduling_failure_handler.call(); + return; + } + + // if we did not incur a length error and still the status is not OK, + // it is a critical failure + status = handle_retransmission(); MBED_ASSERT(status == LORAWAN_STATUS_OK); (void) status; + } else if (status != LORAWAN_STATUS_OK) { + _scheduling_failure_handler.call(); + return; } _params.ack_timeout_retry_counter++; @@ -1064,6 +1084,7 @@ lorawan_status_t LoRaMac::schedule_tx() switch (status) { case LORAWAN_STATUS_NO_CHANNEL_FOUND: case LORAWAN_STATUS_NO_FREE_CHANNEL_FOUND: + _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR; return status; case LORAWAN_STATUS_DUTYCYCLE_RESTRICTED: if (backoff_time != 0) { @@ -1713,12 +1734,14 @@ void LoRaMac::set_tx_continuous_wave(uint8_t channel, int8_t datarate, int8_t tx _lora_phy->set_tx_cont_mode(&continuous_wave); } -lorawan_status_t LoRaMac::initialize(EventQueue *queue) +lorawan_status_t LoRaMac::initialize(EventQueue *queue, + mbed::Callbackscheduling_failure_handler) { _lora_time.activate_timer_subsystem(queue); _lora_phy->initialize(&_lora_time); _ev_queue = queue; + _scheduling_failure_handler = scheduling_failure_handler; _channel_plan.activate_channelplan_subsystem(_lora_phy); diff --git a/features/lorawan/lorastack/mac/LoRaMac.h b/features/lorawan/lorastack/mac/LoRaMac.h index 81d560624ff..783175a2844 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.h +++ b/features/lorawan/lorastack/mac/LoRaMac.h @@ -78,11 +78,15 @@ class LoRaMac { * * @param queue [in] A pointer to the application provided EventQueue. * + * @param scheduling_failure_handler A callback to inform upper layer if a deferred + * transmission (after backoff or retry) fails to schedule. + * * @return `lorawan_status_t` The status of the operation. The possible values are: * \ref LORAWAN_STATUS_OK * \ref LORAWAN_STATUS_PARAMETER_INVALID */ - lorawan_status_t initialize(events::EventQueue *queue); + lorawan_status_t initialize(events::EventQueue *queue, + mbed::Callbackscheduling_failure_handler); /** * @brief Disconnect LoRaMac layer @@ -666,6 +670,14 @@ class LoRaMac { */ mbed::Callback _ack_expiry_handler_for_class_c; + /** + * Transmission is async, i.e., a call to schedule_tx() may be deferred to + * a time after a certain back off. We use this callback to inform the + * controller layer that a specific TX transaction failed to schedule after + * backoff or retry. + */ + mbed::Callback _scheduling_failure_handler; + /** * Structure to hold MCPS indication data. */