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_ */