diff --git a/features/lorawan/LoRaWANBase.h b/features/lorawan/LoRaWANBase.h index 5f63f00863..7543c36098 100644 --- a/features/lorawan/LoRaWANBase.h +++ b/features/lorawan/LoRaWANBase.h @@ -389,6 +389,19 @@ public: * */ virtual lorawan_status_t get_backoff_metadata(int& backoff) = 0; + + /** Cancel outgoing transmission + * + * This API is used to cancel any outstanding transmission in the TX pipe. + * If an event for transmission is not already queued at the end of backoff timer, + * the system can cancel the outstanding outgoing packet. Otherwise, the system is + * busy sending and can't be held back. + * + * @return LORAWAN_STATUS_OK if the sending is cancelled. + * LORAWAN_STATUS_BUSY otherwise. + * + */ + virtual lorawan_status_t cancel_sending(void) = 0; }; #endif /* LORAWAN_BASE_H_ */ diff --git a/features/lorawan/LoRaWANInterface.cpp b/features/lorawan/LoRaWANInterface.cpp index d12537f8ef..ad25e339f3 100644 --- a/features/lorawan/LoRaWANInterface.cpp +++ b/features/lorawan/LoRaWANInterface.cpp @@ -122,6 +122,12 @@ int16_t LoRaWANInterface::send(uint8_t port, const uint8_t* data, uint16_t lengt return _lw_stack.handle_tx(port, data, length, flags); } +lorawan_status_t LoRaWANInterface::cancel_sending(void) +{ + Lock lock(*this); + return _lw_stack.stop_sending(); +} + lorawan_status_t LoRaWANInterface::get_tx_metadata(lorawan_tx_metadata& metadata) { Lock lock(*this); diff --git a/features/lorawan/LoRaWANInterface.h b/features/lorawan/LoRaWANInterface.h index 6e761a9ef7..f86068a767 100644 --- a/features/lorawan/LoRaWANInterface.h +++ b/features/lorawan/LoRaWANInterface.h @@ -488,6 +488,20 @@ public: */ virtual lorawan_status_t get_backoff_metadata(int& backoff); + /** Cancel outgoing transmission + * + * This API is used to cancel any outstanding transmission in the TX pipe. + * If an event for transmission is not already queued at the end of backoff timer, + * the system can cancel the outstanding outgoing packet. Otherwise, the system is + * busy sending and can't be held back. The system will not try to re-send if the + * outgoing message was a CONFIRMED message even if the ack is not received. + * + * @return LORAWAN_STATUS_OK if the sending is cancelled. + * LORAWAN_STATUS_BUSY otherwise. + * + */ + virtual lorawan_status_t cancel_sending(void); + void lock(void) { _lw_stack.lock(); } void unlock(void) { _lw_stack.unlock(); } diff --git a/features/lorawan/LoRaWANStack.cpp b/features/lorawan/LoRaWANStack.cpp index d7fae02436..d7bbe45304 100644 --- a/features/lorawan/LoRaWANStack.cpp +++ b/features/lorawan/LoRaWANStack.cpp @@ -257,6 +257,21 @@ lorawan_status_t LoRaWANStack::enable_adaptive_datarate(bool adr_enabled) return LORAWAN_STATUS_OK; } +lorawan_status_t LoRaWANStack::stop_sending(void) +{ + if (_loramac.clear_tx_pipe() == LORAWAN_STATUS_OK) { + if (_device_current_state == DEVICE_STATE_SENDING) { + _ctrl_flags &= ~TX_DONE_FLAG; + _ctrl_flags &= ~TX_ONGOING_FLAG; + _loramac.set_tx_ongoing(false); + _device_current_state = DEVICE_STATE_IDLE; + return LORAWAN_STATUS_OK; + } + } + + return LORAWAN_STATUS_BUSY; +} + int16_t LoRaWANStack::handle_tx(const uint8_t port, const uint8_t* data, uint16_t length, uint8_t flags, bool null_allowed, bool allow_port_0) @@ -517,8 +532,8 @@ void LoRaWANStack::process_transmission_timeout() // this is a fatal error and should not happen tr_debug("TX Timeout"); _loramac.on_radio_tx_timeout(); - _ctrl_flags &= ~TX_ONGOING_FLAG; - _ctrl_flags |= TX_DONE_FLAG; + _ctrl_flags |= TX_ONGOING_FLAG; + _ctrl_flags &= ~TX_DONE_FLAG; state_controller(DEVICE_STATE_STATUS_CHECK); state_machine_run_to_completion(); } @@ -576,12 +591,21 @@ void LoRaWANStack::process_reception(const uint8_t* const payload, uint16_t size if (_loramac.get_mcps_confirmation()->req_type == MCPS_CONFIRMED) { // if ack was not received, we will try retransmission after // ACK_TIMEOUT. handle_data_frame() already disables ACK_TIMEOUT timer - // if ack was received + // if ack was received. Otherwise, following method will be called in + // LoRaMac.cpp, on_ack_timeout_timer_event(). if (_loramac.get_mcps_indication()->is_ack_recvd) { tr_debug("Ack=OK, NbTrials=%d", _loramac.get_mcps_confirmation()->nb_retries); _loramac.post_process_mcps_req(); _ctrl_flags |= TX_DONE_FLAG; + _ctrl_flags &= ~TX_ONGOING_FLAG; state_controller(DEVICE_STATE_STATUS_CHECK); + } else { + if (!_loramac.continue_sending_process()) { + tr_error("Retries exhausted for Class A device"); + _ctrl_flags &= ~TX_DONE_FLAG; + _ctrl_flags |= TX_ONGOING_FLAG; + state_controller(DEVICE_STATE_STATUS_CHECK); + } } } else { // handle UNCONFIRMED, PROPRIETARY case here, RX slots were turned off due to @@ -636,13 +660,19 @@ void LoRaWANStack::process_reception_timeout(bool is_timeout) * of UNCONFIRMED message after RX windows are done with. * For a CONFIRMED message, it means that we have not received * ack (actually nothing was received), and we should retransmit if we can. + * + * NOTE: This code block doesn't get hit for Class C as in Class C, RX2 timeout + * never occurs. */ if (slot == RX_SLOT_WIN_2) { _loramac.post_process_mcps_req(); - if (_loramac.get_mcps_confirmation()->req_type == MCPS_CONFIRMED - && _loramac.continue_sending_process()) { - return; + if (_loramac.get_mcps_confirmation()->req_type == MCPS_CONFIRMED) { + if (_loramac.continue_sending_process()) { + return; + } else { + tr_error("Retries exhausted for Class A device"); + } } state_controller(DEVICE_STATE_STATUS_CHECK); @@ -666,7 +696,6 @@ void LoRaWANStack::make_tx_metadata_available(void) void LoRaWANStack::make_rx_metadata_available(void) { _rx_metadata.stale = false; - _rx_metadata.fpending_status = _loramac.get_mcps_indication()->fpending_status; _rx_metadata.rx_datarate = _loramac.get_mcps_indication()->rx_datarate; _rx_metadata.rssi = _loramac.get_mcps_indication()->rssi; _rx_metadata.snr = _loramac.get_mcps_indication()->snr; @@ -972,7 +1001,7 @@ void LoRaWANStack::process_status_check_state() // 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. + // case will fall here. Moreover, it will happen for Class A only. _ctrl_flags &= ~TX_DONE_FLAG; _ctrl_flags &= ~TX_ONGOING_FLAG; _loramac.set_tx_ongoing(false); @@ -981,8 +1010,11 @@ void LoRaWANStack::process_status_check_state() } else if (_device_current_state == DEVICE_STATE_RECEIVING) { - if (_ctrl_flags & TX_DONE_FLAG) { + if ((_ctrl_flags & TX_DONE_FLAG) || (_ctrl_flags & TX_ONGOING_FLAG)) { // for CONFIRMED case, ack validity is already checked + // If it was a successful transmission, TX_ONGOING_FLAG will not be set. + // If it was indeed set, that means the device was in Class C mode and + // CONFIRMED transmission was in place and the ack retries maxed out. _ctrl_flags &= ~TX_DONE_FLAG; _ctrl_flags &= ~TX_ONGOING_FLAG; _loramac.set_tx_ongoing(false); diff --git a/features/lorawan/LoRaWANStack.h b/features/lorawan/LoRaWANStack.h index bf4471a08c..3d627b9c3b 100644 --- a/features/lorawan/LoRaWANStack.h +++ b/features/lorawan/LoRaWANStack.h @@ -415,6 +415,16 @@ public: */ lorawan_status_t acquire_backoff_metadata(int& backoff); + /** Stops sending + * + * Stop sending any outstanding messages if they are not yet queued for + * transmission, i.e., if the backoff timer is nhot elapsed yet. + * + * @return LORAWAN_STATUS_OK if the transmission is cancelled. + * LORAWAN_STATUS_BUSY otherwise. + */ + lorawan_status_t stop_sending(void); + void lock(void) { _loramac.lock(); } void unlock(void) { _loramac.unlock(); } diff --git a/features/lorawan/lorastack/mac/LoRaMac.cpp b/features/lorawan/lorastack/mac/LoRaMac.cpp index 4e64d20b91..649a982a3b 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.cpp +++ b/features/lorawan/lorastack/mac/LoRaMac.cpp @@ -208,7 +208,7 @@ void LoRaMac::on_radio_tx_done(void) _lora_time.start(_params.timers.rx_window2_timer, _params.rx_window2_delay); } - if (_params.is_node_ack_requested == true) { + if (_params.is_node_ack_requested) { _lora_time.start(_params.timers.ack_timeout_timer, _params.rx_window2_delay + _lora_phy.get_ack_timeout()); } @@ -1013,6 +1013,22 @@ int LoRaMac::get_backoff_timer_event_id(void) return _params.timers.backoff_timer.timer_id; } +lorawan_status_t LoRaMac::clear_tx_pipe(void) +{ + // check if the event is not already queued + if (_ev_queue->time_left(get_backoff_timer_event_id()) > 0) { + _lora_time.stop(_params.timers.backoff_timer); + _lora_time.stop(_params.timers.ack_timeout_timer); + memset(_params.tx_buffer, 0, sizeof _params.tx_buffer); + _params.tx_buffer_len = 0; + reset_ongoing_tx(true); + tr_debug("Sending Cancelled"); + return LORAWAN_STATUS_OK; + } + + return LORAWAN_STATUS_BUSY; +} + lorawan_status_t LoRaMac::schedule_tx() { channel_selection_params_t next_channel; @@ -1085,6 +1101,12 @@ lorawan_status_t LoRaMac::schedule_tx() + _params.rx_window2_config.window_offset; } + // handle the ack to the server here so that if the sending was cancelled + // by the user in the backoff period, we would still ack the previous frame. + if (_params.is_srv_ack_requested) { + _params.is_srv_ack_requested = false; + } + return send_frame_on_channel(_params.channel); } @@ -1488,7 +1510,6 @@ lorawan_status_t LoRaMac::prepare_frame(loramac_mhdr_t *machdr, } if (_params.is_srv_ack_requested == true) { - _params.is_srv_ack_requested = false; fctrl->bits.ack = 1; } diff --git a/features/lorawan/lorastack/mac/LoRaMac.h b/features/lorawan/lorastack/mac/LoRaMac.h index becef2a22c..47156d92d1 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.h +++ b/features/lorawan/lorastack/mac/LoRaMac.h @@ -447,6 +447,12 @@ public: */ int get_backoff_timer_event_id(void); + /** + * Clears out the TX pipe by discarding any outgoing message if the backoff + * timer is still running. + */ + lorawan_status_t clear_tx_pipe(void); + /** * These locks trample through to the upper layers and make * the stack thread safe. diff --git a/features/lorawan/lorawan_types.h b/features/lorawan/lorawan_types.h index d9b3c3a567..c14636fc7f 100644 --- a/features/lorawan/lorawan_types.h +++ b/features/lorawan/lorawan_types.h @@ -350,29 +350,29 @@ typedef struct lora_channelplan { */ typedef struct { /** - * A boolean to mark if the meta data is stale + * The transmission time on air of the frame. */ - bool stale; + uint32_t tx_toa; /** * The uplink channel used for transmission. */ uint32_t channel; - /** - * The uplink datarate. - */ - uint8_t data_rate; /** * The transmission power. */ int8_t tx_power; + /** + * The uplink datarate. + */ + uint8_t data_rate; /** * Provides the number of retransmissions. */ uint8_t nb_retries; /** - * The transmission time on air of the frame. + * A boolean to mark if the meta data is stale */ - uint32_t tx_toa; + bool stale; } lorawan_tx_metadata; /** @@ -380,25 +380,21 @@ typedef struct { */ typedef struct { /** - * A boolean to mark if the meta data is stale + * The RSSI for the received packet. */ - bool stale; + int16_t rssi; /** * Data rate of reception */ uint8_t rx_datarate; - /** - * Frame pending status. - */ - uint8_t fpending_status; - /** - * The RSSI for the received packet. - */ - int16_t rssi; /** * The SNR for the received packet. */ uint8_t snr; + /** + * A boolean to mark if the meta data is stale + */ + bool stale; } lorawan_rx_metadata; #endif /* MBED_LORAWAN_TYPES_H_ */