mbed-os/features/lorawan/LoRaWANStack.cpp

1651 lines
54 KiB
C++

/**
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(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"
#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
#define REJOIN_IN_PROGRESS 0x00000040
using namespace mbed;
using namespace events;
/**
* Bit mask for message flags
*/
#define MSG_FLAG_MASK 0x0F
/*****************************************************************************
* Constructor *
****************************************************************************/
LoRaWANStack::LoRaWANStack()
: _loramac(),
_device_current_state(DEVICE_STATE_NOT_INITIALIZED),
_lw_session(),
_tx_msg(),
_rx_msg(),
_tx_metadata(),
_rx_metadata(),
_num_retry(1),
_qos_cnt(1),
_ctrl_flags(IDLE_FLAG),
_app_port(INVALID_PORT),
_link_check_requested(false),
_reset_ind_requested(false),
_rekey_ind_needed(false),
_rekey_ind_counter(0),
_device_mode_ind_needed(false),
_device_mode_ind_ongoing(false),
_new_class_type(CLASS_A),
_automatic_uplink_ongoing(false),
_queue(NULL),
_rejoin_type1_send_period(MBED_CONF_LORA_REJOIN_TYPE1_SEND_PERIOD),
_rejoin_type1_stamp(0),
_rejoin_type0_counter(0),
_forced_datarate(DR_0),
_forced_period(0),
_forced_retry_count(0),
_forced_rejoin_type(REJOIN_REQUEST_TYPE0),
_forced_counter(0),
_ping_slot_info_requested(false),
_device_time_requested(false),
_last_beacon_rx_time(0)
{
_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)
{
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;
} else if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
if (_ctrl_flags & REJOIN_IN_PROGRESS) {
return LORAWAN_STATUS_BUSY;
}
// ResetInd is only used for ABP devices after connect, until ResetConf is received
if (_reset_ind_requested) {
set_reset_indication();
} else if (_rekey_ind_needed) {
if (_rekey_ind_counter < _loramac.get_current_adr_ack_limit()) {
set_rekey_indication();
_rekey_ind_counter++;
} else {
//TODO: Check if something else is needed also (reset settings?)
_rekey_ind_needed = false;
send_event_to_application(JOIN_FAILURE);
_device_current_state = DEVICE_STATE_IDLE;
}
}
if (_device_mode_ind_needed) {
set_device_mode_indication();
}
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();
}
// add device time request until the application explicitly removes it
if (_device_time_requested) {
_loramac.setup_device_time_request(callback(this, &LoRaWANStack::handle_device_time_sync_event));
}
// add ping slot info request until the application explicitly removes it
if (_ping_slot_info_requested) {
_loramac.add_ping_slot_info_req();
}
_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;
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
if (_rx_msg.prev_read_size == 0 && _rx_msg.msg.mcps_indication.buffer_size <= length) {
memcpy(data, base_ptr, base_size);
read_complete = true;
} 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);
_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;
_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::handle_device_time_sync_event(lorawan_gps_time_t gps_time)
{
_device_time_requested = false;
// The time provided by the network server is the time captured at
// the end of the uplink transmission
lorawan_time_t uplink_elapsed_time = _loramac.get_current_time() - _tx_timestamp;
set_current_gps_time(gps_time + uplink_elapsed_time);
send_event_to_application(DEVICE_TIME_SYNCHED);
}
lorawan_status_t LoRaWANStack::set_device_time_request(void)
{
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
} else if (!_loramac.nwk_joined()) {
return LORAWAN_STATUS_NO_NETWORK_JOINED;
} else {
_device_time_requested = true;
return LORAWAN_STATUS_OK;
}
}
void LoRaWANStack::remove_device_time_request(void)
{
_device_time_requested = false;
}
void LoRaWANStack::set_reset_indication()
{
_loramac.setup_reset_indication();
}
void LoRaWANStack::set_rekey_indication()
{
_loramac.setup_rekey_indication();
}
void LoRaWANStack::set_device_mode_indication()
{
_loramac.setup_device_mode_indication(_new_class_type);
}
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)
{
lorawan_status_t status = LORAWAN_STATUS_OK;
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
// Only change the class when needed
if (_loramac.get_device_class() != device_class) {
if ((_loramac.get_server_type() == LW1_1) && (device_class != CLASS_B)) {
_new_class_type = device_class;
_device_mode_ind_needed = true;
_device_mode_ind_ongoing = true;
} else {
status = _loramac.set_device_class(device_class,
mbed::callback(this, &LoRaWANStack::post_process_tx_no_reception));
}
}
return status;
}
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;
}
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;
}
_rx_timestamp = _loramac.get_current_time();
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) {
_device_current_state = DEVICE_STATE_IDLE;
tr_error("Joining abandoned: Radio failed to transmit");
send_event_to_application(TX_TIMEOUT);
} else {
state_controller(DEVICE_STATE_STATUS_CHECK);
}
state_machine_run_to_completion();
}
void LoRaWANStack::process_transmission(void)
{
tr_debug("Transmission completed");
make_tx_metadata_available();
if (_device_current_state == DEVICE_STATE_JOINING) {
_device_current_state = DEVICE_STATE_AWAITING_JOIN_ACCEPT;
}
if (_device_current_state == DEVICE_STATE_SENDING) {
if (_loramac.get_mcps_confirmation()->req_type == MCPS_CONFIRMED) {
tr_debug("Awaiting ACK");
_device_current_state = DEVICE_STATE_AWAITING_ACK;
}
}
_loramac.on_radio_tx_done(_tx_timestamp);
if (_loramac.get_server_type() == LW1_1 && _device_mode_ind_ongoing == true) {
_device_mode_ind_ongoing = false;
_loramac.set_device_class(device_class_t(_new_class_type), mbed::callback(this, &LoRaWANStack::post_process_tx_no_reception));
send_event_to_application(CLASS_CHANGED);
}
}
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 (_ctrl_flags & REJOIN_IN_PROGRESS) {
_ctrl_flags &= ~REJOIN_IN_PROGRESS;
goto exit;
}
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);
exit:
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)
{
_device_current_state = DEVICE_STATE_RECEIVING;
_ctrl_flags &= ~MSG_RECVD_FLAG;
_ctrl_flags &= ~TX_DONE_FLAG;
_ctrl_flags &= ~RETRY_EXHAUSTED_FLAG;
_rejoin_type0_counter++;
bool joined = _loramac.nwk_joined();
rx_slot_t rx_slot = _loramac.get_current_slot();
_loramac.on_radio_rx_done(payload, size, rssi, snr, _rx_timestamp,
mbed::callback(this, &LoRaWANStack::mlme_confirm_handler));
if (!joined) {
core_util_atomic_flag_clear(&_rx_payload_in_use);
return;
}
if (_ctrl_flags & REJOIN_IN_PROGRESS) {
_ctrl_flags &= ~REJOIN_IN_PROGRESS;
core_util_atomic_flag_clear(&_rx_payload_in_use);
return;
}
make_rx_metadata_available();
switch (rx_slot) {
case RX_SLOT_WIN_1:
case RX_SLOT_WIN_2:
case RX_SLOT_WIN_CLASS_C: // Is this right?
// 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();
}
//TODO: This does not apply if server does not support 1.1!
//OR if we are in ABP mode
if (MBED_CONF_LORA_VERSION == LORAWAN_VERSION_1_1) {
poll_rejoin();
}
break;
case RX_SLOT_WIN_BEACON:
break;
case RX_SLOT_WIN_UNICAST_PING_SLOT:
case RX_SLOT_WIN_MULTICAST_PING_SLOT:
_ctrl_flags |= MSG_RECVD_FLAG;
state_controller(DEVICE_STATE_STATUS_CHECK);
break;
default:
MBED_ASSERT(false);
}
core_util_atomic_flag_clear(&_rx_payload_in_use);
}
void LoRaWANStack::poll_rejoin(void)
{
if (_ctrl_flags & REJOIN_IN_PROGRESS) {
return;
}
// check if REJOIN_TYPE_1 is due, if it is, do not proceed with
// REJOIN_TYPE_0
if (((_loramac.get_lora_time()->get_current_time() / 1000) -
_rejoin_type1_stamp) > _rejoin_type1_send_period) {
_ctrl_flags |= REJOIN_IN_PROGRESS;
_rejoin_type1_stamp = _loramac.get_lora_time()->get_current_time() / 1000;
const int ret = _queue->call(this, &LoRaWANStack::process_rejoin,
REJOIN_REQUEST_TYPE1, false);
MBED_ASSERT(ret != 0);
(void)ret;
return;
}
uint32_t max_time;
uint32_t max_count;
_loramac.get_rejoin_parameters(max_time, max_count);
if (_rejoin_type0_counter >= max_count) {
_rejoin_type0_counter = 0;
//This causes excactly same handling as a timeout
_ctrl_flags |= REJOIN_IN_PROGRESS;
const int ret = _queue->call(this, &LoRaWANStack::process_rejoin_type0);
MBED_ASSERT(ret != 0);
(void)ret;
}
core_util_atomic_flag_clear(&_rx_payload_in_use);
}
void LoRaWANStack::process_reception_timeout(bool is_timeout)
{
_rejoin_type0_counter++;
rx_slot_t rx_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 (rx_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 (rx_slot == RX_SLOT_WIN_2) {
post_process_tx_no_reception();
state_controller(DEVICE_STATE_STATUS_CHECK);
state_machine_run_to_completion();
//TODO: This does not apply if server does not support 1.1!
//OR if we are in ABP mode
if (MBED_CONF_LORA_VERSION == LORAWAN_VERSION_1_1) {
poll_rejoin();
}
}
}
/*****************************************************************************
* 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;
} 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;
//We cannot set _rekey_ind_needed here, because server might not support LW1.1
} 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
if (MBED_CONF_LORA_VERSION == LORAWAN_VERSION_1_1) {
_reset_ind_requested = true;
//TODO: Switch back to default MAC and radio parameters, but leave counters untouched
}
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(loramac_mlme_confirm_t &mlme_confirm)
{
if (mlme_confirm.type == MLME_LINK_CHECK) {
if (mlme_confirm.status == LORAMAC_EVENT_INFO_STATUS_OK) {
if (_callbacks.link_check_resp) {
const int ret = _queue->call(_callbacks.link_check_resp,
mlme_confirm.demod_margin,
mlme_confirm.nb_gateways);
MBED_ASSERT(ret != 0);
(void)ret;
}
}
} else if (mlme_confirm.type == MLME_RESET) {
_reset_ind_requested = false;
} else if (mlme_confirm.type == MLME_REKEY) {
_rekey_ind_needed = false;
_rekey_ind_counter = 0;
} else if (mlme_confirm.type == MLME_DEVICE_MODE) {
_device_mode_ind_needed = false;
if (_loramac.get_device_class() == mlme_confirm.classType) {
send_event_to_application(SERVER_ACCEPTED_CLASS_IN_USE);
} else {
send_event_to_application(SERVER_DOES_NOT_SUPPORT_CLASS_IN_USE);
}
} else if (mlme_confirm.type == MLME_JOIN_ACCEPT) {
switch (mlme_confirm.status) {
case LORAMAC_EVENT_INFO_STATUS_OK:
if (_loramac.get_server_type() == LW1_1) {
_rekey_ind_needed = true;
_rekey_ind_counter = 0;
// THIS IS NOT ALLOWED HERE!
// We might get JOIN_ACCEPT for rejoin type 1,
// which points to different server!
//reset_forced_rejoin();
} else {
_loramac.get_lora_time()->stop(_forced_timer);
_loramac.get_lora_time()->stop(_rejoin_type0_timer);
}
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;
default:
if (_loramac.get_server_type() == LW1_1 && (_ctrl_flags & REJOIN_IN_PROGRESS)) {
// do not retry, do not send an event
return;
}
// non-fatal, retry if possible
_device_current_state = DEVICE_STATE_AWAITING_JOIN_ACCEPT;
state_controller(DEVICE_STATE_JOINING);
}
} else if (mlme_confirm.type == MLME_FORCE_REJOIN) {
if (join_req_type_t(mlme_confirm.rejoin_type) <= REJOIN_REQUEST_TYPE2 &&
_loramac.get_server_type() == LW1_1) {
_forced_datarate = mlme_confirm.datarate;
_forced_period = ((1 << mlme_confirm.period) * 32 + (rand() % 33)) * 1000;
_forced_retry_count = mlme_confirm.max_retries;
if (_forced_retry_count) {
_forced_retry_count += 1;
}
_forced_rejoin_type = join_req_type_t(mlme_confirm.rejoin_type);
// See LW 1.1 chapter 5.13 - RejoinType
if (join_req_type_t(mlme_confirm.rejoin_type) == REJOIN_REQUEST_TYPE1) {
_forced_rejoin_type = REJOIN_REQUEST_TYPE0;
}
reset_forced_rejoin();
process_rejoin(_forced_rejoin_type, true);
if (_forced_retry_count) {
_loramac.get_lora_time()->start(_forced_timer, _forced_period);
}
}
} else if (mlme_confirm.type == MLME_PING_SLOT_INFO) {
if (_ping_slot_info_requested) {
_ping_slot_info_requested = false;
send_event_to_application(PING_SLOT_INFO_SYNCHED);
}
} else if (mlme_confirm.type == MLME_BEACON_ACQUISITION) {
if (mlme_confirm.status == LORAMAC_EVENT_INFO_STATUS_OK) {
send_event_to_application(BEACON_FOUND);
} else {
send_event_to_application(BEACON_NOT_FOUND);
}
}
}
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
&& mcps_indication->type == MCPS_CONFIRMED)) {
#if (MBED_CONF_LORA_AUTOMATIC_UPLINK_MESSAGE)
// 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:
//Because this is internal function only coding error causes this
tr_error("Unknown state: %d:", new_state);
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 &&
_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;
}
if (MBED_CONF_LORA_VERSION == LORAWAN_VERSION_1_1) {
_loramac.get_lora_time()->init(_forced_timer,
mbed::callback(this, &LoRaWANStack::forced_timer_expiry));
_loramac.get_lora_time()->init(_rejoin_type0_timer,
mbed::callback(this, &LoRaWANStack::process_rejoin_type0));
_rejoin_type1_stamp = _loramac.get_lora_time()->get_current_time() / 1000;
}
}
void LoRaWANStack::process_rejoin(join_req_type_t rejoin_type, bool is_forced)
{
if (_loramac.get_server_type() == LW1_1) {
_loramac.rejoin(rejoin_type, is_forced, _forced_datarate);
if (rejoin_type == REJOIN_REQUEST_TYPE0) {
_loramac.get_lora_time()->stop(_rejoin_type0_timer);
_rejoin_type0_counter = 0;
uint32_t max_time;
uint32_t max_count;
_loramac.get_rejoin_parameters(max_time, max_count);
// start() takes parameters in ms, max_time is in seconds
_loramac.get_lora_time()->start(_rejoin_type0_timer, max_time * 1000);
}
}
}
void LoRaWANStack::reset_forced_rejoin()
{
_forced_counter = 0;
_loramac.get_lora_time()->stop(_forced_timer);
}
void LoRaWANStack::forced_timer_expiry()
{
if (_loramac.get_server_type() == LW1_1) {
if (_forced_counter < _forced_retry_count) {
process_rejoin(_forced_rejoin_type, true);
_loramac.get_lora_time()->start(_forced_timer, _forced_period);
} else {
reset_forced_rejoin();
}
}
}
void LoRaWANStack::process_rejoin_type0()
{
if (_loramac.get_server_type() == LW1_1) {
//stop in case counter was exceeded
process_rejoin(REJOIN_REQUEST_TYPE0, false);
}
}
lorawan_gps_time_t LoRaWANStack::get_current_gps_time()
{
return _loramac.get_gps_time();
}
void LoRaWANStack::set_current_gps_time(lorawan_gps_time_t gps_time)
{
_loramac.set_gps_time(gps_time);
}
lorawan_status_t LoRaWANStack::add_ping_slot_info_request(uint8_t periodicity)
{
lorawan_status_t status;
if (DEVICE_STATE_NOT_INITIALIZED == _device_current_state) {
return LORAWAN_STATUS_NOT_INITIALIZED;
}
// Cannot only change periodicity when device is in class A
if (_loramac.get_device_class() != CLASS_A) {
return LORAWAN_STATUS_NO_OP;
}
status = _loramac.set_ping_slot_info(periodicity);
if (status == LORAWAN_STATUS_OK) {
_ping_slot_info_requested = true;
}
return status;
}
void LoRaWANStack::remove_ping_slot_info_request()
{
_ping_slot_info_requested = false;
}
void LoRaWANStack::process_beacon_event(loramac_beacon_status_t status, const loramac_beacon_t *beacon)
{
loramac_mlme_confirm_t mlme_confirm;
switch (status) {
case BEACON_STATUS_ACQUISITION_FAILED:
mlme_confirm.type = MLME_BEACON_ACQUISITION;
mlme_confirm.status = LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND;
mlme_confirm_handler(mlme_confirm);
break;
case BEACON_STATUS_ACQUISITION_SUCCESS:
_last_beacon_rx_time = _loramac.get_current_time();
mlme_confirm.type = MLME_BEACON_ACQUISITION;
mlme_confirm.status = LORAMAC_EVENT_INFO_STATUS_OK;
mlme_confirm_handler(mlme_confirm);
break;
case BEACON_STATUS_LOCK:
_last_beacon_rx_time = _loramac.get_current_time();
send_event_to_application(BEACON_LOCK);
break;
case BEACON_STATUS_MISS:
send_event_to_application(BEACON_MISS);
// Switch back to class A after beacon-less operation timeout (12.1)
if (_loramac.get_device_class() == CLASS_B) {
lorawan_time_t no_beacon_rx_time = _loramac.get_current_time() - _last_beacon_rx_time;
if ((no_beacon_rx_time / 1000) >= MBED_CONF_LORA_CLASS_B_BEACONLESS_PERIOD) {
device_class_t device_class = CLASS_A;
_loramac.set_device_class(device_class,
mbed::callback(this, &LoRaWANStack::post_process_tx_no_reception));
send_event_to_application(SWITCH_CLASS_B_TO_A);
}
}
break;
default:
tr_error("Unknown Beacon Status %d", status);
MBED_ASSERT(false);
}
}
lorawan_status_t LoRaWANStack::enable_beacon_acquisition()
{
return _loramac.enable_beacon_acquisition(mbed::callback(this, &LoRaWANStack::process_beacon_event));
}
lorawan_status_t LoRaWANStack::get_last_rx_beacon(loramac_beacon_t &beacon)
{
return _loramac.get_last_rx_beacon(beacon);
}