/** / _____) _ | | ( (____ _____ ____ _| |_ _____ ____| |__ \____ \| ___ | (_ _) ___ |/ ___) _ \ _____) ) ____| | | || |_| ____( (___| | | | (______/|_____)_|_|_| \__)_____)\____)_| |_| (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 #include #include "LoRaMac.h" #include "LoRaMacClassBInterface.h" #include "mbed-trace/mbed_trace.h" #define TRACE_GROUP "LMAC" 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 #define MHDR_LEN 1 #define PORT_FIELD_LEN 1 #define FHDR_LEN_WITHOUT_FOPTS 7 #define JOIN_ACCEPT_LEN_WITHOUT_CFLIST 12 #define RJCOUNT_ROLLOVER 65535 static void memcpy_convert_endianess(uint8_t *dst, const uint8_t *src, uint16_t size) { dst = dst + (size - 1); while (size--) { *dst-- = *src++; } } static const char *rx_slot_strings[RX_SLOT_MAX] = { "RX1", "RX2", "Class-C", "Beacon", "Unicast Ping-Slot", "Multicast Ping-Slot" }; inline const char *get_rx_slot_string(rx_slot_t rx_slot) { return (rx_slot < RX_SLOT_MAX) ? rx_slot_strings[rx_slot] : "RX?"; } LoRaMac::LoRaMac() : _lora_time(), _lora_phy(NULL), _mac_commands(), _channel_plan(), _lora_crypto(), _params(), _ev_queue(NULL), _mcps_indication(), _mcps_confirmation(), _mlme_indication(), _mlme_confirmation(), _ongoing_tx_msg(), _is_nwk_joined(false), _can_cancel_tx(true), _continuous_rx2_window_open(false), _dl_fport_available(true), _device_class(CLASS_A), _prev_qos_level(LORAWAN_DEFAULT_QOS), _demod_ongoing(false), _mod_ongoing(false) { _params.rejoin_forced = false; _params.forced_datarate = DR_0; memset(&_params.sys_params, 0, sizeof(_params.sys_params)); _params.is_rx_window_enabled = true; _params.max_ack_timeout_retries = 1; _params.ack_timeout_retry_counter = 1; _params.join_request_type = JOIN_REQUEST; //TODO: RJcount1 must be stored to NVM! _params.RJcount0 = 0; _params.RJcount1 = 0; reset_mcps_confirmation(); reset_mcps_indication(); set_ping_slot_info(MBED_CONF_LORA_PING_SLOT_PERIODICITY); } 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_indication_t *LoRaMac::get_mlme_indication() const { return &_mlme_indication; } 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; } else { if (_params.server_type == LW1_1) { // because network server will not accept un-incremented fcnt _params.ul_frame_counter++; } _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR; } _params.ul_frame_counter++; _params.adr_ack_counter++; } else { //UNCONFIRMED or PROPRIETARY _params.ul_frame_counter++; _params.adr_ack_counter++; if (_params.sys_params.nb_trans > 1) { _mcps_confirmation.nb_retries = _params.ul_nb_rep_counter; } } } void LoRaMac::post_process_mcps_ind() { _mcps_indication.pending = false; } void LoRaMac::post_process_mlme_ind() { _mlme_indication.pending = false; } lorawan_time_t LoRaMac::get_current_time(void) { return _lora_time.get_current_time(); } lorawan_gps_time_t LoRaMac::get_gps_time(void) { return _lora_time.get_gps_time(); } void LoRaMac::set_gps_time(lorawan_gps_time_t gps_time) { _lora_time.set_gps_time(gps_time); } rx_slot_t LoRaMac::get_current_slot(void) { return _params.rx_slot; } /** * This part handles incoming frames in response to Radio RX Interrupt */ loramac_event_info_status_t LoRaMac::handle_join_accept_frame(const uint8_t *payload, uint16_t size) { uint32_t mic = 0; uint32_t mic_rx = 0; server_type_t stype = LW1_0_2; bool is_cflist_present = false; if (size > JOIN_ACCEPT_LEN_WITHOUT_CFLIST) { is_cflist_present = true; } if (0 != _lora_crypto.decrypt_join_frame(payload + 1, size - 1, _params.rx_buffer + 1, (_params.join_request_type == JOIN_REQUEST))) { return LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; } _params.rx_buffer[0] = payload[0]; //Store server type to local so that invalid join accept of rejoin request won't affect the orig. type. if ((((_params.rx_buffer[11] >> 7) & 0x01) == 1) && MBED_CONF_LORA_VERSION == LORAWAN_VERSION_1_1) { stype = LW1_1; tr_debug("LoRaWAN 1.1.x server"); } else { stype = LW1_0_2; tr_debug("LoRaWAN 1.0.x server"); _lora_crypto.unset_js_keys(); } uint8_t payload_start = 1; uint8_t mic_start = 0; uint8_t args_size = 0; uint8_t args[16]; uint16_t nonce_or_rj_cnt = 0; if (stype == LW1_0_2) { mic_start = size - LORAMAC_MFR_LEN; memcpy(args, _params.rx_buffer + 1, 6); memcpy(args + 6, (uint8_t *) &_params.dev_nonce, 2); args_size = 8; } else { //MIC calculation needs more params, so we move the payload a bit memmove(_params.rx_buffer + 11, _params.rx_buffer, size); _params.rx_buffer[0] = _params.join_request_type; // JoinReqType memcpy_convert_endianess(_params.rx_buffer + 1, _params.app_eui, 8); // JoinEUI // RJCntX are always incremented so the RJCntLast = RJCntX - 1 switch (_params.join_request_type) { case JOIN_REQUEST: nonce_or_rj_cnt = _params.dev_nonce; break; case REJOIN_REQUEST_TYPE0: case REJOIN_REQUEST_TYPE2: nonce_or_rj_cnt = _params.RJcount0 - 1; break; case REJOIN_REQUEST_TYPE1: nonce_or_rj_cnt = _params.RJcount1 - 1; break; default: tr_error("Unknown Join Request Type"); MBED_ASSERT(false); } _params.rx_buffer[9] = nonce_or_rj_cnt & 0xFF; // DevNonce _params.rx_buffer[10] = (nonce_or_rj_cnt >> 8) & 0xFF; // MIC is encrypted as part of payload mic_start = size + 11 - LORAMAC_MFR_LEN; payload_start += 11; memcpy(args, _params.rx_buffer + payload_start, 3); memcpy_convert_endianess(args + 3, _params.app_eui, 8); args[3 + 8] = nonce_or_rj_cnt & 0xFF; args[3 + 9] = (nonce_or_rj_cnt >> 8) & 0xFF; args_size = 13; } if (_lora_crypto.compute_join_frame_mic(_params.rx_buffer, mic_start, JOIN_ACCEPT, &mic) != 0) { return LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; } mic_rx |= (uint32_t) _params.rx_buffer[mic_start]; mic_rx |= ((uint32_t) _params.rx_buffer[mic_start + 1] << 8); mic_rx |= ((uint32_t) _params.rx_buffer[mic_start + 2] << 16); mic_rx |= ((uint32_t) _params.rx_buffer[mic_start + 3] << 24); if (mic_rx == mic) { _lora_time.stop(_params.timers.rx_window2_timer); _params.server_type = stype; if (_lora_crypto.compute_skeys_for_join_frame(args, args_size, _params.server_type) != 0) { return LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; } _params.net_id = (uint32_t) _params.rx_buffer[payload_start + 3]; _params.net_id |= ((uint32_t) _params.rx_buffer[payload_start + 4] << 8); _params.net_id |= ((uint32_t) _params.rx_buffer[payload_start + 5] << 16); _params.dev_addr = (uint32_t) _params.rx_buffer[payload_start + 6]; _params.dev_addr |= ((uint32_t) _params.rx_buffer[payload_start + 7] << 8); _params.dev_addr |= ((uint32_t) _params.rx_buffer[payload_start + 8] << 16); _params.dev_addr |= ((uint32_t) _params.rx_buffer[payload_start + 9] << 24); if (_params.server_type == LW1_0_2 || _params.join_request_type != REJOIN_REQUEST_TYPE2) { reset_mac_parameters(); reset_frame_counters(); reset_phy_params(); } else if (_params.server_type == LW1_1 && _params.join_request_type == REJOIN_REQUEST_TYPE2) { reset_frame_counters(); } _params.sys_params.rx1_dr_offset = (_params.rx_buffer[payload_start + 10] >> 4) & 0x07; _params.sys_params.rx2_channel.datarate = _params.rx_buffer[payload_start + 10] & 0x0F; _params.sys_params.recv_delay1 = (_params.rx_buffer[payload_start + 11] & 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 (== 17) //TODO: join request type is needed here also! See LW1.1 lines 1711 -> 1719 (Reset or not) // LW1.1 CF_LIST's 16th byte is CFListType! if (is_cflist_present) { _lora_phy->apply_cf_list(&_params.rx_buffer[payload_start + JOIN_ACCEPT_LEN_WITHOUT_CFLIST], size - (JOIN_ACCEPT_LEN_WITHOUT_CFLIST + MHDR_LEN + LORAMAC_MFR_LEN)); } else { if (_params.join_request_type != REJOIN_REQUEST_TYPE2) { _lora_phy->restore_default_channels(); } } _is_nwk_joined = true; if (_params.join_request_type == REJOIN_REQUEST_TYPE0 || _params.join_request_type == REJOIN_REQUEST_TYPE2) { _params.RJcount0 = 0; } } else { return LORAMAC_EVENT_INFO_STATUS_JOIN_FAIL; } return LORAMAC_EVENT_INFO_STATUS_OK; } 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, uint16_t confFCnt, uint32_t address, uint32_t *downlink_counter) { uint32_t mic = 0; uint32_t mic_rx = 0; uint32_t args = confFCnt; 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_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; } _lora_crypto.compute_mic(payload, size - LORAMAC_MFR_LEN, args, address, DOWN_LINK, *downlink_counter, 0, &mic); if (mic_rx != mic) { _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL; return false; } return true; } void LoRaMac::extract_data_and_mac_commands(const uint8_t *payload, uint16_t size, uint8_t fopts_len, uint32_t address, uint32_t downlink_counter, seq_counter_type_t cnt_type, int16_t rssi, int8_t snr, Callback confirm_handler) { uint8_t frame_len = 0; uint8_t payload_start_index = FHDR_LEN_WITHOUT_FOPTS + PORT_FIELD_LEN + 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) { if (_lora_crypto.decrypt_payload(payload + payload_start_index, frame_len, address, DOWN_LINK, downlink_counter, cnt_type, FRMPAYLOAD, _params.rx_buffer, _params.server_type, true) != 0) { _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; } if (_mac_commands.process_mac_commands(_params.rx_buffer, 0, frame_len, snr, _params.sys_params, *_lora_phy, confirm_handler, get_current_slot()) != 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; } if (!extract_mac_commands_only(payload, size, snr, fopts_len, confirm_handler)) { return; } if (_lora_crypto.decrypt_payload(payload + payload_start_index, frame_len, address, DOWN_LINK, downlink_counter, cnt_type, FRMPAYLOAD, _params.rx_buffer, _params.server_type, false) != 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; } } bool LoRaMac::extract_mac_commands_only(const uint8_t *payload, uint16_t size, int8_t snr, uint8_t fopts_len, Callback confirm_handler) { if (fopts_len > 0) { uint8_t buffer[15] = {0}; unsigned pld_idx = MHDR_LEN + FHDR_LEN_WITHOUT_FOPTS; if (_params.server_type == LW1_1) { if (0 != _lora_crypto.decrypt_payload(payload + pld_idx, fopts_len, _params.dev_addr, DOWN_LINK, _params.dl_frame_counter, NFCNT_DOWN, FOPTS, buffer, _params.server_type, true)) { _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_CRYPTO_FAIL; return false; } } else { memcpy(buffer, payload + pld_idx, fopts_len); } if (_mac_commands.process_mac_commands(buffer, 0, fopts_len, snr, _params.sys_params, *_lora_phy, confirm_handler, get_current_slot()) != LORAWAN_STATUS_OK) { _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ERROR; return false; } if (_mac_commands.has_sticky_mac_cmd()) { set_mlme_schedule_ul_indication(); _mac_commands.clear_sticky_mac_cmd(); } } return true; } multicast_params_t *LoRaMac::get_multicast_params(uint32_t address) { multicast_params_t *obj = _params.multicast_channels; while (obj != NULL) { if (address == obj->address) { return obj; } obj = obj->next; } return NULL; } void LoRaMac::reset_multicast_counters() { multicast_params_t *obj = _params.multicast_channels; while (obj != NULL) { obj->dl_frame_counter = 0; obj = obj->next; } } 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, Callback confirm_handler) { 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; seq_counter_type_t cnt_type = NFCNT_DOWN; uint16_t fport = 0; uint16_t confFCnt = 0; // always assume in the beginning that a downlink fport field is included _dl_fport_available = true; 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); fctrl.value = payload[ptr_pos++]; int check_frm_len = size - (fctrl.bits.fopts_len + LORA_MAC_FRMPAYLOAD_OVERHEAD); if (check_frm_len < 0) { _dl_fport_available = false; } if (_dl_fport_available) { fport = payload[FHDR_LEN_WITHOUT_FOPTS + fctrl.bits.fopts_len]; } //TODO: Move this handling to LoRaMACCrypto class! if (address != _params.dev_addr) { // check if Multicast is destined for us cur_multicast_params = get_multicast_params(address); if (cur_multicast_params) { is_multicast = true; downlink_counter = cur_multicast_params->dl_frame_counter; } else { // We are not the destination of this frame. _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL; _mcps_indication.pending = false; return; } } else { is_multicast = false; downlink_counter = _params.dl_frame_counter; } if (_params.server_type == LW1_1) { if (_params.is_node_ack_requested && fctrl.bits.ack) { confFCnt = _mcps_confirmation.ul_frame_counter; } if (!is_multicast) { if (_dl_fport_available && fport != 0) { downlink_counter = _params.app_dl_frame_counter++; cnt_type = AFCNT_DOWN; } else { cnt_type = NFCNT_DOWN; } } } //perform MIC check if (!message_integrity_check(payload, size, &ptr_pos, confFCnt, address, &downlink_counter)) { tr_error("MIC failed"); _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_MIC_FAIL; _mcps_indication.pending = false; 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.dl_fpending_ul_class_b; _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(); _mac_commands.clear_command_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; return; } cur_multicast_params->dl_frame_counter = downlink_counter; } else { if (msg_type == FRAME_TYPE_DATA_CONFIRMED_DOWN) { _params.is_srv_ack_requested = true; _params.counterForAck = downlink_counter; _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; return; } } 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; return; } } _params.dl_frame_counter = downlink_counter; } // message is intended for us and MIC have passed, stop RX2 Window // Spec: 3.3.4 Receiver Activity during the receive windows if (get_current_slot() == RX_SLOT_WIN_1) { _lora_time.stop(_params.timers.rx_window2_timer); } else { _lora_time.stop(_params.timers.rx_window1_timer); _lora_time.stop(_params.timers.rx_window2_timer); } if (_device_class == CLASS_C) { _lora_time.stop(_rx2_closure_timer_for_class_c); } if (_params.is_node_ack_requested && fctrl.bits.ack) { _mcps_confirmation.ack_received = fctrl.bits.ack; _mcps_indication.is_ack_recvd = fctrl.bits.ack; } if (check_frm_len > 0) { extract_data_and_mac_commands(payload, size, fctrl.bits.fopts_len, address, downlink_counter, cnt_type, rssi, snr, confirm_handler); } else { extract_mac_commands_only(payload, size, snr, fctrl.bits.fopts_len, confirm_handler); } // 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; } // only stop ack timer, if the ack is actually received if (_mcps_confirmation.ack_received) { _lora_time.stop(_params.timers.ack_timeout_timer); } channel_params_t *list = _lora_phy->get_phy_channels(); _mcps_indication.channel = list[_params.channel].frequency; if (get_current_slot() == RX_SLOT_WIN_1) { _mcps_indication.rx_toa = _lora_phy->get_rx_time_on_air(_params.rx_window1_config.modem_type, _mcps_indication.buffer_size); } else { _mcps_indication.rx_toa = _lora_phy->get_rx_time_on_air(_params.rx_window2_config.modem_type, _mcps_indication.buffer_size); } } void LoRaMac::set_batterylevel_callback(mbed::Callback battery_level) { _mac_commands.set_batterylevel_callback(battery_level); } void LoRaMac::on_radio_tx_done(lorawan_time_t timestamp) { _mod_ongoing = false; if (_device_class == CLASS_C) { // this will open a continuous RX2 window until time==RECV_DELAY1 open_rx2_window(); } else { _lora_phy->put_radio_to_sleep(); } if ((_mcps_confirmation.req_type == MCPS_UNCONFIRMED) && (_params.sys_params.nb_trans > 1)) { //MBED_ASSERT(_params.ul_nb_rep_counter <= _params.sys_params.nb_trans); _params.ul_nb_rep_counter++; } if (_params.is_rx_window_enabled == true) { lorawan_time_t time_diff = _lora_time.get_current_time() - timestamp; // start timer after which rx1_window will get opened _lora_time.start(_params.timers.rx_window1_timer, _params.rx_window1_delay - time_diff); // start timer after which rx2_window will get opened _lora_time.start(_params.timers.rx_window2_timer, _params.rx_window2_delay - time_diff); // If class C and an Unconfirmed messgae is outgoing, // this will start a timer which will invoke rx2 would be // closure handler if (get_device_class() == CLASS_C) { _lora_time.start(_rx2_closure_timer_for_class_c, (_params.rx_window2_delay - time_diff) + _params.rx_window2_config.window_timeout_ms); } // start timer after which ack wait will timeout (for Confirmed messages) if (_params.is_node_ack_requested) { _lora_time.start(_params.timers.ack_timeout_timer, (_params.rx_window2_delay - time_diff) + _params.rx_window2_config.window_timeout_ms + _lora_phy->get_ack_timeout()); } } else { _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_OK; } _params.last_channel_idx = _params.channel; _lora_phy->set_last_tx_done(_params.channel, _is_nwk_joined, timestamp); _params.timers.aggregated_last_tx_time = timestamp; _mac_commands.clear_command_buffer(); } void LoRaMac::on_radio_rx_done(const uint8_t *const payload, uint16_t size, int16_t rssi, int8_t snr, uint32_t rx_timestamp, Callback confirm_handler) { _demod_ongoing = false; rx_slot_t rx_slot = get_current_slot(); if (_device_class == CLASS_C && !_continuous_rx2_window_open) { _lora_time.stop(_rx2_closure_timer_for_class_c); open_rx2_window(); } else if (_device_class != CLASS_C) { _lora_time.stop(_params.timers.rx_window1_timer); /*Put radio to sleep if not in class C and not transmitting. Check for transmit is * necessary because class B rx slot radio interrupts are asynchronous with transmits, * so rx done/timeout can run at anytime, including during a transmit.*/ if (!_mod_ongoing) { _lora_phy->put_radio_to_sleep(); } } // Class B handling LoRaMacClassBInterface::handle_rx(rx_slot, payload, size, rx_timestamp); if (rx_slot == RX_SLOT_WIN_BEACON) { return; } loramac_event_info_status_t ret; loramac_mhdr_t mac_hdr; loramac_mlme_confirm_t mlme; uint8_t pos = 0; mac_hdr.value = payload[pos++]; switch (mac_hdr.bits.mtype) { case FRAME_TYPE_JOIN_ACCEPT: ret = handle_join_accept_frame(payload, size); mlme.type = MLME_JOIN_ACCEPT; mlme.status = ret; confirm_handler(mlme); 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, confirm_handler); break; default: // This can happen e.g. if we happen to receive uplink of another device // during the receive window. Block RX2 window since it can overlap with // QOS TX and cause a mess. tr_debug("RX unexpected mtype %u", mac_hdr.bits.mtype); if (get_current_slot() == RX_SLOT_WIN_1) { _lora_time.stop(_params.timers.rx_window2_timer); } _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ADDRESS_FAIL; _mcps_indication.pending = false; break; } } void LoRaMac::on_radio_tx_timeout(void) { _mod_ongoing = false; _lora_time.stop(_params.timers.rx_window1_timer); _lora_time.stop(_params.timers.rx_window2_timer); _lora_time.stop(_rx2_closure_timer_for_class_c); _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; _mac_commands.clear_command_buffer(); if (_mcps_confirmation.req_type == MCPS_CONFIRMED) { _mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter; } else { _mcps_confirmation.nb_retries = _params.ul_nb_rep_counter; } _mcps_confirmation.ack_received = false; _mcps_confirmation.tx_toa = 0; } void LoRaMac::on_radio_rx_timeout(bool is_timeout) { _demod_ongoing = false; /*Put radio to sleep if not in class C and not transmitting. Check for transmit * because class B rx slot radio interrupts are asynchronous with transmits, * so rx done/timeout can run at anytime, including during a transmit.*/ if ((_device_class != CLASS_C) && (!_mod_ongoing)) { _lora_phy->put_radio_to_sleep(); } // Class B Rx timeout notification LoRaMacClassBInterface::handle_rx_timeout(_params.rx_slot); if (_params.rx_slot == RX_SLOT_WIN_1) { if (_params.is_node_ack_requested == true) { _mcps_confirmation.status = 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.rx_slot == RX_SLOT_WIN_2) { if (_params.is_node_ack_requested == true) { _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_RX2_ERROR; } } } 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_CONNECT_IN_PROGRESS) { return false; } return true; } bool LoRaMac::continue_sending_process() { if (_params.ack_timeout_retry_counter >= _params.max_ack_timeout_retries) { _lora_time.stop(_params.timers.ack_timeout_timer); 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; if (_params.join_request_type == JOIN_REQUEST) { mac_hdr.bits.mtype = FRAME_TYPE_JOIN_REQ; _params.is_last_tx_join_request = true; } else { mac_hdr.bits.mtype = FRAME_TYPE_REJOIN_REQUEST; _params.is_last_tx_join_request = false; } fctrl.value = 0; fctrl.bits.adr = _params.sys_params.adr_on; /* 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 replay attacks. */ status = prepare_frame(&mac_hdr, &fctrl, 0, NULL, 0); if (status == LORAWAN_STATUS_OK) { if (schedule_tx() == LORAWAN_STATUS_OK) { status = LORAWAN_STATUS_CONNECT_IN_PROGRESS; } } else { tr_error("Couldn't send a JoinRequest: error %d", status); } return status; } /** * This function handles retransmission of failed or unacknowledged * outgoing traffic */ lorawan_status_t LoRaMac::handle_retransmission() { //TODO: Rejoin requests are not retransmitted (except in case of ForceRejoinReq), in most of the cases server won't respond if (!nwk_joined() && _params.is_last_tx_join_request) { 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); _lora_time.stop(_params.timers.backoff_timer); if ((schedule_tx() != LORAWAN_STATUS_OK) && nwk_joined()) { _scheduling_failure_handler.call(); } } void LoRaMac::open_rx1_window(void) { if (!set_rx_slot(RX_SLOT_WIN_1)) { return; } Lock lock(*this); _continuous_rx2_window_open = false; _lora_time.stop(_params.timers.rx_window1_timer); channel_params_t *active_channel_list = _lora_phy->get_phy_channels(); _params.rx_window1_config.channel = _params.channel; _params.rx_window1_config.frequency = active_channel_list[_params.channel].frequency; // Apply the alternative RX 1 window frequency, if it is available if (active_channel_list[_params.channel].rx1_frequency != 0) { _params.rx_window1_config.frequency = active_channel_list[_params.channel].rx1_frequency; } _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->handle_receive(); tr_debug("RX1 slot open, Freq = %lu", _params.rx_window1_config.frequency); } void LoRaMac::open_rx2_window() { if (!set_rx_slot(RX_SLOT_WIN_2)) { return; } Lock lock(*this); _continuous_rx2_window_open = true; _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; if (get_device_class() == CLASS_C) { _params.rx_window2_config.is_rx_continuous = true; } else { _params.rx_window2_config.is_rx_continuous = false; } _params.rx_window2_config.rx_slot = _params.rx_window2_config.is_rx_continuous ? RX_SLOT_WIN_CLASS_C : RX_SLOT_WIN_2; _mcps_indication.rx_datarate = _params.rx_window2_config.datarate; _lora_phy->rx_config(&_params.rx_window2_config); _lora_phy->handle_receive(); _params.rx_slot = _params.rx_window2_config.rx_slot; tr_debug("RX2 slot open, Freq = %lu", _params.rx_window2_config.frequency); } bool LoRaMac::set_rx_slot(rx_slot_t rx_slot) { Lock(*this); if (_demod_ongoing) { /* rx_slot_t is ordered from high to low priority. Class A receive windows being highest * priority then class C followed by beacon, multicast ping slot and coming in last * is unicast ping slot. */ if (rx_slot <= _params.rx_slot) { _lora_phy->put_radio_to_sleep(); _params.rx_slot = rx_slot; return true; } else { tr_info("%s Demodulation ongoing, skip %s window opening", get_rx_slot_string(rx_slot), get_rx_slot_string(get_current_slot())); return false; } } else { _params.rx_slot = rx_slot; _demod_ongoing = true; return true; } } bool LoRaMac::open_rx_window(rx_config_params_t *rx_config) { // Open slot if not transmitting and no higher priority slot is active if (!tx_ongoing() && set_rx_slot(rx_config->rx_slot)) { _lora_phy->rx_config(rx_config); _lora_phy->handle_receive(); return true; } return false; } void LoRaMac::close_rx_window(rx_slot_t slot) { if ((_params.rx_slot == slot) && (_demod_ongoing)) { _demod_ongoing = false; if (!_mod_ongoing) { _lora_phy->put_radio_to_sleep(); } } } void LoRaMac::on_ack_timeout_timer_event(void) { Lock lock(*this); if (_params.ack_timeout_retry_counter > _params.max_ack_timeout_retries) { return; } tr_debug("ACK_TIMEOUT Elapses, Retrying ..."); _lora_time.stop(_params.timers.ack_timeout_timer); // reduce data rate on every 2nd attempt if and only if the // ADR is on if ((_params.ack_timeout_retry_counter % 2) && (_params.sys_params.adr_on)) { tr_debug("Trading datarate for range"); _params.sys_params.channel_data_rate = _lora_phy->get_next_lower_tx_datarate(_params.sys_params.channel_data_rate); } _mcps_confirmation.nb_retries = _params.ack_timeout_retry_counter; // Schedule a retry lorawan_status_t status = handle_retransmission(); if (status == LORAWAN_STATUS_NO_CHANNEL_FOUND || status == LORAWAN_STATUS_NO_FREE_CHANNEL_FOUND) { // 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; // For the next attempt we need to make sure that we do not incur length error // which would mean that the datarate changed during retransmissions and // the original packet doesn't fit into allowed payload buffer anymore. status = handle_retransmission(); if (status == LORAWAN_STATUS_LENGTH_ERROR) { _scheduling_failure_handler.call(); return; } // if we did not incur a length error and still the status is not OK, // it is a critical failure status = handle_retransmission(); MBED_ASSERT(status == LORAWAN_STATUS_OK); (void) status; } else if (status != LORAWAN_STATUS_OK) { _scheduling_failure_handler.call(); return; } _params.ack_timeout_retry_counter++; } 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.dl_fpending_ul_class_b = get_device_class() == CLASS_B ? 1 : 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; } int LoRaMac::get_backoff_timer_event_id(void) { return _params.timers.backoff_timer.timer_id; } lorawan_status_t LoRaMac::clear_tx_pipe(void) { if (!_can_cancel_tx) { return LORAWAN_STATUS_BUSY; } // check if the event is not already queued const int id = get_backoff_timer_event_id(); if (id == 0) { // No queued send request return LORAWAN_STATUS_NO_OP; } if (_ev_queue->time_left(id) > 0) { _lora_time.stop(_params.timers.backoff_timer); _lora_time.stop(_params.timers.ack_timeout_timer); memset(_params.tx_buffer, 0, sizeof _params.tx_buffer); _params.tx_buffer_len = 0; reset_ongoing_tx(true); tr_debug("Sending Cancelled"); return LORAWAN_STATUS_OK; } else { // Event is already being dispatched so it cannot be cancelled return LORAWAN_STATUS_BUSY; } } lorawan_status_t LoRaMac::schedule_tx() { channel_selection_params_t next_channel; lorawan_time_t backoff_time = 0; lorawan_time_t aggregated_timeoff = 0; uint8_t channel = 0; uint8_t fopts_len = 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; } if (MBED_CONF_LORA_DUTY_CYCLE_ON && _lora_phy->verify_duty_cycle(true)) { _params.is_dutycycle_on = true; } else { _params.is_dutycycle_on = false; } calculate_backOff(_params.last_channel_idx); next_channel.aggregate_timeoff = _params.timers.aggregated_timeoff; next_channel.current_datarate = _params.sys_params.channel_data_rate; 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, &channel, &backoff_time, &aggregated_timeoff); _params.channel = channel; _params.timers.aggregated_timeoff = aggregated_timeoff; switch (status) { case LORAWAN_STATUS_NO_CHANNEL_FOUND: case LORAWAN_STATUS_NO_FREE_CHANNEL_FOUND: _mcps_confirmation.status = LORAMAC_EVENT_INFO_STATUS_ERROR; return status; case LORAWAN_STATUS_DUTYCYCLE_RESTRICTED: if (backoff_time != 0) { tr_debug("DC enforced: Transmitting in %lu ms", backoff_time); _can_cancel_tx = true; _lora_time.start(_params.timers.backoff_timer, backoff_time); } return LORAWAN_STATUS_OK; default: break; } uint8_t rx1_dr = _lora_phy->apply_DR_offset(_params.sys_params.channel_data_rate, _params.sys_params.rx1_dr_offset); bool process_mic = true; loramac_mhdr_t mac_hdr; mac_hdr.value = _params.tx_buffer[0]; if (mac_hdr.bits.mtype == FRAME_TYPE_JOIN_REQ || mac_hdr.bits.mtype == FRAME_TYPE_REJOIN_REQUEST) { // JOIN and REJOIN frames already has own MIC process_mic = false; } if (process_mic) { status = calculate_userdata_mic(); if (status != LORAWAN_STATUS_OK) { return status; } } tr_debug("TX: Channel=%d, TX DR=%d, RX1 DR=%d", _params.channel, _params.sys_params.channel_data_rate, rx1_dr); _lora_phy->compute_rx_win_params(rx1_dr, MBED_CONF_LORA_DOWNLINK_PREAMBLE_LENGTH, MBED_CONF_LORA_MAX_SYS_RX_ERROR, &_params.rx_window1_config); _lora_phy->compute_rx_win_params(_params.sys_params.rx2_channel.datarate, MBED_CONF_LORA_DOWNLINK_PREAMBLE_LENGTH, MBED_CONF_LORA_MAX_SYS_RX_ERROR, &_params.rx_window2_config); if (mac_hdr.bits.mtype == FRAME_TYPE_JOIN_REQ || mac_hdr.bits.mtype == FRAME_TYPE_REJOIN_REQUEST) { _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 the outgoing message is a proprietary message, it doesn't include any // standard message formatting except port and MHDR. if (_ongoing_tx_msg.type == MCPS_PROPRIETARY) { fopts_len = 0; } else { fopts_len = _mac_commands.get_mac_cmd_length() + _mac_commands.get_repeat_commands_length(); } // A check was performed for validity of FRMPayload in ::prepare_ongoing_tx() API. // However, owing to the asynch nature of the send() API, we should check the // validity again, as datarate may have changed since we last attempted to transmit. if (validate_payload_length(_ongoing_tx_msg.f_buffer_size, _params.sys_params.channel_data_rate, fopts_len) == false) { tr_error("Allowed FRMPayload = %d, FRMPayload = %d, MAC commands pending = %d", _lora_phy->get_max_payload(_params.sys_params.channel_data_rate, _params.is_repeater_supported), _ongoing_tx_msg.f_buffer_size, fopts_len); 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; } // handle the ack to the server here so that if the sending was cancelled // by the user in the backoff period, we would still ack the previous frame. if (_params.is_srv_ack_requested) { _params.is_srv_ack_requested = false; } _can_cancel_tx = false; status = send_frame_on_channel(_params.channel); // We must increment RJCountX after every transmission, including // retransmissions if (mac_hdr.bits.mtype == FRAME_TYPE_REJOIN_REQUEST) { if (_params.join_request_type == REJOIN_REQUEST_TYPE0 || _params.join_request_type == REJOIN_REQUEST_TYPE2) { _params.RJcount0 = _params.RJcount0 < RJCOUNT_ROLLOVER ? _params.RJcount0 + 1 : _params.RJcount0; } else { _params.RJcount1 = _params.RJcount1 < RJCOUNT_ROLLOVER ? _params.RJcount1 + 1 : _params.RJcount1; } } // If MIC was calculated, remove it from buffer after sending // so it can be recalculated and added to the buffer in case // of retransmission. if (process_mic) { _params.tx_buffer_len -= LORAMAC_MFR_LEN; } return status; } void LoRaMac::calculate_backOff(uint8_t channel) { lorawan_time_t elapsed_time = _lora_time.get_elapsed_time(_params.timers.mac_init_time); _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_frame_counters(void) { _params.ul_frame_counter = 0; _params.dl_frame_counter = 0; } void LoRaMac::reset_phy_params(void) { _lora_phy->reset_to_default_values(&_params, false); } void LoRaMac::reset_mac_parameters(void) { _is_nwk_joined = false; _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(); _params.is_rx_window_enabled = true; _params.is_node_ack_requested = false; _params.is_srv_ack_requested = false; reset_multicast_counters(); _params.channel = 0; _params.last_channel_idx = _params.channel; _demod_ongoing = false; _mod_ongoing = false; } 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) { _can_cancel_tx = true; _ongoing_tx_msg.tx_ongoing = ongoing; // Notify Class B to resume its reception slots. if (!ongoing) { LoRaMacClassBInterface::resume(); } } 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; } LoRaMacClassBInterface::resume(); } 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 = 0; uint8_t fopts_len = _mac_commands.get_mac_cmd_length() + _mac_commands.get_repeat_commands_length(); // Handles unconfirmed messages if (flags & MSG_UNCONFIRMED_FLAG) { _ongoing_tx_msg.type = MCPS_UNCONFIRMED; _ongoing_tx_msg.fport = port; _ongoing_tx_msg.nb_trials = 1; } // Handles confirmed messages if (flags & MSG_CONFIRMED_FLAG) { _ongoing_tx_msg.type = MCPS_CONFIRMED; _ongoing_tx_msg.fport = port; _ongoing_tx_msg.nb_trials = num_retries; } // Handles proprietary messages if (flags & MSG_PROPRIETARY_FLAG) { _ongoing_tx_msg.type = MCPS_PROPRIETARY; _ongoing_tx_msg.fport = port; _ongoing_tx_msg.nb_trials = _params.sys_params.nb_trans > 0 ? _params.sys_params.nb_trans : 1; // a proprietary frame only includes an MHDR field which contains MTYPE field. // Everything else is at the discretion of the implementer fopts_len = 0; } max_possible_size = get_max_possible_tx_size(fopts_len); 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); } } 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; // This prohibits the data rate going below the minimum value. 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; if (_params.server_type == LW1_1) { _params.max_ack_timeout_retries = _params.sys_params.nb_trans; } else { _params.max_ack_timeout_retries = _ongoing_tx_msg.nb_trials; } } else if (_ongoing_tx_msg.type == MCPS_PROPRIETARY) { 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; } lorawan_status_t LoRaMac::set_device_class(const device_class_t &device_class, mbed::Callbackrx2_would_be_closure_handler) { lorawan_status_t status = LORAWAN_STATUS_OK; _rx2_would_be_closure_for_class_c = rx2_would_be_closure_handler; _lora_time.init(_rx2_closure_timer_for_class_c, _rx2_would_be_closure_for_class_c); if (CLASS_B == _device_class) { LoRaMacClassBInterface::disable(); } if (CLASS_A == device_class) { tr_debug("Changing device class to -> CLASS_A"); _lora_phy->put_radio_to_sleep(); } else if (CLASS_B == device_class) { status = LoRaMacClassBInterface::enable(); if (status == LORAWAN_STATUS_OK) { tr_debug("Changing device class to -> CLASS_B"); _lora_phy->put_radio_to_sleep(); } } else if (CLASS_C == device_class) { tr_debug("Changing device class to -> CLASS_C"); _params.is_node_ack_requested = false; _lora_phy->put_radio_to_sleep(); _lora_phy->compute_rx_win_params(_params.sys_params.rx2_channel.datarate, MBED_CONF_LORA_DOWNLINK_PREAMBLE_LENGTH, MBED_CONF_LORA_MAX_SYS_RX_ERROR, &_params.rx_window2_config); } if (status == LORAWAN_STATUS_OK) { _device_class = device_class; } if (CLASS_C == _device_class) { open_rx2_window(); } return status; } void LoRaMac::setup_link_check_request() { _mac_commands.add_link_check_req(); } lorawan_status_t LoRaMac::setup_device_time_request(mbed::Callback notify) { return _mac_commands.add_device_time_req(notify); } void LoRaMac::setup_reset_indication() { _mac_commands.add_reset_ind(1); } void LoRaMac::setup_rekey_indication() { _mac_commands.add_rekey_ind(1); } void LoRaMac::setup_device_mode_indication(uint8_t classType) { _mac_commands.add_device_mode_indication(classType); } 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.nwk_key == NULL) || (params->connection_u.otaa.nb_trials == 0)) { return LORAWAN_STATUS_PARAMETER_INVALID; } _params.dev_eui = params->connection_u.otaa.dev_eui; _params.app_eui = params->connection_u.otaa.app_eui; lorawan_status_t ret; if (MBED_CONF_LORA_VERSION < LORAWAN_VERSION_1_1) { ret = _lora_crypto.set_keys(params->connection_u.otaa.app_key, params->connection_u.otaa.app_key); } else { ret = _lora_crypto.set_keys(params->connection_u.otaa.nwk_key, params->connection_u.otaa.app_key); } if (ret != LORAWAN_STATUS_OK) { return LORAWAN_STATUS_CRYPTO_FAIL; } uint8_t converted_eui[8]; memcpy_convert_endianess(converted_eui, _params.dev_eui, 8); if (0 != _lora_crypto.compute_join_server_keys(converted_eui)) { return LORAWAN_STATUS_CRYPTO_FAIL; } _params.max_join_request_trials = params->connection_u.otaa.nb_trials; if (!_lora_phy->verify_nb_join_trials(params->connection_u.otaa.nb_trials)) { _params.max_join_request_trials = MBED_CONF_LORA_NB_TRIALS; } _params.join_request_trial_counter = 0; reset_mac_parameters(); reset_frame_counters(); reset_phy_params(); _params.sys_params.channel_data_rate = _lora_phy->get_alternate_DR(_params.join_request_trial_counter + 1); } else { if ((params->connection_u.abp.dev_addr == 0) || (params->connection_u.abp.nwk_id == 0) || (params->connection_u.abp.nwk_skey == NULL) || (params->connection_u.abp.app_skey == NULL)) { return LORAWAN_STATUS_PARAMETER_INVALID; } if (MBED_CONF_LORA_VERSION == LORAWAN_VERSION_1_1 && ((params->connection_u.abp.snwk_sintkey == NULL) || (params->connection_u.abp.nwk_senckey == NULL))) { return LORAWAN_STATUS_PARAMETER_INVALID; } _params.net_id = params->connection_u.abp.nwk_id; _params.dev_addr = params->connection_u.abp.dev_addr; lorawan_status_t ret; if (MBED_CONF_LORA_VERSION < LORAWAN_VERSION_1_1) { ret = _lora_crypto.set_keys(NULL, NULL, params->connection_u.abp.nwk_skey, params->connection_u.abp.app_skey, params->connection_u.abp.nwk_skey, params->connection_u.abp.nwk_skey); _params.server_type = LW1_0_2; } else { ret = _lora_crypto.set_keys(NULL, NULL, params->connection_u.abp.nwk_skey, params->connection_u.abp.app_skey, params->connection_u.abp.snwk_sintkey, params->connection_u.abp.nwk_senckey); _params.server_type = LW1_1; } if (ret != LORAWAN_STATUS_OK) { return LORAWAN_STATUS_CRYPTO_FAIL; } } } 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; const static uint8_t nwk_key[] = MBED_CONF_LORA_NETWORK_KEY; _params.app_eui = const_cast(app_eui); _params.dev_eui = const_cast(dev_eui); _params.max_join_request_trials = MBED_CONF_LORA_NB_TRIALS; lorawan_status_t ret; if (MBED_CONF_LORA_VERSION < LORAWAN_VERSION_1_1) { ret = _lora_crypto.set_keys(const_cast(app_key), const_cast(app_key)); } else { ret = _lora_crypto.set_keys(const_cast(nwk_key), const_cast(app_key)); } if (ret != LORAWAN_STATUS_OK) { return LORAWAN_STATUS_CRYPTO_FAIL; } uint8_t converted_eui[8]; memcpy_convert_endianess(converted_eui, _params.dev_eui, 8); if (0 != _lora_crypto.compute_join_server_keys(converted_eui)) { return LORAWAN_STATUS_CRYPTO_FAIL; } // Reset variable JoinRequestTrials _params.join_request_trial_counter = 0; reset_mac_parameters(); reset_frame_counters(); reset_phy_params(); _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; const static uint8_t snwk_sintkey[] = MBED_CONF_LORA_SNWKSINTKEY; const static uint8_t nwk_senckey[] = MBED_CONF_LORA_NWKSENCKEY; _params.net_id = (MBED_CONF_LORA_DEVICE_ADDRESS & LORAWAN_NETWORK_ID_MASK) >> 25; _params.dev_addr = MBED_CONF_LORA_DEVICE_ADDRESS; lorawan_status_t ret; if (MBED_CONF_LORA_VERSION < LORAWAN_VERSION_1_1) { ret = _lora_crypto.set_keys(NULL, NULL, const_cast(nwk_skey), const_cast(app_skey), const_cast(nwk_skey), const_cast(nwk_skey)); _params.server_type = LW1_0_2; } else { ret = _lora_crypto.set_keys(NULL, NULL, const_cast(nwk_skey), const_cast(app_skey), const_cast(snwk_sintkey), const_cast(nwk_senckey)); _params.server_type = LW1_1; } if (ret != LORAWAN_STATUS_OK) { return LORAWAN_STATUS_CRYPTO_FAIL; } #endif } return LORAWAN_STATUS_OK; } lorawan_status_t LoRaMac::join(bool is_otaa) { if (!is_otaa) { set_nwk_joined(true); return LORAWAN_STATUS_OK; } _params.join_request_type = JOIN_REQUEST; return send_join_request(); } lorawan_status_t LoRaMac::rejoin(join_req_type_t rejoin_type, bool is_forced, uint8_t datarate) { _params.join_request_type = rejoin_type; _params.rejoin_forced = is_forced; _params.forced_datarate = datarate; if (rejoin_type == REJOIN_REQUEST_TYPE0 || rejoin_type == REJOIN_REQUEST_TYPE2) { if (_params.RJcount0 == RJCOUNT_ROLLOVER) { return LORAWAN_STATUS_SERVICE_UNKNOWN; } } else if (rejoin_type == REJOIN_REQUEST_TYPE1) { if (_params.RJcount1 == RJCOUNT_ROLLOVER) { return LORAWAN_STATUS_SERVICE_UNKNOWN; } } return send_join_request(); } 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.app_eui, 8); _params.tx_buffer_len += 8; memcpy_convert_endianess(_params.tx_buffer + _params.tx_buffer_len, _params.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, JOIN_REQ, &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_REJOIN_REQUEST: { _params.tx_buffer_len = pkt_header_len; _params.tx_buffer[_params.tx_buffer_len++] = (uint8_t)_params.join_request_type; join_frame_type_t type = REJOIN0_REQ; if (_params.join_request_type == REJOIN_REQUEST_TYPE0 || _params.join_request_type == REJOIN_REQUEST_TYPE2) { if (_params.join_request_type == REJOIN_REQUEST_TYPE2) { type = REJOIN2_REQ; } _params.tx_buffer[_params.tx_buffer_len++] = _params.net_id & 0xFF; _params.tx_buffer[_params.tx_buffer_len++] = (_params.net_id >> 8) & 0xFF; _params.tx_buffer[_params.tx_buffer_len++] = (_params.net_id >> 16) & 0xFF; memcpy_convert_endianess(_params.tx_buffer + _params.tx_buffer_len, _params.dev_eui, 8); _params.tx_buffer_len += 8; _params.tx_buffer[_params.tx_buffer_len++] = _params.RJcount0 & 0xFF; _params.tx_buffer[_params.tx_buffer_len++] = (_params.RJcount0 >> 8) & 0xFF; } else { //_params.join_request_type == REJOIN_REQUEST_TYPE1 type = REJOIN1_REQ; memcpy_convert_endianess(_params.tx_buffer + _params.tx_buffer_len, _params.app_eui, 8); _params.tx_buffer_len += 8; memcpy_convert_endianess(_params.tx_buffer + _params.tx_buffer_len, _params.dev_eui, 8); _params.tx_buffer_len += 8; _params.tx_buffer[_params.tx_buffer_len++] = _params.RJcount1 & 0xFF; _params.tx_buffer[_params.tx_buffer_len++] = (_params.RJcount1 >> 8) & 0xFF; } if (0 != _lora_crypto.compute_join_frame_mic(_params.tx_buffer, _params.tx_buffer_len & 0xFF, type, &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) { tr_debug("Acking to NS"); 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_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(); if (_params.server_type == LW1_1) { if (0 != _lora_crypto.encrypt_payload(buffer, mac_commands_len, _params.dev_addr, UP_LINK, _params.ul_frame_counter, FCNT_UP, FOPTS, &_params.tx_buffer[pkt_header_len], _params.server_type, true)) { status = LORAWAN_STATUS_CRYPTO_FAIL; } pkt_header_len += mac_commands_len; } else { 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 { _params.tx_buffer_len = mac_commands_len; payload = _mac_commands.get_mac_commands_buffer(); frame_port = 0; } _mac_commands.parse_mac_commands_to_repeat(); // We always add Port Field. Spec leaves it optional. _params.tx_buffer[pkt_header_len++] = frame_port; if ((payload != NULL) && (_params.tx_buffer_len > 0)) { if (0 != _lora_crypto.encrypt_payload((uint8_t *) payload, _params.tx_buffer_len, _params.dev_addr, UP_LINK, _params.ul_frame_counter, FCNT_UP, FRMPAYLOAD, &_params.tx_buffer[pkt_header_len], _params.server_type, (frame_port == 0))) { status = LORAWAN_STATUS_CRYPTO_FAIL; } } _params.tx_buffer_len = pkt_header_len + _params.tx_buffer_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; if (_params.rejoin_forced) { tx_config.datarate = _params.forced_datarate; } else { 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; // Pause Class B reception slots LoRaMacClassBInterface::pause(); _mod_ongoing = true; _lora_phy->tx_config(&tx_config, &tx_power, &_params.timers.tx_toa); _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; 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_mcps_indication() { memset((uint8_t *) &_mcps_indication, 0, sizeof(_mcps_indication)); _mcps_indication.status = LORAMAC_EVENT_INFO_STATUS_ERROR; } LoRaWANTimeHandler *LoRaMac::get_lora_time() { return &_lora_time; } lorawan_status_t LoRaMac::initialize(EventQueue *queue, mbed::Callbackscheduling_failure_handler) { _lora_time.activate_timer_subsystem(queue); _lora_phy->initialize(&_lora_time); _ev_queue = queue; _scheduling_failure_handler = scheduling_failure_handler; _rx2_closure_timer_for_class_c.callback = NULL; _rx2_closure_timer_for_class_c.timer_id = -1; _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.nb_trans = 1; reset_mac_parameters(); reset_frame_counters(); 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.sys_params.channel_data_rate = _lora_phy->get_default_max_tx_datarate(); LoRaMacClassBInterface::initialize(&_lora_time, _lora_phy, &_lora_crypto, &_params, mbed::callback(this, &LoRaMac::open_rx_window), mbed::callback(this, &LoRaMac::close_rx_window)); 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(); reset_mcps_confirmation(); reset_mcps_indication(); } uint8_t LoRaMac::get_max_possible_tx_size(uint8_t fopts_len) { uint8_t max_possible_payload_size = 0; uint8_t allowed_frm_payload_size = 0; int8_t datarate = _params.sys_params.channel_data_rate; int8_t tx_power = _params.sys_params.channel_tx_power; uint32_t adr_ack_counter = _params.adr_ack_counter; if (_params.sys_params.adr_on) { // Just query the information. We do not want to apply them into use // at this point. _lora_phy->get_next_ADR(false, datarate, tx_power, adr_ack_counter); } allowed_frm_payload_size = _lora_phy->get_max_payload(datarate, _params.is_repeater_supported); if (allowed_frm_payload_size >= fopts_len) { max_possible_payload_size = allowed_frm_payload_size - fopts_len; } else { max_possible_payload_size = allowed_frm_payload_size; _mac_commands.clear_command_buffer(); _mac_commands.clear_repeat_buffer(); } return max_possible_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_phy(LoRaPHY &phy) { _lora_phy = &phy; } uint8_t LoRaMac::get_QOS_level() { if (_prev_qos_level != _params.sys_params.nb_trans) { _prev_qos_level = _params.sys_params.nb_trans; } return _params.sys_params.nb_trans; } uint8_t LoRaMac::get_prev_QOS_level() { return _prev_qos_level; } server_type_t LoRaMac::get_server_type() { return _params.server_type; } uint8_t LoRaMac::get_current_adr_ack_limit() { return _lora_phy->get_adr_ack_limit(); } void LoRaMac::get_rejoin_parameters(uint32_t &max_time, uint32_t &max_count) { max_time = _lora_phy->get_rejoin_max_time(); max_count = _lora_phy->get_rejoin_max_count(); } lorawan_status_t LoRaMac::calculate_userdata_mic() { lorawan_status_t status = LORAWAN_STATUS_OK; uint32_t mic = 0; uint32_t mic2 = 0; uint32_t args = 0; if (0 != _lora_crypto.compute_mic(_params.tx_buffer, _params.tx_buffer_len, args, _params.dev_addr, UP_LINK, _params.ul_frame_counter, 0, &mic)) { status = LORAWAN_STATUS_CRYPTO_FAIL; } if (_params.server_type == LW1_1) { if (_params.is_srv_ack_requested) { args = _params.counterForAck; } args |= _params.sys_params.channel_data_rate << 16; args |= _params.channel << 24; if (0 != _lora_crypto.compute_mic(_params.tx_buffer, _params.tx_buffer_len, args, _params.dev_addr, UP_LINK, _params.ul_frame_counter, 1, &mic2)) { status = LORAWAN_STATUS_CRYPTO_FAIL; } _params.tx_buffer[_params.tx_buffer_len + 0] = mic2 & 0xFF; _params.tx_buffer[_params.tx_buffer_len + 1] = (mic2 >> 8) & 0xFF; _params.tx_buffer[_params.tx_buffer_len + 2] = mic & 0xFF; _params.tx_buffer[_params.tx_buffer_len + 3] = (mic >> 8) & 0xFF; } else { _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; return status; } lorawan_status_t LoRaMac::set_ping_slot_info(uint8_t periodicity) { /* Must be in Class A to change ping slot periodicity (1.1 Chapter 14.1)*/ if (get_device_class() != CLASS_A) { return LORAWAN_STATUS_NO_OP; /* Periodicity is encoded in 3 bits */ } else if (periodicity > 7) { return LORAWAN_STATUS_PARAMETER_INVALID; } else { _params.sys_params.ping_slot.periodicity = periodicity; _params.sys_params.ping_slot.ping_nb = 1 << (7 - periodicity); _params.sys_params.ping_slot.ping_period = 1 << (5 + periodicity); return LORAWAN_STATUS_OK; } } lorawan_status_t LoRaMac::add_ping_slot_info_req() { return _mac_commands.add_ping_slot_info_req(_params.sys_params.ping_slot.periodicity); } lorawan_status_t LoRaMac::enable_beacon_acquisition(mbed::Callbackbeacon_event_cb) { return LoRaMacClassBInterface::enable_beacon_acquisition(beacon_event_cb); } lorawan_status_t LoRaMac::get_last_rx_beacon(loramac_beacon_t &beacon) { return LoRaMacClassBInterface::get_last_rx_beacon(beacon); }