From c861c321bed93a7b968f99fdfb2be13507ef5b2f Mon Sep 17 00:00:00 2001 From: Hasnain Virk Date: Mon, 27 Nov 2017 13:58:15 +0200 Subject: [PATCH] Adding PHY layer for LoRaWAN LoRaPHY is the abstract class for the LoRa PHY layer which governs the LoRaRadio and provides some common functionality to all regional implementations. We support 10 regions and every region comes loaded with default parameters. These parameters can be changed by the Mac layer or explicitely by the stack controller layer using APIs provided. This layer in essence detaches Mac completely from PHY and provides more modular approach to the entire system. Apart from class structure, the internal functionality is directly deduced from semtech reference implementation that's why most of the internal data structures are used on 'as is' basis. In addition to that, the PHY layer provides APIs to control the LoRaRadio layer, i.e., the lora radio driver, ensuring that the radio is accessed from a single entry point. A seperate data structure file is added which is common to PHY layers only. --- features/lorawan/lorastack/phy/LoRaPHY.cpp | 437 ++++++ features/lorawan/lorastack/phy/LoRaPHY.h | 677 +++++++++ .../lorawan/lorastack/phy/LoRaPHYAS923.cpp | 1338 +++++++++++++++++ features/lorawan/lorastack/phy/LoRaPHYAS923.h | 363 +++++ .../lorawan/lorastack/phy/LoRaPHYAU915.cpp | 984 ++++++++++++ features/lorawan/lorastack/phy/LoRaPHYAU915.h | 373 +++++ .../lorawan/lorastack/phy/LoRaPHYCN470.cpp | 979 ++++++++++++ features/lorawan/lorastack/phy/LoRaPHYCN470.h | 365 +++++ .../lorawan/lorastack/phy/LoRaPHYCN779.cpp | 1239 +++++++++++++++ features/lorawan/lorastack/phy/LoRaPHYCN779.h | 354 +++++ .../lorawan/lorastack/phy/LoRaPHYEU433.cpp | 1244 +++++++++++++++ features/lorawan/lorastack/phy/LoRaPHYEU433.h | 361 +++++ .../lorawan/lorastack/phy/LoRaPHYEU868.cpp | 1296 ++++++++++++++++ features/lorawan/lorastack/phy/LoRaPHYEU868.h | 358 +++++ .../lorawan/lorastack/phy/LoRaPHYIN865.cpp | 1243 +++++++++++++++ features/lorawan/lorastack/phy/LoRaPHYIN865.h | 361 +++++ .../lorawan/lorastack/phy/LoRaPHYKR920.cpp | 1258 ++++++++++++++++ features/lorawan/lorastack/phy/LoRaPHYKR920.h | 359 +++++ .../lorawan/lorastack/phy/LoRaPHYUS915.cpp | 1037 +++++++++++++ features/lorawan/lorastack/phy/LoRaPHYUS915.h | 365 +++++ .../lorastack/phy/LoRaPHYUS915Hybrid.cpp | 1127 ++++++++++++++ .../lorastack/phy/LoRaPHYUS915Hybrid.h | 366 +++++ features/lorawan/lorastack/phy/lora_phy_ds.h | 1177 +++++++++++++++ 23 files changed, 17661 insertions(+) create mode 100644 features/lorawan/lorastack/phy/LoRaPHY.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHY.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYAS923.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYAS923.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYAU915.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYAU915.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYCN470.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYCN470.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYCN779.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYCN779.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYEU433.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYEU433.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYEU868.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYEU868.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYIN865.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYIN865.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYKR920.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYKR920.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYUS915.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYUS915.h create mode 100644 features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.cpp create mode 100644 features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.h create mode 100644 features/lorawan/lorastack/phy/lora_phy_ds.h diff --git a/features/lorawan/lorastack/phy/LoRaPHY.cpp b/features/lorawan/lorastack/phy/LoRaPHY.cpp new file mode 100644 index 0000000000..7a7c43b724 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHY.cpp @@ -0,0 +1,437 @@ +/** + / _____) _ | | +( (____ _____ ____ _| |_ _____ ____| |__ + \____ \| ___ | (_ _) ___ |/ ___) _ \ + _____) ) ____| | | || |_| ____( (___| | | | +(______/|_____)_|_|_| \__)_____)\____)_| |_| + (C)2013 Semtech + ___ _____ _ ___ _ _____ ___ ___ ___ ___ +/ __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| +\__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| +|___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| +embedded.connectivity.solutions=============== + +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 +#include +#include +#include "lorawan/lorastack/phy/LoRaPHY.h" +#include "lorawan/system/LoRaWANTimer.h" + +#define BACKOFF_DC_1_HOUR 100 +#define BACKOFF_DC_10_HOURS 1000 +#define BACKOFF_DC_24_HOURS 10000 + +static uint8_t CountChannels( uint16_t mask, uint8_t nbBits ) +{ + uint8_t nbActiveBits = 0; + + for( uint8_t j = 0; j < nbBits; j++ ) + { + if( ( mask & ( 1 << j ) ) == ( 1 << j ) ) + { + nbActiveBits++; + } + } + return nbActiveBits; +} + +LoRaPHY::LoRaPHY() +{ +} + +LoRaPHY::~LoRaPHY() +{ + _radio = NULL; +} + +void LoRaPHY::set_radio_instance(LoRaRadio& radio) +{ + _radio = &radio; +} + +void LoRaPHY::put_radio_to_sleep() { + _radio->lock(); + _radio->sleep(); + _radio->unlock(); +} + +void LoRaPHY::put_radio_to_standby() { + _radio->lock(); + _radio->standby(); + _radio->unlock(); +} + +void LoRaPHY::setup_tx_cont_wave_mode(uint16_t timeout, uint32_t frequency, + uint8_t power) +{ + _radio->lock(); + _radio->set_tx_continuous_wave(frequency, power, timeout); + _radio->unlock(); +} + +void LoRaPHY::setup_public_network_mode(bool set) +{ + _radio->lock(); + _radio->set_public_network(set); + _radio->unlock(); +} + +void LoRaPHY::setup_rx_window(bool rx_continuous, uint32_t max_rx_window) +{ + _radio->lock(); + + if (!rx_continuous) { + _radio->receive(max_rx_window); + _radio->unlock(); + return; + } + + _radio->receive(0); // Continuous mode + + _radio->unlock(); +} + +// For DevNonce for example +uint32_t LoRaPHY::get_radio_rng() +{ + uint32_t rand; + + _radio->lock(); + rand =_radio->random(); + _radio->unlock(); + + return rand; +} + +void LoRaPHY::handle_send(uint8_t *buf, uint8_t size) +{ + _radio->lock(); + _radio->send(buf, size); + _radio->unlock(); +} + +int32_t LoRaPHY::get_random(int32_t min, int32_t max) +{ + return (int32_t) rand() % (max - min + 1) + min; +} + +uint16_t LoRaPHY::get_join_DC( TimerTime_t elapsedTime ) +{ + uint16_t dutyCycle = 0; + + if( elapsedTime < 3600000 ) + { + dutyCycle = BACKOFF_DC_1_HOUR; + } + else if( elapsedTime < ( 3600000 + 36000000 ) ) + { + dutyCycle = BACKOFF_DC_10_HOURS; + } + else + { + dutyCycle = BACKOFF_DC_24_HOURS; + } + return dutyCycle; +} + +bool LoRaPHY::verify_channel_DR( uint8_t nbChannels, uint16_t* channelsMask, int8_t dr, int8_t minDr, int8_t maxDr, ChannelParams_t* channels ) +{ + if( val_in_range( dr, minDr, maxDr ) == 0 ) + { + return false; + } + + for( uint8_t i = 0, k = 0; i < nbChannels; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( ( channelsMask[k] & ( 1 << j ) ) != 0 ) ) + {// Check datarate validity for enabled channels + if( val_in_range( dr, ( channels[i + j].DrRange.Fields.Min & 0x0F ), + ( channels[i + j].DrRange.Fields.Max & 0x0F ) ) == 1 ) + { + // At least 1 channel has been found we can return OK. + return true; + } + } + } + } + return false; +} + +uint8_t LoRaPHY::val_in_range( int8_t value, int8_t min, int8_t max ) +{ + if( ( value >= min ) && ( value <= max ) ) + { + return 1; + } + return 0; +} + +bool LoRaPHY::disable_channel( uint16_t* channelsMask, uint8_t id, uint8_t maxChannels ) +{ + uint8_t index = id / 16; + + if( ( index > ( maxChannels / 16 ) ) || ( id >= maxChannels ) ) + { + return false; + } + + // Deactivate channel + channelsMask[index] &= ~( 1 << ( id % 16 ) ); + + return true; +} + +uint8_t LoRaPHY::num_active_channels( uint16_t* channelsMask, uint8_t startIdx, uint8_t stopIdx ) +{ + uint8_t nbChannels = 0; + + if( channelsMask == NULL ) + { + return 0; + } + + for( uint8_t i = startIdx; i < stopIdx; i++ ) + { + nbChannels += CountChannels( channelsMask[i], 16 ); + } + + return nbChannels; +} + +void LoRaPHY::copy_channel_mask( uint16_t* channelsMaskDest, uint16_t* channelsMaskSrc, uint8_t len ) +{ + if( ( channelsMaskDest != NULL ) && ( channelsMaskSrc != NULL ) ) + { + for( uint8_t i = 0; i < len; i++ ) + { + channelsMaskDest[i] = channelsMaskSrc[i]; + } + } +} + +void LoRaPHY::set_last_tx_done( bool joined, Band_t* band, TimerTime_t lastTxDone ) +{ + if( joined == true ) + { + band->LastTxDoneTime = lastTxDone; + } + else + { + band->LastTxDoneTime = lastTxDone; + band->LastJoinTxDoneTime = lastTxDone; + } +} + +TimerTime_t LoRaPHY::update_band_timeoff( bool joined, bool dutyCycle, Band_t* bands, uint8_t nbBands ) +{ + TimerTime_t nextTxDelay = ( TimerTime_t )( -1 ); + + // Update bands Time OFF + for( uint8_t i = 0; i < nbBands; i++ ) + { + if( joined == false ) + { + uint32_t txDoneTime = MAX( TimerGetElapsedTime( bands[i].LastJoinTxDoneTime ), + ( dutyCycle == true ) ? TimerGetElapsedTime( bands[i].LastTxDoneTime ) : 0 ); + + if( bands[i].TimeOff <= txDoneTime ) + { + bands[i].TimeOff = 0; + } + if( bands[i].TimeOff != 0 ) + { + nextTxDelay = MIN( bands[i].TimeOff - txDoneTime, nextTxDelay ); + } + } + else + { + if( dutyCycle == true ) + { + if( bands[i].TimeOff <= TimerGetElapsedTime( bands[i].LastTxDoneTime ) ) + { + bands[i].TimeOff = 0; + } + if( bands[i].TimeOff != 0 ) + { + nextTxDelay = MIN( bands[i].TimeOff - TimerGetElapsedTime( bands[i].LastTxDoneTime ), + nextTxDelay ); + } + } + else + { + nextTxDelay = 0; + bands[i].TimeOff = 0; + } + } + } + return nextTxDelay; +} + +uint8_t LoRaPHY::parse_link_ADR_req( uint8_t* payload, RegionCommonLinkAdrParams_t* linkAdrParams ) +{ + uint8_t retIndex = 0; + + if( payload[0] == SRV_MAC_LINK_ADR_REQ ) + { + // Parse datarate and tx power + linkAdrParams->Datarate = payload[1]; + linkAdrParams->TxPower = linkAdrParams->Datarate & 0x0F; + linkAdrParams->Datarate = ( linkAdrParams->Datarate >> 4 ) & 0x0F; + // Parse ChMask + linkAdrParams->ChMask = ( uint16_t )payload[2]; + linkAdrParams->ChMask |= ( uint16_t )payload[3] << 8; + // Parse ChMaskCtrl and nbRep + linkAdrParams->NbRep = payload[4]; + linkAdrParams->ChMaskCtrl = ( linkAdrParams->NbRep >> 4 ) & 0x07; + linkAdrParams->NbRep &= 0x0F; + + // LinkAdrReq has 4 bytes length + 1 byte CMD + retIndex = 5; + } + return retIndex; +} + +uint8_t LoRaPHY::verify_link_ADR_req( RegionCommonLinkAdrReqVerifyParams_t* verifyParams, int8_t* dr, int8_t* txPow, uint8_t* nbRep ) +{ + uint8_t status = verifyParams->Status; + int8_t datarate = verifyParams->Datarate; + int8_t txPower = verifyParams->TxPower; + int8_t nbRepetitions = verifyParams->NbRep; + + // Handle the case when ADR is off. + if( verifyParams->AdrEnabled == false ) + { + // When ADR is off, we are allowed to change the channels mask and the NbRep, + // if the datarate and the TX power of the LinkAdrReq are set to 0x0F. + if( ( verifyParams->Datarate != 0x0F ) || ( verifyParams->TxPower != 0x0F ) ) + { + status = 0; + nbRepetitions = verifyParams->CurrentNbRep; + } + // Get the current datarate and tx power + datarate = verifyParams->CurrentDatarate; + txPower = verifyParams->CurrentTxPower; + } + + if( status != 0 ) + { + // Verify datarate. The variable phyParam. Value contains the minimum allowed datarate. + if( verify_channel_DR( verifyParams->NbChannels, verifyParams->ChannelsMask, datarate, + verifyParams->MinDatarate, verifyParams->MaxDatarate, verifyParams->Channels ) == false ) + { + status &= 0xFD; // Datarate KO + } + + // Verify tx power + if( val_in_range( txPower, verifyParams->MaxTxPower, verifyParams->MinTxPower ) == 0 ) + { + // Verify if the maximum TX power is exceeded + if( verifyParams->MaxTxPower > txPower ) + { // Apply maximum TX power. Accept TX power. + txPower = verifyParams->MaxTxPower; + } + else + { + status &= 0xFB; // TxPower KO + } + } + } + + // If the status is ok, verify the NbRep + if( status == 0x07 ) + { + if( nbRepetitions == 0 ) + { // Keep the current one + nbRepetitions = verifyParams->CurrentNbRep; + } + } + + // Apply changes + *dr = datarate; + *txPow = txPower; + *nbRep = nbRepetitions; + + return status; +} + +double LoRaPHY::compute_symb_timeout_lora( uint8_t phyDr, uint32_t bandwidth ) +{ + return ( ( double )( 1 << phyDr ) / ( double )bandwidth ) * 1000; +} + +double LoRaPHY::compute_symb_timeout_fsk( uint8_t phyDr ) +{ + return ( 8.0 / ( double )phyDr ); // 1 symbol equals 1 byte +} + +void LoRaPHY::get_rx_window_params( double tSymbol, uint8_t minRxSymbols, uint32_t rxError, uint32_t wakeUpTime, uint32_t* windowTimeout, int32_t* windowOffset ) +{ + *windowTimeout = MAX( ( uint32_t )ceil( ( ( 2 * minRxSymbols - 8 ) * tSymbol + 2 * rxError ) / tSymbol ), minRxSymbols ); // Computed number of symbols + *windowOffset = ( int32_t )ceil( ( 4.0 * tSymbol ) - ( ( *windowTimeout * tSymbol ) / 2.0 ) - wakeUpTime ); +} + +int8_t LoRaPHY::compute_tx_power( int8_t txPowerIndex, float maxEirp, float antennaGain ) +{ + int8_t phyTxPower = 0; + + phyTxPower = ( int8_t )floor( ( maxEirp - ( txPowerIndex * 2U ) ) - antennaGain ); + + return phyTxPower; +} + +void LoRaPHY::get_DC_backoff( RegionCommonCalcBackOffParams_t* calcBackOffParams ) +{ + uint8_t bandIdx = calcBackOffParams->Channels[calcBackOffParams->Channel].Band; + uint16_t dutyCycle = calcBackOffParams->Bands[bandIdx].DCycle; + uint16_t joinDutyCycle = 0; + + // Reset time-off to initial value. + calcBackOffParams->Bands[bandIdx].TimeOff = 0; + + if( calcBackOffParams->Joined == false ) + { + // Get the join duty cycle + joinDutyCycle = get_join_DC( calcBackOffParams->ElapsedTime ); + // Apply the most restricting duty cycle + dutyCycle = MAX( dutyCycle, joinDutyCycle ); + // Reset the timeoff if the last frame was not a join request and when the duty cycle is not enabled + if( ( calcBackOffParams->DutyCycleEnabled == false ) && ( calcBackOffParams->LastTxIsJoinRequest == false ) ) + { + // This is the case when the duty cycle is off and the last uplink frame was not a join. + // This could happen in case of a rejoin, e.g. in compliance test mode. + // In this special case we have to set the time off to 0, since the join duty cycle shall only + // be applied after the first join request. + calcBackOffParams->Bands[bandIdx].TimeOff = 0; + } + else + { + // Apply band time-off. + calcBackOffParams->Bands[bandIdx].TimeOff = calcBackOffParams->TxTimeOnAir * dutyCycle - calcBackOffParams->TxTimeOnAir; + } + } + else + { + if( calcBackOffParams->DutyCycleEnabled == true ) + { + calcBackOffParams->Bands[bandIdx].TimeOff = calcBackOffParams->TxTimeOnAir * dutyCycle - calcBackOffParams->TxTimeOnAir; + } + else + { + calcBackOffParams->Bands[bandIdx].TimeOff = 0; + } + } +} diff --git a/features/lorawan/lorastack/phy/LoRaPHY.h b/features/lorawan/lorastack/phy/LoRaPHY.h new file mode 100644 index 0000000000..2ab7eb7acf --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHY.h @@ -0,0 +1,677 @@ +/** + * @file LoRaPHY.h + * + * @brief An abstract class providing radio object to children and + * provide base for implementing LoRa PHY layer + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * Description: LoRa PHY layer + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_BASE_ +#define MBED_OS_LORAPHY_BASE_ + +#include "lorawan/system/LoRaWANTimer.h" +#include "lorawan/lorastack/phy/lora_phy_ds.h" +#include "netsocket/LoRaRadio.h" + +class LoRaPHY { + +public: + LoRaPHY(); + virtual ~LoRaPHY(); + + void set_radio_instance(LoRaRadio& radio); + + void put_radio_to_sleep(void); + + void put_radio_to_standby(void); + + void setup_rx_window(bool is_rx_continuous, uint32_t max_rx_window); + + void setup_tx_cont_wave_mode(uint16_t timeout, uint32_t frequency, + uint8_t power); + + void handle_send(uint8_t *buf, uint8_t size); + + void setup_public_network_mode(bool set); + + uint32_t get_radio_rng(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval A structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ) = 0; + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ) = 0; + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ) = 0; + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute for which the verification is needed. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ) = 0; + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ) = 0; + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ) = 0; + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ) = 0; + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ) = 0; + + /* + * RX window precise timing + * + * For more details please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol. + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + * + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) = 0; + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ) = 0; + + /*! + * \brief The function processes a Link ADR Request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ) = 0; + + /*! + * \brief The function processes a RX Parameter Setup Request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ) = 0; + + /*! + * \brief The function processes a New Channel Request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ) = 0; + + /*! + * \brief The function processes a TX ParamSetup Request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ) = 0; + + /*! + * \brief The function processes a DlChannel Request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ) = 0; + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ) = 0; + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ) = 0; + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams Parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval Function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ) = 0; + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ) = 0; + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ) = 0; + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ) = 0; + + /*! + * \brief Computes new datarate according to the given offset + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ) = 0; + +protected: + LoRaRadio *_radio; + + typedef struct sRegionCommonLinkAdrParams + { + /*! + * The number of repetitions. + */ + uint8_t NbRep; + /*! + * Datarate. + */ + int8_t Datarate; + /*! + * TX power. + */ + int8_t TxPower; + /*! + * Channels mask control field. + */ + uint8_t ChMaskCtrl; + /*! + * Channels mask field. + */ + uint16_t ChMask; + }RegionCommonLinkAdrParams_t; + + typedef struct sRegionCommonLinkAdrReqVerifyParams + { + /*! + * The current status of the AdrLinkRequest. + */ + uint8_t Status; + /*! + * Set to true, if ADR is enabled. + */ + bool AdrEnabled; + /*! + * The datarate the AdrLinkRequest wants to set. + */ + int8_t Datarate; + /*! + * The TX power the AdrLinkRequest wants to set. + */ + int8_t TxPower; + /*! + * The number of repetitions the AdrLinkRequest wants to set. + */ + uint8_t NbRep; + /*! + * The current datarate the node is using. + */ + int8_t CurrentDatarate; + /*! + * The current TX power the node is using. + */ + int8_t CurrentTxPower; + /*! + * The current number of repetitions the node is using. + */ + int8_t CurrentNbRep; + /*! + * The number of channels. + */ + uint8_t NbChannels; + /*! + * A pointer to the first element of the channels mask. + */ + uint16_t* ChannelsMask; + /*! + * The minimum possible datarate. + */ + int8_t MinDatarate; + /*! + * The maximum possible datarate. + */ + int8_t MaxDatarate; + /*! + * A pointer to the channels. + */ + ChannelParams_t* Channels; + /*! + * The minimum possible TX power. + */ + int8_t MinTxPower; + /*! + * The maximum possible TX power. + */ + int8_t MaxTxPower; + }RegionCommonLinkAdrReqVerifyParams_t; + + typedef struct sRegionCommonCalcBackOffParams + { + /*! + * A pointer to region specific channels. + */ + ChannelParams_t* Channels; + /*! + * A pointer to region specific bands. + */ + Band_t* Bands; + /*! + * Set to true, if the last uplink was a join request. + */ + bool LastTxIsJoinRequest; + /*! + * Set to true, if the node is joined. + */ + bool Joined; + /*! + * Set to true, if the duty cycle is enabled. + */ + bool DutyCycleEnabled; + /*! + * The current channel. + */ + uint8_t Channel; + /*! + * The elapsed time since initialization. + */ + TimerTime_t ElapsedTime; + /*! + * The time on air of the last TX frame. + */ + TimerTime_t TxTimeOnAir; + }RegionCommonCalcBackOffParams_t; + + /*! + * \brief Calculates the join duty cycle. + * This is a generic function and valid for all regions. + * + * \param [in] elapsedTime The time elapsed since starting the device. + * + * \retval Duty cycle restriction. + */ + uint16_t get_join_DC( TimerTime_t elapsedTime ); + + /*! + * \brief Verifies, if a value is in a given range. + * This is a generic function and valid for all regions. + * + * \param [in] value The value to verify, if it is in range. + * + * \param [in] min The minimum possible value. + * + * \param [in] max The maximum possible value. + * + * \retval 1 if the value is in range, otherwise 0. + */ + uint8_t val_in_range( int8_t value, int8_t min, int8_t max ); + + /*! + * \brief Verifies, if a datarate is available on an active channel. + * This is a generic function and valid for all regions. + * + * \param [in] nbChannels The number of channels. + * + * \param [in] channelsMask The channels mask of the region. + * + * \param [in] dr The datarate to verify. + * + * \param [in] minDr The minimum datarate. + * + * \param [in] maxDr The maximum datarate. + * + * \param [in] channels The channels of the region. + * + * \retval True if the datarate is supported, false if not. + */ + bool verify_channel_DR( uint8_t nbChannels, uint16_t* channelsMask, int8_t dr, + int8_t minDr, int8_t maxDr, ChannelParams_t* channels ); + + /*! + * \brief Disables a channel in a given channels mask. + * This is a generic function and valid for all regions. + * + * \param [in] channelsMask The channels mask of the region. + * + * \param [in] id The ID of the channels mask to disable. + * + * \param [in] maxChannels The maximum number of channels. + * + * \retval True if the channel could be disabled, false if not. + */ + bool disable_channel( uint16_t* channelsMask, uint8_t id, uint8_t maxChannels ); + + /*! + * \brief Counts the number of active channels in a given channels mask. + * This is a generic function and valid for all regions. + * + * \param [in] channelsMask The channels mask of the region. + * + * \param [in] startIdx The start index. + * + * \param [in] stopIdx The stop index (the channels of this index will not be counted). + * + * \retval The number of active channels. + */ + uint8_t num_active_channels( uint16_t* channelsMask, uint8_t startIdx, uint8_t stopIdx ); + + /*! + * \brief Copy a channels mask. + * This is a generic function and valid for all regions. + * + * \param [in] channelsMaskDest The destination channels mask. + * + * \param [in] channelsMaskSrc The source channels mask. + * + * \param [in] len The index length to copy. + */ + void copy_channel_mask( uint16_t* channelsMaskDest, uint16_t* channelsMaskSrc, uint8_t len ); + + /*! + * \brief Sets the last TX done property. + * This is a generic function and valid for all regions. + * + * \param [in] joined Set to true, if the node has joined the network + * + * \param [in] band The band to be updated. + * + * \param [in] lastTxDone The time of the last TX done. + */ + void set_last_tx_done( bool joined, Band_t* band, TimerTime_t lastTxDone ); + + /*! + * \brief Updates the time-offs of the bands. + * This is a generic function and valid for all regions. + * + * \param [in] joined Set to true, if the node has joined the network + * + * \param [in] dutyCycle Set to true, if the duty cycle is enabled. + * + * \param [in] bands A pointer to the bands. + * + * \param [in] nbBands The number of bands available. + * + * \retval The time which must be waited to perform the next uplink. + */ + TimerTime_t update_band_timeoff( bool joined, bool dutyCycle, Band_t* bands, uint8_t nbBands ); + + /*! + * \brief Parses the parameter of an LinkAdrRequest. + * This is a generic function and valid for all regions. + * + * \param [in] payload A pointer to the payload containing the MAC commands. The payload + * must contain the CMD identifier, followed by the parameters. + * + * \param [out] parseLinkAdr The function fills the structure with the ADR parameters. + * + * \retval The length of the ADR request, if a request was found. Otherwise, the + * function returns 0. + */ + uint8_t parse_link_ADR_req( uint8_t* payload, RegionCommonLinkAdrParams_t* parseLinkAdr ); + + /*! + * \brief Verifies and updates the datarate, the TX power and the number of repetitions + * of a LinkAdrRequest. This also depends on the ADR configuration. + * + * \param [in] verifyParams A pointer to a structure containing the input parameters. + * + * \param [out] dr The updated datarate. + * + * \param [out] txPow The updated TX power. + * + * \param [out] nbRep The updated number of repetitions. + * + * \retval The status according to the LinkAdrRequest definition. + */ + uint8_t verify_link_ADR_req( RegionCommonLinkAdrReqVerifyParams_t* verifyParams, int8_t* dr, int8_t* txPow, uint8_t* nbRep ); + + /*! + * \brief Computes the symbol time for LoRa modulation. + * + * \param [in] phyDr The physical datarate to use. + * + * \param [in] bandwidth The bandwidth to use. + * + * \retval The symbol time. + */ + double compute_symb_timeout_lora( uint8_t phyDr, uint32_t bandwidth ); + + /*! + * \brief Computes the symbol time for FSK modulation. + * + * \param [in] phyDr The physical datarate to use. + * + * \retval The symbol time. + */ + double compute_symb_timeout_fsk( uint8_t phyDr ); + + /*! + * \brief Computes the RX window timeout and the RX window offset. + * + * \param [in] tSymbol The symbol timeout. + * + * \param [in] minRxSymbols The minimum required number of symbols to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds + * The receiver will turn on in a [-rxError : +rxError] ms interval around RxOffset. + * + * \param [in] wakeUpTime The wakeup time of the system. + * + * \param [out] windowTimeout The RX window timeout. + * + * \param [out] windowOffset The RX window time offset to be applied to the RX delay. + */ + void get_rx_window_params( double tSymbol, uint8_t minRxSymbols, uint32_t rxError, uint32_t wakeUpTime, uint32_t* windowTimeout, int32_t* windowOffset ); + + /*! + * \brief Computes the txPower, based on the max EIRP and the antenna gain. + * + * \param [in] txPowerIndex The TX power index. + * + * \param [in] maxEirp The maximum EIRP. + * + * \param [in] antennaGain The antenna gain. + * + * \retval The physical TX power. + */ + int8_t compute_tx_power( int8_t txPowerIndex, float maxEirp, float antennaGain ); + + /*! + * \brief Provides a random number in the range provided. + * + * \param [in] min lower boundary + * \param [in] max upper boundary + */ + int32_t get_random(int32_t min, int32_t max); + + /*! + * \brief Calculates the duty cycle for the current band. + * + * \param [in] calcBackOffParams A pointer to the input parameters. + */ + void get_DC_backoff( RegionCommonCalcBackOffParams_t* calcBackOffParams ); +}; + +#endif /* MBED_OS_LORAPHY_BASE_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYAS923.cpp b/features/lorawan/lorastack/phy/LoRaPHYAS923.cpp new file mode 100644 index 0000000000..69ec6ee94c --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYAS923.cpp @@ -0,0 +1,1338 @@ +/** + * @file LoRaPHYAS923.cpp + * + * @brief Implements LoRaPHY for Asia-Pacific 923 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYAS923.h" +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + + +/*! + * Number of default channels + */ +#define AS923_NUMB_DEFAULT_CHANNELS 2 + +/*! + * Number of channels to apply for the CF list + */ +#define AS923_NUMB_CHANNELS_CF_LIST 5 + +/*! + * Minimal datarate that can be used by the node + */ +#define AS923_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define AS923_TX_MAX_DATARATE DR_7 + +/*! + * Minimal datarate that can be used by the node + */ +#define AS923_RX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define AS923_RX_MAX_DATARATE DR_7 + +/*! + * Default datarate used by the node + */ +#define AS923_DEFAULT_DATARATE DR_2 + +/*! + * The minimum datarate which is used when the + * dwell time is limited. + */ +#define AS923_DWELL_LIMIT_DATARATE DR_2 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define AS923_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define AS923_MAX_RX1_DR_OFFSET 7 + +/*! + * Default Rx1 receive datarate offset + */ +#define AS923_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define AS923_MIN_TX_POWER TX_POWER_7 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define AS923_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define AS923_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default uplink dwell time configuration + */ +#define AS923_DEFAULT_UPLINK_DWELL_TIME 1 + +/*! + * Default downlink dwell time configuration + */ +#define AS923_DEFAULT_DOWNLINK_DWELL_TIME 1 + +/*! + * Default Max EIRP + */ +#define AS923_DEFAULT_MAX_EIRP 16.0f + +/*! + * Default antenna gain + */ +#define AS923_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define AS923_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define AS923_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define AS923_DUTY_CYCLE_ENABLED 0 + +/*! + * Maximum RX window duration + */ +#define AS923_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define AS923_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define AS923_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define AS923_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define AS923_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define AS923_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define AS923_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define AS923_ACK_TIMEOUT_RND 1000 + +#if ( AS923_DEFAULT_DATARATE > DR_5 ) +#error "A default DR higher than DR_5 may lead to connectivity loss." +#endif + +/*! + * Second reception window channel frequency definition. + */ +#define AS923_RX_WND_2_FREQ 923200000 + +/*! + * Second reception window channel datarate definition. + */ +#define AS923_RX_WND_2_DR DR_2 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define AS923_BAND0 { 100, AS923_MAX_TX_POWER, 0, 0 } // 1.0 % + +/*! + * LoRaMac default channel 1 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define AS923_LC1 { 923200000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 2 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define AS923_LC2 { 923400000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac channels which are allowed for the join procedure + */ +#define AS923_JOIN_CHANNELS ( uint16_t )( LC( 1 ) | LC( 2 ) ) + +/*! + * RSSI threshold for a free channel [dBm] + */ +#define AS923_RSSI_FREE_TH -85 + +/*! + * Specifies the time the node performs a carrier sense + */ +#define AS923_CARRIER_SENSE_TIME 6 + +/*! + * Data rates table definition + */ +static const uint8_t DataratesAS923[] = { 12, 11, 10, 9, 8, 7, 7, 50 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsAS923[] = { 125000, 125000, 125000, 125000, 125000, 125000, 250000, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + * The table is valid for the dwell time configuration of 0 for uplinks and downlinks. + */ +static const uint8_t MaxPayloadOfDatarateDwell0AS923[] = { 51, 51, 51, 115, 242, 242, 242, 242 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + * The table is valid for the dwell time configuration of 0 for uplinks and downlinks. The table provides + * repeater support. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterDwell0AS923[] = { 51, 51, 51, 115, 222, 222, 222, 222 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with and without repeater. + * The table proides repeater support. The table is only valid for uplinks. + */ +static const uint8_t MaxPayloadOfDatarateDwell1UpAS923[] = { 0, 0, 11, 53, 125, 242, 242, 242 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with and without repeater. + * The table proides repeater support. The table is only valid for downlinks. + */ +static const uint8_t MaxPayloadOfDatarateDwell1DownAS923[] = { 0, 0, 11, 53, 126, 242, 242, 242 }; + +/*! + * Effective datarate offsets for receive window 1. + */ +static const int8_t EffectiveRx1DrOffsetAS923[] = { 0, 1, 2, 3, 4, 5, -1, -2 }; + + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsAS923[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + return txPowerResult; +} + +static bool VerifyTxFreq( uint32_t freq ) +{ + // Not checking the radio drivers here as all radio drivers + // always return true. Just check for Asia-Pacific band here + if( ( freq < 915000000 ) || ( freq > 928000000 ) ) + { + return false; + } + return true; +} + +uint8_t LoRaPHYAS923::CountNbOfEnabledChannels(bool joined, uint8_t datarate, + uint16_t* channelsMask, + ChannelParams_t* channels, Band_t* bands, + uint8_t* enabledChannels, uint8_t* delayTx) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < AS923_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( joined == false ) + { + if( ( AS923_JOIN_CHANNELS & ( 1 << j ) ) == 0 ) + { + continue; + } + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYAS923::LoRaPHYAS923() +{ + const Band_t band0 = AS923_BAND0; + Bands[0] = band0; +} + +LoRaPHYAS923::~LoRaPHYAS923() +{ +} + +PhyParam_t LoRaPHYAS923::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + if( getPhy->DownlinkDwellTime == 0 ) + { + phyParam.Value = AS923_RX_MIN_DATARATE; + } + else + { + phyParam.Value = AS923_DWELL_LIMIT_DATARATE; + } + break; + } + case PHY_MIN_TX_DR: + { + if( getPhy->UplinkDwellTime == 0 ) + { + phyParam.Value = AS923_TX_MIN_DATARATE; + } + else + { + phyParam.Value = AS923_DWELL_LIMIT_DATARATE; + } + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = AS923_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + if( getPhy->UplinkDwellTime == 0 ) + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, AS923_TX_MIN_DATARATE ); + } + else + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, AS923_DWELL_LIMIT_DATARATE ); + } + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = AS923_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + if( getPhy->UplinkDwellTime == 0 ) + { + phyParam.Value = MaxPayloadOfDatarateDwell0AS923[getPhy->Datarate]; + } + else + { + phyParam.Value = MaxPayloadOfDatarateDwell1UpAS923[getPhy->Datarate]; + } + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + if( getPhy->UplinkDwellTime == 0 ) + { + phyParam.Value = MaxPayloadOfDatarateRepeaterDwell0AS923[getPhy->Datarate]; + } + else + { + phyParam.Value = MaxPayloadOfDatarateDwell1UpAS923[getPhy->Datarate]; + } + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = AS923_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = AS923_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = AS923_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = AS923_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = AS923_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = AS923_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = AS923_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( AS923_ACKTIMEOUT + get_random( -AS923_ACK_TIMEOUT_RND, AS923_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = AS923_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = AS923_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = AS923_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = AS923_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + { + phyParam.Value = AS923_DEFAULT_UPLINK_DWELL_TIME; + break; + } + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = AS923_DEFAULT_DOWNLINK_DWELL_TIME; + break; + } + case PHY_DEF_MAX_EIRP: + { + phyParam.fValue = AS923_DEFAULT_MAX_EIRP; + break; + } + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = AS923_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 1; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYAS923::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYAS923::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + const ChannelParams_t channel1 = AS923_LC1; + const ChannelParams_t channel2 = AS923_LC2; + Channels[0] = channel1; + Channels[1] = channel2; + + // Initialize the channels default mask + ChannelsDefaultMask[0] = LC( 1 ) + LC( 2 ); + // Update the channels mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 1 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Restore channels default mask + ChannelsMask[0] |= ChannelsDefaultMask[0]; + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYAS923::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + if( verify->DatarateParams.UplinkDwellTime == 0 ) + { + return val_in_range( verify->DatarateParams.Datarate, AS923_TX_MIN_DATARATE, AS923_TX_MAX_DATARATE ); + } + else + { + return val_in_range( verify->DatarateParams.Datarate, AS923_DWELL_LIMIT_DATARATE, AS923_TX_MAX_DATARATE ); + } + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + if( verify->DatarateParams.DownlinkDwellTime == 0 ) + { + return val_in_range( verify->DatarateParams.Datarate, AS923_RX_MIN_DATARATE, AS923_RX_MAX_DATARATE ); + } + else + { + return val_in_range( verify->DatarateParams.Datarate, AS923_DWELL_LIMIT_DATARATE, AS923_RX_MAX_DATARATE ); + } + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, AS923_MAX_TX_POWER, AS923_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return AS923_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + return true; + } + default: + return false; + } +} + +void LoRaPHYAS923::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + ChannelParams_t newChannel; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + // Setup default datarate range + newChannel.DrRange.Value = ( DR_5 << 4 ) | DR_0; + + // Size of the optional CF list + if( applyCFList->Size != 16 ) + { + return; + } + + // Last byte is RFU, don't take it into account + for( uint8_t i = 0, chanIdx = AS923_NUMB_DEFAULT_CHANNELS; chanIdx < AS923_MAX_NB_CHANNELS; i+=3, chanIdx++ ) + { + if( chanIdx < ( AS923_NUMB_CHANNELS_CF_LIST + AS923_NUMB_DEFAULT_CHANNELS ) ) + { + // Channel frequency + newChannel.Frequency = (uint32_t) applyCFList->Payload[i]; + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 1] << 8 ); + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 2] << 16 ); + newChannel.Frequency *= 100; + + // Initialize alternative frequency to 0 + newChannel.Rx1Frequency = 0; + } + else + { + newChannel.Frequency = 0; + newChannel.DrRange.Value = 0; + newChannel.Rx1Frequency = 0; + } + + if( newChannel.Frequency != 0 ) + { + channelAdd.NewChannel = &newChannel; + channelAdd.ChannelId = chanIdx; + + // Try to add all channels + add_channel(&channelAdd); + } + else + { + channelRemove.ChannelId = chanIdx; + + remove_channel(&channelRemove); + } + } +} + +bool LoRaPHYAS923::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYAS923::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t minTxDatarate = 0; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + minTxDatarate = phyParam.Value; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + // Apply the minimum possible datarate. + datarate = MAX( datarate, minTxDatarate ); + + if( adrNext->AdrEnabled == true ) + { + if( datarate == minTxDatarate ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= AS923_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = AS923_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( AS923_ADR_ACK_LIMIT + AS923_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % AS923_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + datarate = phyParam.Value; + + if( datarate == minTxDatarate ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ); + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYAS923::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, AS923_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + if( rxConfigParams->Datarate == DR_7 ) + { // FSK + tSymbol = compute_symb_timeout_fsk( DataratesAS923[rxConfigParams->Datarate] ); + } + else + { // LoRa + tSymbol = compute_symb_timeout_lora( DataratesAS923[rxConfigParams->Datarate], BandwidthsAS923[rxConfigParams->Datarate] ); + } + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYAS923::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + radio_modems_t modem; + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if( _radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = Channels[rxConfig->Channel].Frequency; + // Apply the alternative RX 1 window frequency, if it is available + if( Channels[rxConfig->Channel].Rx1Frequency != 0 ) + { + frequency = Channels[rxConfig->Channel].Rx1Frequency; + } + } + + // Read the physical datarate from the datarates table + phyDr = DataratesAS923[dr]; + + _radio->set_channel(frequency); + + // Radio configuration + if( dr == DR_7 ) + { + modem = MODEM_FSK; + _radio->set_rx_config(modem, 50000, phyDr * 1000, 0, 83333, 5, + rxConfig->WindowTimeout, false, 0, true, 0, + 0, false, rxConfig->RxContinuous); + } + else + { + modem = MODEM_LORA; + _radio->set_rx_config(modem, rxConfig->Bandwidth, phyDr, 1, 0, 8, + rxConfig->WindowTimeout, false, 0, false, 0, 0, + true, rxConfig->RxContinuous); + } + + // Check for repeater support + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterDwell0AS923[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateDwell0AS923[dr]; + } + + _radio->set_max_payload_length(modem, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYAS923::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + radio_modems_t modem; + int8_t phyDr = DataratesAS923[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, txConfig->MaxEirp, txConfig->AntennaGain ); + + // Setup the radio frequency + _radio->set_channel(Channels[txConfig->Channel].Frequency); + + if( txConfig->Datarate == DR_7 ) + { // High Speed FSK channel + modem = MODEM_FSK; + _radio->set_tx_config(modem, phyTxPower, 25000, bandwidth, phyDr * 1000, + 0, 5, false, true, 0, 0, false, 3000 ); + } + else + { + modem = MODEM_LORA; + _radio->set_tx_config(modem, phyTxPower, 0, bandwidth, phyDr, 1, 8, + false, true, 0, 0, false, 3000); + } + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length(modem, txConfig->PktLen); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air(modem, txConfig->PktLen); + + *txPower = txPowerLimited; + return true; +} + +uint8_t LoRaPHYAS923::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t chMask = 0; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + // Get ADR request parameters + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + // Setup temporary channels mask + chMask = linkAdrParams.ChMask; + + // Verify channels mask + if( ( linkAdrParams.ChMaskCtrl == 0 ) && ( chMask == 0 ) ) + { + status &= 0xFE; // Channel mask KO + } + else if( ( ( linkAdrParams.ChMaskCtrl >= 1 ) && ( linkAdrParams.ChMaskCtrl <= 5 )) || + ( linkAdrParams.ChMaskCtrl >= 7 ) ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + for( uint8_t i = 0; i < AS923_MAX_NB_CHANNELS; i++ ) + { + if( linkAdrParams.ChMaskCtrl == 6 ) + { + if( Channels[i].Frequency != 0 ) + { + chMask |= 1 << i; + } + } + else + { + if( ( ( chMask & ( 1 << i ) ) != 0 ) && + ( Channels[i].Frequency == 0 ) ) + {// Trying to enable an undefined channel + status &= 0xFE; // Channel mask KO + } + } + } + } + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = AS923_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = &chMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = AS923_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = AS923_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = AS923_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Set the channels mask to a default value + memset( ChannelsMask, 0, sizeof( ChannelsMask ) ); + // Update the channels mask + ChannelsMask[0] = chMask; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYAS923::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + + // Verify radio frequency + if(_radio->check_rf_frequency(rxParamSetupReq->Frequency) == false ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, AS923_RX_MIN_DATARATE, AS923_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, AS923_MIN_RX1_DR_OFFSET, AS923_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYAS923::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + uint8_t status = 0x03; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + if( newChannelReq->NewChannel->Frequency == 0 ) + { + channelRemove.ChannelId = newChannelReq->ChannelId; + + // Remove + if( remove_channel(&channelRemove) == false ) + { + status &= 0xFC; + } + } + else + { + channelAdd.NewChannel = newChannelReq->NewChannel; + channelAdd.ChannelId = newChannelReq->ChannelId; + + switch( add_channel (&channelAdd )) + { + case LORAMAC_STATUS_OK: + { + break; + } + case LORAMAC_STATUS_FREQUENCY_INVALID: + { + status &= 0xFE; + break; + } + case LORAMAC_STATUS_DATARATE_INVALID: + { + status &= 0xFD; + break; + } + case LORAMAC_STATUS_FREQ_AND_DR_INVALID: + { + status &= 0xFC; + break; + } + default: + { + status &= 0xFC; + break; + } + } + } + + return status; +} + +int8_t LoRaPHYAS923::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + // Accept the request + return 0; +} + +uint8_t LoRaPHYAS923::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + uint8_t status = 0x03; + + // Verify if the frequency is supported + if( VerifyTxFreq( dlChannelReq->Rx1Frequency ) == false ) + { + status &= 0xFE; + } + + // Verify if an uplink frequency exists + if( Channels[dlChannelReq->ChannelId].Frequency == 0 ) + { + status &= 0xFD; + } + + // Apply Rx1 frequency, if the status is OK + if( status == 0x03 ) + { + Channels[dlChannelReq->ChannelId].Rx1Frequency = dlChannelReq->Rx1Frequency; + } + + return status; +} + +int8_t LoRaPHYAS923::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + // Only AS923_DWELL_LIMIT_DATARATE is supported + return AS923_DWELL_LIMIT_DATARATE; +} + +void LoRaPHYAS923::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYAS923::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t channelNext = 0; + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[AS923_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + if( num_active_channels( ChannelsMask, 0, 1 ) == 0 ) + { // Reactivate default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ); + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, AS923_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Joined, nextChanParams->Datarate, + ChannelsMask, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + for( uint8_t i = 0, j = get_random( 0, nbEnabledChannels - 1 ); i < AS923_MAX_NB_CHANNELS; i++ ) + { + channelNext = enabledChannels[j]; + j = ( j + 1 ) % nbEnabledChannels; + + // Perform carrier sense for AS923_CARRIER_SENSE_TIME + // If the channel is free, we can stop the LBT mechanism + if( _radio->perform_carrier_sense(MODEM_LORA, + Channels[channelNext].Frequency, + AS923_RSSI_FREE_TH, + AS923_CARRIER_SENSE_TIME ) == true) + { + // Free channel found + *channel = channelNext; + *time = 0; + return true; + } + } + return false; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel, restore defaults + ChannelsMask[0] |= LC( 1 ) + LC( 2 ); + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYAS923::add_channel(ChannelAddParams_t* channelAdd) +{ + uint8_t band = 0; + bool drInvalid = false; + bool freqInvalid = false; + uint8_t id = channelAdd->ChannelId; + + if( id >= AS923_MAX_NB_CHANNELS ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Validate the datarate range + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Min, AS923_TX_MIN_DATARATE, AS923_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, AS923_TX_MIN_DATARATE, AS923_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( channelAdd->NewChannel->DrRange.Fields.Min > channelAdd->NewChannel->DrRange.Fields.Max ) + { + drInvalid = true; + } + + // Default channels don't accept all values + if( id < AS923_NUMB_DEFAULT_CHANNELS ) + { + // Validate the datarate range for min: must be DR_0 + if( channelAdd->NewChannel->DrRange.Fields.Min > DR_0 ) + { + drInvalid = true; + } + // Validate the datarate range for max: must be DR_5 <= Max <= TX_MAX_DATARATE + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, DR_5, AS923_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + // We are not allowed to change the frequency + if( channelAdd->NewChannel->Frequency != Channels[id].Frequency ) + { + freqInvalid = true; + } + } + + // Check frequency + if( freqInvalid == false ) + { + if( VerifyTxFreq( channelAdd->NewChannel->Frequency ) == false ) + { + freqInvalid = true; + } + } + + // Check status + if( ( drInvalid == true ) && ( freqInvalid == true ) ) + { + return LORAMAC_STATUS_FREQ_AND_DR_INVALID; + } + if( drInvalid == true ) + { + return LORAMAC_STATUS_DATARATE_INVALID; + } + if( freqInvalid == true ) + { + return LORAMAC_STATUS_FREQUENCY_INVALID; + } + + memcpy( &(Channels[id]), channelAdd->NewChannel, sizeof( Channels[id] ) ); + Channels[id].Band = band; + ChannelsMask[0] |= ( 1 << id ); + return LORAMAC_STATUS_OK; +} + +bool LoRaPHYAS923::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + uint8_t id = channelRemove->ChannelId; + + if( id < AS923_NUMB_DEFAULT_CHANNELS ) + { + return false; + } + + // Remove the channel from the list of channels + const ChannelParams_t empty_channel = { 0, 0, { 0 }, 0 }; + Channels[id] = empty_channel; + + return disable_channel( ChannelsMask, id, AS923_MAX_NB_CHANNELS ); +} + +void LoRaPHYAS923::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, continuousWave->MaxEirp, continuousWave->AntennaGain ); + + _radio->set_tx_continuous_wave(frequency, phyTxPower, continuousWave->Timeout); +} + +uint8_t LoRaPHYAS923::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, + int8_t drOffset) +{ + // Initialize minDr for a downlink dwell time configuration of 0 + int8_t minDr = DR_0; + + // Update the minDR for a downlink dwell time configuration of 1 + if( downlinkDwellTime == 1 ) + { + minDr = AS923_DWELL_LIMIT_DATARATE; + } + + // Apply offset formula + return MIN( DR_5, MAX( minDr, dr - EffectiveRx1DrOffsetAS923[drOffset] ) ); +} + + diff --git a/features/lorawan/lorastack/phy/LoRaPHYAS923.h b/features/lorawan/lorastack/phy/LoRaPHYAS923.h new file mode 100644 index 0000000000..2dabdafccc --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYAS923.h @@ -0,0 +1,363 @@ +/** + * @file LoRaPHYAS923.h + * + * @brief Implements LoRaPHY for Asia-Pacific 923 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_AS923_H_ +#define MBED_OS_LORAPHY_AS923_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +/*! + * LoRaMac maximum number of channels + */ +#define AS923_MAX_NB_CHANNELS 16 + +/*! + * Maximum number of bands + */ +#define AS923_MAX_NB_BANDS 1 + +#define AS923_CHANNELS_MASK_SIZE 1 + + +class LoRaPHYAS923 : public LoRaPHY { + +public: + + LoRaPHYAS923(); + virtual ~LoRaPHYAS923(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval A structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to be verified. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the + * CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing. + * + * For more details please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum required number of symbols to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams The updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR Request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval Function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + uint8_t CountNbOfEnabledChannels(bool joined, uint8_t datarate, + uint16_t* channelsMask, + ChannelParams_t* channels, Band_t* bands, + uint8_t* enabledChannels, uint8_t* delayTx); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[AS923_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[AS923_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[AS923_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[AS923_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_AS923_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYAU915.cpp b/features/lorawan/lorastack/phy/LoRaPHYAU915.cpp new file mode 100644 index 0000000000..370b9fa4b0 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYAU915.cpp @@ -0,0 +1,984 @@ +/** + * @file LoRaPHYAU915.cpp + * + * @brief Implements LoRaPHY for Australian 915 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYAU915.h" +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + +/*! + * Minimal datarate that can be used by the node + */ +#define AU915_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define AU915_TX_MAX_DATARATE DR_6 + +/*! + * Minimal datarate that can be used by the node + */ +#define AU915_RX_MIN_DATARATE DR_8 + +/*! + * Maximal datarate that can be used by the node + */ +#define AU915_RX_MAX_DATARATE DR_13 + +/*! + * Default datarate used by the node + */ +#define AU915_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define AU915_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define AU915_MAX_RX1_DR_OFFSET 6 + +/*! + * Default Rx1 receive datarate offset + */ +#define AU915_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define AU915_MIN_TX_POWER TX_POWER_10 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define AU915_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define AU915_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max EIRP + */ +#define AU915_DEFAULT_MAX_EIRP 30.0f + +/*! + * Default antenna gain + */ +#define AU915_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define AU915_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define AU915_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define AU915_DUTY_CYCLE_ENABLED 0 + +/*! + * Maximum RX window duration + */ +#define AU915_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define AU915_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define AU915_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define AU915_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define AU915_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define AU915_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define AU915_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define AU915_ACK_TIMEOUT_RND 1000 + +/*! + * Second reception window channel frequency definition. + */ +#define AU915_RX_WND_2_FREQ 923300000 + +/*! + * Second reception window channel datarate definition. + */ +#define AU915_RX_WND_2_DR DR_8 + + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define AU915_BAND0 { 1, AU915_MAX_TX_POWER, 0, 0 } // 100.0 % + +/*! + * Defines the first channel for RX window 1 for US band + */ +#define AU915_FIRST_RX1_CHANNEL ( (uint32_t) 923300000 ) + +/*! + * Defines the last channel for RX window 1 for US band + */ +#define AU915_LAST_RX1_CHANNEL ( (uint32_t) 927500000 ) + +/*! + * Defines the step width of the channels for RX window 1 + */ +#define AU915_STEPWIDTH_RX1_CHANNEL ( (uint32_t) 600000 ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesAU915[] = { 12, 11, 10, 9, 8, 7, 8, 0, 12, 11, 10, + 9, 8, 7, 0, 0 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsAU915[] = { 125000, 125000, 125000, 125000, + 125000, 125000, 500000, 0, 500000, 500000, 500000, 500000, 500000, 500000, + 0, 0 }; + +/*! + * Up/Down link data rates offset definition + */ +static const int8_t DatarateOffsetsAU915[7][6] = { { DR_8, DR_8, DR_8, DR_8, + DR_8, DR_8 }, // DR_0 + { DR_9, DR_8, DR_8, DR_8, DR_8, DR_8 }, // DR_1 + { DR_10, DR_9, DR_8, DR_8, DR_8, DR_8 }, // DR_2 + { DR_11, DR_10, DR_9, DR_8, DR_8, DR_8 }, // DR_3 + { DR_12, DR_11, DR_10, DR_9, DR_8, DR_8 }, // DR_4 + { DR_13, DR_12, DR_11, DR_10, DR_9, DR_8 }, // DR_5 + { DR_13, DR_13, DR_12, DR_11, DR_10, DR_9 }, // DR_6 + }; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateAU915[] = { 51, 51, 51, 115, 242, 242, + 242, 0, 53, 129, 242, 242, 242, 242, 0, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterAU915[] = { 51, 51, 51, 115, + 222, 222, 222, 0, 33, 109, 222, 222, 222, 222, 0, 0 }; + + +// Static functions +static int8_t GetNextLowerTxDr(int8_t dr, int8_t minDr) +{ + uint8_t nextLowerDr = 0; + + if (dr == minDr) { + nextLowerDr = minDr; + } else { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth(uint32_t drIndex) +{ + switch (BandwidthsAU915[drIndex]) { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower(int8_t txPower, int8_t maxBandTxPower, + int8_t datarate, uint16_t* channelsMask) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX(txPower, maxBandTxPower); + + return txPowerResult; +} + +uint8_t LoRaPHYAU915::CountNbOfEnabledChannels(uint8_t datarate, + uint16_t* channelsMask, + ChannelParams_t* channels, + Band_t* bands, uint8_t* enabledChannels, + uint8_t* delayTx) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for (uint8_t i = 0, k = 0; i < AU915_MAX_NB_CHANNELS; i += 16, k++) { + for (uint8_t j = 0; j < 16; j++) { + if ((channelsMask[k] & (1 << j)) != 0) { + if (channels[i + j].Frequency == 0) { // Check if the channel is enabled + continue; + } + if (val_in_range(datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max) == 0) { // Check if the current channel selection supports the given datarate + continue; + } + if (bands[channels[i + j].Band].TimeOff > 0) { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYAU915::LoRaPHYAU915() +{ + const Band_t band0 = AU915_BAND0; + Bands[0] = band0; +} + +LoRaPHYAU915::~LoRaPHYAU915() +{ +} + +PhyParam_t LoRaPHYAU915::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch (getPhy->Attribute) { + case PHY_MIN_RX_DR: { + phyParam.Value = AU915_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: { + phyParam.Value = AU915_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: { + phyParam.Value = AU915_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: { + phyParam.Value = GetNextLowerTxDr(getPhy->Datarate, + AU915_TX_MIN_DATARATE); + break; + } + case PHY_DEF_TX_POWER: { + phyParam.Value = AU915_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: { + phyParam.Value = MaxPayloadOfDatarateAU915[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: { + phyParam.Value = + MaxPayloadOfDatarateRepeaterAU915[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: { + phyParam.Value = AU915_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: { + phyParam.Value = AU915_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: { + phyParam.Value = AU915_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: { + phyParam.Value = AU915_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: { + phyParam.Value = AU915_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: { + phyParam.Value = AU915_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: { + phyParam.Value = AU915_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: { + phyParam.Value = + ( AU915_ACKTIMEOUT + + get_random(-AU915_ACK_TIMEOUT_RND, + AU915_ACK_TIMEOUT_RND)); + break; + } + case PHY_DEF_DR1_OFFSET: { + phyParam.Value = AU915_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: { + phyParam.Value = AU915_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: { + phyParam.Value = AU915_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: { + phyParam.Value = AU915_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: { + phyParam.fValue = AU915_DEFAULT_MAX_EIRP; + break; + } + case PHY_DEF_ANTENNA_GAIN: { + phyParam.fValue = AU915_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: { + phyParam.Value = 2; + break; + } + default: { + break; + } + } + + return phyParam; +} + +void LoRaPHYAU915::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done(txDone->Joined, &Bands[Channels[txDone->Channel].Band], + txDone->LastTxDoneTime); +} + +void LoRaPHYAU915::load_defaults(InitType_t type) +{ + switch (type) { + case INIT_TYPE_INIT: { + // Channels + // 125 kHz channels + for (uint8_t i = 0; i < AU915_MAX_NB_CHANNELS - 8; i++) { + Channels[i].Frequency = 915200000 + i * 200000; + Channels[i].DrRange.Value = ( DR_5 << 4) | DR_0; + Channels[i].Band = 0; + } + // 500 kHz channels + for (uint8_t i = AU915_MAX_NB_CHANNELS - 8; + i < AU915_MAX_NB_CHANNELS; i++) { + Channels[i].Frequency = 915900000 + + (i - ( AU915_MAX_NB_CHANNELS - 8)) * 1600000; + Channels[i].DrRange.Value = ( DR_6 << 4) | DR_6; + Channels[i].Band = 0; + } + + // Initialize channels default mask + ChannelsDefaultMask[0] = 0xFFFF; + ChannelsDefaultMask[1] = 0xFFFF; + ChannelsDefaultMask[2] = 0xFFFF; + ChannelsDefaultMask[3] = 0xFFFF; + ChannelsDefaultMask[4] = 0x00FF; + ChannelsDefaultMask[5] = 0x0000; + + // Copy channels default mask + copy_channel_mask(ChannelsMask, ChannelsDefaultMask, 6); + + // Copy into channels mask remaining + copy_channel_mask(ChannelsMaskRemaining, ChannelsMask, 6); + break; + } + case INIT_TYPE_RESTORE: { + // Copy channels default mask + copy_channel_mask(ChannelsMask, ChannelsDefaultMask, 6); + + for (uint8_t i = 0; i < 6; i++) { // Copy-And the channels mask + ChannelsMaskRemaining[i] &= ChannelsMask[i]; + } + break; + } + default: { + break; + } + } +} + +bool LoRaPHYAU915::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch (phyAttribute) { + case PHY_TX_DR: + case PHY_DEF_TX_DR: { + return val_in_range(verify->DatarateParams.Datarate, + AU915_TX_MIN_DATARATE, AU915_TX_MAX_DATARATE); + } + case PHY_RX_DR: { + return val_in_range(verify->DatarateParams.Datarate, + AU915_RX_MIN_DATARATE, AU915_RX_MAX_DATARATE); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: { + // Remark: switched min and max! + return val_in_range(verify->TxPower, AU915_MAX_TX_POWER, + AU915_MIN_TX_POWER); + } + case PHY_DUTY_CYCLE: { + return AU915_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: { + if (verify->NbJoinTrials < 2) { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYAU915::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + return; +} + +bool LoRaPHYAU915::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + uint8_t nbChannels = num_active_channels(chanMaskSet->ChannelsMaskIn, 0, 4); + + // Check the number of active channels + // According to ACMA regulation, we require at least 20 125KHz channels, if + // the node shall utilize 125KHz channels. + if ((nbChannels < 20) && (nbChannels > 0)) { + return false; + } + + switch (chanMaskSet->ChannelsMaskType) { + case CHANNELS_MASK: { + copy_channel_mask(ChannelsMask, chanMaskSet->ChannelsMaskIn, 6); + + for (uint8_t i = 0; i < 6; i++) { // Copy-And the channels mask + ChannelsMaskRemaining[i] &= ChannelsMask[i]; + } + break; + } + case CHANNELS_DEFAULT_MASK: { + copy_channel_mask(ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, + 6); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYAU915::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if (adrNext->AdrEnabled == true) { + if (datarate == AU915_TX_MIN_DATARATE) { + *adrAckCounter = 0; + adrAckReq = false; + } else { + if (adrNext->AdrAckCounter >= AU915_ADR_ACK_LIMIT) { + adrAckReq = true; + txPower = AU915_MAX_TX_POWER; + } else { + adrAckReq = false; + } + if (adrNext->AdrAckCounter + >= ( AU915_ADR_ACK_LIMIT + AU915_ADR_ACK_DELAY)) { + if ((adrNext->AdrAckCounter % AU915_ADR_ACK_DELAY) == 1) { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + datarate = phyParam.Value; + + if (datarate == AU915_TX_MIN_DATARATE) { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if (adrNext->UpdateChanMask == true) { + // Re-enable default channels + ChannelsMask[0] = 0xFFFF; + ChannelsMask[1] = 0xFFFF; + ChannelsMask[2] = 0xFFFF; + ChannelsMask[3] = 0xFFFF; + ChannelsMask[4] = 0x00FF; + ChannelsMask[5] = 0x0000; + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYAU915::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN(datarate, AU915_RX_MAX_DATARATE); + rxConfigParams->Bandwidth = GetBandwidth(rxConfigParams->Datarate); + + tSymbol = compute_symb_timeout_lora( + DataratesAU915[rxConfigParams->Datarate], + BandwidthsAU915[rxConfigParams->Datarate]); + + get_rx_window_params(tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, + &rxConfigParams->WindowTimeout, + &rxConfigParams->WindowOffset); +} + +bool LoRaPHYAU915::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if (_radio->get_status() != RF_IDLE) { + return false; + } + + if (rxConfig->Window == 0) { + // Apply window 1 frequency + frequency = AU915_FIRST_RX1_CHANNEL + + (rxConfig->Channel % 8) * AU915_STEPWIDTH_RX1_CHANNEL; + } + + // Read the physical datarate from the datarates table + phyDr = DataratesAU915[dr]; + + _radio->set_channel(frequency); + + // Radio configuration + _radio->set_rx_config(MODEM_LORA, rxConfig->Bandwidth, phyDr, 1, 0, 8, + rxConfig->WindowTimeout, false, 0, false, 0, 0, true, + rxConfig->RxContinuous); + + if (rxConfig->RepeaterSupport == true) { + maxPayload = MaxPayloadOfDatarateRepeaterAU915[dr]; + } else { + maxPayload = MaxPayloadOfDatarateAU915[dr]; + } + _radio->set_max_payload_length(MODEM_LORA, + maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYAU915::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + int8_t phyDr = DataratesAU915[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( + txConfig->TxPower, + Bands[Channels[txConfig->Channel].Band].TxMaxPower, + txConfig->Datarate, ChannelsMask); + uint32_t bandwidth = GetBandwidth(txConfig->Datarate); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power(txPowerLimited, txConfig->MaxEirp, + txConfig->AntennaGain); + + // Setup the radio frequency + _radio->set_channel(Channels[txConfig->Channel].Frequency); + + _radio->set_tx_config(MODEM_LORA, phyTxPower, 0, bandwidth, phyDr, 1, 8, + false, true, 0, 0, false, 3000); + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length(MODEM_LORA, txConfig->PktLen); + + *txTimeOnAir = _radio->time_on_air(MODEM_LORA, txConfig->PktLen); + *txPower = txPowerLimited; + + return true; +} + +uint8_t LoRaPHYAU915::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t channelsMask[6] = { 0, 0, 0, 0, 0, 0 }; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + // Initialize local copy of channels mask + copy_channel_mask(channelsMask, ChannelsMask, 6); + + while (bytesProcessed < linkAdrReq->PayloadSize) { + nextIndex = parse_link_ADR_req(&(linkAdrReq->Payload[bytesProcessed]), + &linkAdrParams); + + if (nextIndex == 0) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + if (linkAdrParams.ChMaskCtrl == 6) { + // Enable all 125 kHz channels + channelsMask[0] = 0xFFFF; + channelsMask[1] = 0xFFFF; + channelsMask[2] = 0xFFFF; + channelsMask[3] = 0xFFFF; + // Apply chMask to channels 64 to 71 + channelsMask[4] = linkAdrParams.ChMask; + } else if (linkAdrParams.ChMaskCtrl == 7) { + // Disable all 125 kHz channels + channelsMask[0] = 0x0000; + channelsMask[1] = 0x0000; + channelsMask[2] = 0x0000; + channelsMask[3] = 0x0000; + // Apply chMask to channels 64 to 71 + channelsMask[4] = linkAdrParams.ChMask; + } else if (linkAdrParams.ChMaskCtrl == 5) { + // RFU + status &= 0xFE; // Channel mask KO + } else { + channelsMask[linkAdrParams.ChMaskCtrl] = linkAdrParams.ChMask; + } + } + + // FCC 15.247 paragraph F mandates to hop on at least 2 125 kHz channels + if ((linkAdrParams.Datarate < DR_6) + && (num_active_channels(channelsMask, 0, 4) < 2)) { + status &= 0xFE; // Channel mask KO + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = AU915_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = channelsMask; + linkAdrVerifyParams.MinDatarate = (int8_t) phyParam.Value; + linkAdrVerifyParams.MaxDatarate = AU915_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = AU915_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = AU915_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req(&linkAdrVerifyParams, &linkAdrParams.Datarate, + &linkAdrParams.TxPower, &linkAdrParams.NbRep); + + // Update channelsMask if everything is correct + if (status == 0x07) { + // Copy Mask + copy_channel_mask(ChannelsMask, channelsMask, 6); + + ChannelsMaskRemaining[0] &= ChannelsMask[0]; + ChannelsMaskRemaining[1] &= ChannelsMask[1]; + ChannelsMaskRemaining[2] &= ChannelsMask[2]; + ChannelsMaskRemaining[3] &= ChannelsMask[3]; + ChannelsMaskRemaining[4] = ChannelsMask[4]; + ChannelsMaskRemaining[5] = ChannelsMask[5]; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYAU915::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + uint32_t freq = rxParamSetupReq->Frequency; + + // Verify radio frequency + if ((_radio->check_rf_frequency(freq) == false) + || (freq < AU915_FIRST_RX1_CHANNEL) + || (freq > AU915_LAST_RX1_CHANNEL) + || (((freq - (uint32_t) AU915_FIRST_RX1_CHANNEL) + % (uint32_t) AU915_STEPWIDTH_RX1_CHANNEL) != 0)) { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if (val_in_range(rxParamSetupReq->Datarate, AU915_RX_MIN_DATARATE, + AU915_RX_MAX_DATARATE) == 0) { + status &= 0xFD; // Datarate KO + } + if ((rxParamSetupReq->Datarate == DR_7) + || (rxParamSetupReq->Datarate > DR_13)) { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if (val_in_range(rxParamSetupReq->DrOffset, AU915_MIN_RX1_DR_OFFSET, + AU915_MAX_RX1_DR_OFFSET) == 0) { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYAU915::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + // Datarate and frequency KO + return 0; +} + +int8_t LoRaPHYAU915::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYAU915::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + return 0; +} + +int8_t LoRaPHYAU915::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + // Re-enable 500 kHz default channels + ChannelsMask[4] = 0x00FF; + + if ((alternateDr->NbTrials & 0x01) == 0x01) { + datarate = DR_6; + } else { + datarate = DR_0; + } + return datarate; +} + +void LoRaPHYAU915::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff(&calcBackOffParams); +} + +bool LoRaPHYAU915::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[AU915_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + // Count 125kHz channels + if (num_active_channels(ChannelsMaskRemaining, 0, 4) == 0) { // Reactivate default channels + copy_channel_mask(ChannelsMaskRemaining, ChannelsMask, 4); + } + // Check other channels + if (nextChanParams->Datarate >= DR_6) { + if ((ChannelsMaskRemaining[4] & 0x00FF) == 0) { + ChannelsMaskRemaining[4] = ChannelsMask[4]; + } + } + + if (nextChanParams->AggrTimeOff + <= TimerGetElapsedTime(nextChanParams->LastAggrTx)) { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff(nextChanParams->Joined, + nextChanParams->DutyCycleEnabled, + Bands, AU915_MAX_NB_BANDS); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels(nextChanParams->Datarate, + ChannelsMaskRemaining, + Channels, Bands, + enabledChannels, &delayTx); + } else { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff + - TimerGetElapsedTime(nextChanParams->LastAggrTx); + } + + if (nbEnabledChannels > 0) { + // We found a valid channel + *channel = enabledChannels[get_random(0, nbEnabledChannels - 1)]; + // Disable the channel in the mask + disable_channel(ChannelsMaskRemaining, *channel, + AU915_MAX_NB_CHANNELS - 8); + + *time = 0; + return true; + } else { + if (delayTx > 0) { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYAU915::add_channel(ChannelAddParams_t* channelAdd) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +bool LoRaPHYAU915::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +void LoRaPHYAU915::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( + continuousWave->TxPower, + Bands[Channels[continuousWave->Channel].Band].TxMaxPower, + continuousWave->Datarate, ChannelsMask); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power(txPowerLimited, continuousWave->MaxEirp, + continuousWave->AntennaGain); + + _radio->set_tx_continuous_wave(frequency, phyTxPower, + continuousWave->Timeout); +} + +uint8_t LoRaPHYAU915::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, + int8_t drOffset) +{ + int8_t datarate = DatarateOffsetsAU915[dr][drOffset]; + + if (datarate < 0) { + datarate = DR_0; + } + return datarate; +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYAU915.h b/features/lorawan/lorastack/phy/LoRaPHYAU915.h new file mode 100644 index 0000000000..ab0651db0a --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYAU915.h @@ -0,0 +1,373 @@ +/** + * @file LoRaPHYAU915.h + * + * @brief Implements LoRaPHY for Australian 915 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_AU915_H_ + +#define MBED_OS_LORAPHY_AU915_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +// Definitions +/*! + * LoRaMac maximum number of channels + */ +#define AU915_MAX_NB_CHANNELS 72 + +/*! + * LoRaMac maximum number of bands + */ +#define AU915_MAX_NB_BANDS 1 + +#define AU915_CHANNELS_MASK_SIZE 6 + + +class LoRaPHYAU915 : public LoRaPHY{ + +public: + + LoRaPHYAU915(); + virtual ~LoRaPHYAU915(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval A structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to be verified. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the + * CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the Rx window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams The updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR Request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty + * cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval The function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + uint8_t CountNbOfEnabledChannels(uint8_t datarate, + uint16_t* channelsMask, + ChannelParams_t* channels, + Band_t* bands, uint8_t* enabledChannels, + uint8_t* delayTx); + + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[AU915_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[AU915_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[AU915_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels remaining + */ + uint16_t ChannelsMaskRemaining[AU915_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[AU915_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_AU915_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYCN470.cpp b/features/lorawan/lorastack/phy/LoRaPHYCN470.cpp new file mode 100644 index 0000000000..61031fb923 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYCN470.cpp @@ -0,0 +1,979 @@ +/** + * @file LoRaPHYCN470.cpp + * + * @brief Implements LoRaPHY for Chinese 470 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYCN470.h" + +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + +/*! + * Minimal datarate that can be used by the node + */ +#define CN470_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define CN470_TX_MAX_DATARATE DR_5 + +/*! + * Minimal datarate that can be used by the node + */ +#define CN470_RX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define CN470_RX_MAX_DATARATE DR_5 + +/*! + * Default datarate used by the node + */ +#define CN470_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define CN470_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define CN470_MAX_RX1_DR_OFFSET 3 + +/*! + * Default Rx1 receive datarate offset + */ +#define CN470_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define CN470_MIN_TX_POWER TX_POWER_7 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define CN470_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define CN470_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max EIRP + */ +#define CN470_DEFAULT_MAX_EIRP 19.15f + +/*! + * Default antenna gain + */ +#define CN470_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define CN470_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define CN470_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define CN470_DUTY_CYCLE_ENABLED 0 + +/*! + * Maximum RX window duration + */ +#define CN470_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define CN470_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define CN470_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define CN470_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define CN470_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define CN470_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define CN470_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define CN470_ACK_TIMEOUT_RND 1000 + +/*! + * Second reception window channel frequency definition. + */ +#define CN470_RX_WND_2_FREQ 505300000 + +/*! + * Second reception window channel datarate definition. + */ +#define CN470_RX_WND_2_DR DR_0 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define CN470_BAND0 { 1, CN470_MAX_TX_POWER, 0, 0 } // 100.0 % + +/*! + * Defines the first channel for RX window 1 for CN470 band + */ +#define CN470_FIRST_RX1_CHANNEL ( (uint32_t) 500300000 ) + +/*! + * Defines the last channel for RX window 1 for CN470 band + */ +#define CN470_LAST_RX1_CHANNEL ( (uint32_t) 509700000 ) + +/*! + * Defines the step width of the channels for RX window 1 + */ +#define CN470_STEPWIDTH_RX1_CHANNEL ( (uint32_t) 200000 ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesCN470[] = { 12, 11, 10, 9, 8, 7 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsCN470[] = { 125000, 125000, 125000, 125000, 125000, 125000 }; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateCN470[] = { 51, 51, 51, 115, 222, 222 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterCN470[] = { 51, 51, 51, 115, 222, 222 }; + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsCN470[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + return txPowerResult; +} + +uint8_t LoRaPHYCN470::CountNbOfEnabledChannels( uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < CN470_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYCN470::LoRaPHYCN470() +{ + const Band_t band0 = CN470_BAND0; + Bands[0] = band0; +} + +LoRaPHYCN470::~LoRaPHYCN470() +{ +} + +PhyParam_t LoRaPHYCN470::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = CN470_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = CN470_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = CN470_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, CN470_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = CN470_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateCN470[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterCN470[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = CN470_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = CN470_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = CN470_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = CN470_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = CN470_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = CN470_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = CN470_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( CN470_ACKTIMEOUT + get_random( -CN470_ACK_TIMEOUT_RND, CN470_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = CN470_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = CN470_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = CN470_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = CN470_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + { + phyParam.fValue = CN470_DEFAULT_MAX_EIRP; + break; + } + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = CN470_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 48; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYCN470::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYCN470::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + // 125 kHz channels + for( uint8_t i = 0; i < CN470_MAX_NB_CHANNELS; i++ ) + { + Channels[i].Frequency = 470300000 + i * 200000; + Channels[i].DrRange.Value = ( DR_5 << 4 ) | DR_0; + Channels[i].Band = 0; + } + + // Initialize the channels default mask + ChannelsDefaultMask[0] = 0xFFFF; + ChannelsDefaultMask[1] = 0xFFFF; + ChannelsDefaultMask[2] = 0xFFFF; + ChannelsDefaultMask[3] = 0xFFFF; + ChannelsDefaultMask[4] = 0xFFFF; + ChannelsDefaultMask[5] = 0xFFFF; + + // Update the channels mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 6 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Restore channels default mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 6 ); + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYCN470::verify( VerifyParams_t* verify, PhyAttribute_t phyAttribute ) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, CN470_TX_MIN_DATARATE, CN470_TX_MAX_DATARATE ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, CN470_RX_MIN_DATARATE, CN470_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, CN470_MAX_TX_POWER, CN470_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return CN470_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 48 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYCN470::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + return; +} + +bool LoRaPHYCN470::set_channel_mask( ChanMaskSetParams_t* chanMaskSet ) +{ + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 6 ); + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 6 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYCN470::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == CN470_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= CN470_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = CN470_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( CN470_ADR_ACK_LIMIT + CN470_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % CN470_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + datarate = phyParam.Value; + + if( datarate == CN470_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] = 0xFFFF; + ChannelsMask[1] = 0xFFFF; + ChannelsMask[2] = 0xFFFF; + ChannelsMask[3] = 0xFFFF; + ChannelsMask[4] = 0xFFFF; + ChannelsMask[5] = 0xFFFF; + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYCN470::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, CN470_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + tSymbol = compute_symb_timeout_lora( DataratesCN470[rxConfigParams->Datarate], BandwidthsCN470[rxConfigParams->Datarate] ); + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYCN470::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if(_radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = CN470_FIRST_RX1_CHANNEL + ( rxConfig->Channel % 48 ) * CN470_STEPWIDTH_RX1_CHANNEL; + } + + // Read the physical datarate from the datarates table + phyDr = DataratesCN470[dr]; + + _radio->set_channel(frequency); + + // Radio configuration + _radio->set_rx_config(MODEM_LORA, rxConfig->Bandwidth, phyDr, 1, 0, 8, + rxConfig->WindowTimeout, false, 0, false, 0, 0, true, + rxConfig->RxContinuous); + + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterCN470[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateCN470[dr]; + } + _radio->set_max_payload_length(MODEM_LORA, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYCN470::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + int8_t phyDr = DataratesCN470[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, txConfig->MaxEirp, txConfig->AntennaGain ); + + // Setup the radio frequency + _radio->set_channel(Channels[txConfig->Channel].Frequency); + + _radio->set_tx_config(MODEM_LORA, phyTxPower, 0, 0, phyDr, 1, 8, false, true, + 0, 0, false, 3000); + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length(MODEM_LORA, txConfig->PktLen); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air(MODEM_LORA, txConfig->PktLen); + *txPower = txPowerLimited; + + return true; +} + +uint8_t LoRaPHYCN470::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t channelsMask[6] = { 0, 0, 0, 0, 0, 0 }; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + // Initialize local copy of channels mask + copy_channel_mask( channelsMask, ChannelsMask, 6 ); + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + // Get ADR request parameters + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + if( linkAdrParams.ChMaskCtrl == 6 ) + { + // Enable all 125 kHz channels + channelsMask[0] = 0xFFFF; + channelsMask[1] = 0xFFFF; + channelsMask[2] = 0xFFFF; + channelsMask[3] = 0xFFFF; + channelsMask[4] = 0xFFFF; + channelsMask[5] = 0xFFFF; + } + else if( linkAdrParams.ChMaskCtrl == 7 ) + { + status &= 0xFE; // Channel mask KO + } + else + { + for( uint8_t i = 0; i < 16; i++ ) + { + if( ( ( linkAdrParams.ChMask & ( 1 << i ) ) != 0 ) && + ( Channels[linkAdrParams.ChMaskCtrl * 16 + i].Frequency == 0 ) ) + {// Trying to enable an undefined channel + status &= 0xFE; // Channel mask KO + } + } + channelsMask[linkAdrParams.ChMaskCtrl] = linkAdrParams.ChMask; + } + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = CN470_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = channelsMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = CN470_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = CN470_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = CN470_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Copy Mask + copy_channel_mask( ChannelsMask, channelsMask, 6 ); + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYCN470::setup_rx_params( RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + uint32_t freq = rxParamSetupReq->Frequency; + + // Verify radio frequency + if( (_radio->check_rf_frequency(freq) == false ) || + ( freq < CN470_FIRST_RX1_CHANNEL ) || + ( freq > CN470_LAST_RX1_CHANNEL ) || + ( ( ( freq - ( uint32_t ) CN470_FIRST_RX1_CHANNEL ) % ( uint32_t ) CN470_STEPWIDTH_RX1_CHANNEL ) != 0 ) ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, CN470_RX_MIN_DATARATE, CN470_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, CN470_MIN_RX1_DR_OFFSET, CN470_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYCN470::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + // Datarate and frequency KO + return 0; +} + +int8_t LoRaPHYCN470::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYCN470::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + return 0; +} + +int8_t LoRaPHYCN470::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + if( ( alternateDr->NbTrials % 48 ) == 0 ) + { + datarate = DR_0; + } + else if( ( alternateDr->NbTrials % 32 ) == 0 ) + { + datarate = DR_1; + } + else if( ( alternateDr->NbTrials % 24 ) == 0 ) + { + datarate = DR_2; + } + else if( ( alternateDr->NbTrials % 16 ) == 0 ) + { + datarate = DR_3; + } + else if( ( alternateDr->NbTrials % 8 ) == 0 ) + { + datarate = DR_4; + } + else + { + datarate = DR_5; + } + return datarate; +} + +void LoRaPHYCN470::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYCN470::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[CN470_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + // Count 125kHz channels + if( num_active_channels( ChannelsMask, 0, 6 ) == 0 ) + { // Reactivate default channels + ChannelsMask[0] = 0xFFFF; + ChannelsMask[1] = 0xFFFF; + ChannelsMask[2] = 0xFFFF; + ChannelsMask[3] = 0xFFFF; + ChannelsMask[4] = 0xFFFF; + ChannelsMask[5] = 0xFFFF; + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, CN470_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Datarate, + ChannelsMask, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + // We found a valid channel + *channel = enabledChannels[get_random( 0, nbEnabledChannels - 1 )]; + + *time = 0; + return true; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYCN470::add_channel(ChannelAddParams_t* channelAdd) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +bool LoRaPHYCN470::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +void LoRaPHYCN470::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, continuousWave->MaxEirp, continuousWave->AntennaGain ); + + _radio->set_tx_continuous_wave(frequency, phyTxPower, continuousWave->Timeout); +} + +uint8_t LoRaPHYCN470::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, + int8_t drOffset) +{ + int8_t datarate = dr - drOffset; + + if( datarate < 0 ) + { + datarate = DR_0; + } + return datarate; +} + + + diff --git a/features/lorawan/lorastack/phy/LoRaPHYCN470.h b/features/lorawan/lorastack/phy/LoRaPHYCN470.h new file mode 100644 index 0000000000..4c5bcd09bb --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYCN470.h @@ -0,0 +1,365 @@ +/** + * @file LoRaPHYCN470.h + * + * @brief Implements LoRaPHY for Chinese 470 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_CN470_H_ +#define MBED_OS_LORAPHY_CN470_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +// Definitions +/*! + * LoRaMac maximum number of channels + */ +#define CN470_MAX_NB_CHANNELS 96 + +/*! + * LoRaMac maximum number of bands + */ +#define CN470_MAX_NB_BANDS 1 + + +#define CN470_CHANNELS_MASK_SIZE 6 + + +class LoRaPHYCN470 : public LoRaPHY { + +public: + + LoRaPHYCN470(); + virtual ~LoRaPHYCN470(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval A structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to be verified. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the + * CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams The updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty + * cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval Function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + uint8_t CountNbOfEnabledChannels( uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + bool RegionCN470ChanMaskSet( ChanMaskSetParams_t* chanMaskSet ); + + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[CN470_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[CN470_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[CN470_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[CN470_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_CN470_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYCN779.cpp b/features/lorawan/lorastack/phy/LoRaPHYCN779.cpp new file mode 100644 index 0000000000..9e527cc147 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYCN779.cpp @@ -0,0 +1,1239 @@ +/** + * @file LoRaPHYCN779.cpp + * + * @brief Implements LoRaPHY for Chinese 779 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYCN779.h" + +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + + +/*! + * Number of default channels + */ +#define CN779_NUMB_DEFAULT_CHANNELS 3 + +/*! + * Number of channels to apply for the CF list + */ +#define CN779_NUMB_CHANNELS_CF_LIST 5 + +/*! + * Minimal datarate that can be used by the node + */ +#define CN779_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define CN779_TX_MAX_DATARATE DR_7 + +/*! + * Minimal datarate that can be used by the node + */ +#define CN779_RX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define CN779_RX_MAX_DATARATE DR_7 + +/*! + * Default datarate used by the node + */ +#define CN779_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define CN779_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define CN779_MAX_RX1_DR_OFFSET 5 + +/*! + * Default Rx1 receive datarate offset + */ +#define CN779_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define CN779_MIN_TX_POWER TX_POWER_5 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define CN779_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define CN779_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max EIRP + */ +#define CN779_DEFAULT_MAX_EIRP 12.15f + +/*! + * Default antenna gain + */ +#define CN779_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define CN779_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define CN779_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define CN779_DUTY_CYCLE_ENABLED 1 + +/*! + * Maximum RX window duration + */ +#define CN779_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define CN779_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define CN779_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define CN779_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define CN779_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define CN779_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define CN779_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define CN779_ACK_TIMEOUT_RND 1000 + +/*! + * Verification of default datarate + */ +#if ( CN779_DEFAULT_DATARATE > DR_5 ) +#error "A default DR higher than DR_5 may lead to connectivity loss." +#endif + +/*! + * Second reception window channel frequency definition. + */ +#define CN779_RX_WND_2_FREQ 786000000 + +/*! + * Second reception window channel datarate definition. + */ +#define CN779_RX_WND_2_DR DR_0 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define CN779_BAND0 { 100, CN779_MAX_TX_POWER, 0, 0 } // 1.0 % + +/*! + * LoRaMac default channel 1 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define CN779_LC1 { 779500000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } +/*! + * LoRaMac default channel 2 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define CN779_LC2 { 779700000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 3 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define CN779_LC3 { 779900000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac channels which are allowed for the join procedure + */ +#define CN779_JOIN_CHANNELS ( uint16_t )( LC( 1 ) | LC( 2 ) | LC( 3 ) ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesCN779[] = { 12, 11, 10, 9, 8, 7, 7, 50 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsCN779[] = { 125000, 125000, 125000, 125000, 125000, 125000, 250000, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateCN779[] = { 51, 51, 51, 115, 242, 242, 242, 242 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterCN779[] = { 51, 51, 51, 115, 222, 222, 222, 222 }; + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsCN779[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + return txPowerResult; +} + +static bool VerifyTxFreq( uint32_t freq, LoRaRadio *radio) +{ + // Check radio driver support + if(radio->check_rf_frequency(freq) == false) + { + return false; + } + + if( ( freq < 779500000 ) || ( freq > 786500000 ) ) + { + return false; + } + return true; +} + +uint8_t LoRaPHYCN779::CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < CN779_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( joined == false ) + { + if( ( CN779_JOIN_CHANNELS & ( 1 << j ) ) == 0 ) + { + continue; + } + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYCN779::LoRaPHYCN779() +{ + const Band_t band0 = CN779_BAND0; + Bands[0] = band0; +} + +LoRaPHYCN779::~LoRaPHYCN779() +{ +} + +PhyParam_t LoRaPHYCN779::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = CN779_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = CN779_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = CN779_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, CN779_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = CN779_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateCN779[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterCN779[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = CN779_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = CN779_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = CN779_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = CN779_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = CN779_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = CN779_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = CN779_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = (CN779_ACKTIMEOUT + get_random(-CN779_ACK_TIMEOUT_RND, CN779_ACK_TIMEOUT_RND)); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = CN779_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = CN779_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = CN779_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = CN779_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + { + phyParam.fValue = CN779_DEFAULT_MAX_EIRP; + break; + } + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = CN779_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 48; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYCN779::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYCN779::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + const ChannelParams_t channel1 = CN779_LC1; + const ChannelParams_t channel2 = CN779_LC2; + const ChannelParams_t channel3 = CN779_LC3; + Channels[0] = channel1; + Channels[1] = channel2; + Channels[2] = channel3; + + // Initialize the channels default mask + ChannelsDefaultMask[0] = LC( 1 ) + LC( 2 ) + LC( 3 ); + // Update the channels mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 1 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Restore channels default mask + ChannelsMask[0] |= ChannelsDefaultMask[0]; + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYCN779::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, CN779_TX_MIN_DATARATE, CN779_TX_MAX_DATARATE ); + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, CN779_RX_MIN_DATARATE, CN779_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, CN779_MAX_TX_POWER, CN779_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return CN779_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 48 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYCN779::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + ChannelParams_t newChannel; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + // Setup default datarate range + newChannel.DrRange.Value = ( DR_5 << 4 ) | DR_0; + + // Size of the optional CF list + if( applyCFList->Size != 16 ) + { + return; + } + + // Last byte is RFU, don't take it into account + for( uint8_t i = 0, chanIdx = CN779_NUMB_DEFAULT_CHANNELS; chanIdx < CN779_MAX_NB_CHANNELS; i+=3, chanIdx++ ) + { + if( chanIdx < ( CN779_NUMB_CHANNELS_CF_LIST + CN779_NUMB_DEFAULT_CHANNELS ) ) + { + // Channel frequency + newChannel.Frequency = (uint32_t) applyCFList->Payload[i]; + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 1] << 8 ); + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 2] << 16 ); + newChannel.Frequency *= 100; + + // Initialize alternative frequency to 0 + newChannel.Rx1Frequency = 0; + } + else + { + newChannel.Frequency = 0; + newChannel.DrRange.Value = 0; + newChannel.Rx1Frequency = 0; + } + + if( newChannel.Frequency != 0 ) + { + channelAdd.NewChannel = &newChannel; + channelAdd.ChannelId = chanIdx; + + // Try to add all channels + add_channel(&channelAdd); + } + else + { + channelRemove.ChannelId = chanIdx; + + remove_channel(&channelRemove); + } + } +} + +bool LoRaPHYCN779::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYCN779::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == CN779_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= CN779_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = CN779_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( CN779_ADR_ACK_LIMIT + CN779_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % CN779_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + datarate = phyParam.Value; + + if( datarate == CN779_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYCN779::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, CN779_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + if( rxConfigParams->Datarate == DR_7 ) + { // FSK + tSymbol = compute_symb_timeout_fsk( DataratesCN779[rxConfigParams->Datarate] ); + } + else + { // LoRa + tSymbol = compute_symb_timeout_lora( DataratesCN779[rxConfigParams->Datarate], BandwidthsCN779[rxConfigParams->Datarate] ); + } + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYCN779::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + radio_modems_t modem; + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if(_radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = Channels[rxConfig->Channel].Frequency; + // Apply the alternative RX 1 window frequency, if it is available + if( Channels[rxConfig->Channel].Rx1Frequency != 0 ) + { + frequency = Channels[rxConfig->Channel].Rx1Frequency; + } + } + + // Read the physical datarate from the datarates table + phyDr = DataratesCN779[dr]; + + _radio->set_channel(frequency); + + // Radio configuration + if( dr == DR_7 ) + { + modem = MODEM_FSK; + _radio->set_rx_config(modem, 50000, phyDr * 1000, 0, 83333, 5, rxConfig->WindowTimeout, false, 0, true, 0, 0, false, rxConfig->RxContinuous); + } + else + { + modem = MODEM_LORA; + _radio->set_rx_config(modem, rxConfig->Bandwidth, phyDr, 1, 0, 8, rxConfig->WindowTimeout, false, 0, false, 0, 0, true, rxConfig->RxContinuous); + } + + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterCN779[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateCN779[dr]; + } + _radio->set_max_payload_length(modem, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYCN779::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + radio_modems_t modem; + int8_t phyDr = DataratesCN779[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, txConfig->MaxEirp, txConfig->AntennaGain ); + + // Setup the radio frequency + _radio->set_channel(Channels[txConfig->Channel].Frequency); + + if( txConfig->Datarate == DR_7 ) + { // High Speed FSK channel + modem = MODEM_FSK; + _radio->set_tx_config(modem, phyTxPower, 25000, bandwidth, phyDr * 1000, 0, 5, false, true, 0, 0, false, 3000); + } + else + { + modem = MODEM_LORA; + _radio->set_tx_config(modem, phyTxPower, 0, bandwidth, phyDr, 1, 8, false, true, 0, 0, false, 3000); + } + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length(modem, txConfig->PktLen); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air(modem, txConfig->PktLen); + + *txPower = txPowerLimited; + return true; +} + +uint8_t LoRaPHYCN779::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t chMask = 0; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + // Get ADR request parameters + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + // Setup temporary channels mask + chMask = linkAdrParams.ChMask; + + // Verify channels mask + if( ( linkAdrParams.ChMaskCtrl == 0 ) && ( chMask == 0 ) ) + { + status &= 0xFE; // Channel mask KO + } + else if( ( ( linkAdrParams.ChMaskCtrl >= 1 ) && ( linkAdrParams.ChMaskCtrl <= 5 )) || + ( linkAdrParams.ChMaskCtrl >= 7 ) ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + for( uint8_t i = 0; i < CN779_MAX_NB_CHANNELS; i++ ) + { + if( linkAdrParams.ChMaskCtrl == 6 ) + { + if( Channels[i].Frequency != 0 ) + { + chMask |= 1 << i; + } + } + else + { + if( ( ( chMask & ( 1 << i ) ) != 0 ) && + ( Channels[i].Frequency == 0 ) ) + {// Trying to enable an undefined channel + status &= 0xFE; // Channel mask KO + } + } + } + } + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params(&getPhy); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = CN779_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = &chMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = CN779_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = CN779_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = CN779_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Set the channels mask to a default value + memset( ChannelsMask, 0, sizeof( ChannelsMask ) ); + // Update the channels mask + ChannelsMask[0] = chMask; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYCN779::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + + // Verify radio frequency + if(_radio->check_rf_frequency(rxParamSetupReq->Frequency) == false ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, CN779_RX_MIN_DATARATE, CN779_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, CN779_MIN_RX1_DR_OFFSET, CN779_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYCN779::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + uint8_t status = 0x03; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + if( newChannelReq->NewChannel->Frequency == 0 ) + { + channelRemove.ChannelId = newChannelReq->ChannelId; + + // Remove + if(remove_channel(&channelRemove) == false ) + { + status &= 0xFC; + } + } + else + { + channelAdd.NewChannel = newChannelReq->NewChannel; + channelAdd.ChannelId = newChannelReq->ChannelId; + + switch (add_channel(&channelAdd)) + { + case LORAMAC_STATUS_OK: + { + break; + } + case LORAMAC_STATUS_FREQUENCY_INVALID: + { + status &= 0xFE; + break; + } + case LORAMAC_STATUS_DATARATE_INVALID: + { + status &= 0xFD; + break; + } + case LORAMAC_STATUS_FREQ_AND_DR_INVALID: + { + status &= 0xFC; + break; + } + default: + { + status &= 0xFC; + break; + } + } + } + + return status; +} + +int8_t LoRaPHYCN779::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYCN779::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + uint8_t status = 0x03; + + // Verify if the frequency is supported + if( VerifyTxFreq(dlChannelReq->Rx1Frequency, _radio) == false ) + { + status &= 0xFE; + } + + // Verify if an uplink frequency exists + if( Channels[dlChannelReq->ChannelId].Frequency == 0 ) + { + status &= 0xFD; + } + + // Apply Rx1 frequency, if the status is OK + if( status == 0x03 ) + { + Channels[dlChannelReq->ChannelId].Rx1Frequency = dlChannelReq->Rx1Frequency; + } + + return status; +} + +int8_t LoRaPHYCN779::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + if( ( alternateDr->NbTrials % 48 ) == 0 ) + { + datarate = DR_0; + } + else if( ( alternateDr->NbTrials % 32 ) == 0 ) + { + datarate = DR_1; + } + else if( ( alternateDr->NbTrials % 24 ) == 0 ) + { + datarate = DR_2; + } + else if( ( alternateDr->NbTrials % 16 ) == 0 ) + { + datarate = DR_3; + } + else if( ( alternateDr->NbTrials % 8 ) == 0 ) + { + datarate = DR_4; + } + else + { + datarate = DR_5; + } + return datarate; +} + +void LoRaPHYCN779::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYCN779::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[CN779_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + if( num_active_channels( ChannelsMask, 0, 1 ) == 0 ) + { // Reactivate default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, CN779_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Joined, nextChanParams->Datarate, + ChannelsMask, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + // We found a valid channel + *channel = enabledChannels[get_random( 0, nbEnabledChannels - 1 )]; + + *time = 0; + return true; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel, restore defaults + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYCN779::add_channel(ChannelAddParams_t* channelAdd) +{ + uint8_t band = 0; + bool drInvalid = false; + bool freqInvalid = false; + uint8_t id = channelAdd->ChannelId; + + if( id >= CN779_MAX_NB_CHANNELS ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Validate the datarate range + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Min, CN779_TX_MIN_DATARATE, CN779_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, CN779_TX_MIN_DATARATE, CN779_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( channelAdd->NewChannel->DrRange.Fields.Min > channelAdd->NewChannel->DrRange.Fields.Max ) + { + drInvalid = true; + } + + // Default channels don't accept all values + if( id < CN779_NUMB_DEFAULT_CHANNELS ) + { + // Validate the datarate range for min: must be DR_0 + if( channelAdd->NewChannel->DrRange.Fields.Min > DR_0 ) + { + drInvalid = true; + } + // Validate the datarate range for max: must be DR_5 <= Max <= TX_MAX_DATARATE + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, DR_5, CN779_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + // We are not allowed to change the frequency + if( channelAdd->NewChannel->Frequency != Channels[id].Frequency ) + { + freqInvalid = true; + } + } + + // Check frequency + if( freqInvalid == false ) + { + if( VerifyTxFreq(channelAdd->NewChannel->Frequency, _radio) == false ) + { + freqInvalid = true; + } + } + + // Check status + if( ( drInvalid == true ) && ( freqInvalid == true ) ) + { + return LORAMAC_STATUS_FREQ_AND_DR_INVALID; + } + if( drInvalid == true ) + { + return LORAMAC_STATUS_DATARATE_INVALID; + } + if( freqInvalid == true ) + { + return LORAMAC_STATUS_FREQUENCY_INVALID; + } + + memcpy( &(Channels[id]), channelAdd->NewChannel, sizeof( Channels[id] ) ); + Channels[id].Band = band; + ChannelsMask[0] |= ( 1 << id ); + return LORAMAC_STATUS_OK; +} + +bool LoRaPHYCN779::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + uint8_t id = channelRemove->ChannelId; + + if( id < CN779_NUMB_DEFAULT_CHANNELS ) + { + return false; + } + + // Remove the channel from the list of channels + const ChannelParams_t empty_channel = { 0, 0, { 0 }, 0 }; + Channels[id] = empty_channel; + + return disable_channel( ChannelsMask, id, CN779_MAX_NB_CHANNELS ); +} + +void LoRaPHYCN779::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, continuousWave->MaxEirp, continuousWave->AntennaGain ); + + _radio->set_tx_continuous_wave(frequency, phyTxPower, continuousWave->Timeout); +} + +uint8_t LoRaPHYCN779::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset) +{ + int8_t datarate = dr - drOffset; + + if( datarate < 0 ) + { + datarate = DR_0; + } + return datarate; +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYCN779.h b/features/lorawan/lorastack/phy/LoRaPHYCN779.h new file mode 100644 index 0000000000..8be486db5f --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYCN779.h @@ -0,0 +1,354 @@ +/** + * @file LoRaPHYCN779.h + * + * @brief Implements LoRaPHY for Chinese 779 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_CN779_H_ +#define MBED_OS_LORAPHY_CN779_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +#define CN779_MAX_NB_CHANNELS 16 + +#define CN779_MAX_NB_BANDS 1 + +#define CN779_CHANNELS_MASK_SIZE 1 + + +class LoRaPHYCN779 : public LoRaPHY { + +public: + + LoRaPHYCN779(); + virtual ~LoRaPHYCN779(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval The structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to verify. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum required number of symbols to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty + * cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval Function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + uint8_t CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[CN779_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[CN779_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[CN779_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[CN779_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_CN779_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYEU433.cpp b/features/lorawan/lorastack/phy/LoRaPHYEU433.cpp new file mode 100644 index 0000000000..d68d0d5bdb --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYEU433.cpp @@ -0,0 +1,1244 @@ +/** + * @file LoRaPHYEU433.cpp + * + * @brief Implements LoRaPHY for European 433 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYEU433.h" + +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + +/*! + * Number of default channels + */ +#define EU433_NUMB_DEFAULT_CHANNELS 3 + +/*! + * Number of channels to apply for the CF list + */ +#define EU433_NUMB_CHANNELS_CF_LIST 5 + +/*! + * Minimal datarate that can be used by the node + */ +#define EU433_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define EU433_TX_MAX_DATARATE DR_7 + +/*! + * Minimal datarate that can be used by the node + */ +#define EU433_RX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define EU433_RX_MAX_DATARATE DR_7 + +/*! + * Default datarate used by the node + */ +#define EU433_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define EU433_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define EU433_MAX_RX1_DR_OFFSET 5 + +/*! + * Default Rx1 receive datarate offset + */ +#define EU433_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define EU433_MIN_TX_POWER TX_POWER_5 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define EU433_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define EU433_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max EIRP + */ +#define EU433_DEFAULT_MAX_EIRP 12.15f + +/*! + * Default antenna gain + */ +#define EU433_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define EU433_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define EU433_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define EU433_DUTY_CYCLE_ENABLED 1 + +/*! + * Maximum RX window duration + */ +#define EU433_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define EU433_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define EU433_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define EU433_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define EU433_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define EU433_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define EU433_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define EU433_ACK_TIMEOUT_RND 1000 + +/*! + * Verification of default datarate + */ +#if ( EU433_DEFAULT_DATARATE > DR_5 ) +#error "A default DR higher than DR_5 may lead to connectivity loss." +#endif + +/*! + * Second reception window channel frequency definition. + */ +#define EU433_RX_WND_2_FREQ 434665000 + +/*! + * Second reception window channel datarate definition. + */ +#define EU433_RX_WND_2_DR DR_0 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define EU433_BAND0 { 100, EU433_MAX_TX_POWER, 0, 0 } // 1.0 % + +/*! + * LoRaMac default channel 1 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define EU433_LC1 { 433175000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 2 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define EU433_LC2 { 433375000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 3 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define EU433_LC3 { 433575000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac channels which are allowed for the join procedure + */ +#define EU433_JOIN_CHANNELS ( uint16_t )( LC( 1 ) | LC( 2 ) | LC( 3 ) ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesEU433[] = { 12, 11, 10, 9, 8, 7, 7, 50 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsEU433[] = { 125000, 125000, 125000, 125000, 125000, 125000, 250000, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateEU433[] = { 51, 51, 51, 115, 242, 242, 242, 242 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterEU433[] = { 51, 51, 51, 115, 222, 222, 222, 222 }; + + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsEU433[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + return txPowerResult; +} + +static bool VerifyTxFreq( uint32_t freq, LoRaRadio *radio ) +{ + // Check radio driver support + if(radio->check_rf_frequency(freq) == false ) + { + return false; + } + + if( ( freq < 433175000 ) || ( freq > 434665000 ) ) + { + return false; + } + return true; +} + +uint8_t LoRaPHYEU433::CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < EU433_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( joined == false ) + { + if( ( EU433_JOIN_CHANNELS & ( 1 << j ) ) == 0 ) + { + continue; + } + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYEU433::LoRaPHYEU433() +{ + const Band_t band0 = EU433_BAND0; + Bands[0] = band0; +} + +LoRaPHYEU433::~LoRaPHYEU433() +{ +} + +PhyParam_t LoRaPHYEU433::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = EU433_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = EU433_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = EU433_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, EU433_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = EU433_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateEU433[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterEU433[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = EU433_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = EU433_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = EU433_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = EU433_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = EU433_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = EU433_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = EU433_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( EU433_ACKTIMEOUT + get_random( -EU433_ACK_TIMEOUT_RND, EU433_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = EU433_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = EU433_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = EU433_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = EU433_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + { + phyParam.fValue = EU433_DEFAULT_MAX_EIRP; + break; + } + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = EU433_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 48; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYEU433::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYEU433::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + const ChannelParams_t channel1 = EU433_LC1; + const ChannelParams_t channel2 = EU433_LC2; + const ChannelParams_t channel3 = EU433_LC3; + Channels[0] = channel1; + Channels[1] = channel2; + Channels[2] = channel3; + + // Initialize the channels default mask + ChannelsDefaultMask[0] = LC( 1 ) + LC( 2 ) + LC( 3 ); + // Update the channels mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 1 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Restore channels default mask + ChannelsMask[0] |= ChannelsDefaultMask[0]; + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYEU433::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, EU433_TX_MIN_DATARATE, EU433_TX_MAX_DATARATE ); + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, EU433_RX_MIN_DATARATE, EU433_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, EU433_MAX_TX_POWER, EU433_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return EU433_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 48 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYEU433::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + ChannelParams_t newChannel; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + // Setup default datarate range + newChannel.DrRange.Value = ( DR_5 << 4 ) | DR_0; + + // Size of the optional CF list + if( applyCFList->Size != 16 ) + { + return; + } + + // Last byte is RFU, don't take it into account + for( uint8_t i = 0, chanIdx = EU433_NUMB_DEFAULT_CHANNELS; chanIdx < EU433_MAX_NB_CHANNELS; i+=3, chanIdx++ ) + { + if( chanIdx < ( EU433_NUMB_CHANNELS_CF_LIST + EU433_NUMB_DEFAULT_CHANNELS ) ) + { + // Channel frequency + newChannel.Frequency = (uint32_t) applyCFList->Payload[i]; + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 1] << 8 ); + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 2] << 16 ); + newChannel.Frequency *= 100; + + // Initialize alternative frequency to 0 + newChannel.Rx1Frequency = 0; + } + else + { + newChannel.Frequency = 0; + newChannel.DrRange.Value = 0; + newChannel.Rx1Frequency = 0; + } + + if( newChannel.Frequency != 0 ) + { + channelAdd.NewChannel = &newChannel; + channelAdd.ChannelId = chanIdx; + + // Try to add all channels + add_channel( &channelAdd ); + } + else + { + channelRemove.ChannelId = chanIdx; + + remove_channel( &channelRemove ); + } + } +} + +bool LoRaPHYEU433::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYEU433::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == EU433_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= EU433_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = EU433_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( EU433_ADR_ACK_LIMIT + EU433_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % EU433_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + datarate = phyParam.Value; + + if( datarate == EU433_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYEU433::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, EU433_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + if( rxConfigParams->Datarate == DR_7 ) + { // FSK + tSymbol = compute_symb_timeout_fsk( DataratesEU433[rxConfigParams->Datarate] ); + } + else + { // LoRa + tSymbol = compute_symb_timeout_lora( DataratesEU433[rxConfigParams->Datarate], BandwidthsEU433[rxConfigParams->Datarate] ); + } + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYEU433::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + radio_modems_t modem; + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if( _radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = Channels[rxConfig->Channel].Frequency; + // Apply the alternative RX 1 window frequency, if it is available + if( Channels[rxConfig->Channel].Rx1Frequency != 0 ) + { + frequency = Channels[rxConfig->Channel].Rx1Frequency; + } + } + + // Read the physical datarate from the datarates table + phyDr = DataratesEU433[dr]; + + _radio->set_channel( frequency ); + + // Radio configuration + if( dr == DR_7 ) + { + modem = MODEM_FSK; + _radio->set_rx_config( modem, 50000, phyDr * 1000, 0, 83333, 5, + rxConfig->WindowTimeout, false, 0, true, 0, 0, + false, rxConfig->RxContinuous ); + } + else + { + modem = MODEM_LORA; + _radio->set_rx_config( modem, rxConfig->Bandwidth, phyDr, 1, 0, 8, + rxConfig->WindowTimeout, false, 0, false, 0, 0, + true, rxConfig->RxContinuous ); + } + + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterEU433[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateEU433[dr]; + } + _radio->set_max_payload_length( modem, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD ); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYEU433::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + radio_modems_t modem; + int8_t phyDr = DataratesEU433[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, txConfig->MaxEirp, txConfig->AntennaGain ); + + // Setup the radio frequency + _radio->set_channel( Channels[txConfig->Channel].Frequency ); + + if( txConfig->Datarate == DR_7 ) + { // High Speed FSK channel + modem = MODEM_FSK; + _radio->set_tx_config( modem, phyTxPower, 25000, bandwidth, phyDr * 1000, 0, 5, false, true, 0, 0, false, 3000 ); + } + else + { + modem = MODEM_LORA; + _radio->set_tx_config( modem, phyTxPower, 0, bandwidth, phyDr, 1, 8, false, true, 0, 0, false, 3000 ); + } + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length( modem, txConfig->PktLen ); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air( modem, txConfig->PktLen ); + + *txPower = txPowerLimited; + return true; +} + +uint8_t LoRaPHYEU433::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t chMask = 0; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + // Get ADR request parameters + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + // Setup temporary channels mask + chMask = linkAdrParams.ChMask; + + // Verify channels mask + if( ( linkAdrParams.ChMaskCtrl == 0 ) && ( chMask == 0 ) ) + { + status &= 0xFE; // Channel mask KO + } + else if( ( ( linkAdrParams.ChMaskCtrl >= 1 ) && ( linkAdrParams.ChMaskCtrl <= 5 )) || + ( linkAdrParams.ChMaskCtrl >= 7 ) ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + for( uint8_t i = 0; i < EU433_MAX_NB_CHANNELS; i++ ) + { + if( linkAdrParams.ChMaskCtrl == 6 ) + { + if( Channels[i].Frequency != 0 ) + { + chMask |= 1 << i; + } + } + else + { + if( ( ( chMask & ( 1 << i ) ) != 0 ) && + ( Channels[i].Frequency == 0 ) ) + {// Trying to enable an undefined channel + status &= 0xFE; // Channel mask KO + } + } + } + } + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = EU433_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = &chMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = EU433_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = EU433_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = EU433_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Set the channels mask to a default value + memset( ChannelsMask, 0, sizeof( ChannelsMask ) ); + // Update the channels mask + ChannelsMask[0] = chMask; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYEU433::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + + // Verify radio frequency + if( _radio->check_rf_frequency( rxParamSetupReq->Frequency ) == false ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, EU433_RX_MIN_DATARATE, EU433_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, EU433_MIN_RX1_DR_OFFSET, EU433_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYEU433::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + uint8_t status = 0x03; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + if( newChannelReq->NewChannel->Frequency == 0 ) + { + channelRemove.ChannelId = newChannelReq->ChannelId; + + // Remove + if( remove_channel( &channelRemove ) == false ) + { + status &= 0xFC; + } + } + else + { + channelAdd.NewChannel = newChannelReq->NewChannel; + channelAdd.ChannelId = newChannelReq->ChannelId; + + switch( add_channel( &channelAdd ) ) + { + case LORAMAC_STATUS_OK: + { + break; + } + case LORAMAC_STATUS_FREQUENCY_INVALID: + { + status &= 0xFE; + break; + } + case LORAMAC_STATUS_DATARATE_INVALID: + { + status &= 0xFD; + break; + } + case LORAMAC_STATUS_FREQ_AND_DR_INVALID: + { + status &= 0xFC; + break; + } + default: + { + status &= 0xFC; + break; + } + } + } + + return status; +} + +int8_t LoRaPHYEU433::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYEU433::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + uint8_t status = 0x03; + + // Verify if the frequency is supported + if( VerifyTxFreq( dlChannelReq->Rx1Frequency, _radio ) == false ) + { + status &= 0xFE; + } + + // Verify if an uplink frequency exists + if( Channels[dlChannelReq->ChannelId].Frequency == 0 ) + { + status &= 0xFD; + } + + // Apply Rx1 frequency, if the status is OK + if( status == 0x03 ) + { + Channels[dlChannelReq->ChannelId].Rx1Frequency = dlChannelReq->Rx1Frequency; + } + + return status; +} + +int8_t LoRaPHYEU433::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + if( ( alternateDr->NbTrials % 48 ) == 0 ) + { + datarate = DR_0; + } + else if( ( alternateDr->NbTrials % 32 ) == 0 ) + { + datarate = DR_1; + } + else if( ( alternateDr->NbTrials % 24 ) == 0 ) + { + datarate = DR_2; + } + else if( ( alternateDr->NbTrials % 16 ) == 0 ) + { + datarate = DR_3; + } + else if( ( alternateDr->NbTrials % 8 ) == 0 ) + { + datarate = DR_4; + } + else + { + datarate = DR_5; + } + return datarate; +} + +void LoRaPHYEU433::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYEU433::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[EU433_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + if( num_active_channels( ChannelsMask, 0, 1 ) == 0 ) + { // Reactivate default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, EU433_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Joined, nextChanParams->Datarate, + ChannelsMask, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + // We found a valid channel + *channel = enabledChannels[get_random( 0, nbEnabledChannels - 1 )]; + + *time = 0; + return true; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel, restore defaults + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYEU433::add_channel(ChannelAddParams_t* channelAdd) +{ + uint8_t band = 0; + bool drInvalid = false; + bool freqInvalid = false; + uint8_t id = channelAdd->ChannelId; + + if( id >= EU433_MAX_NB_CHANNELS ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Validate the datarate range + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Min, EU433_TX_MIN_DATARATE, EU433_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, EU433_TX_MIN_DATARATE, EU433_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( channelAdd->NewChannel->DrRange.Fields.Min > channelAdd->NewChannel->DrRange.Fields.Max ) + { + drInvalid = true; + } + + // Default channels don't accept all values + if( id < EU433_NUMB_DEFAULT_CHANNELS ) + { + // Validate the datarate range for min: must be DR_0 + if( channelAdd->NewChannel->DrRange.Fields.Min > DR_0 ) + { + drInvalid = true; + } + // Validate the datarate range for max: must be DR_5 <= Max <= TX_MAX_DATARATE + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, DR_5, EU433_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + // We are not allowed to change the frequency + if( channelAdd->NewChannel->Frequency != Channels[id].Frequency ) + { + freqInvalid = true; + } + } + + // Check frequency + if( freqInvalid == false ) + { + if( VerifyTxFreq( channelAdd->NewChannel->Frequency, _radio ) == false ) + { + freqInvalid = true; + } + } + + // Check status + if( ( drInvalid == true ) && ( freqInvalid == true ) ) + { + return LORAMAC_STATUS_FREQ_AND_DR_INVALID; + } + if( drInvalid == true ) + { + return LORAMAC_STATUS_DATARATE_INVALID; + } + if( freqInvalid == true ) + { + return LORAMAC_STATUS_FREQUENCY_INVALID; + } + + memcpy( &(Channels[id]), channelAdd->NewChannel, sizeof( Channels[id] ) ); + Channels[id].Band = band; + ChannelsMask[0] |= ( 1 << id ); + return LORAMAC_STATUS_OK; +} + +bool LoRaPHYEU433::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + uint8_t id = channelRemove->ChannelId; + + if( id < EU433_NUMB_DEFAULT_CHANNELS ) + { + return false; + } + + // Remove the channel from the list of channels + const ChannelParams_t empty_channel = { 0, 0, { 0 }, 0 }; + Channels[id] = empty_channel; + + return disable_channel( ChannelsMask, id, EU433_MAX_NB_CHANNELS ); +} + +void LoRaPHYEU433::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, continuousWave->MaxEirp, continuousWave->AntennaGain ); + + _radio->set_tx_continuous_wave( frequency, phyTxPower, continuousWave->Timeout ); +} + +uint8_t LoRaPHYEU433::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset) +{ + int8_t datarate = dr - drOffset; + + if( datarate < 0 ) + { + datarate = DR_0; + } + return datarate; +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYEU433.h b/features/lorawan/lorastack/phy/LoRaPHYEU433.h new file mode 100644 index 0000000000..8090b42c4d --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYEU433.h @@ -0,0 +1,361 @@ +/** + * @file LoRaPHYEU433.h + * + * @brief Implements LoRaPHY for European 433 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_EU433_H_ +#define MBED_OS_LORAPHY_EU433_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +/*! + * LoRaMac maximum number of channels + */ +#define EU433_MAX_NB_CHANNELS 16 + +/*! + * LoRaMac maximum number of bands + */ +#define EU433_MAX_NB_BANDS 1 + +#define EU433_CHANNELS_MASK_SIZE 1 + + +class LoRaPHYEU433 : public LoRaPHY { + +public: + + LoRaPHYEU433(); + virtual ~LoRaPHYEU433(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval The structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to verify. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty + * cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval Function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + uint8_t CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[EU433_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[EU433_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[EU433_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[EU433_CHANNELS_MASK_SIZE]; +}; + + +#endif /* MBED_OS_LORAPHY_EU433_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYEU868.cpp b/features/lorawan/lorastack/phy/LoRaPHYEU868.cpp new file mode 100644 index 0000000000..d89054bb6a --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYEU868.cpp @@ -0,0 +1,1296 @@ +/** + * @file LoRaPHYEU868.cpp + * + * @brief Implements LoRaPHY for European 868 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYEU868.h" +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + +/*! + * Number of default channels + */ +#define EU868_NUMB_DEFAULT_CHANNELS 3 + +/*! + * Number of channels to apply for the CF list + */ +#define EU868_NUMB_CHANNELS_CF_LIST 5 + +/*! + * Minimal datarate that can be used by the node + */ +#define EU868_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define EU868_TX_MAX_DATARATE DR_7 + +/*! + * Minimal datarate that can be used by the node + */ +#define EU868_RX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define EU868_RX_MAX_DATARATE DR_7 + +/*! + * Default datarate used by the node + */ +#define EU868_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define EU868_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define EU868_MAX_RX1_DR_OFFSET 5 + +/*! + * Default Rx1 receive datarate offset + */ +#define EU868_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define EU868_MIN_TX_POWER TX_POWER_7 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define EU868_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define EU868_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max EIRP + */ +#define EU868_DEFAULT_MAX_EIRP 16.0f + +/*! + * Default antenna gain + */ +#define EU868_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define EU868_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define EU868_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define EU868_DUTY_CYCLE_ENABLED 1 + +/*! + * Maximum RX window duration + */ +#define EU868_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define EU868_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define EU868_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define EU868_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define EU868_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define EU868_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define EU868_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define EU868_ACK_TIMEOUT_RND 1000 + +#if ( EU868_DEFAULT_DATARATE > DR_5 ) +#error "A default DR higher than DR_5 may lead to connectivity loss." +#endif + +/*! + * Second reception window channel frequency definition. + */ +#define EU868_RX_WND_2_FREQ 869525000 + +/*! + * Second reception window channel datarate definition. + */ +#define EU868_RX_WND_2_DR DR_0 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define EU868_BAND0 { 100 , EU868_MAX_TX_POWER, 0, 0 } // 1.0 % + +/*! + * Band 1 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define EU868_BAND1 { 100 , EU868_MAX_TX_POWER, 0, 0 } // 1.0 % + +/*! + * Band 2 definition + * Band = { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define EU868_BAND2 { 1000, EU868_MAX_TX_POWER, 0, 0 } // 0.1 % + +/*! + * Band 2 definition + * Band = { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define EU868_BAND3 { 10 , EU868_MAX_TX_POWER, 0, 0 } // 10.0 % + +/*! + * Band 2 definition + * Band = { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define EU868_BAND4 { 100 , EU868_MAX_TX_POWER, 0, 0 } // 1.0 % + +/*! + * LoRaMac default channel 1 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define EU868_LC1 { 868100000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 1 } + +/*! + * LoRaMac default channel 2 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define EU868_LC2 { 868300000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 1 } + +/*! + * LoRaMac default channel 3 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define EU868_LC3 { 868500000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 1 } + +/*! + * LoRaMac channels which are allowed for the join procedure + */ +#define EU868_JOIN_CHANNELS ( uint16_t )( LC( 1 ) | LC( 2 ) | LC( 3 ) ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesEU868[] = { 12, 11, 10, 9, 8, 7, 7, 50 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsEU868[] = { 125000, 125000, 125000, 125000, 125000, 125000, 250000, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateEU868[] = { 51, 51, 51, 115, 242, 242, 242, 242 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterEU868[] = { 51, 51, 51, 115, 222, 222, 222, 222 }; + + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsEU868[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + return txPowerResult; +} + +static bool VerifyTxFreq( uint32_t freq, uint8_t *band, LoRaRadio *radio ) +{ + // Check radio driver support + if( radio->check_rf_frequency( freq ) == false ) + { + return false; + } + + // Check frequency bands + if( ( freq >= 863000000 ) && ( freq < 865000000 ) ) + { + *band = 2; + } + else if( ( freq >= 865000000 ) && ( freq <= 868000000 ) ) + { + *band = 0; + } + else if( ( freq > 868000000 ) && ( freq <= 868600000 ) ) + { + *band = 1; + } + else if( ( freq >= 868700000 ) && ( freq <= 869200000 ) ) + { + *band = 2; + } + else if( ( freq >= 869400000 ) && ( freq <= 869650000 ) ) + { + *band = 3; + } + else if( ( freq >= 869700000 ) && ( freq <= 870000000 ) ) + { + *band = 4; + } + else + { + return false; + } + return true; +} + +uint8_t LoRaPHYEU868::CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < EU868_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( joined == false ) + { + if( ( EU868_JOIN_CHANNELS & ( 1 << j ) ) == 0 ) + { + continue; + } + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYEU868::LoRaPHYEU868() +{ + const Band_t band0 = EU868_BAND0; + const Band_t band1 = EU868_BAND1; + const Band_t band2 = EU868_BAND2; + const Band_t band3 = EU868_BAND3; + const Band_t band4 = EU868_BAND4; + + Bands[0] = band0; + Bands[1] = band1; + Bands[2] = band2; + Bands[3] = band3; + Bands[4] = band4; +} + +LoRaPHYEU868::~LoRaPHYEU868() +{ +} + +PhyParam_t LoRaPHYEU868::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = EU868_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = EU868_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = EU868_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, EU868_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = EU868_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateEU868[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterEU868[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = EU868_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = EU868_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = EU868_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = EU868_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = EU868_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = EU868_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = EU868_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( EU868_ACKTIMEOUT + get_random( -EU868_ACK_TIMEOUT_RND, EU868_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = EU868_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = EU868_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = EU868_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = EU868_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + { + phyParam.fValue = EU868_DEFAULT_MAX_EIRP; + break; + } + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = EU868_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 48; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYEU868::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYEU868::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + const ChannelParams_t channel1 = EU868_LC1; + const ChannelParams_t channel2 = EU868_LC2; + const ChannelParams_t channel3 = EU868_LC3; + Channels[0] = channel1; + Channels[1] = channel2; + Channels[2] = channel3; + + // Initialize the channels default mask + ChannelsDefaultMask[0] = LC( 1 ) + LC( 2 ) + LC( 3 ); + // Update the channels mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 1 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Restore channels default mask + ChannelsMask[0] |= ChannelsDefaultMask[0]; + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYEU868::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, EU868_TX_MIN_DATARATE, EU868_TX_MAX_DATARATE ); + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, EU868_RX_MIN_DATARATE, EU868_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, EU868_MAX_TX_POWER, EU868_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return EU868_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 48 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYEU868::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + ChannelParams_t newChannel; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + // Setup default datarate range + newChannel.DrRange.Value = ( DR_5 << 4 ) | DR_0; + + // Size of the optional CF list + if( applyCFList->Size != 16 ) + { + return; + } + + // Last byte is RFU, don't take it into account + for( uint8_t i = 0, chanIdx = EU868_NUMB_DEFAULT_CHANNELS; chanIdx < EU868_MAX_NB_CHANNELS; i+=3, chanIdx++ ) + { + if( chanIdx < ( EU868_NUMB_CHANNELS_CF_LIST + EU868_NUMB_DEFAULT_CHANNELS ) ) + { + // Channel frequency + newChannel.Frequency = (uint32_t) applyCFList->Payload[i]; + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 1] << 8 ); + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 2] << 16 ); + newChannel.Frequency *= 100; + + // Initialize alternative frequency to 0 + newChannel.Rx1Frequency = 0; + } + else + { + newChannel.Frequency = 0; + newChannel.DrRange.Value = 0; + newChannel.Rx1Frequency = 0; + } + + if( newChannel.Frequency != 0 ) + { + channelAdd.NewChannel = &newChannel; + channelAdd.ChannelId = chanIdx; + + // Try to add all channels + add_channel( &channelAdd ); + } + else + { + channelRemove.ChannelId = chanIdx; + + remove_channel( &channelRemove ); + } + } +} + +bool LoRaPHYEU868::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYEU868::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == EU868_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= EU868_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = EU868_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( EU868_ADR_ACK_LIMIT + EU868_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % EU868_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + datarate = phyParam.Value; + + if( datarate == EU868_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYEU868::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, EU868_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + if( rxConfigParams->Datarate == DR_7 ) + { // FSK + tSymbol = compute_symb_timeout_fsk( DataratesEU868[rxConfigParams->Datarate] ); + } + else + { // LoRa + tSymbol = compute_symb_timeout_lora( DataratesEU868[rxConfigParams->Datarate], BandwidthsEU868[rxConfigParams->Datarate] ); + } + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYEU868::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + radio_modems_t modem; + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if( _radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = Channels[rxConfig->Channel].Frequency; + // Apply the alternative RX 1 window frequency, if it is available + if( Channels[rxConfig->Channel].Rx1Frequency != 0 ) + { + frequency = Channels[rxConfig->Channel].Rx1Frequency; + } + } + + // Read the physical datarate from the datarates table + phyDr = DataratesEU868[dr]; + + _radio->set_channel( frequency ); + + // Radio configuration + if( dr == DR_7 ) + { + modem = MODEM_FSK; + _radio->set_rx_config( modem, 50000, phyDr * 1000, 0, 83333, 5, rxConfig->WindowTimeout, false, 0, true, 0, 0, false, rxConfig->RxContinuous ); + } + else + { + modem = MODEM_LORA; + _radio->set_rx_config( modem, rxConfig->Bandwidth, phyDr, 1, 0, 8, rxConfig->WindowTimeout, false, 0, false, 0, 0, true, rxConfig->RxContinuous ); + } + + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterEU868[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateEU868[dr]; + } + + _radio->set_max_payload_length( modem, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD ); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYEU868::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + radio_modems_t modem; + int8_t phyDr = DataratesEU868[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, txConfig->MaxEirp, txConfig->AntennaGain ); + + // Setup the radio frequency + _radio->set_channel( Channels[txConfig->Channel].Frequency ); + + if( txConfig->Datarate == DR_7 ) + { // High Speed FSK channel + modem = MODEM_FSK; + _radio->set_tx_config( modem, phyTxPower, 25000, bandwidth, phyDr * 1000, 0, 5, false, true, 0, 0, false, 3000 ); + } + else + { + modem = MODEM_LORA; + _radio->set_tx_config( modem, phyTxPower, 0, bandwidth, phyDr, 1, 8, false, true, 0, 0, false, 3000 ); + } + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length( modem, txConfig->PktLen ); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air( modem, txConfig->PktLen ); + + *txPower = txPowerLimited; + return true; +} + +uint8_t LoRaPHYEU868::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t chMask = 0; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + // Get ADR request parameters + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + // Setup temporary channels mask + chMask = linkAdrParams.ChMask; + + // Verify channels mask + if( ( linkAdrParams.ChMaskCtrl == 0 ) && ( chMask == 0 ) ) + { + status &= 0xFE; // Channel mask KO + } + else if( ( ( linkAdrParams.ChMaskCtrl >= 1 ) && ( linkAdrParams.ChMaskCtrl <= 5 )) || + ( linkAdrParams.ChMaskCtrl >= 7 ) ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + for( uint8_t i = 0; i < EU868_MAX_NB_CHANNELS; i++ ) + { + if( linkAdrParams.ChMaskCtrl == 6 ) + { + if( Channels[i].Frequency != 0 ) + { + chMask |= 1 << i; + } + } + else + { + if( ( ( chMask & ( 1 << i ) ) != 0 ) && + ( Channels[i].Frequency == 0 ) ) + {// Trying to enable an undefined channel + status &= 0xFE; // Channel mask KO + } + } + } + } + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = EU868_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = &chMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = EU868_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = EU868_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = EU868_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Set the channels mask to a default value + memset( ChannelsMask, 0, sizeof( ChannelsMask ) ); + // Update the channels mask + ChannelsMask[0] = chMask; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYEU868::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + + // Verify radio frequency + if( _radio->check_rf_frequency( rxParamSetupReq->Frequency ) == false ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, EU868_RX_MIN_DATARATE, EU868_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, EU868_MIN_RX1_DR_OFFSET, EU868_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYEU868::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + uint8_t status = 0x03; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + if( newChannelReq->NewChannel->Frequency == 0 ) + { + channelRemove.ChannelId = newChannelReq->ChannelId; + + // Remove + if( remove_channel( &channelRemove ) == false ) + { + status &= 0xFC; + } + } + else + { + channelAdd.NewChannel = newChannelReq->NewChannel; + channelAdd.ChannelId = newChannelReq->ChannelId; + + switch( add_channel( &channelAdd ) ) + { + case LORAMAC_STATUS_OK: + { + break; + } + case LORAMAC_STATUS_FREQUENCY_INVALID: + { + status &= 0xFE; + break; + } + case LORAMAC_STATUS_DATARATE_INVALID: + { + status &= 0xFD; + break; + } + case LORAMAC_STATUS_FREQ_AND_DR_INVALID: + { + status &= 0xFC; + break; + } + default: + { + status &= 0xFC; + break; + } + } + } + + return status; +} + +int8_t LoRaPHYEU868::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYEU868::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + uint8_t status = 0x03; + uint8_t band = 0; + + // Verify if the frequency is supported + if( VerifyTxFreq( dlChannelReq->Rx1Frequency, &band, _radio ) == false ) + { + status &= 0xFE; + } + + // Verify if an uplink frequency exists + if( Channels[dlChannelReq->ChannelId].Frequency == 0 ) + { + status &= 0xFD; + } + + // Apply Rx1 frequency, if the status is OK + if( status == 0x03 ) + { + Channels[dlChannelReq->ChannelId].Rx1Frequency = dlChannelReq->Rx1Frequency; + } + + return status; +} + +int8_t LoRaPHYEU868::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + if( ( alternateDr->NbTrials % 48 ) == 0 ) + { + datarate = DR_0; + } + else if( ( alternateDr->NbTrials % 32 ) == 0 ) + { + datarate = DR_1; + } + else if( ( alternateDr->NbTrials % 24 ) == 0 ) + { + datarate = DR_2; + } + else if( ( alternateDr->NbTrials % 16 ) == 0 ) + { + datarate = DR_3; + } + else if( ( alternateDr->NbTrials % 8 ) == 0 ) + { + datarate = DR_4; + } + else + { + datarate = DR_5; + } + return datarate; +} + +void LoRaPHYEU868::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYEU868::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[EU868_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + if( num_active_channels( ChannelsMask, 0, 1 ) == 0 ) + { // Reactivate default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, EU868_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Joined, nextChanParams->Datarate, + ChannelsMask, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + // We found a valid channel + *channel = enabledChannels[get_random( 0, nbEnabledChannels - 1 )]; + + *time = 0; + return true; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel, restore defaults + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYEU868::add_channel(ChannelAddParams_t* channelAdd) +{ + uint8_t band = 0; + bool drInvalid = false; + bool freqInvalid = false; + uint8_t id = channelAdd->ChannelId; + + if( id >= EU868_MAX_NB_CHANNELS ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Validate the datarate range + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Min, EU868_TX_MIN_DATARATE, EU868_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, EU868_TX_MIN_DATARATE, EU868_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( channelAdd->NewChannel->DrRange.Fields.Min > channelAdd->NewChannel->DrRange.Fields.Max ) + { + drInvalid = true; + } + + // Default channels don't accept all values + if( id < EU868_NUMB_DEFAULT_CHANNELS ) + { + // Validate the datarate range for min: must be DR_0 + if( channelAdd->NewChannel->DrRange.Fields.Min > DR_0 ) + { + drInvalid = true; + } + // Validate the datarate range for max: must be DR_5 <= Max <= TX_MAX_DATARATE + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, DR_5, EU868_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + // We are not allowed to change the frequency + if( channelAdd->NewChannel->Frequency != Channels[id].Frequency ) + { + freqInvalid = true; + } + } + + // Check frequency + if( freqInvalid == false ) + { + if( VerifyTxFreq( channelAdd->NewChannel->Frequency, &band, _radio ) == false ) + { + freqInvalid = true; + } + } + + // Check status + if( ( drInvalid == true ) && ( freqInvalid == true ) ) + { + return LORAMAC_STATUS_FREQ_AND_DR_INVALID; + } + if( drInvalid == true ) + { + return LORAMAC_STATUS_DATARATE_INVALID; + } + if( freqInvalid == true ) + { + return LORAMAC_STATUS_FREQUENCY_INVALID; + } + + memcpy( &(Channels[id]), channelAdd->NewChannel, sizeof( Channels[id] ) ); + Channels[id].Band = band; + ChannelsMask[0] |= ( 1 << id ); + return LORAMAC_STATUS_OK; +} + +bool LoRaPHYEU868::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + uint8_t id = channelRemove->ChannelId; + + if( id < EU868_NUMB_DEFAULT_CHANNELS ) + { + return false; + } + + // Remove the channel from the list of channels + const ChannelParams_t empty_channel = { 0, 0, { 0 }, 0 }; + Channels[id] = empty_channel; + + return disable_channel( ChannelsMask, id, EU868_MAX_NB_CHANNELS ); +} + +void LoRaPHYEU868::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, continuousWave->MaxEirp, continuousWave->AntennaGain ); + + _radio->set_tx_continuous_wave( frequency, phyTxPower, continuousWave->Timeout ); +} + +uint8_t LoRaPHYEU868::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset) +{ + int8_t datarate = dr - drOffset; + + if( datarate < 0 ) + { + datarate = DR_0; + } + return datarate; +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYEU868.h b/features/lorawan/lorastack/phy/LoRaPHYEU868.h new file mode 100644 index 0000000000..0c588c5417 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYEU868.h @@ -0,0 +1,358 @@ +/** + * @file LoRaPHYEU868.h + * + * @brief Implements LoRaPHY for European 868 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_EU868_H_ +#define MBED_OS_LORAPHY_EU868_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +/*! + * LoRaMac maximum number of channels + */ +#define EU868_MAX_NB_CHANNELS 16 + +/*! + * Maximum number of bands + */ +#define EU868_MAX_NB_BANDS 5 + +#define EU868_CHANNELS_MASK_SIZE 1 + + +class LoRaPHYEU868 : public LoRaPHY { + +public: + LoRaPHYEU868(); + virtual ~LoRaPHYEU868(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval The structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to verify. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval The function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + uint8_t CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[EU868_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[EU868_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[EU868_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[EU868_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_EU868_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYIN865.cpp b/features/lorawan/lorastack/phy/LoRaPHYIN865.cpp new file mode 100644 index 0000000000..26196569c5 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYIN865.cpp @@ -0,0 +1,1243 @@ +/** + * @file LoRaPHYIN865.cpp + * + * @brief Implements LoRaPHY for Indian 865 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYIN865.h" + +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + + +/*! + * Number of default channels + */ +#define IN865_NUMB_DEFAULT_CHANNELS 3 + +/*! + * Number of channels to apply for the CF list + */ +#define IN865_NUMB_CHANNELS_CF_LIST 5 + +/*! + * Minimal datarate that can be used by the node + */ +#define IN865_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define IN865_TX_MAX_DATARATE DR_7 + +/*! + * Minimal datarate that can be used by the node + */ +#define IN865_RX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define IN865_RX_MAX_DATARATE DR_7 + +/*! + * Default datarate used by the node + */ +#define IN865_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define IN865_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define IN865_MAX_RX1_DR_OFFSET 7 + +/*! + * Default Rx1 receive datarate offset + */ +#define IN865_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define IN865_MIN_TX_POWER TX_POWER_10 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define IN865_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define IN865_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max EIRP + */ +#define IN865_DEFAULT_MAX_EIRP 30.0f + +/*! + * Default antenna gain + */ +#define IN865_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define IN865_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define IN865_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define IN865_DUTY_CYCLE_ENABLED 1 + +/*! + * Maximum RX window duration + */ +#define IN865_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define IN865_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define IN865_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define IN865_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define IN865_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define IN865_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define IN865_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define IN865_ACK_TIMEOUT_RND 1000 + +#if ( IN865_DEFAULT_DATARATE > DR_5 ) +#error "A default DR higher than DR_5 may lead to connectivity loss." +#endif + +/*! + * Second reception window channel frequency definition. + */ +#define IN865_RX_WND_2_FREQ 866550000 + +/*! + * Second reception window channel datarate definition. + */ +#define IN865_RX_WND_2_DR DR_2 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define IN865_BAND0 { 1 , IN865_MAX_TX_POWER, 0, 0 } // 100.0 % + +/*! + * LoRaMac default channel 1 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define IN865_LC1 { 865062500, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 2 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define IN865_LC2 { 865402500, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 3 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define IN865_LC3 { 865985000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac channels which are allowed for the join procedure + */ +#define IN865_JOIN_CHANNELS ( uint16_t )( LC( 1 ) | LC( 2 ) | LC( 3 ) ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesIN865[] = { 12, 11, 10, 9, 8, 7, 7, 50 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsIN865[] = { 125000, 125000, 125000, 125000, 125000, 125000, 250000, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateIN865[] = { 51, 51, 51, 115, 242, 242, 242, 242 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterIN865[] = { 51, 51, 51, 115, 222, 222, 222, 222 }; + +/*! + * Effective datarate offsets for receive window 1. + */ +static const int8_t EffectiveRx1DrOffsetIN865[] = { 0, 1, 2, 3, 4, 5, -1, -2 }; + + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else if( dr == DR_7 ) + { + nextLowerDr = DR_5; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsIN865[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + return txPowerResult; +} + +static bool VerifyTxFreq( uint32_t freq, uint8_t *band, LoRaRadio *radio ) +{ + // Check radio driver support + if( radio->check_rf_frequency( freq ) == false ) + { + return false; + } + + if( ( freq < 865000000 ) || ( freq > 867000000 ) ) + { + return false; + } + return true; +} + +uint8_t LoRaPHYIN865::CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < IN865_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( joined == false ) + { + if( ( IN865_JOIN_CHANNELS & ( 1 << j ) ) == 0 ) + { + continue; + } + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYIN865::LoRaPHYIN865() +{ + const Band_t band0 = IN865_BAND0; + Bands[0] = band0; +} + +LoRaPHYIN865::~LoRaPHYIN865() +{ +} + +PhyParam_t LoRaPHYIN865::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = IN865_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = IN865_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = IN865_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, IN865_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = IN865_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateIN865[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterIN865[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = IN865_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = IN865_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = IN865_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = IN865_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = IN865_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = IN865_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = IN865_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( IN865_ACKTIMEOUT + get_random( -IN865_ACK_TIMEOUT_RND, IN865_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = IN865_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = IN865_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = IN865_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = IN865_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + { + phyParam.fValue = IN865_DEFAULT_MAX_EIRP; + break; + } + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = IN865_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 48; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYIN865::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYIN865::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + const ChannelParams_t channel1 = IN865_LC1; + const ChannelParams_t channel2 = IN865_LC2; + const ChannelParams_t channel3 = IN865_LC3; + Channels[0] = channel1; + Channels[1] = channel2; + Channels[2] = channel3; + + // Initialize the channels default mask + ChannelsDefaultMask[0] = LC( 1 ) + LC( 2 ) + LC( 3 ); + // Update the channels mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 1 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Restore channels default mask + ChannelsMask[0] |= ChannelsDefaultMask[0]; + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYIN865::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, IN865_TX_MIN_DATARATE, IN865_TX_MAX_DATARATE ); + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, IN865_RX_MIN_DATARATE, IN865_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, IN865_MAX_TX_POWER, IN865_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return IN865_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 48 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYIN865::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + ChannelParams_t newChannel; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + // Setup default datarate range + newChannel.DrRange.Value = ( DR_5 << 4 ) | DR_0; + + // Size of the optional CF list + if( applyCFList->Size != 16 ) + { + return; + } + + // Last byte is RFU, don't take it into account + for( uint8_t i = 0, chanIdx = IN865_NUMB_DEFAULT_CHANNELS; chanIdx < IN865_MAX_NB_CHANNELS; i+=3, chanIdx++ ) + { + if( chanIdx < ( IN865_NUMB_CHANNELS_CF_LIST + IN865_NUMB_DEFAULT_CHANNELS ) ) + { + // Channel frequency + newChannel.Frequency = (uint32_t) applyCFList->Payload[i]; + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 1] << 8 ); + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 2] << 16 ); + newChannel.Frequency *= 100; + + // Initialize alternative frequency to 0 + newChannel.Rx1Frequency = 0; + } + else + { + newChannel.Frequency = 0; + newChannel.DrRange.Value = 0; + newChannel.Rx1Frequency = 0; + } + + if( newChannel.Frequency != 0 ) + { + channelAdd.NewChannel = &newChannel; + channelAdd.ChannelId = chanIdx; + + // Try to add all channels + add_channel( &channelAdd ); + } + else + { + channelRemove.ChannelId = chanIdx; + + remove_channel( &channelRemove ); + } + } +} + +bool LoRaPHYIN865::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYIN865::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == IN865_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= IN865_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = IN865_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( IN865_ADR_ACK_LIMIT + IN865_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % IN865_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + datarate = phyParam.Value; + + if( datarate == IN865_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYIN865::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, IN865_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + if( rxConfigParams->Datarate == DR_7 ) + { // FSK + tSymbol = compute_symb_timeout_fsk( DataratesIN865[rxConfigParams->Datarate] ); + } + else + { // LoRa + tSymbol = compute_symb_timeout_lora( DataratesIN865[rxConfigParams->Datarate], BandwidthsIN865[rxConfigParams->Datarate] ); + } + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYIN865::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + radio_modems_t modem; + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if( _radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = Channels[rxConfig->Channel].Frequency; + // Apply the alternative RX 1 window frequency, if it is available + if( Channels[rxConfig->Channel].Rx1Frequency != 0 ) + { + frequency = Channels[rxConfig->Channel].Rx1Frequency; + } + } + + // Read the physical datarate from the datarates table + phyDr = DataratesIN865[dr]; + + _radio->set_channel( frequency ); + + // Radio configuration + if( dr == DR_7 ) + { + modem = MODEM_FSK; + _radio->set_rx_config( modem, 50000, phyDr * 1000, 0, 83333, 5, rxConfig->WindowTimeout, false, 0, true, 0, 0, false, rxConfig->RxContinuous ); + } + else + { + modem = MODEM_LORA; + _radio->set_rx_config( modem, rxConfig->Bandwidth, phyDr, 1, 0, 8, rxConfig->WindowTimeout, false, 0, false, 0, 0, true, rxConfig->RxContinuous ); + } + + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterIN865[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateIN865[dr]; + } + _radio->set_max_payload_length( modem, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD ); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYIN865::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, TimerTime_t* txTimeOnAir) +{ + radio_modems_t modem; + int8_t phyDr = DataratesIN865[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, txConfig->MaxEirp, txConfig->AntennaGain ); + + // Setup the radio frequency + _radio->set_channel( Channels[txConfig->Channel].Frequency ); + + if( txConfig->Datarate == DR_7 ) + { // High Speed FSK channel + modem = MODEM_FSK; + _radio->set_tx_config( modem, phyTxPower, 25000, bandwidth, phyDr * 1000, 0, 5, false, true, 0, 0, false, 3000 ); + } + else + { + modem = MODEM_LORA; + _radio->set_tx_config( modem, phyTxPower, 0, bandwidth, phyDr, 1, 8, false, true, 0, 0, false, 3000 ); + } + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length( modem, txConfig->PktLen ); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air( modem, txConfig->PktLen ); + + *txPower = txPowerLimited; + return true; +} + +uint8_t LoRaPHYIN865::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t chMask = 0; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + // Get ADR request parameters + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + // Setup temporary channels mask + chMask = linkAdrParams.ChMask; + + // Verify channels mask + if( ( linkAdrParams.ChMaskCtrl == 0 ) && ( chMask == 0 ) ) + { + status &= 0xFE; // Channel mask KO + } + else if( ( ( linkAdrParams.ChMaskCtrl >= 1 ) && ( linkAdrParams.ChMaskCtrl <= 5 )) || + ( linkAdrParams.ChMaskCtrl >= 7 ) ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + for( uint8_t i = 0; i < IN865_MAX_NB_CHANNELS; i++ ) + { + if( linkAdrParams.ChMaskCtrl == 6 ) + { + if( Channels[i].Frequency != 0 ) + { + chMask |= 1 << i; + } + } + else + { + if( ( ( chMask & ( 1 << i ) ) != 0 ) && + ( Channels[i].Frequency == 0 ) ) + {// Trying to enable an undefined channel + status &= 0xFE; // Channel mask KO + } + } + } + } + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = IN865_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = &chMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = IN865_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = IN865_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = IN865_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Set the channels mask to a default value + memset( ChannelsMask, 0, sizeof( ChannelsMask ) ); + // Update the channels mask + ChannelsMask[0] = chMask; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYIN865::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + + // Verify radio frequency + if( _radio->check_rf_frequency( rxParamSetupReq->Frequency ) == false ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, IN865_RX_MIN_DATARATE, IN865_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, IN865_MIN_RX1_DR_OFFSET, IN865_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYIN865::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + uint8_t status = 0x03; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + if( newChannelReq->NewChannel->Frequency == 0 ) + { + channelRemove.ChannelId = newChannelReq->ChannelId; + + // Remove + if( remove_channel( &channelRemove ) == false ) + { + status &= 0xFC; + } + } + else + { + channelAdd.NewChannel = newChannelReq->NewChannel; + channelAdd.ChannelId = newChannelReq->ChannelId; + + switch( add_channel( &channelAdd ) ) + { + case LORAMAC_STATUS_OK: + { + break; + } + case LORAMAC_STATUS_FREQUENCY_INVALID: + { + status &= 0xFE; + break; + } + case LORAMAC_STATUS_DATARATE_INVALID: + { + status &= 0xFD; + break; + } + case LORAMAC_STATUS_FREQ_AND_DR_INVALID: + { + status &= 0xFC; + break; + } + default: + { + status &= 0xFC; + break; + } + } + } + + return status; +} + +int8_t LoRaPHYIN865::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYIN865::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + uint8_t status = 0x03; + uint8_t band = 0; + + // Verify if the frequency is supported + if( VerifyTxFreq( dlChannelReq->Rx1Frequency, &band, _radio ) == false ) + { + status &= 0xFE; + } + + // Verify if an uplink frequency exists + if( Channels[dlChannelReq->ChannelId].Frequency == 0 ) + { + status &= 0xFD; + } + + // Apply Rx1 frequency, if the status is OK + if( status == 0x03 ) + { + Channels[dlChannelReq->ChannelId].Rx1Frequency = dlChannelReq->Rx1Frequency; + } + + return status; +} + +int8_t LoRaPHYIN865::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + if( ( alternateDr->NbTrials % 48 ) == 0 ) + { + datarate = DR_0; + } + else if( ( alternateDr->NbTrials % 32 ) == 0 ) + { + datarate = DR_1; + } + else if( ( alternateDr->NbTrials % 24 ) == 0 ) + { + datarate = DR_2; + } + else if( ( alternateDr->NbTrials % 16 ) == 0 ) + { + datarate = DR_3; + } + else if( ( alternateDr->NbTrials % 8 ) == 0 ) + { + datarate = DR_4; + } + else + { + datarate = DR_5; + } + return datarate; +} + +void LoRaPHYIN865::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYIN865::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[IN865_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + if( num_active_channels( ChannelsMask, 0, 1 ) == 0 ) + { // Reactivate default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, IN865_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Joined, nextChanParams->Datarate, + ChannelsMask, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + // We found a valid channel + *channel = enabledChannels[get_random( 0, nbEnabledChannels - 1 )]; + + *time = 0; + return true; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel, restore defaults + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYIN865::add_channel(ChannelAddParams_t* channelAdd) +{ + uint8_t band = 0; + bool drInvalid = false; + bool freqInvalid = false; + uint8_t id = channelAdd->ChannelId; + + if( id >= IN865_MAX_NB_CHANNELS ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Validate the datarate range + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Min, IN865_TX_MIN_DATARATE, IN865_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, IN865_TX_MIN_DATARATE, IN865_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( channelAdd->NewChannel->DrRange.Fields.Min > channelAdd->NewChannel->DrRange.Fields.Max ) + { + drInvalid = true; + } + + // Default channels don't accept all values + if( id < IN865_NUMB_DEFAULT_CHANNELS ) + { + // Validate the datarate range for min: must be DR_0 + if( channelAdd->NewChannel->DrRange.Fields.Min > DR_0 ) + { + drInvalid = true; + } + // Validate the datarate range for max: must be DR_5 <= Max <= TX_MAX_DATARATE + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, DR_5, IN865_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + // We are not allowed to change the frequency + if( channelAdd->NewChannel->Frequency != Channels[id].Frequency ) + { + freqInvalid = true; + } + } + + // Check frequency + if( freqInvalid == false ) + { + if( VerifyTxFreq( channelAdd->NewChannel->Frequency, &band, _radio ) == false ) + { + freqInvalid = true; + } + } + + // Check status + if( ( drInvalid == true ) && ( freqInvalid == true ) ) + { + return LORAMAC_STATUS_FREQ_AND_DR_INVALID; + } + if( drInvalid == true ) + { + return LORAMAC_STATUS_DATARATE_INVALID; + } + if( freqInvalid == true ) + { + return LORAMAC_STATUS_FREQUENCY_INVALID; + } + + memcpy( &(Channels[id]), channelAdd->NewChannel, sizeof( Channels[id] ) ); + Channels[id].Band = band; + ChannelsMask[0] |= ( 1 << id ); + return LORAMAC_STATUS_OK; +} + +bool LoRaPHYIN865::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + uint8_t id = channelRemove->ChannelId; + + if( id < IN865_NUMB_DEFAULT_CHANNELS ) + { + return false; + } + + // Remove the channel from the list of channels + const ChannelParams_t empty_channel = { 0, 0, { 0 }, 0 }; + Channels[id] = empty_channel; + + return disable_channel( ChannelsMask, id, IN865_MAX_NB_CHANNELS ); +} + +void LoRaPHYIN865::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, continuousWave->MaxEirp, continuousWave->AntennaGain ); + + _radio->set_tx_continuous_wave( frequency, phyTxPower, continuousWave->Timeout ); +} + +uint8_t LoRaPHYIN865::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, + int8_t drOffset) +{ + // Apply offset formula + return MIN( DR_5, MAX( DR_0, dr - EffectiveRx1DrOffsetIN865[drOffset] ) ); +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYIN865.h b/features/lorawan/lorastack/phy/LoRaPHYIN865.h new file mode 100644 index 0000000000..d1be5f5b62 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYIN865.h @@ -0,0 +1,361 @@ +/** + * @file LoRaPHYIN865.h + * + * @brief Implements LoRaPHY for Indian 865 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_IN865_H_ +#define MBED_OS_LORAPHY_IN865_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + + +/*! + * LoRaMac maximum number of channels + */ +#define IN865_MAX_NB_CHANNELS 16 + +/*! + * Maximum number of bands + */ +#define IN865_MAX_NB_BANDS 1 + + +#define IN865_CHANNELS_MASK_SIZE 1 + + +class LoRaPHYIN865 : public LoRaPHY { + +public: + + LoRaPHYIN865(); + virtual ~LoRaPHYIN865(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval The structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to verify. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details, please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval The function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pPointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + uint8_t CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[IN865_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[IN865_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[IN865_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[IN865_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_IN865_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYKR920.cpp b/features/lorawan/lorastack/phy/LoRaPHYKR920.cpp new file mode 100644 index 0000000000..b0229a2e8b --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYKR920.cpp @@ -0,0 +1,1258 @@ +/** + * @file LoRaPHYKR920.cpp + * + * @brief Implements LoRaPHY for Korean 920 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYKR920.h" + +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + + +/*! + * Number of default channels + */ +#define KR920_NUMB_DEFAULT_CHANNELS 3 + +/*! + * Number of channels to apply for the CF list + */ +#define KR920_NUMB_CHANNELS_CF_LIST 5 + +/*! + * Minimal datarate that can be used by the node + */ +#define KR920_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define KR920_TX_MAX_DATARATE DR_5 + +/*! + * Minimal datarate that can be used by the node + */ +#define KR920_RX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define KR920_RX_MAX_DATARATE DR_5 + +/*! + * Default datarate used by the node + */ +#define KR920_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define KR920_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define KR920_MAX_RX1_DR_OFFSET 5 + +/*! + * Default Rx1 receive datarate offset + */ +#define KR920_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define KR920_MIN_TX_POWER TX_POWER_7 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define KR920_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define KR920_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max EIRP for frequency 920.9 MHz - 921.9 MHz + */ +#define KR920_DEFAULT_MAX_EIRP_LOW 10.0f + +/*! + * Default Max EIRP for frequency 922.1 MHz - 923.3 MHz + */ +#define KR920_DEFAULT_MAX_EIRP_HIGH 14.0f + +/*! + * Default antenna gain + */ +#define KR920_DEFAULT_ANTENNA_GAIN 2.15f + +/*! + * ADR Ack limit + */ +#define KR920_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define KR920_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define KR920_DUTY_CYCLE_ENABLED 0 + +/*! + * Maximum RX window duration + */ +#define KR920_MAX_RX_WINDOW 4000 + +/*! + * Receive delay 1 + */ +#define KR920_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define KR920_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define KR920_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define KR920_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define KR920_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define KR920_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define KR920_ACK_TIMEOUT_RND 1000 + +#if ( KR920_DEFAULT_DATARATE > DR_5 ) +#error "A default DR higher than DR_5 may lead to connectivity loss." +#endif + +/*! + * Second reception window channel frequency definition. + */ +#define KR920_RX_WND_2_FREQ 921900000 + +/*! + * Second reception window channel datarate definition. + */ +#define KR920_RX_WND_2_DR DR_0 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define KR920_BAND0 { 1 , KR920_MAX_TX_POWER, 0, 0 } // 100.0 % + +/*! + * LoRaMac default channel 1 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define KR920_LC1 { 922100000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 2 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define KR920_LC2 { 922300000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac default channel 3 + * Channel = { Frequency [Hz], RX1 Frequency [Hz], { ( ( DrMax << 4 ) | DrMin ) }, Band } + */ +#define KR920_LC3 { 922500000, 0, { ( ( DR_5 << 4 ) | DR_0 ) }, 0 } + +/*! + * LoRaMac channels which are allowed for the join procedure + */ +#define KR920_JOIN_CHANNELS ( uint16_t )( LC( 1 ) | LC( 2 ) | LC( 3 ) ) + +/*! + * RSSI threshold for a free channel [dBm] + */ +#define KR920_RSSI_FREE_TH -65 + +/*! + * Specifies the time the node performs a carrier sense + */ +#define KR920_CARRIER_SENSE_TIME 6 + +/*! + * Data rates table definition + */ +static const uint8_t DataratesKR920[] = { 12, 11, 10, 9, 8, 7 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsKR920[] = { 125000, 125000, 125000, 125000, 125000, 125000 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with and without a repeater. + */ +static const uint8_t MaxPayloadOfDatarateKR920[] = { 51, 51, 51, 115, 242, 242 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterKR920[] = { 51, 51, 51, 115, 222, 222 }; + + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static int8_t GetMaxEIRP( uint32_t freq ) +{ + if( freq >= 922100000 ) + {// Limit to 14dBm + return KR920_DEFAULT_MAX_EIRP_HIGH; + } + // Limit to 10dBm + return KR920_DEFAULT_MAX_EIRP_LOW; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsKR920[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + return txPowerResult; +} + +static bool VerifyTxFreq( uint32_t freq, LoRaRadio *radio ) +{ + uint32_t tmpFreq = freq; + + // Check radio driver support + if( radio->check_rf_frequency( tmpFreq ) == false ) + { + return false; + } + + // Verify if the frequency is valid. The frequency must be in a specified + // range and can be set to specific values. + if( ( tmpFreq >= 920900000 ) && ( tmpFreq <=923300000 ) ) + { + // Range ok, check for specific value + tmpFreq -= 920900000; + if( ( tmpFreq % 200000 ) == 0 ) + { + return true; + } + } + return false; +} + +uint8_t LoRaPHYKR920::CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < KR920_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( joined == false ) + { + if( ( KR920_JOIN_CHANNELS & ( 1 << j ) ) == 0 ) + { + continue; + } + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYKR920::LoRaPHYKR920() +{ + const Band_t band0 = KR920_BAND0; + Bands[0] = band0; +} + +LoRaPHYKR920::~LoRaPHYKR920() +{ +} + +PhyParam_t LoRaPHYKR920::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = KR920_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = KR920_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = KR920_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, KR920_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = KR920_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateKR920[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterKR920[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = KR920_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = KR920_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = KR920_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = KR920_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = KR920_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = KR920_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = KR920_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( KR920_ACKTIMEOUT + get_random( -KR920_ACK_TIMEOUT_RND, KR920_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = KR920_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = KR920_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = KR920_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = KR920_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + { + // We set the higher maximum EIRP as default value. + // The reason for this is, that the frequency may + // change during a channel selection for the next uplink. + // The value has to be recalculated in the TX configuration. + phyParam.fValue = KR920_DEFAULT_MAX_EIRP_HIGH; + break; + } + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = KR920_DEFAULT_ANTENNA_GAIN; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 48; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYKR920::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYKR920::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + const ChannelParams_t channel1 = KR920_LC1; + const ChannelParams_t channel2 = KR920_LC2; + const ChannelParams_t channel3 = KR920_LC3; + Channels[0] = channel1; + Channels[1] = channel2; + Channels[2] = channel3; + + // Initialize the channels default mask + ChannelsDefaultMask[0] = LC( 1 ) + LC( 2 ) + LC( 3 ); + // Update the channels mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 1 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Restore channels default mask + ChannelsMask[0] |= ChannelsDefaultMask[0]; + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYKR920::verify( VerifyParams_t* verify, PhyAttribute_t phyAttribute ) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, KR920_TX_MIN_DATARATE, KR920_TX_MAX_DATARATE ); + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, KR920_RX_MIN_DATARATE, KR920_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, KR920_MAX_TX_POWER, KR920_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return KR920_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 48 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYKR920::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + ChannelParams_t newChannel; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + // Setup default datarate range + newChannel.DrRange.Value = ( DR_5 << 4 ) | DR_0; + + // Size of the optional CF list + if( applyCFList->Size != 16 ) + { + return; + } + + // Last byte is RFU, don't take it into account + for( uint8_t i = 0, chanIdx = KR920_NUMB_DEFAULT_CHANNELS; chanIdx < KR920_MAX_NB_CHANNELS; i+=3, chanIdx++ ) + { + if( chanIdx < ( KR920_NUMB_CHANNELS_CF_LIST + KR920_NUMB_DEFAULT_CHANNELS ) ) + { + // Channel frequency + newChannel.Frequency = (uint32_t) applyCFList->Payload[i]; + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 1] << 8 ); + newChannel.Frequency |= ( (uint32_t) applyCFList->Payload[i + 2] << 16 ); + newChannel.Frequency *= 100; + + // Initialize alternative frequency to 0 + newChannel.Rx1Frequency = 0; + } + else + { + newChannel.Frequency = 0; + newChannel.DrRange.Value = 0; + newChannel.Rx1Frequency = 0; + } + + if( newChannel.Frequency != 0 ) + { + channelAdd.NewChannel = &newChannel; + channelAdd.ChannelId = chanIdx; + + // Try to add all channels + add_channel( &channelAdd ); + } + else + { + channelRemove.ChannelId = chanIdx; + + remove_channel( &channelRemove ); + } + } +} + +bool LoRaPHYKR920::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 1 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYKR920::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == KR920_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= KR920_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = KR920_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( KR920_ADR_ACK_LIMIT + KR920_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % KR920_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + datarate = phyParam.Value; + + if( datarate == KR920_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYKR920::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, KR920_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + tSymbol = compute_symb_timeout_lora( DataratesKR920[rxConfigParams->Datarate], BandwidthsKR920[rxConfigParams->Datarate] ); + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYKR920::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if( _radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = Channels[rxConfig->Channel].Frequency; + // Apply the alternative RX 1 window frequency, if it is available + if( Channels[rxConfig->Channel].Rx1Frequency != 0 ) + { + frequency = Channels[rxConfig->Channel].Rx1Frequency; + } + } + + // Read the physical datarate from the datarates table + phyDr = DataratesKR920[dr]; + + _radio->set_channel( frequency ); + + // Radio configuration + _radio->set_rx_config( MODEM_LORA, rxConfig->Bandwidth, phyDr, 1, 0, 8, + rxConfig->WindowTimeout, false, 0, false, 0, 0, true, + rxConfig->RxContinuous ); + maxPayload = MaxPayloadOfDatarateKR920[dr]; + _radio->set_max_payload_length( MODEM_LORA, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD ); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYKR920::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, TimerTime_t* txTimeOnAir) +{ + int8_t phyDr = DataratesKR920[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + float maxEIRP = GetMaxEIRP( Channels[txConfig->Channel].Frequency ); + int8_t phyTxPower = 0; + + // Take the minimum between the maxEIRP and txConfig->MaxEirp. + // The value of txConfig->MaxEirp could have changed during runtime, e.g. due to a MAC command. + maxEIRP = MIN( txConfig->MaxEirp, maxEIRP ); + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, maxEIRP, txConfig->AntennaGain ); + + // Setup the radio frequency + _radio->set_channel( Channels[txConfig->Channel].Frequency ); + + _radio->set_tx_config( MODEM_LORA, phyTxPower, 0, bandwidth, phyDr, 1, 8, false, true, 0, 0, false, 3000 ); + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length( MODEM_LORA, txConfig->PktLen ); + // Get the time-on-air of the next tx frame + *txTimeOnAir =_radio->time_on_air( MODEM_LORA, txConfig->PktLen ); + + *txPower = txPowerLimited; + return true; +} + +uint8_t LoRaPHYKR920::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t chMask = 0; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + // Get ADR request parameters + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + // Setup temporary channels mask + chMask = linkAdrParams.ChMask; + + // Verify channels mask + if( ( linkAdrParams.ChMaskCtrl == 0 ) && ( chMask == 0 ) ) + { + status &= 0xFE; // Channel mask KO + } + else if( ( ( linkAdrParams.ChMaskCtrl >= 1 ) && ( linkAdrParams.ChMaskCtrl <= 5 )) || + ( linkAdrParams.ChMaskCtrl >= 7 ) ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + for( uint8_t i = 0; i < KR920_MAX_NB_CHANNELS; i++ ) + { + if( linkAdrParams.ChMaskCtrl == 6 ) + { + if( Channels[i].Frequency != 0 ) + { + chMask |= 1 << i; + } + } + else + { + if( ( ( chMask & ( 1 << i ) ) != 0 ) && + ( Channels[i].Frequency == 0 ) ) + {// Trying to enable an undefined channel + status &= 0xFE; // Channel mask KO + } + } + } + } + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = KR920_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = &chMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = KR920_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = KR920_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = KR920_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Set the channels mask to a default value + memset( ChannelsMask, 0, sizeof( ChannelsMask ) ); + // Update the channels mask + ChannelsMask[0] = chMask; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYKR920::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + + // Verify radio frequency + if(_radio->check_rf_frequency( rxParamSetupReq->Frequency ) == false ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, KR920_RX_MIN_DATARATE, KR920_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, KR920_MIN_RX1_DR_OFFSET, KR920_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYKR920::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + uint8_t status = 0x03; + ChannelAddParams_t channelAdd; + ChannelRemoveParams_t channelRemove; + + if( newChannelReq->NewChannel->Frequency == 0 ) + { + channelRemove.ChannelId = newChannelReq->ChannelId; + + // Remove + if( remove_channel( &channelRemove ) == false ) + { + status &= 0xFC; + } + } + else + { + channelAdd.NewChannel = newChannelReq->NewChannel; + channelAdd.ChannelId = newChannelReq->ChannelId; + + switch( add_channel( &channelAdd ) ) + { + case LORAMAC_STATUS_OK: + { + break; + } + case LORAMAC_STATUS_FREQUENCY_INVALID: + { + status &= 0xFE; + break; + } + case LORAMAC_STATUS_DATARATE_INVALID: + { + status &= 0xFD; + break; + } + case LORAMAC_STATUS_FREQ_AND_DR_INVALID: + { + status &= 0xFC; + break; + } + default: + { + status &= 0xFC; + break; + } + } + } + + return status; +} + +int8_t LoRaPHYKR920::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYKR920::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + uint8_t status = 0x03; + + // Verify if the frequency is supported + if( VerifyTxFreq( dlChannelReq->Rx1Frequency, _radio ) == false ) + { + status &= 0xFE; + } + + // Verify if an uplink frequency exists + if( Channels[dlChannelReq->ChannelId].Frequency == 0 ) + { + status &= 0xFD; + } + + // Apply Rx1 frequency, if the status is OK + if( status == 0x03 ) + { + Channels[dlChannelReq->ChannelId].Rx1Frequency = dlChannelReq->Rx1Frequency; + } + + return status; +} + +int8_t LoRaPHYKR920::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + if( ( alternateDr->NbTrials % 48 ) == 0 ) + { + datarate = DR_0; + } + else if( ( alternateDr->NbTrials % 32 ) == 0 ) + { + datarate = DR_1; + } + else if( ( alternateDr->NbTrials % 24 ) == 0 ) + { + datarate = DR_2; + } + else if( ( alternateDr->NbTrials % 16 ) == 0 ) + { + datarate = DR_3; + } + else if( ( alternateDr->NbTrials % 8 ) == 0 ) + { + datarate = DR_4; + } + else + { + datarate = DR_5; + } + return datarate; +} + +void LoRaPHYKR920::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYKR920::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t channelNext = 0; + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[KR920_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + if( num_active_channels( ChannelsMask, 0, 1 ) == 0 ) + { // Reactivate default channels + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, KR920_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Joined, nextChanParams->Datarate, + ChannelsMask, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + for( uint8_t i = 0, j = get_random( 0, nbEnabledChannels - 1 ); i < KR920_MAX_NB_CHANNELS; i++ ) + { + channelNext = enabledChannels[j]; + j = ( j + 1 ) % nbEnabledChannels; + + // Perform carrier sense for KR920_CARRIER_SENSE_TIME + // If the channel is free, we can stop the LBT mechanism + if( _radio->perform_carrier_sense( MODEM_LORA, + Channels[channelNext].Frequency, + KR920_RSSI_FREE_TH, + KR920_CARRIER_SENSE_TIME ) == true ) + { + // Free channel found + *channel = channelNext; + *time = 0; + return true; + } + } + return false; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel, restore defaults + ChannelsMask[0] |= LC( 1 ) + LC( 2 ) + LC( 3 ); + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYKR920::add_channel(ChannelAddParams_t* channelAdd) +{ + uint8_t band = 0; + bool drInvalid = false; + bool freqInvalid = false; + uint8_t id = channelAdd->ChannelId; + + if( id >= KR920_MAX_NB_CHANNELS ) + { + return LORAMAC_STATUS_PARAMETER_INVALID; + } + + // Validate the datarate range + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Min, KR920_TX_MIN_DATARATE, KR920_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( val_in_range( channelAdd->NewChannel->DrRange.Fields.Max, KR920_TX_MIN_DATARATE, KR920_TX_MAX_DATARATE ) == 0 ) + { + drInvalid = true; + } + if( channelAdd->NewChannel->DrRange.Fields.Min > channelAdd->NewChannel->DrRange.Fields.Max ) + { + drInvalid = true; + } + + // Default channels don't accept all values + if( id < KR920_NUMB_DEFAULT_CHANNELS ) + { + // All datarates are supported + // We are not allowed to change the frequency + if( channelAdd->NewChannel->Frequency != Channels[id].Frequency ) + { + freqInvalid = true; + } + } + + // Check frequency + if( freqInvalid == false ) + { + if( VerifyTxFreq( channelAdd->NewChannel->Frequency, _radio ) == false ) + { + freqInvalid = true; + } + } + + // Check status + if( ( drInvalid == true ) && ( freqInvalid == true ) ) + { + return LORAMAC_STATUS_FREQ_AND_DR_INVALID; + } + if( drInvalid == true ) + { + return LORAMAC_STATUS_DATARATE_INVALID; + } + if( freqInvalid == true ) + { + return LORAMAC_STATUS_FREQUENCY_INVALID; + } + + memcpy( &(Channels[id]), channelAdd->NewChannel, sizeof( Channels[id] ) ); + Channels[id].Band = band; + ChannelsMask[0] |= ( 1 << id ); + return LORAMAC_STATUS_OK; +} + +bool LoRaPHYKR920::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + uint8_t id = channelRemove->ChannelId; + + if( id < KR920_NUMB_DEFAULT_CHANNELS ) + { + return false; + } + + // Remove the channel from the list of channels + const ChannelParams_t empty_channel = { 0, 0, { 0 }, 0 }; + Channels[id] = empty_channel; + + return disable_channel( ChannelsMask, id, KR920_MAX_NB_CHANNELS ); +} + +void LoRaPHYKR920::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + float maxEIRP = GetMaxEIRP( Channels[continuousWave->Channel].Frequency ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Take the minimum between the maxEIRP and continuousWave->MaxEirp. + // The value of continuousWave->MaxEirp could have changed during runtime, e.g. due to a MAC command. + maxEIRP = MIN( continuousWave->MaxEirp, maxEIRP ); + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, maxEIRP, continuousWave->AntennaGain ); + + _radio->set_tx_continuous_wave( frequency, phyTxPower, continuousWave->Timeout ); +} + +uint8_t LoRaPHYKR920::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset) +{ + int8_t datarate = dr - drOffset; + + if( datarate < 0 ) + { + datarate = DR_0; + } + return datarate; +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYKR920.h b/features/lorawan/lorastack/phy/LoRaPHYKR920.h new file mode 100644 index 0000000000..329bbd1511 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYKR920.h @@ -0,0 +1,359 @@ +/** + * @file LoRaPHYKR920.h + * + * @brief Implements LoRaPHY for Korean 920 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_KR920_H_ +#define MBED_OS_LORAPHY_KR920_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +/*! + * LoRaMac maximum number of channels + */ +#define KR920_MAX_NB_CHANNELS 16 + +/*! + * Maximum number of bands + */ +#define KR920_MAX_NB_BANDS 1 + +#define KR920_CHANNELS_MASK_SIZE 1 + + +class LoRaPHYKR920 : public LoRaPHY { + +public: + + LoRaPHYKR920(); + virtual ~LoRaPHYKR920(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval The structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to verify. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details, please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval The function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + + uint8_t CountNbOfEnabledChannels( bool joined, uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[KR920_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[KR920_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[KR920_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[KR920_CHANNELS_MASK_SIZE]; +}; + +#endif // MBED_OS_LORAPHY_KR920_H_ + diff --git a/features/lorawan/lorastack/phy/LoRaPHYUS915.cpp b/features/lorawan/lorastack/phy/LoRaPHYUS915.cpp new file mode 100644 index 0000000000..145d079de1 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYUS915.cpp @@ -0,0 +1,1037 @@ +/** + * @file LoRaPHUS915.cpp + * + * @brief Implements LoRaPHY for US 915 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYUS915.h" +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + + +/*! + * Minimal datarate that can be used by the node + */ +#define US915_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define US915_TX_MAX_DATARATE DR_4 + +/*! + * Minimal datarate that can be used by the node + */ +#define US915_RX_MIN_DATARATE DR_8 + +/*! + * Maximal datarate that can be used by the node + */ +#define US915_RX_MAX_DATARATE DR_13 + +/*! + * Default datarate used by the node + */ +#define US915_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define US915_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define US915_MAX_RX1_DR_OFFSET 3 + +/*! + * Default Rx1 receive datarate offset + */ +#define US915_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define US915_MIN_TX_POWER TX_POWER_10 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define US915_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define US915_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max ERP + */ +#define US915_DEFAULT_MAX_ERP 30.0f + +/*! + * ADR Ack limit + */ +#define US915_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define US915_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define US915_DUTY_CYCLE_ENABLED 0 + +/*! + * Maximum RX window duration + */ +#define US915_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define US915_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define US915_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define US915_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define US915_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define US915_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define US915_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define US915_ACK_TIMEOUT_RND 1000 + +/*! + * Second reception window channel frequency definition. + */ +#define US915_RX_WND_2_FREQ 923300000 + +/*! + * Second reception window channel datarate definition. + */ +#define US915_RX_WND_2_DR DR_8 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define US915_BAND0 { 1, US915_MAX_TX_POWER, 0, 0 } // 100.0 % + +/*! + * Defines the first channel for RX window 1 for US band + */ +#define US915_FIRST_RX1_CHANNEL ( (uint32_t) 923300000 ) + +/*! + * Defines the last channel for RX window 1 for US band + */ +#define US915_LAST_RX1_CHANNEL ( (uint32_t) 927500000 ) + +/*! + * Defines the step width of the channels for RX window 1 + */ +#define US915_STEPWIDTH_RX1_CHANNEL ( (uint32_t) 600000 ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesUS915[] = { 10, 9, 8, 7, 8, 0, 0, 0, 12, 11, 10, 9, 8, 7, 0, 0 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsUS915[] = { 125000, 125000, 125000, 125000, 500000, 0, 0, 0, 500000, 500000, 500000, 500000, 500000, 500000, 0, 0 }; + +/*! + * Up/Down link data rates offset definition + */ +static const int8_t DatarateOffsetsUS915[5][4] = +{ + { DR_10, DR_9 , DR_8 , DR_8 }, // DR_0 + { DR_11, DR_10, DR_9 , DR_8 }, // DR_1 + { DR_12, DR_11, DR_10, DR_9 }, // DR_2 + { DR_13, DR_12, DR_11, DR_10 }, // DR_3 + { DR_13, DR_13, DR_12, DR_11 }, // DR_4 +}; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateUS915[] = { 11, 53, 125, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterUS915[] = { 11, 53, 125, 242, 242, 0, 0, 0, 33, 109, 222, 222, 222, 222, 0, 0 }; + + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsUS915[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +int8_t LoRaPHYUS915::LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + if( datarate == DR_4 ) + {// Limit tx power to max 26dBm + txPowerResult = MAX( txPower, TX_POWER_2 ); + } + else + { + if( num_active_channels( channelsMask, 0, 4 ) < 50 ) + {// Limit tx power to max 21dBm + txPowerResult = MAX( txPower, TX_POWER_5 ); + } + } + return txPowerResult; +} + +uint8_t LoRaPHYUS915::CountNbOfEnabledChannels( uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < US915_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYUS915::LoRaPHYUS915() +{ + const Band_t band0 = US915_BAND0; + Bands[0] = band0; +} + +LoRaPHYUS915::~LoRaPHYUS915() +{ +} + +PhyParam_t LoRaPHYUS915::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = US915_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = US915_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = US915_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, US915_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = US915_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateUS915[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterUS915[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = US915_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = US915_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = US915_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = US915_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = US915_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = US915_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = US915_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( US915_ACKTIMEOUT + get_random( -US915_ACK_TIMEOUT_RND, US915_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = US915_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = US915_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = US915_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = US915_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = 0; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 2; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYUS915::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYUS915::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + // 125 kHz channels + for( uint8_t i = 0; i < US915_MAX_NB_CHANNELS - 8; i++ ) + { + Channels[i].Frequency = 902300000 + i * 200000; + Channels[i].DrRange.Value = ( DR_3 << 4 ) | DR_0; + Channels[i].Band = 0; + } + // 500 kHz channels + for( uint8_t i = US915_MAX_NB_CHANNELS - 8; i < US915_MAX_NB_CHANNELS; i++ ) + { + Channels[i].Frequency = 903000000 + ( i - ( US915_MAX_NB_CHANNELS - 8 ) ) * 1600000; + Channels[i].DrRange.Value = ( DR_4 << 4 ) | DR_4; + Channels[i].Band = 0; + } + + // ChannelsMask + ChannelsDefaultMask[0] = 0xFFFF; + ChannelsDefaultMask[1] = 0xFFFF; + ChannelsDefaultMask[2] = 0xFFFF; + ChannelsDefaultMask[3] = 0xFFFF; + ChannelsDefaultMask[4] = 0x00FF; + ChannelsDefaultMask[5] = 0x0000; + + // Copy channels default mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 6 ); + + // Copy into channels mask remaining + copy_channel_mask( ChannelsMaskRemaining, ChannelsMask, 6 ); + break; + } + case INIT_TYPE_RESTORE: + { + // Copy channels default mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 6 ); + + for( uint8_t i = 0; i < 6; i++ ) + { // Copy-And the channels mask + ChannelsMaskRemaining[i] &= ChannelsMask[i]; + } + break; + } + default: + { + break; + } + } +} + +bool LoRaPHYUS915::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, US915_TX_MIN_DATARATE, US915_TX_MAX_DATARATE ); + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, US915_RX_MIN_DATARATE, US915_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, US915_MAX_TX_POWER, US915_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return US915_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 2 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYUS915::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + return; +} + +bool LoRaPHYUS915::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + uint8_t nbChannels = num_active_channels( chanMaskSet->ChannelsMaskIn, 0, 4 ); + + // Check the number of active channels + if( ( nbChannels < 2 ) && + ( nbChannels > 0 ) ) + { + return false; + } + + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 6 ); + + for( uint8_t i = 0; i < 6; i++ ) + { // Copy-And the channels mask + ChannelsMaskRemaining[i] &= ChannelsMask[i]; + } + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 6 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYUS915::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == US915_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= US915_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = US915_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( US915_ADR_ACK_LIMIT + US915_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % US915_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + datarate = phyParam.Value; + + if( datarate == US915_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ChannelsMask[0] = 0xFFFF; + ChannelsMask[1] = 0xFFFF; + ChannelsMask[2] = 0xFFFF; + ChannelsMask[3] = 0xFFFF; + ChannelsMask[4] = 0x00FF; + ChannelsMask[5] = 0x0000; + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYUS915::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, US915_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + tSymbol = compute_symb_timeout_lora( DataratesUS915[rxConfigParams->Datarate], BandwidthsUS915[rxConfigParams->Datarate] ); + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYUS915::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if(_radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = US915_FIRST_RX1_CHANNEL + ( rxConfig->Channel % 8 ) * US915_STEPWIDTH_RX1_CHANNEL; + } + + // Read the physical datarate from the datarates table + phyDr = DataratesUS915[dr]; + + _radio->set_channel( frequency ); + + // Radio configuration + _radio->set_rx_config( MODEM_LORA, rxConfig->Bandwidth, phyDr, 1, 0, 8, + rxConfig->WindowTimeout, false, 0, false, 0, 0, true, + rxConfig->RxContinuous ); + + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterUS915[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateUS915[dr]; + } + _radio->set_max_payload_length( MODEM_LORA, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD ); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYUS915::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + int8_t phyDr = DataratesUS915[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, US915_DEFAULT_MAX_ERP, 0 ); + + // Setup the radio frequency + _radio->set_channel( Channels[txConfig->Channel].Frequency ); + + _radio->set_tx_config( MODEM_LORA, phyTxPower, 0, bandwidth, phyDr, 1, 8, + false, true, 0, 0, false, 3000 ); + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length( MODEM_LORA, txConfig->PktLen ); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air( MODEM_LORA, txConfig->PktLen ); + *txPower = txPowerLimited; + + return true; +} + +uint8_t LoRaPHYUS915::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t channelsMask[6] = { 0, 0, 0, 0, 0, 0 }; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + // Initialize local copy of channels mask + copy_channel_mask( channelsMask, ChannelsMask, 6 ); + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + if( linkAdrParams.ChMaskCtrl == 6 ) + { + // Enable all 125 kHz channels + channelsMask[0] = 0xFFFF; + channelsMask[1] = 0xFFFF; + channelsMask[2] = 0xFFFF; + channelsMask[3] = 0xFFFF; + // Apply chMask to channels 64 to 71 + channelsMask[4] = linkAdrParams.ChMask; + } + else if( linkAdrParams.ChMaskCtrl == 7 ) + { + // Disable all 125 kHz channels + channelsMask[0] = 0x0000; + channelsMask[1] = 0x0000; + channelsMask[2] = 0x0000; + channelsMask[3] = 0x0000; + // Apply chMask to channels 64 to 71 + channelsMask[4] = linkAdrParams.ChMask; + } + else if( linkAdrParams.ChMaskCtrl == 5 ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + channelsMask[linkAdrParams.ChMaskCtrl] = linkAdrParams.ChMask; + } + } + + // FCC 15.247 paragraph F mandates to hop on at least 2 125 kHz channels + if( ( linkAdrParams.Datarate < DR_4 ) && ( num_active_channels( channelsMask, 0, 4 ) < 2 ) ) + { + status &= 0xFE; // Channel mask KO + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = US915_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = channelsMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = US915_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = US915_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = US915_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Copy Mask + copy_channel_mask( ChannelsMask, channelsMask, 6 ); + + ChannelsMaskRemaining[0] &= ChannelsMask[0]; + ChannelsMaskRemaining[1] &= ChannelsMask[1]; + ChannelsMaskRemaining[2] &= ChannelsMask[2]; + ChannelsMaskRemaining[3] &= ChannelsMask[3]; + ChannelsMaskRemaining[4] = ChannelsMask[4]; + ChannelsMaskRemaining[5] = ChannelsMask[5]; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYUS915::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + uint32_t freq = rxParamSetupReq->Frequency; + + // Verify radio frequency + if( ( _radio->check_rf_frequency( freq ) == false ) || + ( freq < US915_FIRST_RX1_CHANNEL ) || + ( freq > US915_LAST_RX1_CHANNEL ) || + ( ( ( freq - ( uint32_t ) US915_FIRST_RX1_CHANNEL ) % ( uint32_t ) US915_STEPWIDTH_RX1_CHANNEL ) != 0 ) ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, US915_RX_MIN_DATARATE, US915_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + if( ( val_in_range( rxParamSetupReq->Datarate, DR_5, DR_7 ) == 1 ) || + ( rxParamSetupReq->Datarate > DR_13 ) ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, US915_MIN_RX1_DR_OFFSET, US915_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYUS915::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + // Datarate and frequency KO + return 0; +} + +int8_t LoRaPHYUS915::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYUS915::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + return 0; +} + +int8_t LoRaPHYUS915::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + // Re-enable 500 kHz default channels + ChannelsMask[4] = 0x00FF; + + if( ( alternateDr->NbTrials & 0x01 ) == 0x01 ) + { + datarate = DR_4; + } + else + { + datarate = DR_0; + } + return datarate; +} + +void LoRaPHYUS915::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYUS915::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[US915_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + // Count 125kHz channels + if( num_active_channels( ChannelsMaskRemaining, 0, 4 ) == 0 ) + { // Reactivate default channels + copy_channel_mask( ChannelsMaskRemaining, ChannelsMask, 4 ); + } + // Check other channels + if( nextChanParams->Datarate >= DR_4 ) + { + if( ( ChannelsMaskRemaining[4] & 0x00FF ) == 0 ) + { + ChannelsMaskRemaining[4] = ChannelsMask[4]; + } + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, US915_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Datarate, + ChannelsMaskRemaining, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + // We found a valid channel + *channel = enabledChannels[get_random( 0, nbEnabledChannels - 1 )]; + // Disable the channel in the mask + disable_channel( ChannelsMaskRemaining, *channel, US915_MAX_NB_CHANNELS - 8 ); + + *time = 0; + return true; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYUS915::add_channel(ChannelAddParams_t* channelAdd) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +bool LoRaPHYUS915::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +void LoRaPHYUS915::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, US915_DEFAULT_MAX_ERP, 0 ); + + _radio->set_tx_continuous_wave( frequency, phyTxPower, continuousWave->Timeout ); +} + +uint8_t LoRaPHYUS915::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, + int8_t drOffset) +{ + int8_t datarate = DatarateOffsetsUS915[dr][drOffset]; + + if( datarate < 0 ) + { + datarate = DR_0; + } + return datarate; +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYUS915.h b/features/lorawan/lorastack/phy/LoRaPHYUS915.h new file mode 100644 index 0000000000..3c97014c4e --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYUS915.h @@ -0,0 +1,365 @@ +/** + * @file LoRaPHYUS915.h + * + * @brief Implements LoRaPHY for US 915 MHz band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHYUS_915_H_ +#define MBED_OS_LORAPHYUS_915_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + +/*! + * LoRaMac maximum number of channels + */ +#define US915_MAX_NB_CHANNELS 72 + +/*! + * LoRaMac maximum number of bands + */ +#define US915_MAX_NB_BANDS 1 + +#define US915_CHANNELS_MASK_SIZE 6 + + +class LoRaPHYUS915 : public LoRaPHY { + +public: + + LoRaPHYUS915(); + virtual ~LoRaPHYUS915(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval The structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to verify. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details, please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval The function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ); + uint8_t CountNbOfEnabledChannels( uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[US915_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[US915_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[US915_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels remaining + */ + uint16_t ChannelsMaskRemaining[US915_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[US915_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_US915_H_ */ diff --git a/features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.cpp b/features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.cpp new file mode 100644 index 0000000000..4137f224e9 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.cpp @@ -0,0 +1,1127 @@ +/** + * @file LoRaPHYUS915Hybrid.cpp + * + * @brief Implements LoRaPHY for US 915 MHz Hybrid band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 "LoRaPHYUS915Hybrid.h" + +#include "lora_phy_ds.h" +#include "LoRaRadio.h" + + +/*! + * Minimal datarate that can be used by the node + */ +#define US915_HYBRID_TX_MIN_DATARATE DR_0 + +/*! + * Maximal datarate that can be used by the node + */ +#define US915_HYBRID_TX_MAX_DATARATE DR_4 + +/*! + * Minimal datarate that can be used by the node + */ +#define US915_HYBRID_RX_MIN_DATARATE DR_8 + +/*! + * Maximal datarate that can be used by the node + */ +#define US915_HYBRID_RX_MAX_DATARATE DR_13 + +/*! + * Default datarate used by the node + */ +#define US915_HYBRID_DEFAULT_DATARATE DR_0 + +/*! + * Minimal Rx1 receive datarate offset + */ +#define US915_HYBRID_MIN_RX1_DR_OFFSET 0 + +/*! + * Maximal Rx1 receive datarate offset + */ +#define US915_HYBRID_MAX_RX1_DR_OFFSET 3 + +/*! + * Default Rx1 receive datarate offset + */ +#define US915_HYBRID_DEFAULT_RX1_DR_OFFSET 0 + +/*! + * Minimal Tx output power that can be used by the node + */ +#define US915_HYBRID_MIN_TX_POWER TX_POWER_10 + +/*! + * Maximal Tx output power that can be used by the node + */ +#define US915_HYBRID_MAX_TX_POWER TX_POWER_0 + +/*! + * Default Tx output power used by the node + */ +#define US915_HYBRID_DEFAULT_TX_POWER TX_POWER_0 + +/*! + * Default Max ERP + */ +#define US915_HYBRID_DEFAULT_MAX_ERP 30.0f + +/*! + * ADR Ack limit + */ +#define US915_HYBRID_ADR_ACK_LIMIT 64 + +/*! + * ADR Ack delay + */ +#define US915_HYBRID_ADR_ACK_DELAY 32 + +/*! + * Enabled or disabled the duty cycle + */ +#define US915_HYBRID_DUTY_CYCLE_ENABLED 0 + +/*! + * Maximum RX window duration + */ +#define US915_HYBRID_MAX_RX_WINDOW 3000 + +/*! + * Receive delay 1 + */ +#define US915_HYBRID_RECEIVE_DELAY1 1000 + +/*! + * Receive delay 2 + */ +#define US915_HYBRID_RECEIVE_DELAY2 2000 + +/*! + * Join accept delay 1 + */ +#define US915_HYBRID_JOIN_ACCEPT_DELAY1 5000 + +/*! + * Join accept delay 2 + */ +#define US915_HYBRID_JOIN_ACCEPT_DELAY2 6000 + +/*! + * Maximum frame counter gap + */ +#define US915_HYBRID_MAX_FCNT_GAP 16384 + +/*! + * Ack timeout + */ +#define US915_HYBRID_ACKTIMEOUT 2000 + +/*! + * Random ack timeout limits + */ +#define US915_HYBRID_ACK_TIMEOUT_RND 1000 + +/*! + * Second reception window channel frequency definition. + */ +#define US915_HYBRID_RX_WND_2_FREQ 923300000 + +/*! + * Second reception window channel datarate definition. + */ +#define US915_HYBRID_RX_WND_2_DR DR_8 + +/*! + * Band 0 definition + * { DutyCycle, TxMaxPower, LastTxDoneTime, TimeOff } + */ +#define US915_HYBRID_BAND0 { 1, US915_HYBRID_MAX_TX_POWER, 0, 0 } // 100.0 % + +/*! + * Defines the first channel for RX window 1 for US band + */ +#define US915_HYBRID_FIRST_RX1_CHANNEL ( (uint32_t) 923300000 ) + +/*! + * Defines the last channel for RX window 1 for US band + */ +#define US915_HYBRID_LAST_RX1_CHANNEL ( (uint32_t) 927500000 ) + +/*! + * Defines the step width of the channels for RX window 1 + */ +#define US915_HYBRID_STEPWIDTH_RX1_CHANNEL ( (uint32_t) 600000 ) + +/*! + * Data rates table definition + */ +static const uint8_t DataratesUS915_HYBRID[] = { 10, 9, 8, 7, 8, 0, 0, 0, 12, 11, 10, 9, 8, 7, 0, 0 }; + +/*! + * Bandwidths table definition in Hz + */ +static const uint32_t BandwidthsUS915_HYBRID[] = { 125000, 125000, 125000, 125000, 500000, 0, 0, 0, 500000, 500000, 500000, 500000, 500000, 500000, 0, 0 }; + +/*! + * Up/Down link data rates offset definition + */ +static const int8_t DatarateOffsetsUS915_HYBRID[5][4] = +{ + { DR_10, DR_9 , DR_8 , DR_8 }, // DR_0 + { DR_11, DR_10, DR_9 , DR_8 }, // DR_1 + { DR_12, DR_11, DR_10, DR_9 }, // DR_2 + { DR_13, DR_12, DR_11, DR_10 }, // DR_3 + { DR_13, DR_13, DR_12, DR_11 }, // DR_4 +}; + +/*! + * Maximum payload with respect to the datarate index. Cannot operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateUS915_HYBRID[] = { 11, 53, 125, 242, 242, 0, 0, 0, 53, 129, 242, 242, 242, 242, 0, 0 }; + +/*! + * Maximum payload with respect to the datarate index. Can operate with repeater. + */ +static const uint8_t MaxPayloadOfDatarateRepeaterUS915_HYBRID[] = { 11, 53, 125, 242, 242, 0, 0, 0, 33, 109, 222, 222, 222, 222, 0, 0 }; + + +// Static functions +static int8_t GetNextLowerTxDr( int8_t dr, int8_t minDr ) +{ + uint8_t nextLowerDr = 0; + + if( dr == minDr ) + { + nextLowerDr = minDr; + } + else + { + nextLowerDr = dr - 1; + } + return nextLowerDr; +} + +static uint32_t GetBandwidth( uint32_t drIndex ) +{ + switch( BandwidthsUS915_HYBRID[drIndex] ) + { + default: + case 125000: + return 0; + case 250000: + return 1; + case 500000: + return 2; + } +} + +static void ReenableChannels( uint16_t mask, uint16_t* channelsMask ) +{ + uint16_t blockMask = mask; + + for( uint8_t i = 0, j = 0; i < 4; i++, j += 2 ) + { + channelsMask[i] = 0; + if( ( blockMask & ( 1 << j ) ) != 0 ) + { + channelsMask[i] |= 0x00FF; + } + if( ( blockMask & ( 1 << ( j + 1 ) ) ) != 0 ) + { + channelsMask[i] |= 0xFF00; + } + } + channelsMask[4] = blockMask; + channelsMask[5] = 0x0000; +} + +static uint8_t CountBits( uint16_t mask, uint8_t nbBits ) +{ + uint8_t nbActiveBits = 0; + + for( uint8_t j = 0; j < nbBits; j++ ) + { + if( ( mask & ( 1 << j ) ) == ( 1 << j ) ) + { + nbActiveBits++; + } + } + return nbActiveBits; +} + +int8_t LoRaPHYUS915Hybrid::LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ) +{ + int8_t txPowerResult = txPower; + + // Limit tx power to the band max + txPowerResult = MAX( txPower, maxBandTxPower ); + + if( datarate == DR_4 ) + {// Limit tx power to max 26dBm + txPowerResult = MAX( txPower, TX_POWER_2 ); + } + else + { + if( num_active_channels( channelsMask, 0, 4 ) < 50 ) + {// Limit tx power to max 21dBm + txPowerResult = MAX( txPower, TX_POWER_5 ); + } + } + return txPowerResult; +} + +static bool ValidateChannelsMask( uint16_t* channelsMask ) +{ + bool chanMaskState = false; + uint16_t block1 = 0; + uint16_t block2 = 0; + uint8_t index = 0; + uint16_t channelsMaskCpy[6]; + + // Copy channels mask to not change the input + for( uint8_t i = 0; i < 4; i++ ) + { + channelsMaskCpy[i] = channelsMask[i]; + } + + for( uint8_t i = 0; i < 4; i++ ) + { + block1 = channelsMaskCpy[i] & 0x00FF; + block2 = channelsMaskCpy[i] & 0xFF00; + + if( CountBits( block1, 16 ) > 5 ) + { + channelsMaskCpy[i] &= block1; + channelsMaskCpy[4] = 1 << ( i * 2 ); + chanMaskState = true; + index = i; + break; + } + else if( CountBits( block2, 16 ) > 5 ) + { + channelsMaskCpy[i] &= block2; + channelsMaskCpy[4] = 1 << ( i * 2 + 1 ); + chanMaskState = true; + index = i; + break; + } + } + + // Do only change the channel mask, if we have found a valid block. + if( chanMaskState == true ) + { + // Copy channels mask back again + for( uint8_t i = 0; i < 4; i++ ) + { + channelsMask[i] = channelsMaskCpy[i]; + + if( i != index ) + { + channelsMask[i] = 0; + } + } + channelsMask[4] = channelsMaskCpy[4]; + } + return chanMaskState; +} + +uint8_t LoRaPHYUS915Hybrid::CountNbOfEnabledChannels( uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTransmission = 0; + + for( uint8_t i = 0, k = 0; i < US915_HYBRID_MAX_NB_CHANNELS; i += 16, k++ ) + { + for( uint8_t j = 0; j < 16; j++ ) + { + if( ( channelsMask[k] & ( 1 << j ) ) != 0 ) + { + if( channels[i + j].Frequency == 0 ) + { // Check if the channel is enabled + continue; + } + if( val_in_range( datarate, channels[i + j].DrRange.Fields.Min, + channels[i + j].DrRange.Fields.Max ) == 0 ) + { // Check if the current channel selection supports the given datarate + continue; + } + if( bands[channels[i + j].Band].TimeOff > 0 ) + { // Check if the band is available for transmission + delayTransmission++; + continue; + } + enabledChannels[nbEnabledChannels++] = i + j; + } + } + } + + *delayTx = delayTransmission; + return nbEnabledChannels; +} + +LoRaPHYUS915Hybrid::LoRaPHYUS915Hybrid() +{ + const Band_t band0 = US915_HYBRID_BAND0; + Bands[0] = band0; +} + +LoRaPHYUS915Hybrid::~LoRaPHYUS915Hybrid() +{ +} + +PhyParam_t LoRaPHYUS915Hybrid::get_phy_params(GetPhyParams_t* getPhy) +{ + PhyParam_t phyParam = { 0 }; + + switch( getPhy->Attribute ) + { + case PHY_MIN_RX_DR: + { + phyParam.Value = US915_HYBRID_RX_MIN_DATARATE; + break; + } + case PHY_MIN_TX_DR: + { + phyParam.Value = US915_HYBRID_TX_MIN_DATARATE; + break; + } + case PHY_DEF_TX_DR: + { + phyParam.Value = US915_HYBRID_DEFAULT_DATARATE; + break; + } + case PHY_NEXT_LOWER_TX_DR: + { + phyParam.Value = GetNextLowerTxDr( getPhy->Datarate, US915_HYBRID_TX_MIN_DATARATE ); + break; + } + case PHY_DEF_TX_POWER: + { + phyParam.Value = US915_HYBRID_DEFAULT_TX_POWER; + break; + } + case PHY_MAX_PAYLOAD: + { + phyParam.Value = MaxPayloadOfDatarateUS915_HYBRID[getPhy->Datarate]; + break; + } + case PHY_MAX_PAYLOAD_REPEATER: + { + phyParam.Value = MaxPayloadOfDatarateRepeaterUS915_HYBRID[getPhy->Datarate]; + break; + } + case PHY_DUTY_CYCLE: + { + phyParam.Value = US915_HYBRID_DUTY_CYCLE_ENABLED; + break; + } + case PHY_MAX_RX_WINDOW: + { + phyParam.Value = US915_HYBRID_MAX_RX_WINDOW; + break; + } + case PHY_RECEIVE_DELAY1: + { + phyParam.Value = US915_HYBRID_RECEIVE_DELAY1; + break; + } + case PHY_RECEIVE_DELAY2: + { + phyParam.Value = US915_HYBRID_RECEIVE_DELAY2; + break; + } + case PHY_JOIN_ACCEPT_DELAY1: + { + phyParam.Value = US915_HYBRID_JOIN_ACCEPT_DELAY1; + break; + } + case PHY_JOIN_ACCEPT_DELAY2: + { + phyParam.Value = US915_HYBRID_JOIN_ACCEPT_DELAY2; + break; + } + case PHY_MAX_FCNT_GAP: + { + phyParam.Value = US915_HYBRID_MAX_FCNT_GAP; + break; + } + case PHY_ACK_TIMEOUT: + { + phyParam.Value = ( US915_HYBRID_ACKTIMEOUT + get_random( -US915_HYBRID_ACK_TIMEOUT_RND, US915_HYBRID_ACK_TIMEOUT_RND ) ); + break; + } + case PHY_DEF_DR1_OFFSET: + { + phyParam.Value = US915_HYBRID_DEFAULT_RX1_DR_OFFSET; + break; + } + case PHY_DEF_RX2_FREQUENCY: + { + phyParam.Value = US915_HYBRID_RX_WND_2_FREQ; + break; + } + case PHY_DEF_RX2_DR: + { + phyParam.Value = US915_HYBRID_RX_WND_2_DR; + break; + } + case PHY_CHANNELS_MASK: + { + phyParam.ChannelsMask = ChannelsMask; + break; + } + case PHY_CHANNELS_DEFAULT_MASK: + { + phyParam.ChannelsMask = ChannelsDefaultMask; + break; + } + case PHY_MAX_NB_CHANNELS: + { + phyParam.Value = US915_HYBRID_MAX_NB_CHANNELS; + break; + } + case PHY_CHANNELS: + { + phyParam.Channels = Channels; + break; + } + case PHY_DEF_UPLINK_DWELL_TIME: + case PHY_DEF_DOWNLINK_DWELL_TIME: + { + phyParam.Value = 0; + break; + } + case PHY_DEF_MAX_EIRP: + case PHY_DEF_ANTENNA_GAIN: + { + phyParam.fValue = 0; + break; + } + case PHY_NB_JOIN_TRIALS: + case PHY_DEF_NB_JOIN_TRIALS: + { + phyParam.Value = 2; + break; + } + default: + { + break; + } + } + + return phyParam; +} + +void LoRaPHYUS915Hybrid::set_band_tx_done(SetBandTxDoneParams_t* txDone) +{ + set_last_tx_done( txDone->Joined, &Bands[Channels[txDone->Channel].Band], txDone->LastTxDoneTime ); +} + +void LoRaPHYUS915Hybrid::load_defaults(InitType_t type) +{ + switch( type ) + { + case INIT_TYPE_INIT: + { + // Channels + // 125 kHz channels + for( uint8_t i = 0; i < US915_HYBRID_MAX_NB_CHANNELS - 8; i++ ) + { + Channels[i].Frequency = 902300000 + i * 200000; + Channels[i].DrRange.Value = ( DR_3 << 4 ) | DR_0; + Channels[i].Band = 0; + } + // 500 kHz channels + for( uint8_t i = US915_HYBRID_MAX_NB_CHANNELS - 8; i < US915_HYBRID_MAX_NB_CHANNELS; i++ ) + { + Channels[i].Frequency = 903000000 + ( i - ( US915_HYBRID_MAX_NB_CHANNELS - 8 ) ) * 1600000; + Channels[i].DrRange.Value = ( DR_4 << 4 ) | DR_4; + Channels[i].Band = 0; + } + + // ChannelsMask + ChannelsDefaultMask[0] = 0x00FF; + ChannelsDefaultMask[1] = 0x0000; + ChannelsDefaultMask[2] = 0x0000; + ChannelsDefaultMask[3] = 0x0000; + ChannelsDefaultMask[4] = 0x0001; + ChannelsDefaultMask[5] = 0x0000; + + // Copy channels default mask + copy_channel_mask( ChannelsMask, ChannelsDefaultMask, 6 ); + + // Copy into channels mask remaining + copy_channel_mask( ChannelsMaskRemaining, ChannelsMask, 6 ); + break; + } + case INIT_TYPE_RESTORE: + { + ReenableChannels( ChannelsDefaultMask[4], ChannelsMask ); + + for( uint8_t i = 0; i < 6; i++ ) + { // Copy-And the channels mask + ChannelsMaskRemaining[i] &= ChannelsMask[i]; + } + } + default: + { + break; + } + } +} + +bool LoRaPHYUS915Hybrid::verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute) +{ + switch( phyAttribute ) + { + case PHY_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, US915_HYBRID_TX_MIN_DATARATE, US915_HYBRID_TX_MAX_DATARATE ); + } + case PHY_DEF_TX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, DR_0, DR_5 ); + } + case PHY_RX_DR: + { + return val_in_range( verify->DatarateParams.Datarate, US915_HYBRID_RX_MIN_DATARATE, US915_HYBRID_RX_MAX_DATARATE ); + } + case PHY_DEF_TX_POWER: + case PHY_TX_POWER: + { + // Remark: switched min and max! + return val_in_range( verify->TxPower, US915_HYBRID_MAX_TX_POWER, US915_HYBRID_MIN_TX_POWER ); + } + case PHY_DUTY_CYCLE: + { + return US915_HYBRID_DUTY_CYCLE_ENABLED; + } + case PHY_NB_JOIN_TRIALS: + { + if( verify->NbJoinTrials < 2 ) + { + return false; + } + break; + } + default: + return false; + } + return true; +} + +void LoRaPHYUS915Hybrid::apply_cf_list(ApplyCFListParams_t* applyCFList) +{ + return; +} + +bool LoRaPHYUS915Hybrid::set_channel_mask(ChanMaskSetParams_t* chanMaskSet) +{ + uint8_t nbChannels = num_active_channels( chanMaskSet->ChannelsMaskIn, 0, 4 ); + + // Check the number of active channels + if( ( nbChannels < 2 ) && + ( nbChannels > 0 ) ) + { + return false; + } + + // Validate the channels mask + if( ValidateChannelsMask( chanMaskSet->ChannelsMaskIn ) == false ) + { + return false; + } + + switch( chanMaskSet->ChannelsMaskType ) + { + case CHANNELS_MASK: + { + copy_channel_mask( ChannelsMask, chanMaskSet->ChannelsMaskIn, 6 ); + + for( uint8_t i = 0; i < 6; i++ ) + { // Copy-And the channels mask + ChannelsMaskRemaining[i] &= ChannelsMask[i]; + } + break; + } + case CHANNELS_DEFAULT_MASK: + { + copy_channel_mask( ChannelsDefaultMask, chanMaskSet->ChannelsMaskIn, 6 ); + break; + } + default: + return false; + } + return true; +} + +bool LoRaPHYUS915Hybrid::get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter) +{ + bool adrAckReq = false; + int8_t datarate = adrNext->Datarate; + int8_t txPower = adrNext->TxPower; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + + // Report back the adr ack counter + *adrAckCounter = adrNext->AdrAckCounter; + + if( adrNext->AdrEnabled == true ) + { + if( datarate == US915_HYBRID_TX_MIN_DATARATE ) + { + *adrAckCounter = 0; + adrAckReq = false; + } + else + { + if( adrNext->AdrAckCounter >= US915_HYBRID_ADR_ACK_LIMIT ) + { + adrAckReq = true; + txPower = US915_HYBRID_MAX_TX_POWER; + } + else + { + adrAckReq = false; + } + if( adrNext->AdrAckCounter >= ( US915_HYBRID_ADR_ACK_LIMIT + US915_HYBRID_ADR_ACK_DELAY ) ) + { + if( ( adrNext->AdrAckCounter % US915_HYBRID_ADR_ACK_DELAY ) == 1 ) + { + // Decrease the datarate + getPhy.Attribute = PHY_NEXT_LOWER_TX_DR; + getPhy.Datarate = datarate; + getPhy.UplinkDwellTime = adrNext->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + datarate = phyParam.Value; + + if( datarate == US915_HYBRID_TX_MIN_DATARATE ) + { + // We must set adrAckReq to false as soon as we reach the lowest datarate + adrAckReq = false; + if( adrNext->UpdateChanMask == true ) + { + // Re-enable default channels + ReenableChannels( ChannelsMask[4], ChannelsMask ); + } + } + } + } + } + } + + *drOut = datarate; + *txPowOut = txPower; + return adrAckReq; +} + +void LoRaPHYUS915Hybrid::compute_rx_win_params(int8_t datarate, uint8_t minRxSymbols, + uint32_t rxError, RxConfigParams_t *rxConfigParams) +{ + double tSymbol = 0.0; + + // Get the datarate, perform a boundary check + rxConfigParams->Datarate = MIN( datarate, US915_HYBRID_RX_MAX_DATARATE ); + rxConfigParams->Bandwidth = GetBandwidth( rxConfigParams->Datarate ); + + tSymbol = compute_symb_timeout_lora( DataratesUS915_HYBRID[rxConfigParams->Datarate], BandwidthsUS915_HYBRID[rxConfigParams->Datarate] ); + + get_rx_window_params( tSymbol, minRxSymbols, rxError, RADIO_WAKEUP_TIME, &rxConfigParams->WindowTimeout, &rxConfigParams->WindowOffset ); +} + +bool LoRaPHYUS915Hybrid::rx_config(RxConfigParams_t* rxConfig, int8_t* datarate) +{ + int8_t dr = rxConfig->Datarate; + uint8_t maxPayload = 0; + int8_t phyDr = 0; + uint32_t frequency = rxConfig->Frequency; + + if( _radio->get_status() != RF_IDLE ) + { + return false; + } + + if( rxConfig->Window == 0 ) + { + // Apply window 1 frequency + frequency = US915_HYBRID_FIRST_RX1_CHANNEL + ( rxConfig->Channel % 8 ) * US915_HYBRID_STEPWIDTH_RX1_CHANNEL; + } + + // Read the physical datarate from the datarates table + phyDr = DataratesUS915_HYBRID[dr]; + + _radio->set_channel( frequency ); + + // Radio configuration + _radio->set_rx_config( MODEM_LORA, rxConfig->Bandwidth, phyDr, 1, 0, 8, rxConfig->WindowTimeout, false, 0, false, 0, 0, true, rxConfig->RxContinuous ); + + if( rxConfig->RepeaterSupport == true ) + { + maxPayload = MaxPayloadOfDatarateRepeaterUS915_HYBRID[dr]; + } + else + { + maxPayload = MaxPayloadOfDatarateUS915_HYBRID[dr]; + } + _radio->set_max_payload_length( MODEM_LORA, maxPayload + LORA_MAC_FRMPAYLOAD_OVERHEAD ); + + *datarate = (uint8_t) dr; + return true; +} + +bool LoRaPHYUS915Hybrid::tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir) +{ + int8_t phyDr = DataratesUS915_HYBRID[txConfig->Datarate]; + int8_t txPowerLimited = LimitTxPower( txConfig->TxPower, Bands[Channels[txConfig->Channel].Band].TxMaxPower, txConfig->Datarate, ChannelsMask ); + uint32_t bandwidth = GetBandwidth( txConfig->Datarate ); + int8_t phyTxPower = 0; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, US915_HYBRID_DEFAULT_MAX_ERP, 0 ); + + // Setup the radio frequency + _radio->set_channel( Channels[txConfig->Channel].Frequency ); + + _radio->set_tx_config( MODEM_LORA, phyTxPower, 0, bandwidth, phyDr, 1, 8, false, true, 0, 0, false, 3000 ); + + // Setup maximum payload lenght of the radio driver + _radio->set_max_payload_length( MODEM_LORA, txConfig->PktLen ); + // Get the time-on-air of the next tx frame + *txTimeOnAir = _radio->time_on_air( MODEM_LORA, txConfig->PktLen ); + *txPower = txPowerLimited; + + return true; +} + +uint8_t LoRaPHYUS915Hybrid::link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed) +{ + uint8_t status = 0x07; + RegionCommonLinkAdrParams_t linkAdrParams; + uint8_t nextIndex = 0; + uint8_t bytesProcessed = 0; + uint16_t channelsMask[6] = { 0, 0, 0, 0, 0, 0 }; + GetPhyParams_t getPhy; + PhyParam_t phyParam; + RegionCommonLinkAdrReqVerifyParams_t linkAdrVerifyParams; + + // Initialize local copy of channels mask + copy_channel_mask( channelsMask, ChannelsMask, 6 ); + + while( bytesProcessed < linkAdrReq->PayloadSize ) + { + nextIndex = parse_link_ADR_req( &( linkAdrReq->Payload[bytesProcessed] ), &linkAdrParams ); + + if( nextIndex == 0 ) + break; // break loop, since no more request has been found + + // Update bytes processed + bytesProcessed += nextIndex; + + // Revert status, as we only check the last ADR request for the channel mask KO + status = 0x07; + + if( linkAdrParams.ChMaskCtrl == 6 ) + { + // Enable all 125 kHz channels + channelsMask[0] = 0xFFFF; + channelsMask[1] = 0xFFFF; + channelsMask[2] = 0xFFFF; + channelsMask[3] = 0xFFFF; + // Apply chMask to channels 64 to 71 + channelsMask[4] = linkAdrParams.ChMask; + } + else if( linkAdrParams.ChMaskCtrl == 7 ) + { + // Disable all 125 kHz channels + channelsMask[0] = 0x0000; + channelsMask[1] = 0x0000; + channelsMask[2] = 0x0000; + channelsMask[3] = 0x0000; + // Apply chMask to channels 64 to 71 + channelsMask[4] = linkAdrParams.ChMask; + } + else if( linkAdrParams.ChMaskCtrl == 5 ) + { + // RFU + status &= 0xFE; // Channel mask KO + } + else + { + channelsMask[linkAdrParams.ChMaskCtrl] = linkAdrParams.ChMask; + } + } + + // FCC 15.247 paragraph F mandates to hop on at least 2 125 kHz channels + if( ( linkAdrParams.Datarate < DR_4 ) && ( num_active_channels( channelsMask, 0, 4 ) < 2 ) ) + { + status &= 0xFE; // Channel mask KO + } + + if( ValidateChannelsMask( channelsMask ) == false ) + { + status &= 0xFE; // Channel mask KO + } + + // Get the minimum possible datarate + getPhy.Attribute = PHY_MIN_TX_DR; + getPhy.UplinkDwellTime = linkAdrReq->UplinkDwellTime; + phyParam = get_phy_params( &getPhy ); + + linkAdrVerifyParams.Status = status; + linkAdrVerifyParams.AdrEnabled = linkAdrReq->AdrEnabled; + linkAdrVerifyParams.Datarate = linkAdrParams.Datarate; + linkAdrVerifyParams.TxPower = linkAdrParams.TxPower; + linkAdrVerifyParams.NbRep = linkAdrParams.NbRep; + linkAdrVerifyParams.CurrentDatarate = linkAdrReq->CurrentDatarate; + linkAdrVerifyParams.CurrentTxPower = linkAdrReq->CurrentTxPower; + linkAdrVerifyParams.CurrentNbRep = linkAdrReq->CurrentNbRep; + linkAdrVerifyParams.NbChannels = US915_HYBRID_MAX_NB_CHANNELS; + linkAdrVerifyParams.ChannelsMask = channelsMask; + linkAdrVerifyParams.MinDatarate = ( int8_t )phyParam.Value; + linkAdrVerifyParams.MaxDatarate = US915_HYBRID_TX_MAX_DATARATE; + linkAdrVerifyParams.Channels = Channels; + linkAdrVerifyParams.MinTxPower = US915_HYBRID_MIN_TX_POWER; + linkAdrVerifyParams.MaxTxPower = US915_HYBRID_MAX_TX_POWER; + + // Verify the parameters and update, if necessary + status = verify_link_ADR_req( &linkAdrVerifyParams, &linkAdrParams.Datarate, &linkAdrParams.TxPower, &linkAdrParams.NbRep ); + + // Update channelsMask if everything is correct + if( status == 0x07 ) + { + // Copy Mask + copy_channel_mask( ChannelsMask, channelsMask, 6 ); + + ChannelsMaskRemaining[0] &= ChannelsMask[0]; + ChannelsMaskRemaining[1] &= ChannelsMask[1]; + ChannelsMaskRemaining[2] &= ChannelsMask[2]; + ChannelsMaskRemaining[3] &= ChannelsMask[3]; + ChannelsMaskRemaining[4] = ChannelsMask[4]; + ChannelsMaskRemaining[5] = ChannelsMask[5]; + } + + // Update status variables + *drOut = linkAdrParams.Datarate; + *txPowOut = linkAdrParams.TxPower; + *nbRepOut = linkAdrParams.NbRep; + *nbBytesParsed = bytesProcessed; + + return status; +} + +uint8_t LoRaPHYUS915Hybrid::setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq) +{ + uint8_t status = 0x07; + uint32_t freq = rxParamSetupReq->Frequency; + + // Verify radio frequency + if( ( _radio->check_rf_frequency( freq ) == false ) || + ( freq < US915_HYBRID_FIRST_RX1_CHANNEL ) || + ( freq > US915_HYBRID_LAST_RX1_CHANNEL ) || + ( ( ( freq - ( uint32_t ) US915_HYBRID_FIRST_RX1_CHANNEL ) % ( uint32_t ) US915_HYBRID_STEPWIDTH_RX1_CHANNEL ) != 0 ) ) + { + status &= 0xFE; // Channel frequency KO + } + + // Verify datarate + if( val_in_range( rxParamSetupReq->Datarate, US915_HYBRID_RX_MIN_DATARATE, US915_HYBRID_RX_MAX_DATARATE ) == 0 ) + { + status &= 0xFD; // Datarate KO + } + if( ( val_in_range( rxParamSetupReq->Datarate, DR_5, DR_7 ) == 1 ) || + ( rxParamSetupReq->Datarate > DR_13 ) ) + { + status &= 0xFD; // Datarate KO + } + + // Verify datarate offset + if( val_in_range( rxParamSetupReq->DrOffset, US915_HYBRID_MIN_RX1_DR_OFFSET, US915_HYBRID_MAX_RX1_DR_OFFSET ) == 0 ) + { + status &= 0xFB; // Rx1DrOffset range KO + } + + return status; +} + +uint8_t LoRaPHYUS915Hybrid::request_new_channel(NewChannelReqParams_t* newChannelReq) +{ + // Datarate and frequency KO + return 0; +} + +int8_t LoRaPHYUS915Hybrid::setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq) +{ + return -1; +} + +uint8_t LoRaPHYUS915Hybrid::dl_channel_request(DlChannelReqParams_t* dlChannelReq) +{ + return 0; +} + +int8_t LoRaPHYUS915Hybrid::get_alternate_DR(AlternateDrParams_t* alternateDr) +{ + int8_t datarate = 0; + + // Re-enable 500 kHz default channels + ReenableChannels( ChannelsMask[4], ChannelsMask ); + + if( ( alternateDr->NbTrials & 0x01 ) == 0x01 ) + { + datarate = DR_4; + } + else + { + datarate = DR_0; + } + return datarate; +} + +void LoRaPHYUS915Hybrid::calculate_backoff(CalcBackOffParams_t* calcBackOff) +{ + RegionCommonCalcBackOffParams_t calcBackOffParams; + + calcBackOffParams.Channels = Channels; + calcBackOffParams.Bands = Bands; + calcBackOffParams.LastTxIsJoinRequest = calcBackOff->LastTxIsJoinRequest; + calcBackOffParams.Joined = calcBackOff->Joined; + calcBackOffParams.DutyCycleEnabled = calcBackOff->DutyCycleEnabled; + calcBackOffParams.Channel = calcBackOff->Channel; + calcBackOffParams.ElapsedTime = calcBackOff->ElapsedTime; + calcBackOffParams.TxTimeOnAir = calcBackOff->TxTimeOnAir; + + get_DC_backoff( &calcBackOffParams ); +} + +bool LoRaPHYUS915Hybrid::set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff) +{ + uint8_t nbEnabledChannels = 0; + uint8_t delayTx = 0; + uint8_t enabledChannels[US915_HYBRID_MAX_NB_CHANNELS] = { 0 }; + TimerTime_t nextTxDelay = 0; + + // Count 125kHz channels + if( num_active_channels( ChannelsMaskRemaining, 0, 4 ) == 0 ) + { // Reactivate default channels + copy_channel_mask( ChannelsMaskRemaining, ChannelsMask, 4 ); + } + // Check other channels + if( nextChanParams->Datarate >= DR_4 ) + { + if( ( ChannelsMaskRemaining[4] & 0x00FF ) == 0 ) + { + ChannelsMaskRemaining[4] = ChannelsMask[4]; + } + } + + if( nextChanParams->AggrTimeOff <= TimerGetElapsedTime( nextChanParams->LastAggrTx ) ) + { + // Reset Aggregated time off + *aggregatedTimeOff = 0; + + // Update bands Time OFF + nextTxDelay = update_band_timeoff( nextChanParams->Joined, nextChanParams->DutyCycleEnabled, Bands, US915_HYBRID_MAX_NB_BANDS ); + + // Search how many channels are enabled + nbEnabledChannels = CountNbOfEnabledChannels( nextChanParams->Datarate, + ChannelsMaskRemaining, Channels, + Bands, enabledChannels, &delayTx ); + } + else + { + delayTx++; + nextTxDelay = nextChanParams->AggrTimeOff - TimerGetElapsedTime( nextChanParams->LastAggrTx ); + } + + if( nbEnabledChannels > 0 ) + { + // We found a valid channel + *channel = enabledChannels[get_random( 0, nbEnabledChannels - 1 )]; + // Disable the channel in the mask + disable_channel( ChannelsMaskRemaining, *channel, US915_HYBRID_MAX_NB_CHANNELS - 8 ); + + *time = 0; + return true; + } + else + { + if( delayTx > 0 ) + { + // Delay transmission due to AggregatedTimeOff or to a band time off + *time = nextTxDelay; + return true; + } + // Datarate not supported by any channel + *time = 0; + return false; + } +} + +LoRaMacStatus_t LoRaPHYUS915Hybrid::add_channel(ChannelAddParams_t* channelAdd) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +bool LoRaPHYUS915Hybrid::remove_channel(ChannelRemoveParams_t* channelRemove) +{ + return LORAMAC_STATUS_PARAMETER_INVALID; +} + +void LoRaPHYUS915Hybrid::set_tx_cont_mode(ContinuousWaveParams_t* continuousWave) +{ + int8_t txPowerLimited = LimitTxPower( continuousWave->TxPower, Bands[Channels[continuousWave->Channel].Band].TxMaxPower, continuousWave->Datarate, ChannelsMask ); + int8_t phyTxPower = 0; + uint32_t frequency = Channels[continuousWave->Channel].Frequency; + + // Calculate physical TX power + phyTxPower = compute_tx_power( txPowerLimited, US915_HYBRID_DEFAULT_MAX_ERP, 0 ); + + _radio->set_tx_continuous_wave( frequency, phyTxPower, continuousWave->Timeout ); +} + +uint8_t LoRaPHYUS915Hybrid::apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset) +{ + int8_t datarate = DatarateOffsetsUS915_HYBRID[dr][drOffset]; + + if( datarate < 0 ) + { + datarate = DR_0; + } + return datarate; +} diff --git a/features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.h b/features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.h new file mode 100644 index 0000000000..b84f571770 --- /dev/null +++ b/features/lorawan/lorastack/phy/LoRaPHYUS915Hybrid.h @@ -0,0 +1,366 @@ +/** + * @file LoRaPHYUS915Hybrid.h + * + * @brief Implements LoRaPHY for US 915 MHz Hybrid band + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * + * 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 + * + */ + +#ifndef MBED_OS_LORAPHY_US915HYBRID_H_ +#define MBED_OS_LORAPHY_US915_HYBRID_H_ + +#include "LoRaPHY.h" +#include "netsocket/LoRaRadio.h" + + +/*! + * LoRaMac maximum number of channels + */ +#define US915_HYBRID_MAX_NB_CHANNELS 72 + +/*! + * LoRaMac maximum number of bands + */ +#define US915_HYBRID_MAX_NB_BANDS 1 + +#define US915_HYBRID_CHANNELS_MASK_SIZE 6 + + +class LoRaPHYUS915Hybrid : public LoRaPHY { + +public: + + LoRaPHYUS915Hybrid(); + virtual ~LoRaPHYUS915Hybrid(); + + /*! + * \brief The function gets a value of a specific PHY attribute. + * + * \param [in] getPhy A pointer to the function parameters. + * + * \retval The structure containing the PHY parameter. + */ + virtual PhyParam_t get_phy_params(GetPhyParams_t* getPhy ); + + /*! + * \brief Updates the last TX done parameters of the current channel. + * + * \param [in] txDone A pointer to the function parameters. + */ + virtual void set_band_tx_done(SetBandTxDoneParams_t* txDone ); + + /*! + * \brief Initializes the channels masks and the channels. + * + * \param [in] type Sets the initialization type. + */ + virtual void load_defaults(InitType_t type ); + + /*! + * \brief Verifies a parameter. + * + * \param [in] verify A pointer to the function parameters. + * + * \param [in] phyAttribute The attribute to verify. + * + * \retval True, if the parameter is valid. + */ + virtual bool verify(VerifyParams_t* verify, PhyAttribute_t phyAttribute ); + + /*! + * \brief The function parses the input buffer and sets up the channels of the CF list. + * + * \param [in] applyCFList A pointer to the function parameters. + */ + virtual void apply_cf_list(ApplyCFListParams_t* applyCFList ); + + /*! + * \brief Sets a channels mask. + * + * \param [in] chanMaskSet A pointer to the function parameters. + * + * \retval True, if the channels mask could be set. + */ + virtual bool set_channel_mask(ChanMaskSetParams_t* chanMaskSet ); + + /*! + * \brief Calculates the next datarate to set, when ADR is on or off. + * + * \param [in] adrNext A pointer to the function parameters. + * + * \param [out] drOut The calculated datarate for the next TX. + * + * \param [out] txPowOut The TX power for the next TX. + * + * \param [out] adrAckCounter The calculated ADR acknowledgement counter. + * + * \retval True, if an ADR request should be performed. + */ + virtual bool get_next_ADR(AdrNextParams_t* adrNext, int8_t* drOut, + int8_t* txPowOut, uint32_t* adrAckCounter ); + + /*! + * \brief Configuration of the RX windows. + * + * \param [in] rxConfig A pointer to the function parameters. + * + * \param [out] datarate The datarate index set. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool rx_config(RxConfigParams_t* rxConfig, int8_t* datarate ); + + /* + * RX window precise timing + * + * For more details, please consult the following document, chapter 3.1.2. + * http://www.semtech.com/images/datasheet/SX1272_settings_for_LoRaWAN_v2.0.pdf + * or + * http://www.semtech.com/images/datasheet/SX1276_settings_for_LoRaWAN_v2.0.pdf + * + * Downlink start: T = Tx + 1s (+/- 20 us) + * | + * TRxEarly | TRxLate + * | | | + * | | +---+---+---+---+---+---+---+---+ + * | | | Latest Rx window | + * | | +---+---+---+---+---+---+---+---+ + * | | | + * +---+---+---+---+---+---+---+---+ + * | Earliest Rx window | + * +---+---+---+---+---+---+---+---+ + * | + * +---+---+---+---+---+---+---+---+ + *Downlink preamble 8 symbols | | | | | | | | | + * +---+---+---+---+---+---+---+---+ + * + * Worst case Rx window timings + * + * TRxLate = DEFAULT_MIN_RX_SYMBOLS * tSymbol - RADIO_WAKEUP_TIME + * TRxEarly = 8 - DEFAULT_MIN_RX_SYMBOLS * tSymbol - RxWindowTimeout - RADIO_WAKEUP_TIME + * + * TRxLate - TRxEarly = 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * + * RxOffset = ( TRxLate + TRxEarly ) / 2 + * + * RxWindowTimeout = ( 2 * DEFAULT_MIN_RX_SYMBOLS - 8 ) * tSymbol + 2 * DEFAULT_SYSTEM_MAX_RX_ERROR + * RxOffset = 4 * tSymbol - RxWindowTimeout / 2 - RADIO_WAKE_UP_TIME + * + * The minimum value of RxWindowTimeout must be 5 symbols which implies that the system always tolerates at least an error of 1.5 * tSymbol + */ + /*! + * Computes the RX window timeout and offset. + * + * \param [in] datarate The RX window datarate index to be used. + * + * \param [in] minRxSymbols The minimum number of symbols required to detect an RX frame. + * + * \param [in] rxError The system maximum timing error of the receiver in milliseconds. + * The receiver will turn on in a [-rxError : +rxError] ms + * interval around RxOffset. + * + * \param [out] rxConfigParams Returns the updated WindowTimeout and WindowOffset fields. + */ + virtual void compute_rx_win_params(int8_t datarate, + uint8_t minRxSymbols, + uint32_t rxError, + RxConfigParams_t *rxConfigParams); + + /*! + * \brief TX configuration. + * + * \param [in] txConfig A pointer to the function parameters. + * + * \param [out] txPower The TX power index set. + * + * \param [out] txTimeOnAir The time-on-air of the frame. + * + * \retval True, if the configuration was applied successfully. + */ + virtual bool tx_config(TxConfigParams_t* txConfig, int8_t* txPower, + TimerTime_t* txTimeOnAir ); + + /*! + * \brief The function processes a Link ADR request. + * + * \param [in] linkAdrReq A pointer to the function parameters. + * + * \param [out] drOut The datarate applied. + * + * \param [out] txPowOut The TX power applied. + * + * \param [out] nbRepOut The number of repetitions to apply. + * + * \param [out] nbBytesParsed The number of bytes parsed. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t link_ADR_request(LinkAdrReqParams_t* linkAdrReq, + int8_t* drOut, int8_t* txPowOut, + uint8_t* nbRepOut, + uint8_t* nbBytesParsed ); + + /*! + * \brief The function processes a RX parameter setup request. + * + * \param [in] rxParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t setup_rx_params(RxParamSetupReqParams_t* rxParamSetupReq ); + + /*! + * \brief The function processes a new channel request. + * + * \param [in] newChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t request_new_channel(NewChannelReqParams_t* newChannelReq ); + + /*! + * \brief The function processes a TX ParamSetup request. + * + * \param [in] txParamSetupReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + * Returns -1, if the functionality is not implemented. In this case, the end node + * shall ignore the command. + */ + virtual int8_t setup_tx_params(TxParamSetupReqParams_t* txParamSetupReq ); + + /*! + * \brief The function processes a DlChannel request. + * + * \param [in] dlChannelReq A pointer to the function parameters. + * + * \retval The status of the operation, according to the LoRaMAC specification. + */ + virtual uint8_t dl_channel_request(DlChannelReqParams_t* dlChannelReq ); + + /*! + * \brief Alternates the datarate of the channel for the join request. + * + * \param [in] alternateDr A pointer to the function parameters. + * + * \retval The datarate to apply. + */ + virtual int8_t get_alternate_DR(AlternateDrParams_t* alternateDr ); + + /*! + * \brief Calculates the back-off time. + * + * \param [in] calcBackOff A pointer to the function parameters. + */ + virtual void calculate_backoff(CalcBackOffParams_t* calcBackOff ); + + /*! + * \brief Searches and sets the next random available channel. + * + * \param [in] nextChanParams The parameters for the next channel. + * + * \param [out] channel The next channel to use for TX. + * + * \param [out] time The time to wait for the next transmission according to the duty cycle. + * + * \param [out] aggregatedTimeOff Updates the aggregated time off. + * + * \retval The function status [1: OK, 0: Unable to find a channel on the current datarate]. + */ + virtual bool set_next_channel(NextChanParams_t* nextChanParams, + uint8_t* channel, TimerTime_t* time, + TimerTime_t* aggregatedTimeOff ); + + /*! + * \brief Adds a channel. + * + * \param [in] channelAdd A pointer to the function parameters. + * + * \retval The status of the operation. + */ + virtual LoRaMacStatus_t add_channel(ChannelAddParams_t* channelAdd ); + + /*! + * \brief Removes a channel. + * + * \param [in] channelRemove A pointer to the function parameters. + * + * \retval True, if the channel was removed successfully. + */ + virtual bool remove_channel(ChannelRemoveParams_t* channelRemove ); + + /*! + * \brief Sets the radio into continuous wave mode. + * + * \param [in] continuousWave A pointer to the function parameters. + */ + virtual void set_tx_cont_mode(ContinuousWaveParams_t* continuousWave ); + + /*! + * \brief Computes a new datarate according to the given offset. + * + * \param [in] downlinkDwellTime The downlink dwell time configuration. 0: No limit, 1: 400ms + * + * \param [in] dr The current datarate. + * + * \param [in] drOffset The offset to be applied. + * + * \retval newDr The computed datarate. + */ + virtual uint8_t apply_DR_offset(uint8_t downlinkDwellTime, int8_t dr, int8_t drOffset ); + +private: + int8_t LimitTxPower( int8_t txPower, int8_t maxBandTxPower, int8_t datarate, uint16_t* channelsMask ); + uint8_t CountNbOfEnabledChannels( uint8_t datarate, uint16_t* channelsMask, ChannelParams_t* channels, Band_t* bands, uint8_t* enabledChannels, uint8_t* delayTx ); + + // Global attributes + /*! + * LoRaMAC channels + */ + ChannelParams_t Channels[US915_HYBRID_MAX_NB_CHANNELS]; + + /*! + * LoRaMac bands + */ + Band_t Bands[US915_HYBRID_MAX_NB_BANDS]; + + /*! + * LoRaMac channels mask + */ + uint16_t ChannelsMask[US915_HYBRID_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels remaining + */ + uint16_t ChannelsMaskRemaining[US915_HYBRID_CHANNELS_MASK_SIZE]; + + /*! + * LoRaMac channels default mask + */ + uint16_t ChannelsDefaultMask[US915_HYBRID_CHANNELS_MASK_SIZE]; +}; + +#endif /* MBED_OS_LORAPHY_US915HYBRID_H_ */ diff --git a/features/lorawan/lorastack/phy/lora_phy_ds.h b/features/lorawan/lorastack/phy/lora_phy_ds.h new file mode 100644 index 0000000000..a364a5ef42 --- /dev/null +++ b/features/lorawan/lorastack/phy/lora_phy_ds.h @@ -0,0 +1,1177 @@ +/** + * @file lora_phy_ds.h + * + * @brief Data structures relating to PHY layer + * + * \code + * ______ _ + * / _____) _ | | + * ( (____ _____ ____ _| |_ _____ ____| |__ + * \____ \| ___ | (_ _) ___ |/ ___) _ \ + * _____) ) ____| | | || |_| ____( (___| | | | + * (______/|_____)_|_|_| \__)_____)\____)_| |_| + * (C)2013 Semtech + * ___ _____ _ ___ _ _____ ___ ___ ___ ___ + * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| + * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| + * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| + * embedded.connectivity.solutions=============== + * + * \endcode + * + * 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 + * + */ + +#ifndef MBED_OS_LORA_PHY_DATASTRUCTURES_ +#define MBED_OS_LORA_PHY_DATASTRUCTURES_ + +#include "lorawan/system/lorawan_data_structures.h" + +/*! + * \brief Returns the minimum value between a and b. + * + * \param [in] a The first value. + * \param [in] b The second value. + * \retval minValue The minimum value. + */ +#ifndef MIN +#define MIN( a, b ) ( ( ( a ) < ( b ) ) ? ( a ) : ( b ) ) +#endif + +/*! + * \brief Returns the maximum value between a and b + * + * \param [in] a The first value. + * \param [in] b The second value. + * \retval maxValue The maximum value. + */ +#ifndef MAX +#define MAX( a, b ) ( ( ( a ) > ( b ) ) ? ( a ) : ( b ) ) +#endif + +/** + * LoRaMac maximum number of channels. + */ +#define LORA_MAX_NB_CHANNELS 16 + +/*! + * Macro to compute bit of a channel index. + */ +#define LC( channelIndex ) ( uint16_t )( 1 << ( channelIndex - 1 ) ) + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | SF12 - BW125 + * AU915 | SF10 - BW125 + * CN470 | SF12 - BW125 + * CN779 | SF12 - BW125 + * EU433 | SF12 - BW125 + * EU868 | SF12 - BW125 + * IN865 | SF12 - BW125 + * KR920 | SF12 - BW125 + * US915 | SF10 - BW125 + * US915_HYBRID | SF10 - BW125 + */ +#define DR_0 0 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | SF11 - BW125 + * AU915 | SF9 - BW125 + * CN470 | SF11 - BW125 + * CN779 | SF11 - BW125 + * EU433 | SF11 - BW125 + * EU868 | SF11 - BW125 + * IN865 | SF11 - BW125 + * KR920 | SF11 - BW125 + * US915 | SF9 - BW125 + * US915_HYBRID | SF9 - BW125 + */ +#define DR_1 1 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | SF10 - BW125 + * AU915 | SF8 - BW125 + * CN470 | SF10 - BW125 + * CN779 | SF10 - BW125 + * EU433 | SF10 - BW125 + * EU868 | SF10 - BW125 + * IN865 | SF10 - BW125 + * KR920 | SF10 - BW125 + * US915 | SF8 - BW125 + * US915_HYBRID | SF8 - BW125 + */ +#define DR_2 2 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | SF9 - BW125 + * AU915 | SF7 - BW125 + * CN470 | SF9 - BW125 + * CN779 | SF9 - BW125 + * EU433 | SF9 - BW125 + * EU868 | SF9 - BW125 + * IN865 | SF9 - BW125 + * KR920 | SF9 - BW125 + * US915 | SF7 - BW125 + * US915_HYBRID | SF7 - BW125 + */ +#define DR_3 3 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | SF8 - BW125 + * AU915 | SF8 - BW500 + * CN470 | SF8 - BW125 + * CN779 | SF8 - BW125 + * EU433 | SF8 - BW125 + * EU868 | SF8 - BW125 + * IN865 | SF8 - BW125 + * KR920 | SF8 - BW125 + * US915 | SF8 - BW500 + * US915_HYBRID | SF8 - BW500 + */ +#define DR_4 4 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | SF7 - BW125 + * AU915 | RFU + * CN470 | SF7 - BW125 + * CN779 | SF7 - BW125 + * EU433 | SF7 - BW125 + * EU868 | SF7 - BW125 + * IN865 | SF7 - BW125 + * KR920 | SF7 - BW125 + * US915 | RFU + * US915_HYBRID | RFU + */ +#define DR_5 5 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | SF7 - BW250 + * AU915 | RFU + * CN470 | SF12 - BW125 + * CN779 | SF7 - BW250 + * EU433 | SF7 - BW250 + * EU868 | SF7 - BW250 + * IN865 | SF7 - BW250 + * KR920 | RFU + * US915 | RFU + * US915_HYBRID | RFU + */ +#define DR_6 6 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | FSK + * AU915 | RFU + * CN470 | SF12 - BW125 + * CN779 | FSK + * EU433 | FSK + * EU868 | FSK + * IN865 | FSK + * KR920 | RFU + * US915 | RFU + * US915_HYBRID | RFU + */ +#define DR_7 7 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | SF12 - BW500 + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | SF12 - BW500 + * US915_HYBRID | SF12 - BW500 + */ +#define DR_8 8 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | SF11 - BW500 + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | SF11 - BW500 + * US915_HYBRID | SF11 - BW500 + */ +#define DR_9 9 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | SF10 - BW500 + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | SF10 - BW500 + * US915_HYBRID | SF10 - BW500 + */ +#define DR_10 10 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | SF9 - BW500 + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | SF9 - BW500 + * US915_HYBRID | SF9 - BW500 + */ +#define DR_11 11 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | SF8 - BW500 + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | SF8 - BW500 + * US915_HYBRID | SF8 - BW500 + */ +#define DR_12 12 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | SF7 - BW500 + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | SF7 - BW500 + * US915_HYBRID | SF7 - BW500 + */ +#define DR_13 13 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | RFU + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | RFU + * US915_HYBRID | RFU + */ +#define DR_14 14 + +/*! + * Region | SF + * ------------ | :-----: + * AS923 | RFU + * AU915 | RFU + * CN470 | RFU + * CN779 | RFU + * EU433 | RFU + * EU868 | RFU + * IN865 | RFU + * KR920 | RFU + * US915 | RFU + * US915_HYBRID | RFU + */ +#define DR_15 15 + + + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP + * AU915 | Max EIRP + * CN470 | Max EIRP + * CN779 | Max EIRP + * EU433 | Max EIRP + * EU868 | Max EIRP + * IN865 | Max EIRP + * KR920 | Max EIRP + * US915 | Max ERP + * US915_HYBRID | Max ERP + */ +#define TX_POWER_0 0 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP - 2 + * AU915 | Max EIRP - 2 + * CN470 | Max EIRP - 2 + * CN779 | Max EIRP - 2 + * EU433 | Max EIRP - 2 + * EU868 | Max EIRP - 2 + * IN865 | Max EIRP - 2 + * KR920 | Max EIRP - 2 + * US915 | Max ERP - 2 + * US915_HYBRID | Max ERP - 2 + */ +#define TX_POWER_1 1 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP - 4 + * AU915 | Max EIRP - 4 + * CN470 | Max EIRP - 4 + * CN779 | Max EIRP - 4 + * EU433 | Max EIRP - 4 + * EU868 | Max EIRP - 4 + * IN865 | Max EIRP - 4 + * KR920 | Max EIRP - 4 + * US915 | Max ERP - 4 + * US915_HYBRID | Max ERP - 4 + */ +#define TX_POWER_2 2 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP - 6 + * AU915 | Max EIRP - 6 + * CN470 | Max EIRP - 6 + * CN779 | Max EIRP - 6 + * EU433 | Max EIRP - 6 + * EU868 | Max EIRP - 6 + * IN865 | Max EIRP - 6 + * KR920 | Max EIRP - 6 + * US915 | Max ERP - 6 + * US915_HYBRID | Max ERP - 6 + */ +#define TX_POWER_3 3 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP - 8 + * AU915 | Max EIRP - 8 + * CN470 | Max EIRP - 8 + * CN779 | Max EIRP - 8 + * EU433 | Max EIRP - 8 + * EU868 | Max EIRP - 8 + * IN865 | Max EIRP - 8 + * KR920 | Max EIRP - 8 + * US915 | Max ERP - 8 + * US915_HYBRID | Max ERP - 8 + */ +#define TX_POWER_4 4 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP - 10 + * AU915 | Max EIRP - 10 + * CN470 | Max EIRP - 10 + * CN779 | Max EIRP - 10 + * EU433 | Max EIRP - 10 + * EU868 | Max EIRP - 10 + * IN865 | Max EIRP - 10 + * KR920 | Max EIRP - 10 + * US915 | Max ERP - 10 + * US915_HYBRID | Max ERP - 10 + */ +#define TX_POWER_5 5 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP - 12 + * AU915 | Max EIRP - 12 + * CN470 | Max EIRP - 12 + * CN779 | - + * EU433 | - + * EU868 | Max EIRP - 12 + * IN865 | Max EIRP - 12 + * KR920 | Max EIRP - 12 + * US915 | Max ERP - 12 + * US915_HYBRID | Max ERP - 12 + */ +#define TX_POWER_6 6 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | Max EIRP - 14 + * AU915 | Max EIRP - 14 + * CN470 | Max EIRP - 14 + * CN779 | - + * EU433 | - + * EU868 | Max EIRP - 14 + * IN865 | Max EIRP - 14 + * KR920 | Max EIRP - 14 + * US915 | Max ERP - 14 + * US915_HYBRID | Max ERP - 14 + */ +#define TX_POWER_7 7 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | - + * AU915 | Max EIRP - 16 + * CN470 | - + * CN779 | - + * EU433 | - + * EU868 | - + * IN865 | Max EIRP - 16 + * KR920 | - + * US915 | Max ERP - 16 + * US915_HYBRID | Max ERP -16 + */ +#define TX_POWER_8 8 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | - + * AU915 | Max EIRP - 18 + * CN470 | - + * CN779 | - + * EU433 | - + * EU868 | - + * IN865 | Max EIRP - 18 + * KR920 | - + * US915 | Max ERP - 16 + * US915_HYBRID | Max ERP - 16 + */ +#define TX_POWER_9 9 + +/*! + * Region | dBM + * ------------ | :-----: + * AS923 | - + * AU915 | Max EIRP - 20 + * CN470 | - + * CN779 | - + * EU433 | - + * EU868 | - + * IN865 | Max EIRP - 20 + * KR920 | - + * US915 | Max ERP - 10 + * US915_HYBRID | Max ERP - 10 + */ +#define TX_POWER_10 10 + +/*! + * RFU + */ +#define TX_POWER_11 11 + +/*! + * RFU + */ +#define TX_POWER_12 12 + +/*! + * RFU + */ +#define TX_POWER_13 13 + +/*! + * RFU + */ +#define TX_POWER_14 14 + +/*! + * RFU + */ +#define TX_POWER_15 15 + +/*! + * Enumeration of PHY attributes. + */ +typedef enum ePhyAttribute +{ + /*! + * The minimum RX datarate. + */ + PHY_MIN_RX_DR, + /*! + * The minimum TX datarate. + */ + PHY_MIN_TX_DR, + /*! + * The maximum RX datarate. + */ + PHY_MAX_RX_DR, + /*! + * The maximum TX datarate. + */ + PHY_MAX_TX_DR, + /*! + * The TX datarate. + */ + PHY_TX_DR, + /*! + * The default TX datarate. + */ + PHY_DEF_TX_DR, + /*! + * The RX datarate. + */ + PHY_RX_DR, + /*! + * The TX power. + */ + PHY_TX_POWER, + /*! + * The default TX power. + */ + PHY_DEF_TX_POWER, + /*! + * Maximum payload possible. + */ + PHY_MAX_PAYLOAD, + /*! + * Maximum payload possible when the repeater support is enabled. + */ + PHY_MAX_PAYLOAD_REPEATER, + /*! + * The duty cycle. + */ + PHY_DUTY_CYCLE, + /*! + * The maximum receive window duration. + */ + PHY_MAX_RX_WINDOW, + /*! + * The receive delay for window 1. + */ + PHY_RECEIVE_DELAY1, + /*! + * The receive delay for window 2. + */ + PHY_RECEIVE_DELAY2, + /*! + * The join accept delay for window 1. + */ + PHY_JOIN_ACCEPT_DELAY1, + /*! + * The join accept delay for window 2. + */ + PHY_JOIN_ACCEPT_DELAY2, + /*! + * The maximum frame counter gap. + */ + PHY_MAX_FCNT_GAP, + /*! + * The acknowledgement time out. + */ + PHY_ACK_TIMEOUT, + /*! + * The default datarate offset for window 1. + */ + PHY_DEF_DR1_OFFSET, + /*! + * The default receive window 2 frequency. + */ + PHY_DEF_RX2_FREQUENCY, + /*! + * The default receive window 2 datarate. + */ + PHY_DEF_RX2_DR, + /*! + * The channels mask. + */ + PHY_CHANNELS_MASK, + /*! + * The channels default mask. + */ + PHY_CHANNELS_DEFAULT_MASK, + /*! + * The maximum number of supported channels. + */ + PHY_MAX_NB_CHANNELS, + /*! + * The channels. + */ + PHY_CHANNELS, + /*! + * The default value of the uplink dwell time. + */ + PHY_DEF_UPLINK_DWELL_TIME, + /*! + * The default value of the downlink dwell time. + */ + PHY_DEF_DOWNLINK_DWELL_TIME, + /*! + * The default value of the MaxEIRP. + */ + PHY_DEF_MAX_EIRP, + /*! + * The default value of the antenna gain. + */ + PHY_DEF_ANTENNA_GAIN, + /*! + * The value for the number of join trials. + */ + PHY_NB_JOIN_TRIALS, + /*! + * The default value for the number of join trials. + */ + PHY_DEF_NB_JOIN_TRIALS, + /*! + * The next lower datarate. + */ + PHY_NEXT_LOWER_TX_DR +}PhyAttribute_t; + +/*! + * Enumeration of initialization types. + */ +typedef enum eInitType +{ + /*! + * Performs an initialization and overwrites all existing data. + */ + INIT_TYPE_INIT, + /*! + * Restores default channels only. + */ + INIT_TYPE_RESTORE +}InitType_t; + +/*! + * Selects a given or a default channel mask. + */ +typedef enum eChannelsMask +{ + /*! + * The channels mask. + */ + CHANNELS_MASK, + /*! + * The channels default mask. + */ + CHANNELS_DEFAULT_MASK +}ChannelsMask_t; + +/*! + * The union for the structure uGetPhyParams. + */ +typedef union uPhyParam +{ + /*! + * A parameter value. + */ + uint32_t Value; + /*! + * A floating point value. + */ + float fValue; + /*! + * A pointer to the channels mask. + */ + uint16_t* ChannelsMask; + /*! + * A pointer to the channels. + */ + ChannelParams_t* Channels; +}PhyParam_t; + +/*! + * The parameter structure for the function RegionGetPhyParam. + */ +typedef struct sGetPhyParams +{ + /*! + * Set up the parameter to get. + */ + PhyAttribute_t Attribute; + /*! + * Datarate. + * The parameter is needed for the following queries: + * PHY_MAX_PAYLOAD, PHY_MAX_PAYLOAD_REPEATER, PHY_NEXT_LOWER_TX_DR. + */ + int8_t Datarate; + /*! + * Uplink dwell time. + * The parameter is needed for the following queries: + * PHY_MIN_TX_DR, PHY_MAX_PAYLOAD, PHY_MAX_PAYLOAD_REPEATER, PHY_NEXT_LOWER_TX_DR. + */ + uint8_t UplinkDwellTime; + /*! + * Downlink dwell time. + * The parameter is needed for the following queries: + * PHY_MIN_RX_DR, PHY_MAX_PAYLOAD, PHY_MAX_PAYLOAD_REPEATER. + */ + uint8_t DownlinkDwellTime; +}GetPhyParams_t; + +/*! + * The parameter structure for the function RegionSetBandTxDone. + */ +typedef struct sSetBandTxDoneParams +{ + /*! + * The channel to update. + */ + uint8_t Channel; + /*! + * Joined set to true, if the node has joined the network. + */ + bool Joined; + /*! + * The last TX done time. + */ + TimerTime_t LastTxDoneTime; +}SetBandTxDoneParams_t; + +/*! + * The parameter structure for the function RegionVerify. + */ +typedef union uVerifyParams +{ + /*! + * The TX power to verify. + */ + int8_t TxPower; + /*! + * Set to true, if the duty cycle is enabled, otherwise false. + */ + bool DutyCycle; + /*! + * The number of join trials. + */ + uint8_t NbJoinTrials; + /*! + * The datarate to verify. + */ + struct sDatarateParams + { + /*! + * The datarate to verify. + */ + int8_t Datarate; + /*! + * The downlink dwell time. + */ + uint8_t DownlinkDwellTime; + /*! + * The uplink dwell time. + */ + uint8_t UplinkDwellTime; + }DatarateParams; +}VerifyParams_t; + +/*! + * The parameter structure for the function RegionApplyCFList. + */ +typedef struct sApplyCFListParams +{ + /*! + * The payload containing the CF list. + */ + uint8_t* Payload; + /*! + * The size of the payload. + */ + uint8_t Size; +}ApplyCFListParams_t; + +/*! + * The parameter structure for the function RegionChanMaskSet. + */ +typedef struct sChanMaskSetParams +{ + /*! + * A pointer to the channels mask which should be set. + */ + uint16_t* ChannelsMaskIn; + /*! + * A pointer to the channels mask which should be set. + */ + ChannelsMask_t ChannelsMaskType; +}ChanMaskSetParams_t; + +/*! + * The parameter structure for the function RegionAdrNext. + */ +typedef struct sAdrNextParams +{ + /*! + * Set to true, if the function should update the channels mask. + */ + bool UpdateChanMask; + /*! + * Set to true, if ADR is enabled. + */ + bool AdrEnabled; + /*! + * The ADR ack counter. + */ + uint32_t AdrAckCounter; + /*! + * The datarate used currently. + */ + int8_t Datarate; + /*! + * The TX power used currently. + */ + int8_t TxPower; + /*! + * UplinkDwellTime + */ + uint8_t UplinkDwellTime; +}AdrNextParams_t; + +/*! + * The parameter structure for the function RegionRxConfig. + */ +typedef struct sRxConfigParams +{ + /*! + * The RX channel. + */ + uint8_t Channel; + /*! + * The RX datarate. + */ + int8_t Datarate; + /*! + * The RX bandwidth. + */ + uint8_t Bandwidth; + /*! + * The RX datarate offset. + */ + int8_t DrOffset; + /*! + * The RX frequency. + */ + uint32_t Frequency; + /*! + * The RX window timeout + */ + uint32_t WindowTimeout; + /*! + * The RX window offset + */ + int32_t WindowOffset; + /*! + * The downlink dwell time. + */ + uint8_t DownlinkDwellTime; + /*! + * Set to true, if a repeater is supported. + */ + bool RepeaterSupport; + /*! + * Set to true, if RX should be continuous. + */ + bool RxContinuous; + /*! + * Sets the RX window. 0: RX window 1, 1: RX window 2. + */ + bool Window; +}RxConfigParams_t; + +/*! + * The parameter structure for the function RegionTxConfig. + */ +typedef struct sTxConfigParams +{ + /*! + * The TX channel. + */ + uint8_t Channel; + /*! + * The TX datarate. + */ + int8_t Datarate; + /*! + * The TX power. + */ + int8_t TxPower; + /*! + * The Max EIRP, if applicable. + */ + float MaxEirp; + /*! + * The antenna gain, if applicable. + */ + float AntennaGain; + /*! + * The frame length to set up. + */ + uint16_t PktLen; +}TxConfigParams_t; + +/*! + * The parameter structure for the function RegionLinkAdrReq. + */ +typedef struct sLinkAdrReqParams +{ + /*! + * A pointer to the payload containing the MAC commands. + */ + uint8_t* Payload; + /*! + * The size of the payload. + */ + uint8_t PayloadSize; + /*! + * The uplink dwell time. + */ + uint8_t UplinkDwellTime; + /*! + * Set to true, if ADR is enabled. + */ + bool AdrEnabled; + /*! + * The current datarate. + */ + int8_t CurrentDatarate; + /*! + * The current TX power. + */ + int8_t CurrentTxPower; + /*! + * The current number of repetitions. + */ + uint8_t CurrentNbRep; +}LinkAdrReqParams_t; + +/*! + * The parameter structure for the function RegionRxParamSetupReq. + */ +typedef struct sRxParamSetupReqParams +{ + /*! + * The datarate to set up. + */ + int8_t Datarate; + /*! + * The datarate offset. + */ + int8_t DrOffset; + /*! + * The frequency to set up. + */ + uint32_t Frequency; +}RxParamSetupReqParams_t; + +/*! + * The parameter structure for the function RegionNewChannelReq. + */ +typedef struct sNewChannelReqParams +{ + /*! + * A pointer to the new channels. + */ + ChannelParams_t* NewChannel; + /*! + * The channel ID. + */ + int8_t ChannelId; +}NewChannelReqParams_t; + +/*! + * The parameter structure for the function RegionTxParamSetupReq. + */ +typedef struct sTxParamSetupReqParams +{ + /*! + * The uplink dwell time. + */ + uint8_t UplinkDwellTime; + /*! + * The downlink dwell time. + */ + uint8_t DownlinkDwellTime; + /*! + * The max EIRP. + */ + uint8_t MaxEirp; +}TxParamSetupReqParams_t; + +/*! + * The parameter structure for the function RegionDlChannelReq. + */ +typedef struct sDlChannelReqParams +{ + /*! + * The channel ID to add the frequency. + */ + uint8_t ChannelId; + /*! + * The alternative frequency for the Rx1 window. + */ + uint32_t Rx1Frequency; +}DlChannelReqParams_t; + +/*! + * The parameter structure for the function RegionAlternateDr. + */ +typedef struct sAlternateDrParams +{ + /*! + * The number of trials. + */ + uint16_t NbTrials; +}AlternateDrParams_t; + +/*! + * The parameter structure for the function RegionCalcBackOff. + */ +typedef struct sCalcBackOffParams +{ + /*! + * Set to true, if the node has already joined a network, otherwise false. + */ + bool Joined; + /*! + * Joined set to true, if the last uplink was a join request. + */ + bool LastTxIsJoinRequest; + /*! + * Set to true, if the duty cycle is enabled, otherwise false. + */ + bool DutyCycleEnabled; + /*! + * The current channel index. + */ + uint8_t Channel; + /*! + * Elapsed time since the start of the node. + */ + TimerTime_t ElapsedTime; + /*! + * Time-on-air of the last transmission. + */ + TimerTime_t TxTimeOnAir; +}CalcBackOffParams_t; + +/*! + * The parameter structure for the function RegionNextChannel. + */ +typedef struct sNextChanParams +{ + /*! + * The aggregated time-off time. + */ + TimerTime_t AggrTimeOff; + /*! + * The time of the last aggregated TX. + */ + TimerTime_t LastAggrTx; + /*! + * The current datarate. + */ + int8_t Datarate; + /*! + * Set to true, if the node has already joined a network, otherwise false. + */ + bool Joined; + /*! + * Set to true, if the duty cycle is enabled, otherwise false. + */ + bool DutyCycleEnabled; +}NextChanParams_t; + +/*! + * The parameter structure for the function RegionChannelsAdd. + */ +typedef struct sChannelAddParams +{ + /*! + * A pointer to the new channel to add. + */ + ChannelParams_t* NewChannel; + /*! + * The channel ID to add. + */ + uint8_t ChannelId; +}ChannelAddParams_t; + +/*! + * The parameter structure for the function RegionChannelsRemove. + */ +typedef struct sChannelRemoveParams +{ + /*! + * The channel ID to remove. + */ + uint8_t ChannelId; +}ChannelRemoveParams_t; + +/*! + * The parameter structure for the function RegionContinuousWave. + */ +typedef struct sContinuousWaveParams +{ + /*! + * The current channel index. + */ + uint8_t Channel; + /*! + * The datarate. Used to limit the TX power. + */ + int8_t Datarate; + /*! + * The TX power to set up. + */ + int8_t TxPower; + /*! + * The max EIRP, if applicable. + */ + float MaxEirp; + /*! + * The antenna gain, if applicable. + */ + float AntennaGain; + /*! + * Specifies the time the radio will stay in CW mode. + */ + uint16_t Timeout; +}ContinuousWaveParams_t; + + +#endif /* MBED_OS_LORA_PHY_DATASTRUCTURES_ */