mbed-os/features/lorawan/LoRaWANStack.cpp

1267 lines
40 KiB
C++
Raw Normal View History

/**
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(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"
2018-04-10 11:04:18 +00:00
#include "LoRaWANStack.h"
#include "mbed-trace/mbed_trace.h"
#define TRACE_GROUP "LSTK"
#define INVALID_PORT 0xFF
#define MAX_CONFIRMED_MSG_RETRIES 255
#define COMPLIANCE_TESTING_PORT 224
/**
* Control flags for transient states
*/
#define IDLE_FLAG 0x00000000
#define RETRY_EXHAUSTED_FLAG 0x00000001
#define MSG_RECVD_FLAG 0x00000002
#define CONNECTED_FLAG 0x00000004
#define USING_OTAA_FLAG 0x00000008
#define TX_DONE_FLAG 0x00000010
#define CONN_IN_PROGRESS_FLAG 0x00000020
using namespace mbed;
using namespace events;
/**
* Bit mask for message flags
*/
#define MSG_FLAG_MASK 0x0F
/*****************************************************************************
* Constructor *
****************************************************************************/
LoRaWANStack::LoRaWANStack()
2018-06-12 12:11:09 +00:00
: _loramac(),
_device_current_state(DEVICE_STATE_NOT_INITIALIZED),
_lw_session(),
_tx_msg(),
_rx_msg(),
_tx_metadata(),
_rx_metadata(),
_num_retry(1),
_qos_cnt(1),
2018-06-12 12:11:09 +00:00
_ctrl_flags(IDLE_FLAG),
_app_port(INVALID_PORT),
_link_check_requested(false),
_automatic_uplink_ongoing(false),
_queue(NULL)
{
_tx_metadata.stale = true;
_rx_metadata.stale = true;
core_util_atomic_flag_clear(&_rx_payload_in_use);
#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_phy_and_radio_driver(LoRaRadio &radio, LoRaPHY &phy)
{
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);
phy.set_radio_instance(radio);
_loramac.bind_phy(phy);
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 (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
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;
_loramac.set_batterylevel_callback(callbacks->battery_level);
}
return LORAWAN_STATUS_OK;
}
lorawan_status_t LoRaWANStack::connect()
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (_ctrl_flags & CONN_IN_PROGRESS_FLAG) {
return LORAWAN_STATUS_BUSY;
}
if (_ctrl_flags & CONNECTED_FLAG) {
return LORAWAN_STATUS_ALREADY_CONNECTED;
}
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) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (_ctrl_flags & CONN_IN_PROGRESS_FLAG) {
return LORAWAN_STATUS_BUSY;
}
if (_ctrl_flags & CONNECTED_FLAG) {
return LORAWAN_STATUS_ALREADY_CONNECTED;
}
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)
Architecture rework, bug fixing & missing features MAC layer is now a class rather than being a blob. In addition to that Mac commands are now being handled in a seperate subsystem (a class of its own). In future we will do the same with othe sublayers of MAC like MLME, MCPS etc. The drive behind this exercise is to make MAC and supporting layers into an object oriented system. Major bug fixes include: - last join time inclusion in band parameters - disabling rx2 window if we missed the slot already - MLME uplink schdule hook - nbRep according to spec - maintaining datarate after successful joining - suppressing MLME requests if MAC is in TX_DELAYED state - Uplink dwell time verification Some missing features are implemented. Details are as follows. Support for LinkCheckRequet: An application API is added, add_link_check_request() to delegate a request for Link Check Request MAC command. * Application provides a callback function that needs to be called on reception of link check response. * Mac command is piggybacked with data frames. This API makes the sticky MAC command stick with the application payloads until/unless the application un-sticks the said mac command using remove_link_check_request() API. Handling fPending bit: If in the Downlink, we get the fPending bit set in fctrl octet, we attempt to send an empty message back to Network Server to open additional Receive windows. This operation is independent of the application. An RX_DONE event is queued bedore generating the said empty message. Specification does not mention what can be the type of that empty message. We have decided it to be of CONFIRMED type as it gives us an added benefit of retries if the corresponding RX slots are missed. Radio event callbacks as Mbed callbacks: radio_events_t structure has been carrying C-style callbacks which was inherited from the legacy code. These callbacks has now been changed to Mbed Callbacks that makes sure that we can be object oriented from now on.
2017-12-15 10:30:40 +00:00
{
if (_device_current_state == DEVICE_STATE_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) {
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) {
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) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
return _loramac.get_channel_plan(channel_plan);
}
lorawan_status_t LoRaWANStack::set_confirmed_msg_retry(uint8_t count)
{
if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
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) {
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) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
_loramac.enable_adaptive_datarate(adr_enabled);
return LORAWAN_STATUS_OK;
}
lorawan_status_t LoRaWANStack::stop_sending(void)
{
if (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
lorawan_status_t status = _loramac.clear_tx_pipe();
if (status == LORAWAN_STATUS_OK) {
_ctrl_flags &= ~TX_DONE_FLAG;
_loramac.set_tx_ongoing(false);
_device_current_state = DEVICE_STATE_IDLE;
return LORAWAN_STATUS_OK;
}
return status;
}
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 (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (!null_allowed && !data) {
return LORAWAN_STATUS_PARAMETER_INVALID;
}
if (!_lw_session.active) {
return LORAWAN_STATUS_NO_ACTIVE_SESSIONS;
}
if (_loramac.tx_ongoing()) {
return LORAWAN_STATUS_WOULD_BLOCK;
}
// add a link check request with normal data, until the application
// explicitly removes it.
if (_link_check_requested) {
_loramac.setup_link_check_request();
}
2018-11-14 07:05:30 +00:00
_qos_cnt = 1;
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;
}
// All the flags are mutually exclusive. In addition to that MSG_MULTICAST_FLAG cannot be
// used for uplink.
switch (flags & MSG_FLAG_MASK) {
case MSG_UNCONFIRMED_FLAG:
case MSG_CONFIRMED_FLAG:
case MSG_PROPRIETARY_FLAG:
break;
default:
tr_error("Invalid send flags");
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 (_device_current_state == DEVICE_STATE_NOT_INITIALIZED) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
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 (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;
2018-09-23 12:13:49 +00:00
if (_rx_msg.pending_size == 0) {
_rx_msg.pending_size = _rx_msg.msg.mcps_indication.buffer_size;
_rx_msg.prev_read_size = 0;
}
// check the length of received message whether we can fit into user
// buffer completely or not
2018-09-23 12:13:49 +00:00
if (_rx_msg.prev_read_size == 0 && _rx_msg.msg.mcps_indication.buffer_size <= length) {
memcpy(data, base_ptr, base_size);
read_complete = true;
2018-09-23 12:13:49 +00:00
} else if (_rx_msg.pending_size > length) {
_rx_msg.pending_size = _rx_msg.pending_size - length;
base_size = length;
memcpy(data, base_ptr + _rx_msg.prev_read_size, base_size);
2018-09-23 12:13:49 +00:00
_rx_msg.prev_read_size += base_size;
} else {
base_size = _rx_msg.pending_size;
memcpy(data, base_ptr + _rx_msg.prev_read_size, base_size);
read_complete = true;
}
if (read_complete) {
_rx_msg.msg.mcps_indication.buffer = NULL;
_rx_msg.msg.mcps_indication.buffer_size = 0;
2018-09-23 12:13:49 +00:00
_rx_msg.pending_size = 0;
_rx_msg.receive_ready = false;
}
return base_size;
}
lorawan_status_t LoRaWANStack::set_link_check_request()
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (!_callbacks.link_check_resp) {
tr_error("Must assign a callback function for link check request. ");
return LORAWAN_STATUS_PARAMETER_INVALID;
}
_link_check_requested = true;
return LORAWAN_STATUS_OK;
}
void LoRaWANStack::remove_link_check_request()
{
_link_check_requested = false;
}
lorawan_status_t LoRaWANStack::shutdown()
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
return state_controller(DEVICE_STATE_SHUTDOWN);
}
lorawan_status_t LoRaWANStack::set_device_class(const device_class_t &device_class)
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (device_class == CLASS_B) {
return LORAWAN_STATUS_UNSUPPORTED;
}
_loramac.set_device_class(device_class,
mbed::callback(this, &LoRaWANStack::post_process_tx_no_reception));
return LORAWAN_STATUS_OK;
}
lorawan_status_t LoRaWANStack::acquire_tx_metadata(lorawan_tx_metadata &tx_metadata)
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (!_tx_metadata.stale) {
tx_metadata = _tx_metadata;
_tx_metadata.stale = true;
return LORAWAN_STATUS_OK;
}
return LORAWAN_STATUS_METADATA_NOT_AVAILABLE;
}
lorawan_status_t LoRaWANStack::acquire_rx_metadata(lorawan_rx_metadata &metadata)
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (!_rx_metadata.stale) {
metadata = _rx_metadata;
_rx_metadata.stale = true;
return LORAWAN_STATUS_OK;
}
return LORAWAN_STATUS_METADATA_NOT_AVAILABLE;
}
2018-06-12 12:11:09 +00:00
lorawan_status_t LoRaWANStack::acquire_backoff_metadata(int &backoff)
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
int id = _loramac.get_backoff_timer_event_id();
if (_loramac.get_backoff_timer_event_id() > 0) {
backoff = _queue->time_left(id);
return LORAWAN_STATUS_OK;
}
backoff = -1;
return LORAWAN_STATUS_METADATA_NOT_AVAILABLE;
}
/*****************************************************************************
* Interrupt handlers *
****************************************************************************/
void LoRaWANStack::tx_interrupt_handler(void)
{
_tx_timestamp = _loramac.get_current_time();
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 (size > sizeof _rx_payload || core_util_atomic_flag_test_and_set(&_rx_payload_in_use)) {
return;
}
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_DONE_FLAG;
if (_device_current_state == DEVICE_STATE_JOINING) {
mlme_confirm_handler();
} else {
state_controller(DEVICE_STATE_STATUS_CHECK);
}
state_machine_run_to_completion();
}
void LoRaWANStack::process_transmission(void)
{
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) {
tr_debug("Awaiting ACK");
_device_current_state = DEVICE_STATE_AWAITING_ACK;
}
}
_loramac.on_radio_tx_done(_tx_timestamp);
}
void LoRaWANStack::post_process_tx_with_reception()
{
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;
_loramac.post_process_mcps_req();
make_tx_metadata_available();
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) {
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)
{
tr_error("Failed to schedule transmission");
state_controller(DEVICE_STATE_STATUS_CHECK);
state_machine_run_to_completion();
}
void LoRaWANStack::process_reception(const uint8_t *const payload, uint16_t size,
int16_t rssi, int8_t snr)
Architecture rework, bug fixing & missing features MAC layer is now a class rather than being a blob. In addition to that Mac commands are now being handled in a seperate subsystem (a class of its own). In future we will do the same with othe sublayers of MAC like MLME, MCPS etc. The drive behind this exercise is to make MAC and supporting layers into an object oriented system. Major bug fixes include: - last join time inclusion in band parameters - disabling rx2 window if we missed the slot already - MLME uplink schdule hook - nbRep according to spec - maintaining datarate after successful joining - suppressing MLME requests if MAC is in TX_DELAYED state - Uplink dwell time verification Some missing features are implemented. Details are as follows. Support for LinkCheckRequet: An application API is added, add_link_check_request() to delegate a request for Link Check Request MAC command. * Application provides a callback function that needs to be called on reception of link check response. * Mac command is piggybacked with data frames. This API makes the sticky MAC command stick with the application payloads until/unless the application un-sticks the said mac command using remove_link_check_request() API. Handling fPending bit: If in the Downlink, we get the fPending bit set in fctrl octet, we attempt to send an empty message back to Network Server to open additional Receive windows. This operation is independent of the application. An RX_DONE event is queued bedore generating the said empty message. Specification does not mention what can be the type of that empty message. We have decided it to be of CONFIRMED type as it gives us an added benefit of retries if the corresponding RX slots are missed. Radio event callbacks as Mbed callbacks: radio_events_t structure has been carrying C-style callbacks which was inherited from the legacy code. These callbacks has now been changed to Mbed Callbacks that makes sure that we can be object oriented from now on.
2017-12-15 10:30:40 +00:00
{
_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);
if (_loramac.get_mlme_confirmation()->pending) {
_loramac.post_process_mlme_request();
mlme_confirm_handler();
if (_loramac.get_mlme_confirmation()->req_type == MLME_JOIN) {
core_util_atomic_flag_clear(&_rx_payload_in_use);
return;
}
Architecture rework, bug fixing & missing features MAC layer is now a class rather than being a blob. In addition to that Mac commands are now being handled in a seperate subsystem (a class of its own). In future we will do the same with othe sublayers of MAC like MLME, MCPS etc. The drive behind this exercise is to make MAC and supporting layers into an object oriented system. Major bug fixes include: - last join time inclusion in band parameters - disabling rx2 window if we missed the slot already - MLME uplink schdule hook - nbRep according to spec - maintaining datarate after successful joining - suppressing MLME requests if MAC is in TX_DELAYED state - Uplink dwell time verification Some missing features are implemented. Details are as follows. Support for LinkCheckRequet: An application API is added, add_link_check_request() to delegate a request for Link Check Request MAC command. * Application provides a callback function that needs to be called on reception of link check response. * Mac command is piggybacked with data frames. This API makes the sticky MAC command stick with the application payloads until/unless the application un-sticks the said mac command using remove_link_check_request() API. Handling fPending bit: If in the Downlink, we get the fPending bit set in fctrl octet, we attempt to send an empty message back to Network Server to open additional Receive windows. This operation is independent of the application. An RX_DONE event is queued bedore generating the said empty message. Specification does not mention what can be the type of that empty message. We have decided it to be of CONFIRMED type as it gives us an added benefit of retries if the corresponding RX slots are missed. Radio event callbacks as Mbed callbacks: radio_events_t structure has been carrying C-style callbacks which was inherited from the legacy code. These callbacks has now been changed to Mbed Callbacks that makes sure that we can be object oriented from now on.
2017-12-15 10:30:40 +00:00
}
if (!_loramac.nwk_joined()) {
core_util_atomic_flag_clear(&_rx_payload_in_use);
return;
}
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) {
_loramac.post_process_mcps_ind();
_ctrl_flags |= MSG_RECVD_FLAG;
state_controller(DEVICE_STATE_STATUS_CHECK);
}
// complete the cycle only if TX_DONE_FLAG is set
if (_ctrl_flags & TX_DONE_FLAG) {
state_machine_run_to_completion();
}
// 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");
mlme_indication_handler();
}
core_util_atomic_flag_clear(&_rx_payload_in_use);
Architecture rework, bug fixing & missing features MAC layer is now a class rather than being a blob. In addition to that Mac commands are now being handled in a seperate subsystem (a class of its own). In future we will do the same with othe sublayers of MAC like MLME, MCPS etc. The drive behind this exercise is to make MAC and supporting layers into an object oriented system. Major bug fixes include: - last join time inclusion in band parameters - disabling rx2 window if we missed the slot already - MLME uplink schdule hook - nbRep according to spec - maintaining datarate after successful joining - suppressing MLME requests if MAC is in TX_DELAYED state - Uplink dwell time verification Some missing features are implemented. Details are as follows. Support for LinkCheckRequet: An application API is added, add_link_check_request() to delegate a request for Link Check Request MAC command. * Application provides a callback function that needs to be called on reception of link check response. * Mac command is piggybacked with data frames. This API makes the sticky MAC command stick with the application payloads until/unless the application un-sticks the said mac command using remove_link_check_request() API. Handling fPending bit: If in the Downlink, we get the fPending bit set in fctrl octet, we attempt to send an empty message back to Network Server to open additional Receive windows. This operation is independent of the application. An RX_DONE event is queued bedore generating the said empty message. Specification does not mention what can be the type of that empty message. We have decided it to be of CONFIRMED type as it gives us an added benefit of retries if the corresponding RX slots are missed. Radio event callbacks as Mbed callbacks: radio_events_t structure has been carrying C-style callbacks which was inherited from the legacy code. These callbacks has now been changed to Mbed Callbacks that makes sure that we can be object oriented from now on.
2017-12-15 10:30:40 +00:00
}
void LoRaWANStack::process_reception_timeout(bool is_timeout)
{
rx_slot_t slot = _loramac.get_current_slot();
// 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
_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.
*
* NOTE: This code block doesn't get hit for Class C as in Class C, RX2 timeout
* never occurs.
*/
if (slot == RX_SLOT_WIN_2) {
post_process_tx_no_reception();
}
}
/*****************************************************************************
* Private methods *
****************************************************************************/
void LoRaWANStack::make_tx_metadata_available(void)
{
_tx_metadata.stale = false;
_tx_metadata.channel = _loramac.get_mcps_confirmation()->channel;
_tx_metadata.data_rate = _loramac.get_mcps_confirmation()->data_rate;
_tx_metadata.tx_power = _loramac.get_mcps_confirmation()->tx_power;
_tx_metadata.tx_toa = _loramac.get_mcps_confirmation()->tx_toa;
_tx_metadata.nb_retries = _loramac.get_mcps_confirmation()->nb_retries;
}
void LoRaWANStack::make_rx_metadata_available(void)
{
_rx_metadata.stale = false;
_rx_metadata.rx_datarate = _loramac.get_mcps_indication()->rx_datarate;
_rx_metadata.rssi = _loramac.get_mcps_indication()->rssi;
_rx_metadata.snr = _loramac.get_mcps_indication()->snr;
_rx_metadata.channel = _loramac.get_mcps_indication()->channel;
_rx_metadata.rx_toa = _loramac.get_mcps_indication()->rx_toa;
}
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;
2018-10-29 13:57:53 +00:00
} else if (port == COMPLIANCE_TESTING_PORT) {
#if !defined(LORAWAN_COMPLIANCE_TEST)
return false;
#endif
} else {
return true;
}
// fallback for compliance testing port if LORAWAN_COMPLIANCE_TEST
// was defined
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)
{
// we will silently ignore the automatic uplink event if the user is already
// sending something
const int16_t ret = handle_tx(port, NULL, 0, MSG_CONFIRMED_FLAG, true, true);
if (ret == LORAWAN_STATUS_WOULD_BLOCK) {
_automatic_uplink_ongoing = false;
} else if (ret < 0) {
tr_debug("Failed to generate AUTOMATIC UPLINK, error code = %d", ret);
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)
{
_ctrl_flags |= CONN_IN_PROGRESS_FLAG;
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...");
const uint8_t port = 0;
const int ret = _queue->call(this, &LoRaWANStack::send_automatic_uplink_message, port);
MBED_ASSERT(ret != 0);
(void)ret;
#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 (_callbacks.link_check_resp) {
const int ret = _queue->call(
2018-10-29 13:57:53 +00:00
_callbacks.link_check_resp,
_loramac.get_mlme_confirmation()->demod_margin,
_loramac.get_mlme_confirmation()->nb_gateways);
MBED_ASSERT(ret != 0);
(void) ret;
}
}
}
if (_loramac.get_mlme_confirmation()->req_type == MLME_JOIN) {
switch (_loramac.get_mlme_confirmation()->status) {
case LORAMAC_EVENT_INFO_STATUS_OK:
state_controller(DEVICE_STATE_CONNECTED);
break;
case LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL:
// fatal error
_device_current_state = DEVICE_STATE_IDLE;
tr_error("Joining abandoned: CRYPTO_ERROR");
send_event_to_application(CRYPTO_ERROR);
break;
case LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT:
// fatal error
_device_current_state = DEVICE_STATE_IDLE;
tr_error("Joining abandoned: Radio failed to transmit");
send_event_to_application(TX_TIMEOUT);
break;
default:
// non-fatal, retry if possible
_device_current_state = DEVICE_STATE_AWAITING_JOIN_ACCEPT;
state_controller(DEVICE_STATE_JOINING);
}
}
}
void LoRaWANStack::mcps_confirm_handler()
{
switch (_loramac.get_mcps_confirmation()->status) {
case LORAMAC_EVENT_INFO_STATUS_OK:
_lw_session.uplink_counter = _loramac.get_mcps_confirmation()->ul_frame_counter;
send_event_to_application(TX_DONE);
break;
case LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT:
tr_error("Fatal Error, Radio failed to transmit");
send_event_to_application(TX_TIMEOUT);
break;
case LORAMAC_EVENT_INFO_STATUS_TX_DR_PAYLOAD_SIZE_ERROR:
send_event_to_application(TX_SCHEDULING_ERROR);
break;
default:
// if no ack was received after enough retries, send TX_ERROR
send_event_to_application(TX_ERROR);
}
}
void LoRaWANStack::mcps_indication_handler()
{
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;
/**
* Check port, if it's compliance testing port and the compliance testing is
* not enabled, give up silently
*/
if (mcps_indication->port == COMPLIANCE_TESTING_PORT) {
#if !defined(LORAWAN_COMPLIANCE_TEST)
return;
#endif
}
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, Port=%d",
_rx_msg.msg.mcps_indication.buffer_size,
mcps_indication->port);
_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
2018-10-29 13:57:53 +00:00
&& mcps_indication->type == MCPS_CONFIRMED)) {
#if (MBED_CONF_LORA_AUTOMATIC_UPLINK_MESSAGE)
2018-10-29 13:57:53 +00:00
// 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
}
}
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:
2018-09-23 12:13:49 +00:00
//Because this is internal function only coding error causes this
2018-09-24 18:09:13 +00:00
tr_error("Unknown state: %d:", new_state);
2018-09-23 12:13:49 +00:00
MBED_ASSERT(false);
}
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 = 0;
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) {
// If there was a successful transmission, this block gets a kick after
// RX2 slot is exhausted. We may or may not have a successful UNCONFIRMED transmission
// here. In CONFIRMED case this block is invoked only
// when the MAX number of retries are exhausted, i.e., only error
// case will fall here. Moreover, it will happen for Class A only.
// Another possibility is the case when the stack fails to schedule a
// deferred transmission and a scheduling failure handler is invoked.
_ctrl_flags &= ~TX_DONE_FLAG;
_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 & RETRY_EXHAUSTED_FLAG)) {
_ctrl_flags &= ~TX_DONE_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
// 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_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 &&
2018-10-29 13:57:53 +00:00
_loramac.get_current_slot() != RX_SLOT_WIN_1) {
_device_current_state = DEVICE_STATE_JOINING;
// retry join
bool can_continue = _loramac.continue_joining_process();
if (!can_continue) {
_ctrl_flags &= ~CONN_IN_PROGRESS_FLAG;
send_event_to_application(JOIN_FAILURE);
_device_current_state = DEVICE_STATE_IDLE;
return;
}
}
}
void LoRaWANStack::process_connected_state()
{
_ctrl_flags |= CONNECTED_FLAG;
_ctrl_flags &= ~CONN_IN_PROGRESS_FLAG;
if (_ctrl_flags & USING_OTAA_FLAG) {
tr_debug("OTAA Connection OK!");
}
_lw_session.active = true;
send_event_to_application(CONNECTED);
_device_current_state = DEVICE_STATE_IDLE;
}
void LoRaWANStack::process_connecting_state(lorawan_status_t &op_status)
{
MBED_ASSERT(_device_current_state == DEVICE_STATE_IDLE ||
_device_current_state == DEVICE_STATE_SHUTDOWN);
_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, mbed::callback(this,
&LoRaWANStack::handle_scheduling_failure));
if (op_status == LORAWAN_STATUS_OK) {
_device_current_state = DEVICE_STATE_IDLE;
}
}