From 76814d48478b5ae29804c498f1df873430e8e006 Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Wed, 19 Sep 2018 15:08:17 +0300 Subject: [PATCH 1/7] Style correction LinkADRReq parameters and certain parameters used camel case which is not the recommended style. --- .../lorawan/lorastack/mac/LoRaMacCommand.cpp | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/features/lorawan/lorastack/mac/LoRaMacCommand.cpp b/features/lorawan/lorastack/mac/LoRaMacCommand.cpp index 596d0b8720..d4b5cf3ddd 100644 --- a/features/lorawan/lorastack/mac/LoRaMacCommand.cpp +++ b/features/lorawan/lorastack/mac/LoRaMacCommand.cpp @@ -145,40 +145,40 @@ lorawan_status_t LoRaMacCommand::process_mac_commands(const uint8_t *payload, ui mlme_conf.nb_gateways = payload[mac_index++]; break; case SRV_MAC_LINK_ADR_REQ: { - adr_req_params_t linkAdrReq; - int8_t linkAdrDatarate = DR_0; - int8_t linkAdrTxPower = TX_POWER_0; - uint8_t linkAdrNbRep = 0; - uint8_t linkAdrNbBytesParsed = 0; + adr_req_params_t link_adr_req; + int8_t link_adr_dr = DR_0; + int8_t link_adr_txpower = TX_POWER_0; + uint8_t link_adr_nbtrans = 0; + uint8_t link_adr_nb_bytes_pasred = 0; // Fill parameter structure - linkAdrReq.payload = &payload[mac_index - 1]; - linkAdrReq.payload_size = commands_size - (mac_index - 1); - linkAdrReq.adr_enabled = mac_sys_params.adr_on; - linkAdrReq.ul_dwell_time = mac_sys_params.uplink_dwell_time; - linkAdrReq.current_datarate = mac_sys_params.channel_data_rate; - linkAdrReq.current_tx_power = mac_sys_params.channel_tx_power; - linkAdrReq.current_nb_rep = mac_sys_params.retry_num; + link_adr_req.payload = &payload[mac_index - 1]; + link_adr_req.payload_size = commands_size - (mac_index - 1); + link_adr_req.adr_enabled = mac_sys_params.adr_on; + link_adr_req.ul_dwell_time = mac_sys_params.uplink_dwell_time; + link_adr_req.current_datarate = mac_sys_params.channel_data_rate; + link_adr_req.current_tx_power = mac_sys_params.channel_tx_power; + link_adr_req.current_nb_trans = mac_sys_params.nb_trans; // Process the ADR requests - status = lora_phy.link_ADR_request(&linkAdrReq, - &linkAdrDatarate, - &linkAdrTxPower, - &linkAdrNbRep, - &linkAdrNbBytesParsed); + status = lora_phy.link_ADR_request(&link_adr_req, + &link_adr_dr, + &link_adr_txpower, + &link_adr_nbtrans, + &link_adr_nb_bytes_pasred); if ((status & 0x07) == 0x07) { - mac_sys_params.channel_data_rate = linkAdrDatarate; - mac_sys_params.channel_tx_power = linkAdrTxPower; - mac_sys_params.retry_num = linkAdrNbRep; + mac_sys_params.channel_data_rate = link_adr_dr; + mac_sys_params.channel_tx_power = link_adr_txpower; + mac_sys_params.nb_trans = link_adr_nbtrans; } // Add the answers to the buffer - for (uint8_t i = 0; i < (linkAdrNbBytesParsed / 5); i++) { + for (uint8_t i = 0; i < (link_adr_nb_bytes_pasred / 5); i++) { ret_value = add_link_adr_ans(status); } // Update MAC index - mac_index += linkAdrNbBytesParsed - 1; + mac_index += link_adr_nb_bytes_pasred - 1; } break; case SRV_MAC_DUTY_CYCLE_REQ: From 5bfa7c03ba860ee0a47f50bebb1310bdc09476c2 Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Wed, 19 Sep 2018 15:16:55 +0300 Subject: [PATCH 2/7] Adding check for ongoing automatic-uplink Before going after an automatic uplink, we should check if there was an automatic uplink already ongoing, i.e., the ack for the previous automatic uplink cycle has not been received. If there is we shouldn't queue the new automatic uplink and wait for the previous Ack cycle to complete. --- features/lorawan/LoRaWANStack.cpp | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/features/lorawan/LoRaWANStack.cpp b/features/lorawan/LoRaWANStack.cpp index a73512ebbc..d0edb439ce 100644 --- a/features/lorawan/LoRaWANStack.cpp +++ b/features/lorawan/LoRaWANStack.cpp @@ -692,7 +692,8 @@ void LoRaWANStack::process_reception(const uint8_t *const payload, uint16_t size state_machine_run_to_completion(); } - if (_loramac.get_mlme_indication()->pending) { + // suppress auto uplink if another auto-uplink is in AWAITING_ACK state + if (_loramac.get_mlme_indication()->pending && !_automatic_uplink_ongoing) { tr_debug("MLME Indication pending"); _loramac.post_process_mlme_ind(); tr_debug("Immediate Uplink requested"); @@ -1028,11 +1029,15 @@ void LoRaWANStack::mcps_indication_handler() || (_loramac.get_device_class() == CLASS_C && mcps_indication->type == MCPS_CONFIRMED)) { #if (MBED_CONF_LORA_AUTOMATIC_UPLINK_MESSAGE) - tr_debug("Sending empty uplink message..."); - _automatic_uplink_ongoing = true; - const int ret = _queue->call(this, &LoRaWANStack::send_automatic_uplink_message, mcps_indication->port); - MBED_ASSERT(ret != 0); - (void)ret; + // Do not queue an automatic uplink of there is one already outgoing + // This means we have not received an ack for the previous automatic uplink + if (!_automatic_uplink_ongoing) { + tr_debug("Sending empty uplink message..."); + _automatic_uplink_ongoing = true; + const int ret = _queue->call(this, &LoRaWANStack::send_automatic_uplink_message, mcps_indication->port); + MBED_ASSERT(ret != 0); + (void)ret; + } #else send_event_to_application(UPLINK_REQUIRED); #endif From 94eb4c0bc05e655ee54feaaaf10ea42b77dbabd5 Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Wed, 19 Sep 2018 15:44:09 +0300 Subject: [PATCH 3/7] Adding QOS handling and fixing bugs for Class C LinkADRReq mac command can be used by the network server to set a certain level of QOS using NbTrans field which is applicable to Unconfirmed traffic only for 1.0.2 spec. This commit introduces mechanisms to facilitate this QOS. It means to repeat an outgoing unconfirmed message NbTrans times without changing its frame counter. For class C, we have retired the ack_expiry_timer_for_class_c and have replaced it with another timer which mimics the RX2 closure as in Class A but doesn't actually close RX2 window. It's just a mechanism by which the state machine is informed that the you can proceed forward, we have not received anything in RX2 window either. This is needed as RX2 doesn't timeout in class C (i.e., the radio remains in continuous mode). In addition to that we need to close any pending timers for Receive windows after the MIC has passed and the Duplicate counter check has also been passed. --- features/lorawan/LoRaWANStack.cpp | 188 ++++++++++-------- features/lorawan/LoRaWANStack.h | 5 +- features/lorawan/lorastack/mac/LoRaMac.cpp | 92 +++++++-- features/lorawan/lorastack/mac/LoRaMac.h | 32 ++- features/lorawan/lorastack/phy/LoRaPHY.cpp | 2 +- .../lorawan/lorastack/phy/LoRaPHYAU915.cpp | 2 +- .../lorawan/lorastack/phy/LoRaPHYCN470.cpp | 2 +- .../lorawan/lorastack/phy/LoRaPHYUS915.cpp | 2 +- features/lorawan/lorastack/phy/lora_phy_ds.h | 5 +- .../lorawan/system/lorawan_data_structures.h | 17 +- 10 files changed, 229 insertions(+), 118 deletions(-) diff --git a/features/lorawan/LoRaWANStack.cpp b/features/lorawan/LoRaWANStack.cpp index d0edb439ce..6301a67f46 100644 --- a/features/lorawan/LoRaWANStack.cpp +++ b/features/lorawan/LoRaWANStack.cpp @@ -40,7 +40,7 @@ SPDX-License-Identifier: BSD-3-Clause * Control flags for transient states */ #define IDLE_FLAG 0x00000000 -#define TX_ONGOING_FLAG 0x00000001 +#define RETRY_EXHAUSTED_FLAG 0x00000001 #define MSG_RECVD_FLAG 0x00000002 #define CONNECTED_FLAG 0x00000004 #define USING_OTAA_FLAG 0x00000008 @@ -68,6 +68,7 @@ LoRaWANStack::LoRaWANStack() _tx_metadata(), _rx_metadata(), _num_retry(1), + _qos_cnt(1), _ctrl_flags(IDLE_FLAG), _app_port(INVALID_PORT), _link_check_requested(false), @@ -274,7 +275,6 @@ lorawan_status_t LoRaWANStack::stop_sending(void) if (_loramac.clear_tx_pipe() == LORAWAN_STATUS_OK) { _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; @@ -453,7 +453,8 @@ lorawan_status_t LoRaWANStack::set_device_class(const device_class_t &device_cla if (device_class == CLASS_B) { return LORAWAN_STATUS_UNSUPPORTED; } - _loramac.set_device_class(device_class, mbed::callback(this, &LoRaWANStack::handle_ack_expiry_for_class_c)); + _loramac.set_device_class(device_class, + mbed::callback(this, &LoRaWANStack::post_process_tx_no_reception)); return LORAWAN_STATUS_OK; } @@ -563,7 +564,6 @@ 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; if (_device_current_state == DEVICE_STATE_JOINING) { mlme_confirm_handler(); @@ -579,39 +579,108 @@ void LoRaWANStack::process_transmission(void) tr_debug("Transmission completed"); _loramac.on_radio_tx_done(_tx_timestamp); - make_tx_metadata_available(); - if (_device_current_state == DEVICE_STATE_JOINING) { _device_current_state = DEVICE_STATE_AWAITING_JOIN_ACCEPT; } if (_device_current_state == DEVICE_STATE_SENDING) { if (_loramac.get_mcps_confirmation()->req_type == MCPS_CONFIRMED) { - _ctrl_flags |= TX_ONGOING_FLAG; - _ctrl_flags &= ~TX_DONE_FLAG; tr_debug("Awaiting ACK"); _device_current_state = DEVICE_STATE_AWAITING_ACK; - } else if (_loramac.get_device_class() == CLASS_A) { - // Class A unconfirmed message sent, TX_DONE event will be sent to - // application when RX2 windows is elapsed, i.e., in process_reception_timeout() - _ctrl_flags &= ~TX_ONGOING_FLAG; - _ctrl_flags |= TX_DONE_FLAG; - } else if (_loramac.get_device_class() == CLASS_C) { - // In Class C, reception timeout never happens, so we handle the state - // progression for TX_DONE in UNCONFIRMED case here - _loramac.post_process_mcps_req(); - state_controller(DEVICE_STATE_STATUS_CHECK); - state_machine_run_to_completion(); } } } -void LoRaWANStack::handle_ack_expiry_for_class_c(void) +void LoRaWANStack::post_process_tx_with_reception() { - _ctrl_flags &= ~TX_DONE_FLAG; - _ctrl_flags |= TX_ONGOING_FLAG; - tr_error("Retries exhausted for Class C device"); + 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. Otherwise, following method will be called in + // LoRaMac.cpp, on_ack_timeout_timer_event(). + if (_loramac.get_mcps_indication()->is_ack_recvd) { + _ctrl_flags |= TX_DONE_FLAG; + _ctrl_flags &= ~RETRY_EXHAUSTED_FLAG; + tr_debug("Ack=OK, NbTrials=%d", + _loramac.get_mcps_confirmation()->nb_retries); + _loramac.post_process_mcps_req(); + make_tx_metadata_available(); + state_controller(DEVICE_STATE_STATUS_CHECK); + } else { + if (!_loramac.continue_sending_process() + && _loramac.get_current_slot() != RX_SLOT_WIN_1) { + tr_error("Retries exhausted for Class %s device", + _loramac.get_device_class() == CLASS_A ? "A" : "C"); + _ctrl_flags &= ~TX_DONE_FLAG; + _ctrl_flags |= RETRY_EXHAUSTED_FLAG; + state_controller(DEVICE_STATE_STATUS_CHECK); + } + } + } else { + // handle UNCONFIRMED case here, RX slots were turned off due to + // valid packet reception. + uint8_t prev_QOS_level = _loramac.get_prev_QOS_level(); + uint8_t QOS_level = _loramac.get_QOS_level(); + + // We will not apply QOS on the post-processing of the previous + // outgoing message as we would have received QOS instruction in response + // to that particular message + if (QOS_level > LORAWAN_DEFAULT_QOS && _qos_cnt < QOS_level + && (prev_QOS_level == QOS_level)) { + _ctrl_flags &= ~TX_DONE_FLAG; + const int ret = _queue->call(this, &LoRaWANStack::state_controller, + DEVICE_STATE_SCHEDULING); + MBED_ASSERT(ret != 0); + (void) ret; + _qos_cnt++; + tr_info("QOS: repeated transmission #%d queued", _qos_cnt); + } else { + _loramac.post_process_mcps_req(); + _ctrl_flags |= TX_DONE_FLAG; + make_tx_metadata_available(); + state_controller(DEVICE_STATE_STATUS_CHECK); + } + } +} + +void LoRaWANStack::post_process_tx_no_reception() +{ + if (_loramac.get_mcps_confirmation()->req_type == MCPS_CONFIRMED) { + _loramac.post_process_mcps_req(); + if (_loramac.continue_sending_process()) { + _ctrl_flags &= ~TX_DONE_FLAG; + _ctrl_flags &= ~RETRY_EXHAUSTED_FLAG; + return; + } + + tr_error("Retries exhausted for Class %s device", + _loramac.get_device_class() == CLASS_A ? "A" : "C"); + _ctrl_flags &= ~TX_DONE_FLAG; + _ctrl_flags |= RETRY_EXHAUSTED_FLAG; + } else { + _ctrl_flags |= TX_DONE_FLAG; + + uint8_t prev_QOS_level = _loramac.get_prev_QOS_level(); + uint8_t QOS_level = _loramac.get_QOS_level(); + + if (QOS_level > LORAWAN_DEFAULT_QOS && (prev_QOS_level == QOS_level)) { + if (_qos_cnt < QOS_level) { + const int ret = _queue->call(this, &LoRaWANStack::state_controller, + DEVICE_STATE_SCHEDULING); + MBED_ASSERT(ret != 0); + (void)ret; + _qos_cnt++; + tr_info("QOS: repeated transmission #%d queued", _qos_cnt); + state_machine_run_to_completion(); + return; + } + } + } + + _loramac.post_process_mcps_req(); + make_tx_metadata_available(); state_controller(DEVICE_STATE_STATUS_CHECK); + state_machine_run_to_completion(); } void LoRaWANStack::handle_scheduling_failure(void) @@ -621,16 +690,18 @@ void LoRaWANStack::handle_scheduling_failure(void) state_machine_run_to_completion(); } + void LoRaWANStack::process_reception(const uint8_t *const payload, uint16_t size, int16_t rssi, int8_t snr) { _device_current_state = DEVICE_STATE_RECEIVING; + _ctrl_flags &= ~MSG_RECVD_FLAG; + _ctrl_flags &= ~TX_DONE_FLAG; + _ctrl_flags &= ~RETRY_EXHAUSTED_FLAG; _loramac.on_radio_rx_done(payload, size, rssi, snr); - make_rx_metadata_available(); - if (_loramac.get_mlme_confirmation()->pending) { _loramac.post_process_mlme_request(); mlme_confirm_handler(); @@ -646,36 +717,10 @@ void LoRaWANStack::process_reception(const uint8_t *const payload, uint16_t size return; } - // if the outgoing message was of CONFIRMED type - 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. 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() && - _loramac.get_current_slot() != RX_SLOT_WIN_1) { - 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 if (_loramac.get_device_class() == CLASS_A) { - // handle UNCONFIRMED case here, RX slots were turned off due to - // valid packet reception. For Class C, an outgoing UNCONFIRMED message - // gets its handling in process_transmission. - _loramac.post_process_mcps_req(); - _ctrl_flags |= TX_DONE_FLAG; - state_controller(DEVICE_STATE_STATUS_CHECK); - } + make_rx_metadata_available(); + + // Post process transmission in response to the reception + post_process_tx_with_reception(); // handle any pending MCPS indication if (_loramac.get_mcps_indication()->pending) { @@ -684,11 +729,8 @@ void LoRaWANStack::process_reception(const uint8_t *const payload, uint16_t size state_controller(DEVICE_STATE_STATUS_CHECK); } - // change the state only if a TX cycle completes for Class A - // For class C it's not needed as it will already be in receiving - // state, no matter if the TX cycle completed or not. - if (!(_ctrl_flags & TX_ONGOING_FLAG)) { - // we are done here, update the state + // complete the cycle only if TX_DONE_FLAG is set + if (_ctrl_flags & TX_DONE_FLAG) { state_machine_run_to_completion(); } @@ -729,18 +771,7 @@ void LoRaWANStack::process_reception_timeout(bool is_timeout) * never occurs. */ if (slot == RX_SLOT_WIN_2) { - _loramac.post_process_mcps_req(); - - 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); - state_machine_run_to_completion(); + post_process_tx_no_reception(); } } @@ -1091,8 +1122,7 @@ void LoRaWANStack::process_shutdown_state(lorawan_status_t &op_status) _lw_session.active = false; _device_current_state = DEVICE_STATE_SHUTDOWN; op_status = LORAWAN_STATUS_DEVICE_OFF; - _ctrl_flags &= ~CONNECTED_FLAG; - _ctrl_flags &= ~CONN_IN_PROGRESS_FLAG; + _ctrl_flags = 0; send_event_to_application(DISCONNECTED); } @@ -1108,20 +1138,15 @@ void LoRaWANStack::process_status_check_state() // 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); _loramac.reset_ongoing_tx(); mcps_confirm_handler(); } else if (_device_current_state == DEVICE_STATE_RECEIVING) { - 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. + if ((_ctrl_flags & TX_DONE_FLAG) || (_ctrl_flags & RETRY_EXHAUSTED_FLAG)) { _ctrl_flags &= ~TX_DONE_FLAG; - _ctrl_flags &= ~TX_ONGOING_FLAG; + _ctrl_flags &= ~RETRY_EXHAUSTED_FLAG; _loramac.set_tx_ongoing(false); _loramac.reset_ongoing_tx(); // if an automatic uplink is ongoing, we should not send a TX_DONE @@ -1153,7 +1178,6 @@ void LoRaWANStack::process_scheduling_state(lorawan_status_t &op_status) op_status = _loramac.send_ongoing_tx(); if (op_status == LORAWAN_STATUS_OK) { - _ctrl_flags |= TX_ONGOING_FLAG; _ctrl_flags &= ~TX_DONE_FLAG; _loramac.set_tx_ongoing(true); _device_current_state = DEVICE_STATE_SENDING; diff --git a/features/lorawan/LoRaWANStack.h b/features/lorawan/LoRaWANStack.h index d417108b88..6c5ecafd74 100644 --- a/features/lorawan/LoRaWANStack.h +++ b/features/lorawan/LoRaWANStack.h @@ -483,9 +483,11 @@ private: void make_tx_metadata_available(void); void make_rx_metadata_available(void); - void handle_ack_expiry_for_class_c(void); void handle_scheduling_failure(void); + void post_process_tx_with_reception(void); + void post_process_tx_no_reception(void); + private: LoRaMac _loramac; radio_events_t radio_events; @@ -497,6 +499,7 @@ private: lorawan_tx_metadata _tx_metadata; lorawan_rx_metadata _rx_metadata; uint8_t _num_retry; + uint8_t _qos_cnt; uint32_t _ctrl_flags; uint8_t _app_port; bool _link_check_requested; diff --git a/features/lorawan/lorastack/mac/LoRaMac.cpp b/features/lorawan/lorastack/mac/LoRaMac.cpp index b6a9ff238f..fa039f7a37 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.cpp +++ b/features/lorawan/lorastack/mac/LoRaMac.cpp @@ -78,7 +78,8 @@ LoRaMac::LoRaMac() _mlme_confirmation(), _is_nwk_joined(false), _continuous_rx2_window_open(false), - _device_class(CLASS_A) + _device_class(CLASS_A), + _prev_qos_level(LORAWAN_DEFAULT_QOS) { _params.keys.dev_eui = NULL; _params.keys.app_eui = NULL; @@ -173,6 +174,9 @@ void LoRaMac::post_process_mcps_req() if (_params.is_ul_frame_counter_fixed == false) { _params.ul_frame_counter++; _params.adr_ack_counter++; + if (_params.sys_params.nb_trans > 1) { + _mcps_confirmation.nb_retries = _params.ul_nb_rep_counter; + } } } } @@ -514,10 +518,6 @@ void LoRaMac::handle_data_frame(const uint8_t *const payload, return; } - // message is intended for us and MIC have passed, stop RX2 Window - // Spec: 3.3.4 Receiver Activity during the receive windows - _lora_time.stop(_params.timers.rx_window2_timer); - _mcps_confirmation.ack_received = false; _mcps_indication.is_ack_recvd = false; _mcps_indication.pending = true; @@ -567,6 +567,8 @@ void LoRaMac::handle_data_frame(const uint8_t *const payload, tr_debug("Discarding duplicate frame"); _mcps_indication.pending = false; _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED; + + return; } } else if (msg_type == FRAME_TYPE_DATA_UNCONFIRMED_DOWN) { _params.is_srv_ack_requested = false; @@ -584,6 +586,19 @@ void LoRaMac::handle_data_frame(const uint8_t *const payload, _params.dl_frame_counter = downlink_counter; } + // message is intended for us and MIC have passed, stop RX2 Window + // Spec: 3.3.4 Receiver Activity during the receive windows + if (get_current_slot() == RX_SLOT_WIN_1) { + _lora_time.stop(_params.timers.rx_window2_timer); + } else { + _lora_time.stop(_params.timers.rx_window1_timer); + _lora_time.stop(_params.timers.rx_window2_timer); + } + + if (_device_class == CLASS_C) { + _lora_time.stop(_rx2_closure_timer_for_class_c); + } + if (_params.is_node_ack_requested && fctrl.bits.ack) { _mcps_confirmation.ack_received = fctrl.bits.ack; _mcps_indication.is_ack_recvd = fctrl.bits.ack; @@ -629,17 +644,32 @@ void LoRaMac::on_radio_tx_done(lorawan_time_t timestamp) _lora_phy->put_radio_to_sleep(); } + if ((_mcps_confirmation.req_type == MCPS_UNCONFIRMED) + && (_params.sys_params.nb_trans > 1)) { + _params.ul_nb_rep_counter++; + MBED_ASSERT(_params.ul_nb_rep_counter <= _params.sys_params.nb_trans); + } + if (_params.is_rx_window_enabled == true) { lorawan_time_t time_diff = _lora_time.get_current_time() - timestamp; // start timer after which rx1_window will get opened _lora_time.start(_params.timers.rx_window1_timer, _params.rx_window1_delay - time_diff); - if (_device_class != CLASS_C) { - _lora_time.start(_params.timers.rx_window2_timer, - _params.rx_window2_delay - time_diff); + // start timer after which rx2_window will get opened + _lora_time.start(_params.timers.rx_window2_timer, + _params.rx_window2_delay - time_diff); + + // If class C and an Unconfirmed messgae is outgoing, + // this will start a timer which will invoke rx2 would be + // closure handler + if (get_device_class() == CLASS_C) { + _lora_time.start(_rx2_closure_timer_for_class_c, + (_params.rx_window2_delay - time_diff) + + _params.rx_window2_config.window_timeout); } + // start timer after which ack wait will timeout (for Confirmed messages) if (_params.is_node_ack_requested) { _lora_time.start(_params.timers.ack_timeout_timer, (_params.rx_window2_delay - time_diff) + @@ -663,6 +693,7 @@ void LoRaMac::on_radio_rx_done(const uint8_t *const payload, uint16_t size, int16_t rssi, int8_t snr) { if (_device_class == CLASS_C && !_continuous_rx2_window_open) { + _lora_time.stop(_rx2_closure_timer_for_class_c); open_rx2_window(); } else if (_device_class != CLASS_C){ _lora_time.stop(_params.timers.rx_window1_timer); @@ -704,6 +735,7 @@ void LoRaMac::on_radio_tx_timeout(void) { _lora_time.stop(_params.timers.rx_window1_timer); _lora_time.stop(_params.timers.rx_window2_timer); + _lora_time.stop(_rx2_closure_timer_for_class_c); _lora_time.stop(_params.timers.ack_timeout_timer); if (_device_class == CLASS_C) { @@ -717,16 +749,19 @@ void LoRaMac::on_radio_tx_timeout(void) _mac_commands.clear_command_buffer(); - _mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter; + if (_mcps_confirmation.req_type == MCPS_CONFIRMED) { + _mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter; + } else { + _mcps_confirmation.nb_retries = _params.ul_nb_rep_counter; + } + _mcps_confirmation.ack_received = false; _mcps_confirmation.tx_toa = 0; } void LoRaMac::on_radio_rx_timeout(bool is_timeout) { - if (_device_class == CLASS_C && !_continuous_rx2_window_open) { - open_rx2_window(); - } else { + if (_device_class == CLASS_A) { _lora_phy->put_radio_to_sleep(); } @@ -905,12 +940,6 @@ void LoRaMac::on_ack_timeout_timer_event(void) Lock lock(*this); if (_params.ack_timeout_retry_counter > _params.max_ack_timeout_retries) { - if (get_device_class() == CLASS_C) { - // no need to use EventQueue as LoRaWANStack and LoRaMac are always - // in same context - _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR; - _ack_expiry_handler_for_class_c.call(); - } return; } @@ -1280,7 +1309,7 @@ int16_t LoRaMac::prepare_ongoing_tx(const uint8_t port, if (flags & MSG_PROPRIETARY_FLAG) { _ongoing_tx_msg.type = MCPS_PROPRIETARY; _ongoing_tx_msg.fport = port; - _ongoing_tx_msg.nb_trials = 1; + _ongoing_tx_msg.nb_trials = _params.sys_params.nb_trans; // a proprietary frame only includes an MHDR field which contains MTYPE field. // Everything else is at the discretion of the implementer fopts_len = 0; @@ -1365,10 +1394,12 @@ device_class_t LoRaMac::get_device_class() const } void LoRaMac::set_device_class(const device_class_t &device_class, - mbed::Callbackack_expiry_handler) + mbed::Callbackrx2_would_be_closure_handler) { _device_class = device_class; - _ack_expiry_handler_for_class_c = ack_expiry_handler; + _rx2_would_be_closure_for_class_c = rx2_would_be_closure_handler; + + _lora_time.init(_rx2_closure_timer_for_class_c, _rx2_would_be_closure_for_class_c); if (CLASS_A == _device_class) { tr_debug("Changing device class to -> CLASS_A"); @@ -1744,6 +1775,8 @@ lorawan_status_t LoRaMac::initialize(EventQueue *queue, _ev_queue = queue; _scheduling_failure_handler = scheduling_failure_handler; + _rx2_closure_timer_for_class_c.callback = NULL; + _rx2_closure_timer_for_class_c.timer_id = -1; _channel_plan.activate_channelplan_subsystem(_lora_phy); @@ -1757,7 +1790,7 @@ lorawan_status_t LoRaMac::initialize(EventQueue *queue, _params.timers.aggregated_timeoff = 0; _lora_phy->reset_to_default_values(&_params, true); - _params.sys_params.retry_num = 1; + _params.sys_params.nb_trans = 1; reset_mac_parameters(); @@ -1937,3 +1970,18 @@ void LoRaMac::bind_phy(LoRaPHY &phy) { _lora_phy = &phy; } + +uint8_t LoRaMac::get_QOS_level() +{ + if (_prev_qos_level != _params.sys_params.nb_trans) { + _prev_qos_level = _params.sys_params.nb_trans; + } + + return _params.sys_params.nb_trans; +} + +uint8_t LoRaMac::get_prev_QOS_level() +{ + return _prev_qos_level; +} + diff --git a/features/lorawan/lorastack/mac/LoRaMac.h b/features/lorawan/lorastack/mac/LoRaMac.h index 8ec5a8793f..c04f8db406 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.h +++ b/features/lorawan/lorastack/mac/LoRaMac.h @@ -295,10 +295,11 @@ public: /** * @brief set_device_class Sets active device class. * @param device_class Device class to use. - * @param ack_expiry_handler callback function to inform about ack expiry + * @param rx2_would_be_closure_handler callback function to inform about + * would be closure of RX2 window */ void set_device_class(const device_class_t &device_class, - mbed::Callbackack_expiry_handler); + mbed::Callbackrx2_would_be_closure_handler); /** * @brief setup_link_check_request Adds link check request command @@ -404,6 +405,17 @@ public: */ rx_slot_t get_current_slot(void); + /** + * Indicates what level of QOS is set by network server. QOS level is set + * in response to a LinkADRReq for UNCONFIRMED messages + */ + uint8_t get_QOS_level(void); + + /** + *Indicates level of QOS used for the previous outgoing message + */ + uint8_t get_prev_QOS_level(void); + /** * These locks trample through to the upper layers and make * the stack thread safe. @@ -632,10 +644,16 @@ private: /** * Class C doesn't timeout in RX2 window as it is a continuous window. - * We use this callback to inform the LoRaWANStack controller that the - * system cannot do more retries. + * We use this callback to inform the LoRaWANStack controller that we did + * not receive a downlink in a time equal to normal Class A type RX2 + * window timeout. This marks a 'would-be' closure for RX2, actual RX2 is + * not closed. Mostly network servers will send right at the beginning of + * RX2 window if they have something to send. So if we didn't receive anything + * in the time period equal to would be RX2 delay (which is a function of + * uplink message length and data rate), we will invoke this callback to let + * the upper layer know. */ - mbed::Callback _ack_expiry_handler_for_class_c; + mbed::Callback _rx2_would_be_closure_for_class_c; /** * Transmission is async, i.e., a call to schedule_tx() may be deferred to @@ -645,6 +663,8 @@ private: */ mbed::Callback _scheduling_failure_handler; + timer_event_t _rx2_closure_timer_for_class_c; + /** * Structure to hold MCPS indication data. */ @@ -672,6 +692,8 @@ private: bool _continuous_rx2_window_open; device_class_t _device_class; + + uint8_t _prev_qos_level; }; #endif // MBED_LORAWAN_MAC_H__ diff --git a/features/lorawan/lorastack/phy/LoRaPHY.cpp b/features/lorawan/lorastack/phy/LoRaPHY.cpp index 7a8462fa8e..0d52898c54 100644 --- a/features/lorawan/lorastack/phy/LoRaPHY.cpp +++ b/features/lorawan/lorastack/phy/LoRaPHY.cpp @@ -1031,7 +1031,7 @@ uint8_t LoRaPHY::link_ADR_request(adr_req_params_t *link_adr_req, verify_params.adr_enabled = link_adr_req->adr_enabled; verify_params.current_datarate = link_adr_req->current_datarate; verify_params.current_tx_power = link_adr_req->current_tx_power; - verify_params.current_nb_rep = link_adr_req->current_nb_rep; + verify_params.current_nb_rep = link_adr_req->current_nb_trans; verify_params.datarate = adr_settings.datarate; verify_params.tx_power = adr_settings.tx_power; diff --git a/features/lorawan/lorastack/phy/LoRaPHYAU915.cpp b/features/lorawan/lorastack/phy/LoRaPHYAU915.cpp index 357d382844..0f90509446 100644 --- a/features/lorawan/lorastack/phy/LoRaPHYAU915.cpp +++ b/features/lorawan/lorastack/phy/LoRaPHYAU915.cpp @@ -479,7 +479,7 @@ uint8_t LoRaPHYAU915::link_ADR_request(adr_req_params_t* params, verify_params.nb_rep = adr_settings.nb_rep; verify_params.current_datarate = params->current_datarate; verify_params.current_tx_power = params->current_tx_power; - verify_params.current_nb_rep = params->current_nb_rep; + verify_params.current_nb_rep = params->current_nb_trans; verify_params.channel_mask = temp_channel_masks; diff --git a/features/lorawan/lorastack/phy/LoRaPHYCN470.cpp b/features/lorawan/lorastack/phy/LoRaPHYCN470.cpp index 777677765a..af87d0b913 100644 --- a/features/lorawan/lorastack/phy/LoRaPHYCN470.cpp +++ b/features/lorawan/lorastack/phy/LoRaPHYCN470.cpp @@ -510,7 +510,7 @@ uint8_t LoRaPHYCN470::link_ADR_request(adr_req_params_t* params, verify_params.nb_rep = adr_settings.nb_rep; verify_params.current_datarate = params->current_datarate; verify_params.current_tx_power = params->current_tx_power; - verify_params.current_nb_rep = params->current_nb_rep; + verify_params.current_nb_rep = params->current_nb_trans; verify_params.channel_mask = temp_channel_masks; diff --git a/features/lorawan/lorastack/phy/LoRaPHYUS915.cpp b/features/lorawan/lorastack/phy/LoRaPHYUS915.cpp index a531de055b..a39431c0b4 100644 --- a/features/lorawan/lorastack/phy/LoRaPHYUS915.cpp +++ b/features/lorawan/lorastack/phy/LoRaPHYUS915.cpp @@ -517,7 +517,7 @@ uint8_t LoRaPHYUS915::link_ADR_request(adr_req_params_t* params, verify_params.nb_rep = adr_settings.nb_rep; verify_params.current_datarate = params->current_datarate; verify_params.current_tx_power = params->current_tx_power; - verify_params.current_nb_rep = params->current_nb_rep; + verify_params.current_nb_rep = params->current_nb_trans; verify_params.channel_mask = temp_channel_masks; // Verify the parameters and update, if necessary diff --git a/features/lorawan/lorastack/phy/lora_phy_ds.h b/features/lorawan/lorastack/phy/lora_phy_ds.h index 79af3e9d31..7b7081c539 100644 --- a/features/lorawan/lorastack/phy/lora_phy_ds.h +++ b/features/lorawan/lorastack/phy/lora_phy_ds.h @@ -329,9 +329,10 @@ typedef struct { */ int8_t current_tx_power; /*! - * The current number of repetitions. + * The current number of repetitions for obtaining a QOS level set by + * NS (applicable only to unconfirmed messages). */ - uint8_t current_nb_rep; + uint8_t current_nb_trans; } adr_req_params_t; /** diff --git a/features/lorawan/system/lorawan_data_structures.h b/features/lorawan/system/lorawan_data_structures.h index e3f2418820..545ab111ba 100644 --- a/features/lorawan/system/lorawan_data_structures.h +++ b/features/lorawan/system/lorawan_data_structures.h @@ -70,6 +70,8 @@ typedef uint32_t lorawan_time_t; */ #define LORAMAC_PHY_MAXPAYLOAD 255 +#define LORAWAN_DEFAULT_QOS 1 + /** * * Default user application maximum data size for transmission @@ -187,9 +189,10 @@ typedef struct { */ uint32_t join_accept_delay2; /*! - * The number of uplink messages repetitions (confirmed messages only). + * The number of uplink messages repetitions for QOS set by network server + * in LinkADRReq mac command (unconfirmed messages only). */ - uint8_t retry_num; + uint8_t nb_trans; /*! * The datarate offset between uplink and downlink on first window. */ @@ -874,6 +877,9 @@ typedef struct { */ int8_t data_rate; /*! + * + * For CONFIRMED Messages: + * * The number of trials to transmit the frame, if the LoRaMAC layer did not * receive an acknowledgment. The MAC performs a datarate adaptation * according to the LoRaWAN Specification V1.0.2, chapter 18.4, as in @@ -892,6 +898,13 @@ typedef struct { * * Note that if nb_trials is set to 1 or 2, the MAC will not decrease * the datarate, if the LoRaMAC layer did not receive an acknowledgment. + * + * For UNCONFIRMED Messages: + * + * Provides a certain QOS level set by network server in LinkADRReq MAC + * command. The device will transmit the given UNCONFIRMED message nb_trials + * time with same frame counter until a downlink is received. Standard defined + * range is 1:15. Data rates will NOT be adapted according to chapter 18.4. */ uint8_t nb_trials; From c9d674121278b302097bf59ecf5acd77c7601ecd Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Thu, 20 Sep 2018 14:54:54 +0300 Subject: [PATCH 4/7] Making sure that RX slots open after state change After transmission we should change the state before invoking opening of slots as we may start receiving in the rx slots and the state would suddenly change from SENDING to RECEIVING without going through the ACK_WAIT state (in case of CONFIRMED messages). Tests show that after this slight adjustment, our number of ack retries have significantly reduced. --- features/lorawan/LoRaWANStack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/features/lorawan/LoRaWANStack.cpp b/features/lorawan/LoRaWANStack.cpp index 6301a67f46..93a0e34759 100644 --- a/features/lorawan/LoRaWANStack.cpp +++ b/features/lorawan/LoRaWANStack.cpp @@ -577,7 +577,6 @@ void LoRaWANStack::process_transmission_timeout() void LoRaWANStack::process_transmission(void) { tr_debug("Transmission completed"); - _loramac.on_radio_tx_done(_tx_timestamp); if (_device_current_state == DEVICE_STATE_JOINING) { _device_current_state = DEVICE_STATE_AWAITING_JOIN_ACCEPT; @@ -589,6 +588,8 @@ void LoRaWANStack::process_transmission(void) _device_current_state = DEVICE_STATE_AWAITING_ACK; } } + + _loramac.on_radio_tx_done(_tx_timestamp); } void LoRaWANStack::post_process_tx_with_reception() From 19e87ceab9f536f133785a0051a625184c8ec0be Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Thu, 20 Sep 2018 14:58:40 +0300 Subject: [PATCH 5/7] TX post-process for CONFIRMED UL in no-reception case The idea behind the method post_process_no_reception() is to post process any outgoing TX but we shouldn't do that if a CONFIRMED message is outgoing and there are still some retries left. --- features/lorawan/LoRaWANStack.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/features/lorawan/LoRaWANStack.cpp b/features/lorawan/LoRaWANStack.cpp index 93a0e34759..444f977d96 100644 --- a/features/lorawan/LoRaWANStack.cpp +++ b/features/lorawan/LoRaWANStack.cpp @@ -647,7 +647,6 @@ void LoRaWANStack::post_process_tx_with_reception() void LoRaWANStack::post_process_tx_no_reception() { if (_loramac.get_mcps_confirmation()->req_type == MCPS_CONFIRMED) { - _loramac.post_process_mcps_req(); if (_loramac.continue_sending_process()) { _ctrl_flags &= ~TX_DONE_FLAG; _ctrl_flags &= ~RETRY_EXHAUSTED_FLAG; From 0d3ec26b4a0fe67a6f6991040f305740dc879577 Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Fri, 21 Sep 2018 15:50:04 +0300 Subject: [PATCH 6/7] Proper handling of RX1 frequency in rx_config Previously, we weren't filling in RX1 frequecny in rx_window1_config structure. However, everything worked as in LoRaPHY::rx_config() API there was a check which filled in correct RX1 frequency. Now we are filling in RX1 freq. properly while we are computing parameters for RX1 window. --- features/lorawan/lorastack/mac/LoRaMac.cpp | 4 ++-- features/lorawan/lorastack/phy/LoRaPHY.cpp | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/features/lorawan/lorastack/mac/LoRaMac.cpp b/features/lorawan/lorastack/mac/LoRaMac.cpp index fa039f7a37..47bf516d75 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.cpp +++ b/features/lorawan/lorastack/mac/LoRaMac.cpp @@ -903,7 +903,7 @@ void LoRaMac::open_rx1_window(void) _lora_phy->rx_config(&_params.rx_window1_config); _lora_phy->handle_receive(); - tr_debug("Opening RX1 Window"); + tr_debug("RX1 slot open, Freq = %lu", _params.rx_window1_config.frequency); } void LoRaMac::open_rx2_window() @@ -932,7 +932,7 @@ void LoRaMac::open_rx2_window() _lora_phy->handle_receive(); _params.rx_slot = _params.rx_window2_config.rx_slot; - tr_debug("Opening RX2 Window, Frequency = %lu", _params.rx_window2_config.frequency); + tr_debug("RX2 slot open, Freq = %lu", _params.rx_window2_config.frequency); } void LoRaMac::on_ack_timeout_timer_event(void) diff --git a/features/lorawan/lorastack/phy/LoRaPHY.cpp b/features/lorawan/lorastack/phy/LoRaPHY.cpp index 0d52898c54..322446896c 100644 --- a/features/lorawan/lorastack/phy/LoRaPHY.cpp +++ b/features/lorawan/lorastack/phy/LoRaPHY.cpp @@ -849,6 +849,11 @@ void LoRaPHY::compute_rx_win_params(int8_t datarate, uint8_t min_rx_symbols, ((uint32_t *)phy_params.bandwidths.table)[rx_conf_params->datarate]); } + if (rx_conf_params->rx_slot == RX_SLOT_WIN_1) { + rx_conf_params->frequency = phy_params.channels.channel_list[rx_conf_params->channel].frequency; + } + + get_rx_window_params(t_symbol, min_rx_symbols, rx_error, RADIO_WAKEUP_TIME, &rx_conf_params->window_timeout, &rx_conf_params->window_offset); } From bc976c6545c6304e307728d86fc11338719a7308 Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Fri, 21 Sep 2018 15:53:45 +0300 Subject: [PATCH 7/7] Fixing coverity findings A couple of the coverity analysis findings are being treated here. For the rest there will be a separate PR. --- features/lorawan/lorastack/mac/LoRaMac.cpp | 1 - features/lorawan/lorastack/phy/LoRaPHY.cpp | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/features/lorawan/lorastack/mac/LoRaMac.cpp b/features/lorawan/lorastack/mac/LoRaMac.cpp index 47bf516d75..c9cb2f67a0 100644 --- a/features/lorawan/lorastack/mac/LoRaMac.cpp +++ b/features/lorawan/lorastack/mac/LoRaMac.cpp @@ -1862,7 +1862,6 @@ uint8_t LoRaMac::get_max_possible_tx_size(uint8_t fopts_len) max_possible_payload_size = allowed_frm_payload_size - fopts_len; } else { max_possible_payload_size = allowed_frm_payload_size; - fopts_len = 0; _mac_commands.clear_command_buffer(); _mac_commands.clear_repeat_buffer(); } diff --git a/features/lorawan/lorastack/phy/LoRaPHY.cpp b/features/lorawan/lorastack/phy/LoRaPHY.cpp index 322446896c..6016401a6f 100644 --- a/features/lorawan/lorastack/phy/LoRaPHY.cpp +++ b/features/lorawan/lorastack/phy/LoRaPHY.cpp @@ -36,7 +36,8 @@ SPDX-License-Identifier: BSD-3-Clause #define CHANNELS_IN_MASK 16 LoRaPHY::LoRaPHY() - : _radio(NULL) + : _radio(NULL), + _lora_time(NULL) { memset(&phy_params, 0, sizeof(phy_params)); }