Nicolas Borla
/
BBR_1Ebene
BBR 1 Ebene
Diff: mbed-os/features/lorawan/LoRaWANStack.cpp
- Revision:
- 0:fbdae7e6d805
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-os/features/lorawan/LoRaWANStack.cpp Mon May 14 11:29:06 2018 +0000 @@ -0,0 +1,1221 @@ +/** + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech + ___ _____ _ ___ _ _____ ___ ___ ___ ___ +/ __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| +\__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| +|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| +embedded.connectivity.solutions=============== + +Description: LoRaWAN stack layer that controls both MAC and PHY underneath + +License: Revised BSD License, see LICENSE.TXT file include in the project + +Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jaeckle ( STACKFORCE ) + + +Copyright (c) 2017, Arm Limited and affiliates. + +SPDX-License-Identifier: BSD-3-Clause +*/ + +#include <string.h> +#include <stdlib.h> +#include "platform/Callback.h" +#include "events/EventQueue.h" + +#include "LoRaWANStack.h" +#if defined(FEATURE_COMMON_PAL) +#include "mbed_trace.h" +#define TRACE_GROUP "LSTK" +#else +#define tr_debug(...) (void(0)) //dummies if feature common pal is not added +#define tr_info(...) (void(0)) //dummies if feature common pal is not added +#define tr_error(...) (void(0)) //dummies if feature common pal is not added +#define tr_warn(...) (void(0)) //dummies if feature common pal is not added +#endif //defined(FEATURE_COMMON_PAL) + +#define INVALID_PORT 0xFF +#define MAX_CONFIRMED_MSG_RETRIES 255 +/** + * Control flags for transient states + */ +#define IDLE_FLAG 0x00000000 +#define TX_ONGOING_FLAG 0x00000001 +#define MSG_RECVD_FLAG 0x00000002 +#define CONNECTED_FLAG 0x00000004 +#define USING_OTAA_FLAG 0x00000008 +#define TX_DONE_FLAG 0x00000010 + +using namespace mbed; +using namespace events; + +#if defined(LORAWAN_COMPLIANCE_TEST) + #if (MBED_CONF_LORA_PHY == 0 || MBED_CONF_LORA_PHY == 4 || MBED_CONF_LORA_PHY == 6 || MBED_CONF_LORA_PHY == 7) + #define LORAWAN_COMPLIANCE_TEST_DATA_SIZE 16 + #elif (MBED_CONF_LORA_PHY == 1 || MBED_CONF_LORA_PHY == 2 || MBED_CONF_LORA_PHY == 8 || MBED_CONF_LORA_PHY == 9) + #define LORAWAN_COMPLIANCE_TEST_DATA_SIZE 11 + #else + #error "Must set LoRa PHY layer parameters." + #endif +#endif + +/***************************************************************************** + * Constructor * + ****************************************************************************/ +LoRaWANStack::LoRaWANStack() +: _loramac(), + _device_current_state(DEVICE_STATE_NOT_INITIALIZED), + _lw_session(), + _tx_msg(), + _rx_msg(), + _num_retry(1), + _ctrl_flags(IDLE_FLAG), + _app_port(INVALID_PORT), + _link_check_requested(false), + _automatic_uplink_ongoing(false), + _ready_for_rx(true), + _queue(NULL) +{ +#ifdef MBED_CONF_LORA_APP_PORT + if (is_port_valid(MBED_CONF_LORA_APP_PORT)) { + _app_port = MBED_CONF_LORA_APP_PORT; + } else { + tr_error("User defined port in .json is illegal."); + } +#endif +} + +/***************************************************************************** + * Public Methods * + ****************************************************************************/ +void LoRaWANStack::bind_radio_driver(LoRaRadio& radio) +{ + radio_events.tx_done = mbed::callback(this, &LoRaWANStack::tx_interrupt_handler); + radio_events.rx_done = mbed::callback(this, &LoRaWANStack::rx_interrupt_handler); + radio_events.rx_error = mbed::callback(this, &LoRaWANStack::rx_error_interrupt_handler); + radio_events.tx_timeout = mbed::callback(this, &LoRaWANStack::tx_timeout_interrupt_handler); + radio_events.rx_timeout = mbed::callback(this, &LoRaWANStack::rx_timeout_interrupt_handler); + + _loramac.bind_radio_driver(radio); + + radio.lock(); + radio.init_radio(&radio_events); + radio.unlock(); +} + +lorawan_status_t LoRaWANStack::initialize_mac_layer(EventQueue *queue) +{ + if(!queue) { + return LORAWAN_STATUS_PARAMETER_INVALID; + } + + tr_debug("Initializing MAC layer"); + _queue = queue; + + return state_controller(DEVICE_STATE_IDLE); +} + +lorawan_status_t LoRaWANStack::set_lora_callbacks(const lorawan_app_callbacks_t *callbacks) +{ + if (!callbacks || !callbacks->events) { + return LORAWAN_STATUS_PARAMETER_INVALID; + } + + _callbacks.events = callbacks->events; + + if (callbacks->link_check_resp) { + _callbacks.link_check_resp = callbacks->link_check_resp; + } + + if (callbacks->battery_level) { + _callbacks.battery_level = callbacks->battery_level; + } + + return LORAWAN_STATUS_OK; +} + +lorawan_status_t LoRaWANStack::connect() +{ + if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + + lorawan_status_t status = _loramac.prepare_join(NULL, MBED_CONF_LORA_OVER_THE_AIR_ACTIVATION); + + if (LORAWAN_STATUS_OK != status) { + return status; + } + + return handle_connect(MBED_CONF_LORA_OVER_THE_AIR_ACTIVATION); +} + +lorawan_status_t LoRaWANStack::connect(const lorawan_connect_t &connect) +{ + if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + + if (!(connect.connect_type == LORAWAN_CONNECTION_OTAA) && + !(connect.connect_type == LORAWAN_CONNECTION_ABP)) { + return LORAWAN_STATUS_PARAMETER_INVALID; + } + bool is_otaa = (connect.connect_type == LORAWAN_CONNECTION_OTAA); + + lorawan_status_t status = _loramac.prepare_join(&connect, is_otaa); + + if (LORAWAN_STATUS_OK != status) { + return status; + } + + return handle_connect(is_otaa); +} + +lorawan_status_t LoRaWANStack::add_channels(const lorawan_channelplan_t &channel_plan) +{ + if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + + return _loramac.add_channel_plan(channel_plan); +} + +lorawan_status_t LoRaWANStack::remove_a_channel(uint8_t channel_id) +{ + if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + + return _loramac.remove_single_channel(channel_id); +} + +lorawan_status_t LoRaWANStack::drop_channel_list() +{ + if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + + return _loramac.remove_channel_plan(); +} + +lorawan_status_t LoRaWANStack::get_enabled_channels(lorawan_channelplan_t& channel_plan) +{ + if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + + return _loramac.get_channel_plan(channel_plan); +} + +lorawan_status_t LoRaWANStack::set_confirmed_msg_retry(uint8_t count) +{ + if (count >= MAX_CONFIRMED_MSG_RETRIES) { + return LORAWAN_STATUS_PARAMETER_INVALID; + } + + _num_retry = count; + + return LORAWAN_STATUS_OK; +} + +lorawan_status_t LoRaWANStack::set_channel_data_rate(uint8_t data_rate) +{ + if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) + { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + + return _loramac.set_channel_data_rate(data_rate); +} + + +lorawan_status_t LoRaWANStack::enable_adaptive_datarate(bool adr_enabled) +{ + if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) + { + tr_error("Stack not initialized!"); + return LORAWAN_STATUS_NOT_INITIALIZED; + } + _loramac.enable_adaptive_datarate(adr_enabled); + return LORAWAN_STATUS_OK; +} + +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) +{ + if (!null_allowed && !data) { + return LORAWAN_STATUS_PARAMETER_INVALID; + } + // add a link check request with normal data, until the application + // explicitly removes it. + if (_link_check_requested) { + set_link_check_request(); + } + + if (!_lw_session.active) { + return LORAWAN_STATUS_NO_ACTIVE_SESSIONS; + } + + if(_loramac.tx_ongoing()) { + return LORAWAN_STATUS_WOULD_BLOCK; + } + +#if defined(LORAWAN_COMPLIANCE_TEST) + if (_compliance_test.running) { + return LORAWAN_STATUS_COMPLIANCE_TEST_ON; + } +#endif + + lorawan_status_t status; + + if (_loramac.nwk_joined() == false) { + return LORAWAN_STATUS_NO_NETWORK_JOINED; + } + + status = set_application_port(port, allow_port_0); + + if (status != LORAWAN_STATUS_OK) { + tr_error("Illegal application port definition."); + return status; + } + + if (flags == 0 || + (flags & MSG_FLAG_MASK) == (MSG_CONFIRMED_FLAG|MSG_UNCONFIRMED_FLAG)) { + tr_error("CONFIRMED and UNCONFIRMED are mutually exclusive for send()"); + return LORAWAN_STATUS_PARAMETER_INVALID; + } + + int16_t len = _loramac.prepare_ongoing_tx(port, data, length, flags, _num_retry); + + status = state_controller(DEVICE_STATE_SCHEDULING); + + // send user the length of data which is scheduled now. + // user should take care of the pending data. + return (status == LORAWAN_STATUS_OK) ? len : (int16_t) status; +} + +int16_t LoRaWANStack::handle_rx(uint8_t* data, uint16_t length, uint8_t& port, int& flags, bool validate_params) +{ + if (!_lw_session.active) { + return LORAWAN_STATUS_NO_ACTIVE_SESSIONS; + } + + // No messages to read. + if (!_rx_msg.receive_ready) { + return LORAWAN_STATUS_WOULD_BLOCK; + } + +#if defined(LORAWAN_COMPLIANCE_TEST) + if (_compliance_test.running) { + return LORAWAN_STATUS_COMPLIANCE_TEST_ON; + } +#endif + + if (data == NULL || length == 0) { + return LORAWAN_STATUS_PARAMETER_INVALID; + } + + int received_flags = convert_to_msg_flag(_rx_msg.msg.mcps_indication.type); + if (validate_params) { + // Check received message port and flags match with the ones requested by user + received_flags &= MSG_FLAG_MASK; + + if (_rx_msg.msg.mcps_indication.port != port || !(flags & received_flags)) { + return LORAWAN_STATUS_WOULD_BLOCK; + } + } + + // Report values back to user + port = _rx_msg.msg.mcps_indication.port; + flags = received_flags; + + const uint8_t *base_ptr = _rx_msg.msg.mcps_indication.buffer; + uint16_t base_size = _rx_msg.msg.mcps_indication.buffer_size; + bool read_complete = false; + + // check the length of received message whether we can fit into user + // buffer completely or not + if (_rx_msg.msg.mcps_indication.buffer_size > length && + _rx_msg.prev_read_size == 0) { + // we can't fit into user buffer. Invoke counter measures + _rx_msg.pending_size = _rx_msg.msg.mcps_indication.buffer_size - length; + base_size = length; + _rx_msg.prev_read_size = base_size; + memcpy(data, base_ptr, base_size); + } else if (_rx_msg.prev_read_size == 0) { + _rx_msg.pending_size = 0; + _rx_msg.prev_read_size = 0; + memcpy(data, base_ptr, base_size); + read_complete = true; + } + + // If its the pending read then we should copy only the remaining part of + // the buffer. Due to checks above, in case of a pending read, this block + // will be the only one to get invoked + if (_rx_msg.pending_size > 0 && _rx_msg.prev_read_size > 0) { + memcpy(data, base_ptr+_rx_msg.prev_read_size, base_size); + } + + // we are done handing over received buffer to user. check if there is + // anything pending. If not, memset the buffer to zero and indicate + // that no read is in progress + if (read_complete) { + _rx_msg.msg.mcps_indication.buffer = NULL; + _rx_msg.msg.mcps_indication.buffer_size = 0; + _rx_msg.receive_ready = false; + } + + return base_size; +} + +lorawan_status_t LoRaWANStack::set_link_check_request() +{ + _link_check_requested = true; + if (!_callbacks.link_check_resp) { + tr_error("Must assign a callback function for link check request. "); + return LORAWAN_STATUS_PARAMETER_INVALID; + } + + _loramac.setup_link_check_request(); + return LORAWAN_STATUS_OK; +} + +void LoRaWANStack::remove_link_check_request() +{ + _link_check_requested = false; +} + +lorawan_status_t LoRaWANStack::shutdown() +{ + return state_controller(DEVICE_STATE_SHUTDOWN); +} + +lorawan_status_t LoRaWANStack::set_device_class(const device_class_t& device_class) +{ + if (device_class == CLASS_B) { + return LORAWAN_STATUS_UNSUPPORTED; + } + _loramac.set_device_class(device_class); + return LORAWAN_STATUS_OK; +} + +/***************************************************************************** + * Interrupt handlers * + ****************************************************************************/ +void LoRaWANStack::tx_interrupt_handler(void) +{ + const int ret = _queue->call(this, &LoRaWANStack::process_transmission); + MBED_ASSERT(ret != 0); + (void)ret; +} + +void LoRaWANStack::rx_interrupt_handler(const uint8_t *payload, uint16_t size, + int16_t rssi, int8_t snr) +{ + if (!_ready_for_rx || size > sizeof _rx_payload) { + return; + } + + _ready_for_rx = false; + memcpy(_rx_payload, payload, size); + + const uint8_t *ptr = _rx_payload; + const int ret = _queue->call(this, &LoRaWANStack::process_reception, + ptr, size, rssi, snr); + MBED_ASSERT(ret != 0); + (void)ret; +} + +void LoRaWANStack::rx_error_interrupt_handler(void) +{ + const int ret = _queue->call(this, &LoRaWANStack::process_reception_timeout, + false); + MBED_ASSERT(ret != 0); + (void)ret; +} + +void LoRaWANStack::tx_timeout_interrupt_handler(void) +{ + const int ret = _queue->call(this, &LoRaWANStack::process_transmission_timeout); + MBED_ASSERT(ret != 0); + (void)ret; +} + +void LoRaWANStack::rx_timeout_interrupt_handler(void) +{ + const int ret = _queue->call(this, &LoRaWANStack::process_reception_timeout, + true); + MBED_ASSERT(ret != 0); + (void)ret; +} + +/***************************************************************************** + * Processors for deferred interrupts * + ****************************************************************************/ +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; + state_controller(DEVICE_STATE_STATUS_CHECK); + state_machine_run_to_completion(); +} + +void LoRaWANStack::process_transmission(void) +{ + _loramac.on_radio_tx_done(); + tr_debug("Transmission completed"); + + 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; + _device_current_state = DEVICE_STATE_AWAITING_ACK; + return; + } + + // 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; + + // In Class C, reception timeout never happens, so we handle the state + // progression here + if (_loramac.get_device_class() == CLASS_C) { + _loramac.post_process_mcps_req(); + 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) +{ + _device_current_state = DEVICE_STATE_RECEIVING; + _ctrl_flags &= ~MSG_RECVD_FLAG; + + _loramac.on_radio_rx_done(payload, size, rssi, snr); + + if (_loramac.get_mlme_confirmation()->pending) { + _loramac.post_process_mlme_request(); + mlme_confirm_handler(); + } + + if (_loramac.nwk_joined()) { + if (_loramac.get_mcps_indication()->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 (_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; + state_controller(DEVICE_STATE_STATUS_CHECK); + } + } else { + // handle UNCONFIRMED case here, RX slots were turned off due to + // valid packet reception + _loramac.post_process_mcps_req(); + _ctrl_flags |= TX_DONE_FLAG; + state_controller(DEVICE_STATE_STATUS_CHECK); + } + + // handle any pending MCPS indication + if (_loramac.get_mcps_indication()->pending) { + _loramac.post_process_mcps_ind(); + _ctrl_flags |= MSG_RECVD_FLAG; + 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 + state_machine_run_to_completion(); + } + + if (_loramac.get_mlme_indication()->pending) { + tr_debug("MLME Indication pending"); + _loramac.post_process_mlme_ind(); + tr_debug("Automatic uplink requested"); + mlme_indication_handler(); + } + } + + _ready_for_rx = true; +} + +void LoRaWANStack::process_reception_timeout(bool is_timeout) +{ + // when is_timeout == false, a CRC error took place in the received frame + // we treat that erroneous frame as no frame received at all, hence handle + // it exactly as we would handle timeout + rx_slot_t slot = _loramac.on_radio_rx_timeout(is_timeout); + + if (slot == RX_SLOT_WIN_2 && !_loramac.nwk_joined()) { + state_controller(DEVICE_STATE_JOINING); + return; + } + + /** + * LoRaWAN Specification 1.0.2. Section 3.3.6 + * Main point: + * We indicate successful transmission + * 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. + */ + 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; + } + + state_controller(DEVICE_STATE_STATUS_CHECK); + state_machine_run_to_completion(); + } +} + +/***************************************************************************** + * Private methods * + ****************************************************************************/ +bool LoRaWANStack::is_port_valid(const uint8_t port, bool allow_port_0) +{ + //Application should not use reserved and illegal port numbers. + if (port == 0) { + return allow_port_0; + } else { + return true; + } +} + +lorawan_status_t LoRaWANStack::set_application_port(const uint8_t port, bool allow_port_0) +{ + if (is_port_valid(port, allow_port_0)) { + _app_port = port; + return LORAWAN_STATUS_OK; + } + + return LORAWAN_STATUS_PORT_INVALID; +} + +void LoRaWANStack::state_machine_run_to_completion() +{ + if (_loramac.get_device_class() == CLASS_C) { + _device_current_state = DEVICE_STATE_RECEIVING; + return; + } + + _device_current_state = DEVICE_STATE_IDLE; +} + +void LoRaWANStack::send_event_to_application(const lorawan_event_t event) const +{ + if (_callbacks.events) { + const int ret = _queue->call(_callbacks.events, event); + MBED_ASSERT(ret != 0); + (void)ret; + } +} + +void LoRaWANStack::send_automatic_uplink_message(const uint8_t port) +{ + const int16_t ret = handle_tx(port, NULL, 0, MSG_CONFIRMED_FLAG, true, true); + if (ret < 0) { + send_event_to_application(AUTOMATIC_UPLINK_ERROR); + } +} + +int LoRaWANStack::convert_to_msg_flag(const mcps_type_t type) +{ + int msg_flag = MSG_UNCONFIRMED_FLAG; + switch (type) { + case MCPS_UNCONFIRMED: + msg_flag = MSG_UNCONFIRMED_FLAG; + break; + + case MCPS_CONFIRMED: + msg_flag = MSG_CONFIRMED_FLAG; + break; + + case MCPS_MULTICAST: + msg_flag = MSG_MULTICAST_FLAG; + break; + + case MCPS_PROPRIETARY: + msg_flag = MSG_PROPRIETARY_FLAG; + break; + + default: + tr_error("Unknown message type!"); + MBED_ASSERT(0); + } + + return msg_flag; +} + +lorawan_status_t LoRaWANStack::handle_connect(bool is_otaa) +{ + if (is_otaa) { + tr_debug("Initiating OTAA"); + + // In 1.0.2 spec, counters are always set to zero for new connection. + // This section is common for both normal and + // connection restore at this moment. Will change in future with 1.1 support. + _lw_session.downlink_counter = 0; + _lw_session.uplink_counter = 0; + _ctrl_flags |= USING_OTAA_FLAG; + } else { + // If current state is SHUTDOWN, device may be trying to re-establish + // communication. In case of ABP specification is meddled about frame counters. + // It says to reset counters to zero but there is no mechanism to tell the + // network server that the device was disconnected or restarted. + // At the moment, this implementation does not support a non-volatile + // memory storage. + //_lw_session.downlink_counter; //Get from NVM + //_lw_session.uplink_counter; //Get from NVM + + tr_debug("Initiating ABP"); + tr_debug("Frame Counters. UpCnt=%lu, DownCnt=%lu", + _lw_session.uplink_counter, _lw_session.downlink_counter); + _ctrl_flags &= ~USING_OTAA_FLAG; + } + + return state_controller(DEVICE_STATE_CONNECTING); +} + +void LoRaWANStack::mlme_indication_handler() +{ + if (_loramac.get_mlme_indication()->indication_type == MLME_SCHEDULE_UPLINK) { + // The MAC signals that we shall provide an uplink as soon as possible +#if MBED_CONF_LORA_AUTOMATIC_UPLINK_MESSAGE + _automatic_uplink_ongoing = true; + tr_debug("mlme indication: sending empty uplink to port 0 to acknowledge MAC commands..."); + send_automatic_uplink_message(0); +#else + + send_event_to_application(UPLINK_REQUIRED); +#endif + return; + } + + tr_error("Unknown MLME Indication type."); +} + +void LoRaWANStack::mlme_confirm_handler() +{ + if (_loramac.get_mlme_confirmation()->req_type == MLME_LINK_CHECK) { + if (_loramac.get_mlme_confirmation()->status == LORAMAC_EVENT_INFO_STATUS_OK) { +#if defined(LORAWAN_COMPLIANCE_TEST) + if (_compliance_test.running == true) { + _compliance_test.link_check = true; + _compliance_test.demod_margin = _loramac.get_mlme_confirmation()->demod_margin; + _compliance_test.nb_gateways = _loramac.get_mlme_confirmation()->nb_gateways; + } else +#endif + { + if (_callbacks.link_check_resp) { + const int ret = _queue->call(_callbacks.link_check_resp, + _loramac.get_mlme_confirmation()->demod_margin, + _loramac.get_mlme_confirmation()->nb_gateways); + MBED_ASSERT(ret != 0); + (void)ret; + } + } + } + } else if (_loramac.get_mlme_confirmation()->req_type == MLME_JOIN) { + if (_loramac.get_mlme_confirmation()->status == LORAMAC_EVENT_INFO_STATUS_OK) { + state_controller(DEVICE_STATE_CONNECTED); + } else { + tr_error("Joining error: %d", _loramac.get_mlme_confirmation()->status); + _device_current_state = DEVICE_STATE_AWAITING_JOIN_ACCEPT; + state_controller(DEVICE_STATE_JOINING); + } + } +} + +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; + } + + // failure case + tr_error("mcps_confirmation: Error code = %d", _loramac.get_mcps_confirmation()->status); + + if (_loramac.get_mcps_confirmation()->status == LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT) { + send_event_to_application(TX_TIMEOUT); + return; + } + + // if no ack was received, send TX_ERROR + send_event_to_application(TX_ERROR); +} + +void LoRaWANStack::mcps_indication_handler() +{ + const loramac_mcps_indication_t *mcps_indication = _loramac.get_mcps_indication(); + if (mcps_indication->status != LORAMAC_EVENT_INFO_STATUS_OK) { + tr_error("RX_ERROR: mcps_indication status = %d", mcps_indication->status); + send_event_to_application(RX_ERROR); + return; + } + + _lw_session.downlink_counter = mcps_indication->dl_frame_counter; + +#if defined(LORAWAN_COMPLIANCE_TEST) + if (_compliance_test.running == true) { + _compliance_test.downlink_counter++; + } +#endif + + if (mcps_indication->port == 224) { +#if defined(LORAWAN_COMPLIANCE_TEST) + tr_debug("Compliance test command received."); + compliance_test_handler(mcps_indication); +#else + tr_info("Compliance test disabled."); +#endif + } else { + if (mcps_indication->is_data_recvd) { + // Valid message arrived. + _rx_msg.type = LORAMAC_RX_MCPS_INDICATION; + _rx_msg.msg.mcps_indication.buffer_size = mcps_indication->buffer_size; + _rx_msg.msg.mcps_indication.port = mcps_indication->port; + _rx_msg.msg.mcps_indication.buffer = mcps_indication->buffer; + _rx_msg.msg.mcps_indication.type = mcps_indication->type; + + // Notify application about received frame.. + tr_debug("Packet Received %d bytes", + _rx_msg.msg.mcps_indication.buffer_size); + _rx_msg.receive_ready = true; + send_event_to_application(RX_DONE); + } + + /* + * If fPending bit is set we try to generate an empty packet + * with CONFIRMED flag set. We always set a CONFIRMED flag so + * that we could retry a certain number of times if the uplink + * failed for some reason + * or + * Class C and node received a confirmed message so we need to + * send an empty packet to acknowledge the message. + * This scenario is unspecified by LoRaWAN 1.0.2 specification, + * but version 1.1.0 says that network SHALL not send any new + * confirmed messages until ack has been sent + */ + if ((_loramac.get_device_class() != CLASS_C && mcps_indication->fpending_status) + || + (_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; + send_automatic_uplink_message(mcps_indication->port); +#else + send_event_to_application(UPLINK_REQUIRED); +#endif + } + } +} + +lorawan_status_t LoRaWANStack::state_controller(device_states_t new_state) +{ + lorawan_status_t status = LORAWAN_STATUS_OK; + + switch (new_state) { + case DEVICE_STATE_IDLE: + process_idle_state(status); + break; + case DEVICE_STATE_CONNECTING: + process_connecting_state(status); + break; + case DEVICE_STATE_JOINING: + process_joining_state(status); + break; + case DEVICE_STATE_CONNECTED: + process_connected_state(); + break; + case DEVICE_STATE_SCHEDULING: + process_scheduling_state(status); + break; + case DEVICE_STATE_STATUS_CHECK: + process_status_check_state(); + break; + case DEVICE_STATE_SHUTDOWN: + process_shutdown_state(status); + break; + default: + tr_debug("state_controller: Unknown state!"); + status = LORAWAN_STATUS_SERVICE_UNKNOWN; + break; + } + + return status; +} + +void LoRaWANStack::process_shutdown_state(lorawan_status_t& op_status) +{ + /** + * Remove channels + * Radio will be put to sleep by the APIs underneath + */ + drop_channel_list(); + _loramac.disconnect(); + _lw_session.active = false; + _device_current_state = DEVICE_STATE_SHUTDOWN; + op_status = LORAWAN_STATUS_DEVICE_OFF; + _ctrl_flags &= ~CONNECTED_FLAG; + send_event_to_application(DISCONNECTED); +} + +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 + // 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. + _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) { + // for CONFIRMED case, ack validity is already checked + _ctrl_flags &= ~TX_DONE_FLAG; + _ctrl_flags &= ~TX_ONGOING_FLAG; + _loramac.set_tx_ongoing(false); + _loramac.reset_ongoing_tx(); + // if an automatic uplink is ongoing, we should not send a TX_DONE + // event to application + if (_automatic_uplink_ongoing) { + _automatic_uplink_ongoing = false; + } else { + mcps_confirm_handler(); + } + } + + // handle any received data and send event accordingly + if (_ctrl_flags & MSG_RECVD_FLAG) { + _ctrl_flags &= ~MSG_RECVD_FLAG; + mcps_indication_handler(); + } + } +} + +void LoRaWANStack::process_scheduling_state(lorawan_status_t& op_status) +{ + if (_device_current_state != DEVICE_STATE_IDLE) { + if (_device_current_state != DEVICE_STATE_RECEIVING + && _loramac.get_device_class() != CLASS_C) { + op_status = LORAWAN_STATUS_BUSY; + return; + } + } + + 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; + } +} + +void LoRaWANStack::process_joining_state(lorawan_status_t& op_status) +{ + if (_device_current_state == DEVICE_STATE_CONNECTING) { + _device_current_state = DEVICE_STATE_JOINING; + tr_debug("Sending Join Request ..."); + op_status = _loramac.join(true); + return; + } + + if (_device_current_state == DEVICE_STATE_AWAITING_JOIN_ACCEPT) { + _device_current_state = DEVICE_STATE_JOINING; + // retry join + bool can_continue = _loramac.continue_joining_process(); + + if (!can_continue) { + send_event_to_application(JOIN_FAILURE); + _device_current_state = DEVICE_STATE_IDLE; + return; + } + } +} + +void LoRaWANStack::process_connected_state() +{ + if (_ctrl_flags & USING_OTAA_FLAG) { + tr_debug("OTAA Connection OK!"); + } + + _lw_session.active = true; + send_event_to_application(CONNECTED); + _ctrl_flags |= CONNECTED_FLAG; + + _device_current_state = DEVICE_STATE_IDLE; +} + +void LoRaWANStack::process_connecting_state(lorawan_status_t& op_status) +{ + if (_device_current_state != DEVICE_STATE_IDLE + && + _device_current_state != DEVICE_STATE_SHUTDOWN) { + op_status = LORAWAN_STATUS_BUSY; + return; + } + + if (_ctrl_flags & CONNECTED_FLAG) { + tr_debug("Already connected"); + op_status = LORAWAN_STATUS_OK; + return; + } + + _device_current_state = DEVICE_STATE_CONNECTING; + + if (_ctrl_flags & USING_OTAA_FLAG) { + process_joining_state(op_status); + return; + } + + op_status = _loramac.join(false); + tr_debug("ABP connection OK."); + process_connected_state(); +} + +void LoRaWANStack::process_idle_state(lorawan_status_t& op_status) +{ + if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) { + _device_current_state = DEVICE_STATE_IDLE; + process_uninitialized_state(op_status); + return; + } + + _device_current_state = DEVICE_STATE_IDLE; + op_status = LORAWAN_STATUS_OK; +} + +void LoRaWANStack::process_uninitialized_state(lorawan_status_t& op_status) +{ + op_status = _loramac.initialize(_queue); + + if (op_status == LORAWAN_STATUS_OK) { + _device_current_state = DEVICE_STATE_IDLE; + } +} + +#if defined(LORAWAN_COMPLIANCE_TEST) + +lorawan_status_t LoRaWANStack::send_compliance_test_frame_to_mac() +{ + loramac_compliance_test_req_t test_req; + + //TODO: What if the port is not 224 ??? + if (_compliance_test.app_port == 224) { + // Clear any normal message stuff before compliance test. + memset(&test_req, 0, sizeof(test_req)); + + if (_compliance_test.link_check == true) { + _compliance_test.link_check = false; + _compliance_test.state = 1; + test_req.f_buffer_size = 3; + test_req.f_buffer[0] = 5; + test_req.f_buffer[1] = _compliance_test.demod_margin; + test_req.f_buffer[2] = _compliance_test.nb_gateways; + } else { + switch (_compliance_test.state) { + case 4: + _compliance_test.state = 1; + test_req.f_buffer_size = _compliance_test.app_data_size; + test_req.f_buffer[0] = _compliance_test.app_data_buffer[0]; + for(uint8_t i = 1; i < MIN(_compliance_test.app_data_size, MBED_CONF_LORA_TX_MAX_SIZE); ++i) { + test_req.f_buffer[i] = _compliance_test.app_data_buffer[i]; + } + break; + case 1: + test_req.f_buffer_size = 2; + test_req.f_buffer[0] = _compliance_test.downlink_counter >> 8; + test_req.f_buffer[1] = _compliance_test.downlink_counter; + break; + } + } + } + + //TODO: If port is not 224, this might not work! + //Is there a test case where same _tx_msg's buffer would be used, when port is not 224??? + if (!_compliance_test.is_tx_confirmed) { + test_req.type = MCPS_UNCONFIRMED; + test_req.fport = _compliance_test.app_port; + test_req.nb_trials = 1; + test_req.data_rate = _loramac.get_default_tx_datarate(); + + tr_info("Transmit unconfirmed compliance test frame %d bytes.", test_req.f_buffer_size); + + for (uint8_t i = 0; i < test_req.f_buffer_size; ++i) { + tr_info("Byte %d, data is 0x%x", i+1, ((uint8_t*)test_req.f_buffer)[i]); + } + } else if (_compliance_test.is_tx_confirmed) { + test_req.type = MCPS_CONFIRMED; + test_req.fport = _compliance_test.app_port; + test_req.nb_trials = _num_retry; + test_req.data_rate = _loramac.get_default_tx_datarate(); + + tr_info("Transmit confirmed compliance test frame %d bytes.", test_req.f_buffer_size); + + for (uint8_t i = 0; i < test_req.f_buffer_size; ++i) { + tr_info("Byte %d, data is 0x%x", i+1, ((uint8_t*)test_req.f_buffer)[i]); + } + } else { + return LORAWAN_STATUS_SERVICE_UNKNOWN; + } + + return _loramac.test_request(&test_req); +} + +void LoRaWANStack::compliance_test_handler(loramac_mcps_indication_t *mcps_indication) +{ + if (_compliance_test.running == false) { + // Check compliance test enable command (i) + if ((mcps_indication->buffer_size == 4) && + (mcps_indication->buffer[0] == 0x01) && + (mcps_indication->buffer[1] == 0x01) && + (mcps_indication->buffer[2] == 0x01) && + (mcps_indication->buffer[3] == 0x01)) { + _compliance_test.is_tx_confirmed = false; + _compliance_test.app_port = 224; + _compliance_test.app_data_size = 2; + _compliance_test.downlink_counter = 0; + _compliance_test.link_check = false; + _compliance_test.demod_margin = 0; + _compliance_test.nb_gateways = 0; + _compliance_test.running = true; + _compliance_test.state = 1; + + _loramac.enable_adaptive_datarate(true); + +#if MBED_CONF_LORA_PHY == 0 + _loramac.LoRaMacTestSetDutyCycleOn(false); +#endif + //5000ms + _loramac.LoRaMacSetTxTimer(5000); + + //TODO: Should we call lora_state_machine here instead of just setting the state? + _device_current_state = DEVICE_STATE_COMPLIANCE_TEST; +// lora_state_machine(DEVICE_STATE_COMPLIANCE_TEST); + tr_debug("Compliance test activated."); + } + } else { + _compliance_test.state = mcps_indication->buffer[0]; + switch (_compliance_test.state) { + case 0: // Check compliance test disable command (ii) + _compliance_test.is_tx_confirmed = true; + _compliance_test.app_port = MBED_CONF_LORA_APP_PORT; + _compliance_test.app_data_size = LORAWAN_COMPLIANCE_TEST_DATA_SIZE; + _compliance_test.downlink_counter = 0; + _compliance_test.running = false; + + _loramac.enable_adaptive_datarate(MBED_CONF_LORA_ADR_ON); + +#if MBED_CONF_LORA_PHY == 0 + _loramac.LoRaMacTestSetDutyCycleOn(MBED_CONF_LORA_DUTY_CYCLE_ON); +#endif + // Go to idle state after compliance test mode. + tr_debug("Compliance test disabled."); + _loramac.LoRaMacStopTxTimer(); + + // Clear any compliance test message stuff before going back to normal operation. + _loramac.reset_ongoing_tx(); + lora_state_machine(DEVICE_STATE_IDLE); + break; + case 1: // (iii, iv) + _compliance_test.app_data_size = 2; + break; + case 2: // Enable confirmed messages (v) + _compliance_test.is_tx_confirmed = true; + _compliance_test.state = 1; + break; + case 3: // Disable confirmed messages (vi) + _compliance_test.is_tx_confirmed = false; + _compliance_test.state = 1; + break; + case 4: // (vii) + _compliance_test.app_data_size = mcps_indication->buffer_size; + + _compliance_test.app_data_buffer[0] = 4; + for(uint8_t i = 1; i < MIN(_compliance_test.app_data_size, LORAMAC_PHY_MAXPAYLOAD); ++i) { + _compliance_test.app_data_buffer[i] = mcps_indication->buffer[i] + 1; + } + + send_compliance_test_frame_to_mac(); + break; + case 5: // (viii) + _loramac.setup_link_check_request(); + break; + case 6: // (ix) + // Disable TestMode and revert back to normal operation + _compliance_test.is_tx_confirmed = true; + _compliance_test.app_port = MBED_CONF_LORA_APP_PORT; + _compliance_test.app_data_size = LORAWAN_COMPLIANCE_TEST_DATA_SIZE; + _compliance_test.downlink_counter = 0; + _compliance_test.running = false; + + _loramac.enable_adaptive_datarate(MBED_CONF_LORA_ADR_ON); + +#if MBED_CONF_LORA_PHY == 0 + _loramac.LoRaMacTestSetDutyCycleOn(MBED_CONF_LORA_DUTY_CYCLE_ON); +#endif + _loramac.join(true); + break; + case 7: // (x) + if (mcps_indication->buffer_size == 3) { + loramac_mlme_req_t mlme_req; + mlme_req.type = MLME_TXCW; + mlme_req.cw_tx_mode.timeout = (uint16_t)((mcps_indication->buffer[1] << 8) | mcps_indication->buffer[2]); + _loramac.mlme_request(&mlme_req); + } else if (mcps_indication->buffer_size == 7) { + loramac_mlme_req_t mlme_req; + mlme_req.type = MLME_TXCW_1; + mlme_req.cw_tx_mode.timeout = (uint16_t)((mcps_indication->buffer[1] << 8) | mcps_indication->buffer[2]); + mlme_req.cw_tx_mode.frequency = (uint32_t)((mcps_indication->buffer[3] << 16) | (mcps_indication->buffer[4] << 8) + | mcps_indication->buffer[5]) * 100; + mlme_req.cw_tx_mode.power = mcps_indication->buffer[6]; + _loramac.mlme_request(&mlme_req); + } + _compliance_test.state = 1; + break; + } + } +} +#endif +