BBR 1 Ebene

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
+