mbed-os/features/lorawan/lorastack/mac/LoRaMac.cpp

2012 lines
67 KiB
C++

/**
/ _____) _ | |
( (____ _____ ____ _| |_ _____ ____| |__
\____ \| ___ | (_ _) ___ |/ ___) _ \
_____) ) ____| | | || |_| ____( (___| | | |
(______/|_____)_|_|_| \__)_____)\____)_| |_|
(C)2013 Semtech
___ _____ _ ___ _ _____ ___ ___ ___ ___
/ __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __|
\__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _|
|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___|
embedded.connectivity.solutions===============
Description: LoRa MAC layer implementation
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 <stdlib.h>
#include "LoRaMac.h"
#if defined(FEATURE_COMMON_PAL)
#include "mbed_trace.h"
#define TRACE_GROUP "LMAC"
#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
#endif //defined(FEATURE_COMMON_PAL)
using namespace events;
using namespace mbed;
/*
* LoRaWAN spec 6.2: AppKey is AES-128 key
*/
#define APPKEY_KEY_LENGTH 128
/*!
* Maximum length of the fOpts field
*/
#define LORA_MAC_COMMAND_MAX_FOPTS_LENGTH 15
/*!
* LoRaMac duty cycle for the back-off procedure during the first hour.
*/
#define BACKOFF_DC_1_HOUR 100
/*!
* LoRaMac duty cycle for the back-off procedure during the next 10 hours.
*/
#define BACKOFF_DC_10_HOURS 1000
/*!
* LoRaMac duty cycle for the back-off procedure during the next 24 hours.
*/
#define BACKOFF_DC_24_HOURS 10000
/*!
* The frame direction definition for uplink communications.
*/
#define UP_LINK 0
/*!
* The frame direction definition for downlink communications.
*/
#define DOWN_LINK 1
/**
* A mask for the network ID.
*/
#define LORAWAN_NETWORK_ID_MASK ( uint32_t )0xFE000000
LoRaMac::LoRaMac()
: _lora_time(),
_lora_phy(_lora_time),
_mac_commands(),
_channel_plan(),
_lora_crypto(),
_ev_queue(NULL),
_mcps_indication(),
_mcps_confirmation(),
_mlme_indication(),
_mlme_confirmation(),
_is_nwk_joined(false),
_device_class(CLASS_A)
{
_params.keys.dev_eui = NULL;
_params.keys.app_eui = NULL;
_params.keys.app_key = NULL;
memset(_params.keys.nwk_skey, 0, sizeof(_params.keys.nwk_skey));
memset(_params.keys.app_skey, 0, sizeof(_params.keys.app_skey));
memset(&_ongoing_tx_msg, 0, sizeof(_ongoing_tx_msg));
_params.dev_nonce = 0;
_params.net_id = 0;
_params.dev_addr = 0;
_params.tx_buffer_len = 0;
_params.rx_buffer_len = 0;
_params.ul_frame_counter = 0;
_params.dl_frame_counter = 0;
_params.is_ul_frame_counter_fixed = false;
_params.is_rx_window_enabled = true;
_params.adr_ack_counter = 0;
_params.is_node_ack_requested = false;
_params.is_srv_ack_requested = false;
_params.ul_nb_rep_counter = 0;
_params.timers.mac_init_time = 0;
_params.max_ack_timeout_retries = 1;
_params.ack_timeout_retry_counter = 1;
_params.is_ack_retry_timeout_expired = false;
_params.timers.tx_toa = 0;
_params.multicast_channels = NULL;
_params.sys_params.adr_on = false;
_params.sys_params.max_duty_cycle = 0;
reset_mcps_confirmation();
reset_mlme_confirmation();
reset_mcps_indication();
}
LoRaMac::~LoRaMac()
{
}
/***************************************************************************
* Radio event callbacks - delegated to Radio driver *
**************************************************************************/
const loramac_mcps_confirm_t *LoRaMac::get_mcps_confirmation() const
{
return &_mcps_confirmation;
}
const loramac_mcps_indication_t *LoRaMac::get_mcps_indication() const
{
return &_mcps_indication;
}
const loramac_mlme_confirm_t *LoRaMac::get_mlme_confirmation() const
{
return &_mlme_confirmation;
}
const loramac_mlme_indication_t *LoRaMac::get_mlme_indication() const
{
return &_mlme_indication;
}
void LoRaMac::post_process_mlme_request()
{
_mlme_confirmation.pending = false;
}
void LoRaMac::post_process_mcps_req()
{
_params.is_last_tx_join_request = false;
_mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_OK;
if (_mcps_confirmation.req_type == MCPS_CONFIRMED) {
// An MCPS request for a CONFIRMED message has received an ack
// in the downlink message
if (_mcps_confirmation.ack_received) {
_params.is_node_ack_requested = false;
_mcps_confirmation.ack_received = false;
_mcps_indication.is_ack_recvd = false;
if (_params.is_ul_frame_counter_fixed == false) {
_params.ul_frame_counter++;
}
}
} else {
//UNCONFIRMED
if (_params.is_ul_frame_counter_fixed == false) {
_params.ul_frame_counter++;
}
}
}
void LoRaMac::post_process_mcps_ind()
{
_mcps_indication.pending = false;
}
void LoRaMac::post_process_mlme_ind()
{
_mlme_indication.pending = false;
}
void LoRaMac::on_radio_tx_done(void)
{
lorawan_time_t cur_time = _lora_time.get_current_time();
if (_device_class != CLASS_C) {
_lora_phy.put_radio_to_sleep();
} else {
// this will open a continuous RX2 window until time==RECV_DELAY1
open_rx2_window();
}
if(_params.is_rx_window_enabled == true) {
// start timer after which rx1_window will get opened
_lora_time.start(_params.timers.rx_window1_timer, _params.rx_window1_delay);
if (_device_class != CLASS_C) {
_lora_time.start(_params.timers.rx_window2_timer, _params.rx_window2_delay);
}
if (_params.is_node_ack_requested == true) {
_lora_time.start(_params.timers.ack_timeout_timer,
_params.rx_window2_delay + _lora_phy.get_ack_timeout());
}
} else {
_mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_OK;
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT;
}
_params.last_channel_idx = _params.channel;
_lora_phy.set_last_tx_done(_params.channel, _is_nwk_joined, cur_time);
_params.timers.aggregated_last_tx_time = cur_time;
}
void LoRaMac::abort_rx(void)
{
if (_params.is_node_ack_requested) {
const int ret = _ev_queue->call(this, &LoRaMac::on_ack_timeout_timer_event);
MBED_ASSERT(ret != 0);
(void)ret;
}
}
/**
* This part handles incoming frames in response to Radio RX Interrupt
*/
void LoRaMac::handle_join_accept_frame(const uint8_t *payload,
uint16_t size)
{
uint32_t mic = 0;
uint32_t mic_rx = 0;
_mlme_confirmation.nb_retries = _params.join_request_trial_counter;
if (0 != _lora_crypto.decrypt_join_frame(payload + 1, size - 1,
_params.keys.app_key, APPKEY_KEY_LENGTH,
_params.rx_buffer + 1)) {
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL;
return;
}
_params.rx_buffer[0] = payload[0];
if (_lora_crypto.compute_join_frame_mic(_params.rx_buffer,
size - LORAMAC_MFR_LEN,
_params.keys.app_key,
APPKEY_KEY_LENGTH,
&mic) != 0) {
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL;
return;
}
mic_rx |= (uint32_t) _params.rx_buffer[size - LORAMAC_MFR_LEN];
mic_rx |= ((uint32_t) _params.rx_buffer[size - LORAMAC_MFR_LEN + 1] << 8);
mic_rx |= ((uint32_t) _params.rx_buffer[size - LORAMAC_MFR_LEN + 2] << 16);
mic_rx |= ((uint32_t) _params.rx_buffer[size - LORAMAC_MFR_LEN + 3] << 24);
if (mic_rx == mic) {
if (_lora_crypto.compute_skeys_for_join_frame(_params.keys.app_key,
APPKEY_KEY_LENGTH,
_params.rx_buffer + 1,
_params.dev_nonce,
_params.keys.nwk_skey,
_params.keys.app_skey) != 0) {
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL;
return;
}
_params.net_id = (uint32_t) _params.rx_buffer[4];
_params.net_id |= ((uint32_t) _params.rx_buffer[5] << 8);
_params.net_id |= ((uint32_t) _params.rx_buffer[6] << 16);
_params.dev_addr = (uint32_t) _params.rx_buffer[7];
_params.dev_addr |= ((uint32_t) _params.rx_buffer[8] << 8);
_params.dev_addr |= ((uint32_t) _params.rx_buffer[9] << 16);
_params.dev_addr |= ((uint32_t) _params.rx_buffer[10] << 24);
_params.sys_params.rx1_dr_offset = (_params.rx_buffer[11] >> 4) & 0x07;
_params.sys_params.rx2_channel.datarate = _params.rx_buffer[11] & 0x0F;
_params.sys_params.recv_delay1 = (_params.rx_buffer[12] & 0x0F);
if (_params.sys_params.recv_delay1 == 0) {
_params.sys_params.recv_delay1 = 1;
}
_params.sys_params.recv_delay1 *= 1000;
_params.sys_params.recv_delay2 = _params.sys_params.recv_delay1 + 1000;
// Size of the regular payload is 12. Plus 1 byte MHDR and 4 bytes MIC
_lora_phy.apply_cf_list(&_params.rx_buffer[13], size - 17);
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_OK;
_is_nwk_joined = true;
// Node joined successfully
_params.ul_frame_counter = 0;
_params.ul_nb_rep_counter = 0;
} else {
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL;
}
}
void LoRaMac::check_frame_size(uint16_t size)
{
uint8_t value = _lora_phy.get_max_payload(_mcps_indication.rx_datarate,
_params.is_repeater_supported);
if (MAX(0, (int16_t) ((int16_t)size - (int16_t)LORA_MAC_FRMPAYLOAD_OVERHEAD))
> (int32_t) value) {
tr_error("Invalid frame size");
}
}
bool LoRaMac::message_integrity_check(const uint8_t * const payload,
const uint16_t size,
uint8_t * const ptr_pos,
uint32_t address,
uint32_t* downlink_counter,
const uint8_t *nwk_skey)
{
uint32_t mic = 0;
uint32_t mic_rx = 0;
uint16_t sequence_counter = 0;
uint16_t sequence_counter_prev = 0;
uint16_t sequence_counter_diff = 0;
sequence_counter = (uint16_t) payload[(*ptr_pos)++];
sequence_counter |= (uint16_t) payload[(*ptr_pos)++] << 8;
mic_rx |= (uint32_t) payload[size - LORAMAC_MFR_LEN];
mic_rx |= ((uint32_t) payload[size - LORAMAC_MFR_LEN + 1] << 8);
mic_rx |= ((uint32_t) payload[size - LORAMAC_MFR_LEN + 2] << 16);
mic_rx |= ((uint32_t) payload[size - LORAMAC_MFR_LEN + 3] << 24);
sequence_counter_prev = (uint16_t) *downlink_counter;
sequence_counter_diff = sequence_counter - sequence_counter_prev;
*downlink_counter += sequence_counter_diff;
if (sequence_counter < sequence_counter_prev) {
*downlink_counter += 0x10000;
}
// sizeof nws_skey must be the same as _params.keys.nwk_skey,
_lora_crypto.compute_mic(payload, size - LORAMAC_MFR_LEN,
nwk_skey,
sizeof(_params.keys.nwk_skey)*8,
address, DOWN_LINK, *downlink_counter, &mic);
if (mic_rx != mic) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL;
return false;
}
if (sequence_counter_diff >= _lora_phy.get_maximum_frame_counter_gap()) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_TOO_MANY_FRAMES_LOST;
_mcps_indication.dl_frame_counter = *downlink_counter;
return false;
}
return true;
}
void LoRaMac::extract_data_and_mac_commands(const uint8_t *payload,
uint16_t size,
uint8_t fopts_len,
uint8_t *nwk_skey,
uint8_t *app_skey,
uint32_t address,
uint32_t downlink_counter,
int16_t rssi,
int8_t snr)
{
uint8_t frame_len = 0;
uint8_t payload_start_index = 8 + fopts_len;
uint8_t port = payload[payload_start_index++];
frame_len = (size - 4) - payload_start_index;
_mcps_indication.port = port;
// special handling of control port 0
if (port == 0) {
if (fopts_len == 0) {
// sizeof nws_skey must be the same as _params.keys.nwk_skey,
if (_lora_crypto.decrypt_payload(payload + payload_start_index,
frame_len,
nwk_skey,
sizeof(_params.keys.nwk_skey)*8,
address,
DOWN_LINK,
downlink_counter,
_params.rx_buffer) != 0) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL;
}
if (_mac_commands.process_mac_commands(_params.rx_buffer, 0, frame_len,
snr, _mlme_confirmation,
_params.sys_params, _lora_phy)
!= LORAWAN_STATUS_OK) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
return;
}
if (_mac_commands.has_sticky_mac_cmd()) {
set_mlme_schedule_ul_indication();
_mac_commands.clear_sticky_mac_cmd();
}
return;
}
_mcps_indication.pending = false;
_mcps_confirmation.ack_received = false;
_mcps_indication.is_ack_recvd = false;
return;
}
// normal unicast/multicast port handling
if (fopts_len > 0) {
// Decode Options field MAC commands. Omit the fPort.
if (_mac_commands.process_mac_commands(payload, 8,
payload_start_index - 1, snr,
_mlme_confirmation,
_params.sys_params, _lora_phy)
!= LORAWAN_STATUS_OK) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
return;
}
if (_mac_commands.has_sticky_mac_cmd()) {
set_mlme_schedule_ul_indication();
_mac_commands.clear_sticky_mac_cmd();
}
}
// sizeof app_skey must be the same as _params.keys.app_skey
if (_lora_crypto.decrypt_payload(payload + payload_start_index,
frame_len,
app_skey,
sizeof(_params.keys.app_skey)*8,
address,
DOWN_LINK,
downlink_counter,
_params.rx_buffer) != 0) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL;
} else {
_mcps_indication.buffer = _params.rx_buffer;
_mcps_indication.buffer_size = frame_len;
_mcps_indication.is_data_recvd = true;
}
}
void LoRaMac::extract_mac_commands_only(const uint8_t *payload,
int8_t snr,
uint8_t fopts_len)
{
uint8_t payload_start_index = 8 + fopts_len;
if (fopts_len > 0) {
if (_mac_commands.process_mac_commands(payload, 8, payload_start_index,
snr, _mlme_confirmation,
_params.sys_params, _lora_phy)
!= LORAWAN_STATUS_OK) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
return;
}
if (_mac_commands.has_sticky_mac_cmd()) {
set_mlme_schedule_ul_indication();
_mac_commands.clear_sticky_mac_cmd();
}
}
}
void LoRaMac::handle_data_frame(const uint8_t* const payload,
const uint16_t size,
uint8_t ptr_pos,
uint8_t msg_type,
int16_t rssi,
int8_t snr)
{
check_frame_size(size);
bool is_multicast = false;
loramac_frame_ctrl_t fctrl;
multicast_params_t *cur_multicast_params;
uint32_t address = 0;
uint32_t downlink_counter = 0;
uint8_t app_payload_start_index = 0;
uint8_t *nwk_skey = _params.keys.nwk_skey;
uint8_t *app_skey = _params.keys.app_skey;
address = payload[ptr_pos++];
address |= ((uint32_t) payload[ptr_pos++] << 8);
address |= ((uint32_t) payload[ptr_pos++] << 16);
address |= ((uint32_t) payload[ptr_pos++] << 24);
if (address != _params.dev_addr) {
// check if Multicast is destined for us
cur_multicast_params = _params.multicast_channels;
while (cur_multicast_params != NULL) {
if (address == cur_multicast_params->address) {
is_multicast = true;
nwk_skey = cur_multicast_params->nwk_skey;
app_skey = cur_multicast_params->app_skey;
downlink_counter = cur_multicast_params->dl_frame_counter;
break;
}
cur_multicast_params = cur_multicast_params->next;
}
if (!is_multicast) {
// We are not the destination of this frame.
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL;
abort_rx();
return;
}
} else {
is_multicast = false;
nwk_skey = _params.keys.nwk_skey;
app_skey = _params.keys.app_skey;
downlink_counter = _params.dl_frame_counter;
}
fctrl.value = payload[ptr_pos++];
app_payload_start_index = 8 + fctrl.bits.fopts_len;
//perform MIC check
if (!message_integrity_check(payload, size, &ptr_pos, address,
&downlink_counter, nwk_skey)) {
tr_error("MIC failed");
abort_rx();
return;
}
_mcps_confirmation.ack_received = false;
_mcps_indication.is_ack_recvd = false;
_mcps_indication.pending = true;
_mcps_indication.is_data_recvd = false;
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_OK;
_mcps_indication.multicast = is_multicast;
_mcps_indication.fpending_status = fctrl.bits.fpending;
_mcps_indication.buffer = NULL;
_mcps_indication.buffer_size = 0;
_mcps_indication.dl_frame_counter = downlink_counter;
_mcps_indication.rssi = rssi;
_mcps_indication.snr = snr;
_mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_OK;
_params.adr_ack_counter = 0;
_mac_commands.clear_repeat_buffer();
if (is_multicast) {
_mcps_indication.type = MCPS_MULTICAST;
// Discard if its a repeated message
if ((cur_multicast_params->dl_frame_counter == downlink_counter)
&& (cur_multicast_params->dl_frame_counter != 0)) {
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
_mcps_indication.dl_frame_counter = downlink_counter;
_mcps_indication.pending = false;
abort_rx();
return;
}
cur_multicast_params->dl_frame_counter = downlink_counter;
} else {
if (msg_type == FRAME_TYPE_DATA_CONFIRMED_DOWN) {
_params.is_srv_ack_requested = true;
_mcps_indication.type = MCPS_CONFIRMED;
if ((_params.dl_frame_counter == downlink_counter)
&& (_params.dl_frame_counter != 0)) {
// Duplicated confirmed downlink. Skip indication.
// In this case, the MAC layer shall accept the MAC commands
// which are included in the downlink retransmission.
// It should not provide the same frame to the application
// layer again. The MAC layer accepts the acknowledgement.
tr_debug("Discarding duplicate frame");
_mcps_indication.pending = false;
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
}
} else if (msg_type == FRAME_TYPE_DATA_UNCONFIRMED_DOWN) {
_params.is_srv_ack_requested = false;
_mcps_indication.type = MCPS_UNCONFIRMED;
if ((_params.dl_frame_counter == downlink_counter)
&& (_params.dl_frame_counter != 0)) {
tr_debug("Discarding duplicate frame");
_mcps_indication.pending = false;
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_DOWNLINK_REPEATED;
abort_rx();
return;
}
}
_params.dl_frame_counter = downlink_counter;
}
// This must be done before parsing the payload and the MAC commands.
// We need to reset the MacCommandsBufferIndex here, since we need
// to take retransmissions and repetitions into account. Error cases
// will be handled in function OnMacStateCheckTimerEvent.
if (_params.is_node_ack_requested) {
if (fctrl.bits.ack == 1) {
_mac_commands.clear_command_buffer();
_mcps_confirmation.ack_received = fctrl.bits.ack;
_mcps_indication.is_ack_recvd = fctrl.bits.ack;
}
} else {
_mac_commands.clear_command_buffer();
}
uint8_t frame_len = (size - 4) - app_payload_start_index;
if (frame_len > 0) {
extract_data_and_mac_commands(payload, size, fctrl.bits.fopts_len,
nwk_skey, app_skey, address,
downlink_counter, rssi, snr);
} else {
extract_mac_commands_only(payload, snr, fctrl.bits.fopts_len);
}
// Handle proprietary messages.
if (msg_type == FRAME_TYPE_PROPRIETARY) {
memcpy(_params.rx_buffer, &payload[ptr_pos], size);
_mcps_indication.type = MCPS_PROPRIETARY;
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_OK;
_mcps_indication.buffer = _params.rx_buffer;
_mcps_indication.buffer_size = size - ptr_pos;
}
check_to_disable_ack_timeout(_params.is_node_ack_requested, _device_class, _mcps_confirmation.ack_received,
_params.ack_timeout_retry_counter, _params.max_ack_timeout_retries );
}
void LoRaMac::set_batterylevel_callback(mbed::Callback<uint8_t(void)> battery_level)
{
_mac_commands.set_batterylevel_callback(battery_level);
}
void LoRaMac::on_radio_rx_done(const uint8_t* const payload, uint16_t size,
int16_t rssi, int8_t snr)
{
// on reception turn off queued timers
_lora_time.stop(_params.timers.rx_window1_timer);
_lora_time.stop(_params.timers.rx_window2_timer);
if (_device_class == CLASS_C) {
open_rx2_window();
} else {
_lora_phy.put_radio_to_sleep();
}
loramac_mhdr_t mac_hdr;
uint8_t pos = 0;
mac_hdr.value = payload[pos++];
switch (mac_hdr.bits.mtype) {
case FRAME_TYPE_JOIN_ACCEPT:
if (nwk_joined()) {
abort_rx();
_mlme_confirmation.pending = false;
return;
} else {
handle_join_accept_frame(payload, size);
_mlme_confirmation.pending = true;
}
break;
case FRAME_TYPE_DATA_UNCONFIRMED_DOWN:
case FRAME_TYPE_DATA_CONFIRMED_DOWN:
case FRAME_TYPE_PROPRIETARY:
handle_data_frame(payload, size, pos, mac_hdr.bits.mtype, rssi, snr);
break;
default:
break;
}
}
void LoRaMac::on_radio_tx_timeout( void )
{
_lora_time.stop(_params.timers.rx_window1_timer);
_lora_time.stop(_params.timers.rx_window2_timer);
_lora_time.stop(_params.timers.ack_timeout_timer);
if (_device_class == CLASS_C) {
open_rx2_window();
} else {
_lora_phy.put_radio_to_sleep();
}
_mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT;
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_TX_TIMEOUT;
_mac_commands.clear_command_buffer();
_mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter;
_mcps_confirmation.ack_received = false;
_mcps_confirmation.tx_toa = 0;
post_process_mcps_req();
}
rx_slot_t LoRaMac::on_radio_rx_timeout(bool is_timeout)
{
if (_device_class != CLASS_C) {
_lora_phy.put_radio_to_sleep();
}
if (_params.rx_slot == RX_SLOT_WIN_1) {
if (_params.is_node_ack_requested == true) {
_mcps_confirmation.status = is_timeout ? LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT
: LORAMAC_EVENT_INFO_STATUS_RX1_ERROR;
}
_mlme_confirmation.status = is_timeout ? LORAMAC_EVENT_INFO_STATUS_RX1_TIMEOUT
: LORAMAC_EVENT_INFO_STATUS_RX1_ERROR;
if (_device_class != CLASS_C) {
if (_lora_time.get_elapsed_time(_params.timers.aggregated_last_tx_time ) >= _params.rx_window2_delay) {
_lora_time.stop(_params.timers.rx_window2_timer);
}
}
} else {
if (_params.is_node_ack_requested == true) {
_mcps_confirmation.status = is_timeout ? LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT
: LORAMAC_EVENT_INFO_STATUS_RX2_ERROR;
}
_mlme_confirmation.status = is_timeout ? LORAMAC_EVENT_INFO_STATUS_RX2_TIMEOUT
: LORAMAC_EVENT_INFO_STATUS_RX2_ERROR;
}
if (_device_class == CLASS_C) {
open_rx2_window();
}
return _params.rx_slot;
}
bool LoRaMac::continue_joining_process()
{
if (_params.join_request_trial_counter >= _params.max_join_request_trials) {
return false;
}
// Schedule a retry
if (handle_retransmission() != LORAWAN_STATUS_OK) {
return false;
}
return true;
}
bool LoRaMac::continue_sending_process()
{
if (_params.ack_timeout_retry_counter >= _params.max_ack_timeout_retries) {
_mac_commands.clear_command_buffer();
_params.adr_ack_counter++;
return false;
}
// retransmission will be handled in on_ack_timeout() whence the ACK timeout
// gets fired
return true;
}
lorawan_status_t LoRaMac::send_join_request()
{
lorawan_status_t status = LORAWAN_STATUS_OK;
loramac_mhdr_t mac_hdr;
loramac_frame_ctrl_t fctrl;
_params.sys_params.channel_data_rate = _lora_phy.get_alternate_DR(
_params.join_request_trial_counter + 1);
mac_hdr.value = 0;
mac_hdr.bits.mtype = FRAME_TYPE_JOIN_REQ;
fctrl.value = 0;
fctrl.bits.adr = _params.sys_params.adr_on;
_params.is_last_tx_join_request = true;
/* In case of join request retransmissions, the stack must prepare
* the frame again, because the network server keeps track of the random
* LoRaMacDevNonce values to prevent reply attacks. */
status = prepare_frame(&mac_hdr, &fctrl, 0, NULL, 0);
if (status == LORAWAN_STATUS_OK) {
status = schedule_tx();
} else {
tr_error("Retransmission: error %d", status);
}
return status;
}
/**
* This function handles retransmission of failed or unacknowledged
* outgoing traffic
*/
lorawan_status_t LoRaMac::handle_retransmission()
{
if (!nwk_joined() && (_mlme_confirmation.req_type == MLME_JOIN)) {
return send_join_request();
}
return schedule_tx();
}
/**
* This function is called when the backoff_timer gets fired.
* It is used for re-scheduling an unsent packet in the pipe. This packet
* can be a Join Request or any other data packet.
*/
void LoRaMac::on_backoff_timer_expiry(void)
{
Lock lock(*this);
lorawan_status_t status = schedule_tx();
MBED_ASSERT(status==LORAWAN_STATUS_OK);
}
void LoRaMac::open_rx1_window(void)
{
Lock lock(*this);
_lora_time.stop(_params.timers.rx_window1_timer);
_params.rx_slot = RX_SLOT_WIN_1;
_params.rx_window1_config.channel = _params.channel;
_params.rx_window1_config.dr_offset = _params.sys_params.rx1_dr_offset;
_params.rx_window1_config.dl_dwell_time = _params.sys_params.downlink_dwell_time;
_params.rx_window1_config.is_repeater_supported = _params.is_repeater_supported;
_params.rx_window1_config.is_rx_continuous = false;
_params.rx_window1_config.rx_slot = _params.rx_slot;
if (_device_class == CLASS_C) {
_lora_phy.put_radio_to_standby();
}
_mcps_indication.rx_datarate = _params.rx_window1_config.datarate;
_lora_phy.rx_config(&_params.rx_window1_config);
_lora_phy.setup_rx_window(_params.rx_window1_config.is_rx_continuous,
_params.sys_params.max_rx_win_time);
}
void LoRaMac::open_rx2_window()
{
Lock lock(*this);
_lora_time.stop(_params.timers.rx_window2_timer);
_params.rx_window2_config.channel = _params.channel;
_params.rx_window2_config.frequency = _params.sys_params.rx2_channel.frequency;
_params.rx_window2_config.dl_dwell_time = _params.sys_params.downlink_dwell_time;
_params.rx_window2_config.is_repeater_supported = _params.is_repeater_supported;
_params.rx_window2_config.rx_slot = RX_SLOT_WIN_2;
_params.rx_window2_config.is_rx_continuous = get_device_class()==CLASS_C ? true : false;
_mcps_indication.rx_datarate = _params.rx_window2_config.datarate;
if (_lora_phy.rx_config(&_params.rx_window2_config)) {
_lora_phy.setup_rx_window(_params.rx_window2_config.is_rx_continuous,
_params.sys_params.max_rx_win_time);
_params.rx_slot = _params.rx_window2_config.is_rx_continuous ?
RX_SLOT_WIN_CLASS_C : RX_SLOT_WIN_2;
}
}
void LoRaMac::check_to_disable_ack_timeout(bool node_ack_requested,
device_class_t dev_class,
bool ack_received,
uint8_t ack_timeout_retries_counter,
uint8_t ack_timeout_retries)
{
// There are three cases where we need to stop the AckTimeoutTimer:
if( node_ack_requested == false ) {
if( dev_class == CLASS_C ) {
// FIRST CASE
// We have performed an unconfirmed uplink in class c mode
// and have received a downlink in RX1 or RX2.
_lora_time.stop(_params.timers.ack_timeout_timer);
}
} else {
if( ack_received == 1 ) {
// SECOND CASE
// We received an ACK for previously sent confirmable message
_lora_time.stop(_params.timers.ack_timeout_timer);
} else {
// THIRD CASE
// Max number of retries exceeded for confirmable message
if( ack_timeout_retries_counter > ack_timeout_retries ) {
_lora_time.stop(_params.timers.ack_timeout_timer);
}
}
}
}
void LoRaMac::on_ack_timeout_timer_event(void)
{
Lock lock(*this);
_params.ack_timeout_retry_counter++;
// reduce data rate
if ((_params.ack_timeout_retry_counter % 2)) {
_params.sys_params.channel_data_rate = _lora_phy.get_next_lower_tx_datarate(
_params.sys_params.channel_data_rate);
}
// Schedule a retry
if (handle_retransmission() != LORAWAN_STATUS_OK) {
// In a case when enabled channels are not found, PHY layer
// resorts to default channels. Next attempt should go forward as the
// default channels are always available if there is a base station in the
// vicinity. Otherwise something is wrong with the stack, we should assert
// here
_mac_commands.clear_command_buffer();
_params.is_node_ack_requested = false;
_mcps_confirmation.ack_received = false;
_mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter;
// now that is a critical failure
lorawan_status_t status = handle_retransmission();
MBED_ASSERT(status==LORAWAN_STATUS_OK);
}
}
bool LoRaMac::validate_payload_length(uint16_t length,
int8_t datarate,
uint8_t fopts_len)
{
uint16_t max_value = 0;
uint16_t payloadSize = 0;
max_value = _lora_phy.get_max_payload(datarate, _params.is_repeater_supported);
// Calculate the resulting payload size
payloadSize = (length + fopts_len);
// Validation of the application payload size
if ((payloadSize <= max_value) &&
(payloadSize <= LORAMAC_PHY_MAXPAYLOAD)) {
return true;
}
return false;
}
void LoRaMac::set_mlme_schedule_ul_indication(void)
{
_mlme_indication.indication_type = MLME_SCHEDULE_UPLINK;
_mlme_indication.pending= true;
}
// This is not actual transmission. It just schedules a message in response
// to MCPS request
lorawan_status_t LoRaMac::send(loramac_mhdr_t *machdr, const uint8_t fport,
const void *fbuffer, uint16_t fbuffer_size)
{
loramac_frame_ctrl_t fctrl;
fctrl.value = 0;
fctrl.bits.fopts_len = 0;
fctrl.bits.fpending = 0;
fctrl.bits.ack = false;
fctrl.bits.adr_ack_req = false;
fctrl.bits.adr = _params.sys_params.adr_on;
lorawan_status_t status = prepare_frame(machdr, &fctrl, fport, fbuffer,
fbuffer_size);
if (status != LORAWAN_STATUS_OK) {
return status;
}
// Reset confirm parameters
_mcps_confirmation.nb_retries = 0;
_mcps_confirmation.ack_received = false;
_mcps_confirmation.ul_frame_counter = _params.ul_frame_counter;
status = schedule_tx();
return status;
}
lorawan_status_t LoRaMac::schedule_tx()
{
channel_selection_params_t next_channel;
lorawan_time_t backoff_time = 0;
if (_params.sys_params.max_duty_cycle == 255) {
return LORAWAN_STATUS_DEVICE_OFF;
}
if (_params.sys_params.max_duty_cycle == 0) {
_params.timers.aggregated_timeoff = 0;
}
calculate_backOff(_params.last_channel_idx);
next_channel.aggregate_timeoff = _params.timers.aggregated_timeoff;
next_channel.current_datarate = _params.sys_params.channel_data_rate;
_params.is_dutycycle_on = MBED_CONF_LORA_DUTY_CYCLE_ON;
next_channel.dc_enabled = _params.is_dutycycle_on;
next_channel.joined = _is_nwk_joined;
next_channel.last_aggregate_tx_time = _params.timers.aggregated_last_tx_time;
lorawan_status_t status = _lora_phy.set_next_channel(&next_channel,
&_params.channel,
&backoff_time,
&_params.timers.aggregated_timeoff);
switch (status) {
case LORAWAN_STATUS_NO_CHANNEL_FOUND:
case LORAWAN_STATUS_NO_FREE_CHANNEL_FOUND:
return status;
case LORAWAN_STATUS_DUTYCYCLE_RESTRICTED:
if (backoff_time != 0) {
tr_debug("Next Transmission in %lu ms", backoff_time);
_lora_time.start(_params.timers.backoff_timer, backoff_time);
}
return LORAWAN_STATUS_OK;
default:
break;
}
tr_debug("Next Channel Idx=%d, DR=%d", _params.channel, next_channel.current_datarate);
uint8_t dr_offset = _lora_phy.apply_DR_offset(_params.sys_params.channel_data_rate,
_params.sys_params.rx1_dr_offset);
_lora_phy.compute_rx_win_params(dr_offset, _params.sys_params.min_rx_symb,
_params.sys_params.max_sys_rx_error,
&_params.rx_window1_config);
_lora_phy.compute_rx_win_params(_params.sys_params.rx2_channel.datarate,
_params.sys_params.min_rx_symb,
_params.sys_params.max_sys_rx_error,
&_params.rx_window2_config);
if (!_is_nwk_joined) {
_params.rx_window1_delay = _params.sys_params.join_accept_delay1
+ _params.rx_window1_config.window_offset;
_params.rx_window2_delay = _params.sys_params.join_accept_delay2
+ _params.rx_window2_config.window_offset;
} else {
if (validate_payload_length(_params.tx_buffer_len,
_params.sys_params.channel_data_rate,
_mac_commands.get_mac_cmd_length()) == false) {
return LORAWAN_STATUS_LENGTH_ERROR;
}
_params.rx_window1_delay = _params.sys_params.recv_delay1
+ _params.rx_window1_config.window_offset;
_params.rx_window2_delay = _params.sys_params.recv_delay2
+ _params.rx_window2_config.window_offset;
}
return send_frame_on_channel(_params.channel);
}
void LoRaMac::calculate_backOff(uint8_t channel)
{
lorawan_time_t elapsed_time = _lora_time.get_elapsed_time(_params.timers.mac_init_time);
_params.is_dutycycle_on = MBED_CONF_LORA_DUTY_CYCLE_ON;
_lora_phy.calculate_backoff(_is_nwk_joined, _params.is_last_tx_join_request, _params.is_dutycycle_on,
channel, elapsed_time, _params.timers.tx_toa);
// Update aggregated time-off. This must be an assignment and no incremental
// update as we do only calculate the time-off based on the last transmission
_params.timers.aggregated_timeoff = (_params.timers.tx_toa * _params.sys_params.aggregated_duty_cycle
- _params.timers.tx_toa);
}
void LoRaMac::reset_mac_parameters(void)
{
_is_nwk_joined = false;
_params.ul_frame_counter = 0;
_params.dl_frame_counter = 0;
_params.adr_ack_counter = 0;
_params.ul_nb_rep_counter = 0;
_params.max_ack_timeout_retries = 1;
_params.ack_timeout_retry_counter = 1;
_params.is_ack_retry_timeout_expired = false;
_params.sys_params.max_duty_cycle = 0;
_params.sys_params.aggregated_duty_cycle = 1;
_mac_commands.clear_command_buffer();
_mac_commands.clear_repeat_buffer();
_mac_commands.clear_mac_commands_in_next_tx();
_params.is_rx_window_enabled = true;
_lora_phy.reset_to_default_values(&_params, false);
_params.is_node_ack_requested = false;
_params.is_srv_ack_requested = false;
multicast_params_t *cur = _params.multicast_channels;
while (cur != NULL) {
cur->dl_frame_counter = 0;
cur = cur->next;
}
_params.channel = 0;
_params.last_channel_idx = _params.channel;
}
uint8_t LoRaMac::get_default_tx_datarate()
{
return _lora_phy.get_default_tx_datarate();
}
void LoRaMac::enable_adaptive_datarate(bool adr_enabled)
{
_params.sys_params.adr_on = adr_enabled;
}
lorawan_status_t LoRaMac::set_channel_data_rate(uint8_t data_rate)
{
if (_params.sys_params.adr_on) {
tr_error("Cannot set data rate. Please turn off ADR first.");
return LORAWAN_STATUS_PARAMETER_INVALID;
}
if (_lora_phy.verify_tx_datarate(data_rate, false) == true) {
_params.sys_params.channel_data_rate = data_rate;
} else {
return LORAWAN_STATUS_PARAMETER_INVALID;
}
return LORAWAN_STATUS_OK;
}
bool LoRaMac::tx_ongoing()
{
return _ongoing_tx_msg.tx_ongoing;
}
void LoRaMac::set_tx_ongoing(bool ongoing)
{
_ongoing_tx_msg.tx_ongoing = ongoing;
}
void LoRaMac::reset_ongoing_tx(bool reset_pending)
{
_ongoing_tx_msg.tx_ongoing = false;
memset(_ongoing_tx_msg.f_buffer, 0, MBED_CONF_LORA_TX_MAX_SIZE);
_ongoing_tx_msg.f_buffer_size = 0;
if (reset_pending) {
_ongoing_tx_msg.pending_size = 0;
}
}
int16_t LoRaMac::prepare_ongoing_tx(const uint8_t port,
const uint8_t* const data,
uint16_t length,
uint8_t flags,
uint8_t num_retries)
{
_ongoing_tx_msg.port = port;
uint8_t max_possible_size = get_max_possible_tx_size(length);
if (max_possible_size > MBED_CONF_LORA_TX_MAX_SIZE) {
max_possible_size = MBED_CONF_LORA_TX_MAX_SIZE;
}
if (max_possible_size < length) {
tr_info("Cannot transmit %d bytes. Possible TX Size is %d bytes",
length, max_possible_size);
_ongoing_tx_msg.pending_size = length - max_possible_size;
_ongoing_tx_msg.f_buffer_size = max_possible_size;
memcpy(_ongoing_tx_msg.f_buffer, data, _ongoing_tx_msg.f_buffer_size);
} else {
_ongoing_tx_msg.f_buffer_size = length;
_ongoing_tx_msg.pending_size = 0;
if (length > 0) {
memcpy(_ongoing_tx_msg.f_buffer, data, length);
}
}
// Handles all unconfirmed messages, including proprietary and multicast
if ((flags & MSG_FLAG_MASK) == MSG_UNCONFIRMED_FLAG
|| (flags & MSG_FLAG_MASK) == MSG_UNCONFIRMED_MULTICAST
|| (flags & MSG_FLAG_MASK) == MSG_UNCONFIRMED_PROPRIETARY) {
_ongoing_tx_msg.type = MCPS_UNCONFIRMED;
_ongoing_tx_msg.fport = port;
_ongoing_tx_msg.nb_trials = 1;
}
// Handles all confirmed messages, including proprietary and multicast
if ((flags & MSG_FLAG_MASK) == MSG_CONFIRMED_FLAG
|| (flags & MSG_FLAG_MASK) == MSG_CONFIRMED_MULTICAST
|| (flags & MSG_FLAG_MASK) == MSG_CONFIRMED_PROPRIETARY) {
_ongoing_tx_msg.type = MCPS_CONFIRMED;
_ongoing_tx_msg.fport = port;
_ongoing_tx_msg.nb_trials = num_retries;
}
tr_info("RTS = %u bytes, PEND = %u, Port: %u",
_ongoing_tx_msg.f_buffer_size, _ongoing_tx_msg.pending_size,
_ongoing_tx_msg.fport);
return _ongoing_tx_msg.f_buffer_size;
}
lorawan_status_t LoRaMac::send_ongoing_tx()
{
lorawan_status_t status;
_params.is_last_tx_join_request = false;
int8_t datarate = _params.sys_params.channel_data_rate;
// TODO: The comment is different than the code???
// Apply the minimum possible datarate.
// Some regions have limitations for the minimum datarate.
datarate = MAX(datarate, (int8_t)_lora_phy.get_minimum_tx_datarate());
loramac_mhdr_t machdr;
machdr.value = 0;
reset_mcps_confirmation();
_params.ack_timeout_retry_counter = 1;
_params.max_ack_timeout_retries = 1;
if (MCPS_UNCONFIRMED == _ongoing_tx_msg.type) {
machdr.bits.mtype = FRAME_TYPE_DATA_UNCONFIRMED_UP;
} else if (_ongoing_tx_msg.type == MCPS_CONFIRMED) {
machdr.bits.mtype = FRAME_TYPE_DATA_CONFIRMED_UP;
_params.max_ack_timeout_retries = _ongoing_tx_msg.nb_trials;
} else if (_ongoing_tx_msg.type == MCPS_PROPRIETARY) {
//TODO: Is this dead code currently??? Nobody sets this type
machdr.bits.mtype = FRAME_TYPE_PROPRIETARY;
} else {
return LORAWAN_STATUS_SERVICE_UNKNOWN;
}
if (_params.sys_params.adr_on == false) {
if (_lora_phy.verify_tx_datarate(datarate, false) == true) {
_params.sys_params.channel_data_rate = datarate;
} else {
return LORAWAN_STATUS_PARAMETER_INVALID;
}
}
status = send(&machdr, _ongoing_tx_msg.fport, _ongoing_tx_msg.f_buffer,
_ongoing_tx_msg.f_buffer_size);
if (status == LORAWAN_STATUS_OK) {
_mcps_confirmation.req_type = _ongoing_tx_msg.type;
}
return status;
}
device_class_t LoRaMac::get_device_class() const
{
return _device_class;
}
void LoRaMac::set_device_class(const device_class_t& device_class)
{
_device_class = device_class;
if (CLASS_A == _device_class) {
_lora_phy.put_radio_to_sleep();
} else if (CLASS_C == _device_class) {
_params.is_node_ack_requested = false;
_lora_phy.put_radio_to_sleep();
_lora_phy.compute_rx_win_params(
_params.sys_params.rx2_channel.datarate,
_params.sys_params.min_rx_symb,
_params.sys_params.max_sys_rx_error,
&_params.rx_window2_config);
}
if (CLASS_C == _device_class) {
open_rx2_window();
}
}
void LoRaMac::setup_link_check_request()
{
reset_mlme_confirmation();
_mlme_confirmation.req_type = MLME_LINK_CHECK;
_mlme_confirmation.pending = true;
_mac_commands.add_link_check_req();
}
lorawan_status_t LoRaMac::prepare_join(const lorawan_connect_t *params, bool is_otaa)
{
if (params) {
if (is_otaa) {
if ((params->connection_u.otaa.dev_eui == NULL) ||
(params->connection_u.otaa.app_eui == NULL) ||
(params->connection_u.otaa.app_key == NULL) ||
(params->connection_u.otaa.nb_trials == 0)) {
return LORAWAN_STATUS_PARAMETER_INVALID;
}
_params.keys.dev_eui = params->connection_u.otaa.dev_eui;
_params.keys.app_eui = params->connection_u.otaa.app_eui;
_params.keys.app_key = params->connection_u.otaa.app_key;
_params.max_join_request_trials = params->connection_u.otaa.nb_trials;
if (!_lora_phy.verify_nb_join_trials(params->connection_u.otaa.nb_trials)) {
// Value not supported, get default
_params.max_join_request_trials = MBED_CONF_LORA_NB_TRIALS;
}
// Reset variable JoinRequestTrials
_params.join_request_trial_counter = 0;
} else {
_params.net_id = params->connection_u.abp.nwk_id;
_params.dev_addr = params->connection_u.abp.dev_addr;
memcpy(_params.keys.nwk_skey, params->connection_u.abp.nwk_skey,
sizeof(_params.keys.nwk_skey));
memcpy(_params.keys.app_skey, params->connection_u.abp.app_skey,
sizeof(_params.keys.app_skey));
}
} else {
#if MBED_CONF_LORA_OVER_THE_AIR_ACTIVATION
const static uint8_t dev_eui[] = MBED_CONF_LORA_DEVICE_EUI;
const static uint8_t app_eui[] = MBED_CONF_LORA_APPLICATION_EUI;
const static uint8_t app_key[] = MBED_CONF_LORA_APPLICATION_KEY;
_params.keys.app_eui = const_cast<uint8_t *>(app_eui);
_params.keys.dev_eui = const_cast<uint8_t *>(dev_eui);
_params.keys.app_key = const_cast<uint8_t *>(app_key);
_params.max_join_request_trials = MBED_CONF_LORA_NB_TRIALS;
// Reset variable JoinRequestTrials
_params.join_request_trial_counter = 0;
reset_mac_parameters();
_params.sys_params.channel_data_rate =
_lora_phy.get_alternate_DR(_params.join_request_trial_counter + 1);
#else
const static uint8_t nwk_skey[] = MBED_CONF_LORA_NWKSKEY;
const static uint8_t app_skey[] = MBED_CONF_LORA_APPSKEY;
_params.net_id = (MBED_CONF_LORA_DEVICE_ADDRESS & LORAWAN_NETWORK_ID_MASK);
_params.dev_addr = MBED_CONF_LORA_DEVICE_ADDRESS;
memcpy(_params.keys.nwk_skey, nwk_skey,
sizeof(_params.keys.nwk_skey));
memcpy(_params.keys.app_skey, app_skey,
sizeof(_params.keys.app_skey));
#endif
}
return LORAWAN_STATUS_OK;
}
lorawan_status_t LoRaMac::join(bool is_otaa)
{
if (!is_otaa) {
set_nwk_joined(true);
return LORAWAN_STATUS_OK;
}
reset_mlme_confirmation();
_mlme_confirmation.req_type = MLME_JOIN;
return send_join_request();
}
static void memcpy_convert_endianess(uint8_t *dst,
const uint8_t *src,
uint16_t size)
{
dst = dst + (size - 1);
while (size--) {
*dst-- = *src++;
}
}
lorawan_status_t LoRaMac::prepare_frame(loramac_mhdr_t *machdr,
loramac_frame_ctrl_t *fctrl,
const uint8_t fport,
const void *fbuffer,
uint16_t fbuffer_size)
{
uint16_t i;
uint8_t pkt_header_len = 0;
uint32_t mic = 0;
const void* payload = fbuffer;
uint8_t frame_port = fport;
lorawan_status_t status = LORAWAN_STATUS_OK;
_params.tx_buffer_len = 0;
_params.is_node_ack_requested = false;
if (fbuffer == NULL) {
fbuffer_size = 0;
}
_params.tx_buffer_len = fbuffer_size;
_params.tx_buffer[pkt_header_len++] = machdr->value;
switch (machdr->bits.mtype) {
case FRAME_TYPE_JOIN_REQ:
_params.tx_buffer_len = pkt_header_len;
memcpy_convert_endianess(_params.tx_buffer + _params.tx_buffer_len,
_params.keys.app_eui, 8);
_params.tx_buffer_len += 8;
memcpy_convert_endianess(_params.tx_buffer + _params.tx_buffer_len,
_params.keys.dev_eui, 8);
_params.tx_buffer_len += 8;
_params.dev_nonce = _lora_phy.get_radio_rng();
_params.tx_buffer[_params.tx_buffer_len++] = _params.dev_nonce & 0xFF;
_params.tx_buffer[_params.tx_buffer_len++] = (_params.dev_nonce >> 8) & 0xFF;
if (0 != _lora_crypto.compute_join_frame_mic(_params.tx_buffer,
_params.tx_buffer_len & 0xFF,
_params.keys.app_key,
APPKEY_KEY_LENGTH,
&mic)) {
return LORAWAN_STATUS_CRYPTO_FAIL;
}
_params.tx_buffer[_params.tx_buffer_len++] = mic & 0xFF;
_params.tx_buffer[_params.tx_buffer_len++] = (mic >> 8) & 0xFF;
_params.tx_buffer[_params.tx_buffer_len++] = (mic >> 16) & 0xFF;
_params.tx_buffer[_params.tx_buffer_len++] = (mic >> 24) & 0xFF;
break;
case FRAME_TYPE_DATA_CONFIRMED_UP:
_params.is_node_ack_requested = true;
//Intentional fallthrough
case FRAME_TYPE_DATA_UNCONFIRMED_UP: {
if (!_is_nwk_joined) {
return LORAWAN_STATUS_NO_NETWORK_JOINED;
}
if (_params.sys_params.adr_on) {
if (_lora_phy.get_next_ADR(true,
_params.sys_params.channel_data_rate,
_params.sys_params.channel_tx_power,
_params.adr_ack_counter)) {
fctrl->bits.adr_ack_req = 1;
}
}
if (_params.is_srv_ack_requested == true) {
_params.is_srv_ack_requested = false;
fctrl->bits.ack = 1;
}
_params.tx_buffer[pkt_header_len++] = (_params.dev_addr) & 0xFF;
_params.tx_buffer[pkt_header_len++] = (_params.dev_addr >> 8) & 0xFF;
_params.tx_buffer[pkt_header_len++] = (_params.dev_addr >> 16) & 0xFF;
_params.tx_buffer[pkt_header_len++] = (_params.dev_addr >> 24) & 0xFF;
_params.tx_buffer[pkt_header_len++] = fctrl->value;
_params.tx_buffer[pkt_header_len++] = _params.ul_frame_counter & 0xFF;
_params.tx_buffer[pkt_header_len++] = (_params.ul_frame_counter >> 8)
& 0xFF;
_mac_commands.copy_repeat_commands_to_buffer();
const uint8_t mac_commands_len = _mac_commands.get_mac_cmd_length();
if ((payload != NULL) && (_params.tx_buffer_len > 0)) {
if (_mac_commands.is_mac_command_in_next_tx() == true) {
if (mac_commands_len <= LORA_MAC_COMMAND_MAX_FOPTS_LENGTH) {
fctrl->bits.fopts_len += mac_commands_len;
// Update FCtrl field with new value of OptionsLength
_params.tx_buffer[0x05] = fctrl->value;
const uint8_t *buffer =
_mac_commands.get_mac_commands_buffer();
for (i = 0; i < mac_commands_len; i++) {
_params.tx_buffer[pkt_header_len++] = buffer[i];
}
} else {
_params.tx_buffer_len = mac_commands_len;
payload = _mac_commands.get_mac_commands_buffer();
frame_port = 0;
}
}
} else {
if ((mac_commands_len > 0)
&& (_mac_commands.is_mac_command_in_next_tx() == true)) {
_params.tx_buffer_len = mac_commands_len;
payload = _mac_commands.get_mac_commands_buffer();
frame_port = 0;
}
}
_mac_commands.parse_mac_commands_to_repeat();
if ((payload != NULL) && (_params.tx_buffer_len > 0)) {
_params.tx_buffer[pkt_header_len++] = frame_port;
uint8_t *key = _params.keys.app_skey;
uint32_t key_length = sizeof(_params.keys.app_skey)*8;
if (frame_port == 0) {
_mac_commands.clear_command_buffer();
key = _params.keys.nwk_skey;
key_length = sizeof(_params.keys.nwk_skey)*8;
}
if (0 != _lora_crypto.encrypt_payload((uint8_t*) payload, _params.tx_buffer_len,
key, key_length,
_params.dev_addr, UP_LINK,
_params.ul_frame_counter,
&_params.tx_buffer[pkt_header_len])) {
status = LORAWAN_STATUS_CRYPTO_FAIL;
}
}
_params.tx_buffer_len = pkt_header_len + _params.tx_buffer_len;
if (0 != _lora_crypto.compute_mic(_params.tx_buffer, _params.tx_buffer_len,
_params.keys.nwk_skey, sizeof(_params.keys.nwk_skey)*8,
_params.dev_addr,
UP_LINK, _params.ul_frame_counter, &mic)) {
status = LORAWAN_STATUS_CRYPTO_FAIL;
}
_params.tx_buffer[_params.tx_buffer_len + 0] = mic & 0xFF;
_params.tx_buffer[_params.tx_buffer_len + 1] = (mic >> 8) & 0xFF;
_params.tx_buffer[_params.tx_buffer_len + 2] = (mic >> 16) & 0xFF;
_params.tx_buffer[_params.tx_buffer_len + 3] = (mic >> 24) & 0xFF;
_params.tx_buffer_len += LORAMAC_MFR_LEN;
}
break;
case FRAME_TYPE_PROPRIETARY:
if ((fbuffer != NULL) && (_params.tx_buffer_len > 0)) {
memcpy(_params.tx_buffer + pkt_header_len, (uint8_t*) fbuffer,
_params.tx_buffer_len);
_params.tx_buffer_len = pkt_header_len + _params.tx_buffer_len;
}
break;
default:
status = LORAWAN_STATUS_SERVICE_UNKNOWN;
}
tr_debug("Frame prepared to send at port %u", frame_port);
return status;
}
lorawan_status_t LoRaMac::send_frame_on_channel(uint8_t channel)
{
tx_config_params_t tx_config;
int8_t tx_power = 0;
tx_config.channel = channel;
tx_config.datarate = _params.sys_params.channel_data_rate;
tx_config.tx_power = _params.sys_params.channel_tx_power;
tx_config.max_eirp = _params.sys_params.max_eirp;
tx_config.antenna_gain = _params.sys_params.antenna_gain;
tx_config.pkt_len = _params.tx_buffer_len;
_lora_phy.tx_config(&tx_config, &tx_power, &_params.timers.tx_toa);
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
_mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
_mcps_confirmation.data_rate = _params.sys_params.channel_data_rate;
_mcps_confirmation.tx_power = tx_power;
_mcps_confirmation.channel = channel;
_mcps_confirmation.tx_toa = _params.timers.tx_toa;
_mlme_confirmation.tx_toa = _params.timers.tx_toa;
if (!_is_nwk_joined) {
_params.join_request_trial_counter++;
}
_lora_phy.handle_send(_params.tx_buffer, _params.tx_buffer_len);
return LORAWAN_STATUS_OK;
}
void LoRaMac::reset_mcps_confirmation()
{
memset((uint8_t*) &_mcps_confirmation, 0, sizeof(_mcps_confirmation));
_mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
}
void LoRaMac::reset_mlme_confirmation()
{
memset((uint8_t*) &_mlme_confirmation, 0, sizeof(_mlme_confirmation));
_mlme_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
}
void LoRaMac::reset_mcps_indication()
{
memset((uint8_t*) &_mcps_indication, 0, sizeof(_mcps_indication));
_mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ERROR;
}
void LoRaMac::set_tx_continuous_wave(uint8_t channel, int8_t datarate, int8_t tx_power,
float max_eirp, float antenna_gain, uint16_t timeout)
{
cw_mode_params_t continuous_wave;
continuous_wave.channel = channel;
continuous_wave.datarate = datarate;
continuous_wave.tx_power = tx_power;
continuous_wave.max_eirp = max_eirp;
continuous_wave.antenna_gain = antenna_gain;
continuous_wave.timeout = timeout;
_lora_phy.set_tx_cont_mode(&continuous_wave);
}
lorawan_status_t LoRaMac::initialize(EventQueue *queue)
{
_lora_time.activate_timer_subsystem(queue);
_ev_queue = queue;
_channel_plan.activate_channelplan_subsystem(&_lora_phy);
_device_class = CLASS_A;
_params.join_request_trial_counter = 0;
_params.max_join_request_trials = 1;
_params.is_repeater_supported = false;
_params.timers.aggregated_last_tx_time = 0;
_params.timers.aggregated_timeoff = 0;
_lora_phy.reset_to_default_values(&_params, true);
_params.sys_params.max_sys_rx_error = 10;
_params.sys_params.min_rx_symb = 6;
_params.sys_params.retry_num = 1;
reset_mac_parameters();
srand(_lora_phy.get_radio_rng());
_params.is_nwk_public = MBED_CONF_LORA_PUBLIC_NETWORK;
_lora_phy.setup_public_network_mode(_params.is_nwk_public);
_lora_phy.put_radio_to_sleep();
_lora_time.init(_params.timers.backoff_timer,
mbed::callback(this, &LoRaMac::on_backoff_timer_expiry));
_lora_time.init(_params.timers.rx_window1_timer,
mbed::callback(this, &LoRaMac::open_rx1_window));
_lora_time.init(_params.timers.rx_window2_timer,
mbed::callback(this, &LoRaMac::open_rx2_window));
_lora_time.init(_params.timers.ack_timeout_timer,
mbed::callback(this, &LoRaMac::on_ack_timeout_timer_event));
_params.timers.mac_init_time = _lora_time.get_current_time();
_params.sys_params.adr_on = MBED_CONF_LORA_ADR_ON;
_params.is_nwk_public = MBED_CONF_LORA_PUBLIC_NETWORK;
_lora_phy.setup_public_network_mode(MBED_CONF_LORA_PUBLIC_NETWORK);
return LORAWAN_STATUS_OK;
}
void LoRaMac::disconnect()
{
_lora_time.stop(_params.timers.backoff_timer);
_lora_time.stop(_params.timers.rx_window1_timer);
_lora_time.stop(_params.timers.rx_window2_timer);
_lora_time.stop(_params.timers.ack_timeout_timer);
_lora_phy.put_radio_to_sleep();
_is_nwk_joined = false;
_params.is_ack_retry_timeout_expired = false;
_params.is_rx_window_enabled = true;
_params.is_node_ack_requested = false;
_params.is_srv_ack_requested = false;
_mac_commands.clear_command_buffer();
_mac_commands.clear_repeat_buffer();
_mac_commands.clear_mac_commands_in_next_tx();
reset_mcps_confirmation();
reset_mlme_confirmation();
reset_mcps_indication();
}
uint8_t LoRaMac::get_max_possible_tx_size(uint8_t size)
{
uint8_t max_possible_payload_size = 0;
uint8_t current_payload_size = 0;
uint8_t fopt_len = _mac_commands.get_mac_cmd_length()
+ _mac_commands.get_repeat_commands_length();
if (_params.sys_params.adr_on) {
_lora_phy.get_next_ADR(false, _params.sys_params.channel_data_rate,
_params.sys_params.channel_tx_power,
_params.adr_ack_counter);
}
current_payload_size = _lora_phy.get_max_payload(_params.sys_params.channel_data_rate, _params.is_repeater_supported);
if (current_payload_size >= fopt_len) {
max_possible_payload_size = current_payload_size - fopt_len;
} else {
max_possible_payload_size = current_payload_size;
fopt_len = 0;
_mac_commands.clear_command_buffer();
_mac_commands.clear_repeat_buffer();
}
if (validate_payload_length(size, _params.sys_params.channel_data_rate,
fopt_len) == false) {
return max_possible_payload_size;
}
return current_payload_size;
}
bool LoRaMac::nwk_joined()
{
return _is_nwk_joined;
}
void LoRaMac::set_nwk_joined(bool joined)
{
_is_nwk_joined = joined;
}
lorawan_status_t LoRaMac::add_channel_plan(const lorawan_channelplan_t& plan)
{
if (tx_ongoing()) {
return LORAWAN_STATUS_BUSY;
}
return _channel_plan.set_plan(plan);
}
lorawan_status_t LoRaMac::remove_channel_plan()
{
if (tx_ongoing()) {
return LORAWAN_STATUS_BUSY;
}
return _channel_plan.remove_plan();
}
lorawan_status_t LoRaMac::get_channel_plan(lorawan_channelplan_t& plan)
{
return _channel_plan.get_plan(plan, _lora_phy.get_phy_channels());
}
lorawan_status_t LoRaMac::remove_single_channel(uint8_t id)
{
if (tx_ongoing()) {
return LORAWAN_STATUS_BUSY;
}
return _channel_plan.remove_single_channel(id);
}
lorawan_status_t LoRaMac::multicast_channel_link(multicast_params_t *channel_param)
{
if (channel_param == NULL) {
return LORAWAN_STATUS_PARAMETER_INVALID;
}
if (tx_ongoing()) {
return LORAWAN_STATUS_BUSY;
}
channel_param->dl_frame_counter = 0;
if (_params.multicast_channels == NULL) {
_params.multicast_channels = channel_param;
} else {
multicast_params_t *cur = _params.multicast_channels;
while (cur->next != NULL) {
cur = cur->next;
}
cur->next = channel_param;
}
return LORAWAN_STATUS_OK;
}
lorawan_status_t LoRaMac::multicast_channel_unlink(
multicast_params_t *channel_param)
{
if (channel_param == NULL) {
return LORAWAN_STATUS_PARAMETER_INVALID;
}
if (tx_ongoing()) {
return LORAWAN_STATUS_BUSY;
}
if (_params.multicast_channels != NULL) {
if (_params.multicast_channels == channel_param) {
_params.multicast_channels = channel_param->next;
} else {
multicast_params_t *cur = _params.multicast_channels;
while (cur->next && cur->next != channel_param) {
cur = cur->next;
}
if (cur->next) {
cur->next = channel_param->next;
}
}
channel_param->next = NULL;
}
return LORAWAN_STATUS_OK;
}
void LoRaMac::bind_radio_driver(LoRaRadio& radio)
{
_lora_phy.set_radio_instance(radio);
}
#if defined(LORAWAN_COMPLIANCE_TEST)
/***************************************************************************
* Compliance testing *
**************************************************************************/
lorawan_status_t LoRaMac::mlme_request( loramac_mlme_req_t *mlmeRequest )
{
if (LORAMAC_IDLE != _params.mac_state) {
return LORAWAN_STATUS_BUSY;
}
reset_mlme_confirmation();
_mlme_confirmation.req_type = mlmeRequest->type;
_params.flags.bits.mlme_req = 1;
lorawan_status_t status = LORAWAN_STATUS_SERVICE_UNKNOWN;
if (MLME_TXCW == mlmeRequest->type) {
set_tx_continuous_wave(_params.channel, _params.sys_params.channel_data_rate, _params.sys_params.channel_tx_power,
_params.sys_params.max_eirp, _params.sys_params.antenna_gain, mlmeRequest->cw_tx_mode.timeout);
_lora_time.start(_params.timers.mac_state_check_timer,
MAC_STATE_CHECK_TIMEOUT);
_params.mac_state |= LORAMAC_TX_RUNNING;
status = LORAWAN_STATUS_OK;
} else if (MLME_TXCW_1 == mlmeRequest->type) {
set_tx_continuous_wave(0, 0, mlmeRequest->cw_tx_mode.power, 0, 0, mlmeRequest->cw_tx_mode.timeout);
_lora_time.start(_params.timers.mac_state_check_timer,
MAC_STATE_CHECK_TIMEOUT);
_params.mac_state |= LORAMAC_TX_RUNNING;
status = LORAWAN_STATUS_OK;
}
if (status != LORAWAN_STATUS_OK) {
_params.is_node_ack_requested = false;
_params.flags.bits.mlme_req = 0;
}
return status;
}
lorawan_status_t LoRaMac::test_request( loramac_compliance_test_req_t *mcpsRequest )
{
if (_params.mac_state != LORAMAC_IDLE) {
return LORAWAN_STATUS_BUSY;
}
loramac_mhdr_t machdr;
int8_t datarate = mcpsRequest->data_rate;
// TODO: The comment is different than the code???
// Apply the minimum possible datarate.
// Some regions have limitations for the minimum datarate.
datarate = MAX(datarate, (int8_t)_lora_phy.get_minimum_tx_datarate());
machdr.value = 0;
reset_mcps_confirmation();
_params.ack_timeout_retry_counter = 1;
_params.max_ack_timeout_retries = 1;
switch (mcpsRequest->type) {
case MCPS_UNCONFIRMED: {
machdr.bits.mtype = FRAME_TYPE_DATA_UNCONFIRMED_UP;
break;
}
case MCPS_CONFIRMED: {
machdr.bits.mtype = FRAME_TYPE_DATA_CONFIRMED_UP;
_params.max_ack_timeout_retries = mcpsRequest->nb_trials;
break;
}
case MCPS_PROPRIETARY: {
machdr.bits.mtype = FRAME_TYPE_PROPRIETARY;
break;
}
default:
return LORAWAN_STATUS_PARAMETER_INVALID;
}
// Filter fPorts
// TODO: Does not work with PROPRIETARY messages
// if( IsFPortAllowed( mcpsRequest->fport ) == false ) {
// return LORAWAN_STATUS_PARAMETER_INVALID;
// }
if (_params.sys_params.adr_on == false) {
if (_lora_phy.verify_tx_datarate(datarate, false) == true) {
_params.sys_params.channel_data_rate = datarate;
} else {
return LORAWAN_STATUS_PARAMETER_INVALID;
}
}
lorawan_status_t status = send(&machdr, mcpsRequest->fport, mcpsRequest->f_buffer,
mcpsRequest->f_buffer_size);
if (status == LORAWAN_STATUS_OK) {
_mcps_confirmation.req_type = mcpsRequest->type;
_params.flags.bits.mcps_req = 1;
} else {
_params.is_node_ack_requested = false;
}
return status;
}
lorawan_status_t LoRaMac::LoRaMacSetTxTimer( uint32_t TxDutyCycleTime )
{
_lora_time.start(tx_next_packet_timer, TxDutyCycleTime);
return LORAWAN_STATUS_OK;
}
lorawan_status_t LoRaMac::LoRaMacStopTxTimer( )
{
_lora_time.stop(tx_next_packet_timer);
return LORAWAN_STATUS_OK;
}
void LoRaMac::LoRaMacTestRxWindowsOn( bool enable )
{
_params.is_rx_window_enabled = enable;
}
void LoRaMac::LoRaMacTestSetMic( uint16_t txPacketCounter )
{
_params.ul_frame_counter = txPacketCounter;
_params.is_ul_frame_counter_fixed = true;
}
void LoRaMac::LoRaMacTestSetDutyCycleOn( bool enable )
{
if(_lora_phy.verify_duty_cycle(enable) == true)
{
_params.is_dutycycle_on = enable;
}
}
void LoRaMac::LoRaMacTestSetChannel( uint8_t channel )
{
_params.channel = channel;
}
#endif