/*! * \file RegionCommon.c * * \brief LoRa MAC common region implementation * * \copyright Revised BSD License, see section \ref LICENSE. * * \code * ______ _ * / _____) _ | | * ( (____ _____ ____ _| |_ _____ ____| |__ * \____ \| ___ | (_ _) ___ |/ ___) _ \ * _____) ) ____| | | || |_| ____( (___| | | | * (______/|_____)_|_|_| \__)_____)\____)_| |_| * (C)2013-2017 Semtech * * ___ _____ _ ___ _ _____ ___ ___ ___ ___ * / __|_ _/_\ / __| |/ / __/ _ \| _ \/ __| __| * \__ \ | |/ _ \ (__| ' <| _| (_) | / (__| _| * |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___| * embedded.connectivity.solutions=============== * * \endcode * * \author Miguel Luis ( Semtech ) * * \author Gregory Cristian ( Semtech ) * * \author Daniel Jaeckle ( STACKFORCE ) */ #include #include "RegionCommon.h" #include "mw_log_conf.h" #define BACKOFF_DC_1_HOUR 100 #define BACKOFF_DC_10_HOURS 1000 #define BACKOFF_DC_24_HOURS 10000 #define BACKOFF_DC_TIMER_PERIOD_FACTOR 100 #ifndef DUTY_CYCLE_TIME_PERIOD /*! * Default duty cycle time period is 1 hour = 3600000 ms */ #define DUTY_CYCLE_TIME_PERIOD 3600000 #endif static uint16_t GetDutyCycle( Band_t* band, bool joined, SysTime_t elapsedTimeSinceStartup ) { uint16_t joinDutyCycle = RegionCommonGetJoinDc( elapsedTimeSinceStartup ); uint16_t dutyCycle = band->DCycle; if( joined == false ) { // Get the join duty cycle which depends on the runtime joinDutyCycle = RegionCommonGetJoinDc( elapsedTimeSinceStartup ); // Take the most restrictive duty cycle dutyCycle = MAX( dutyCycle, joinDutyCycle ); } // Prevent value of 0 if( dutyCycle == 0 ) { dutyCycle = 1; } return dutyCycle; } static uint16_t SetMaxTimeCredits( Band_t* band, bool joined, SysTime_t elapsedTimeSinceStartup ) { uint16_t dutyCycle = band->DCycle; uint8_t timePeriodFactor = 1; // Get the band duty cycle. If not joined, the function either returns the join duty cycle // or the band duty cycle, whichever is more restrictive. dutyCycle = GetDutyCycle( band, joined, elapsedTimeSinceStartup ); if( joined == false ) { // Apply a factor to increase the maximum time period of observation timePeriodFactor = dutyCycle / BACKOFF_DC_TIMER_PERIOD_FACTOR; } // Setup the maximum allowed credits band->MaxTimeCredits = DUTY_CYCLE_TIME_PERIOD * timePeriodFactor; // In case if it is the first time, update also the current // time credits if( band->LastBandUpdateTime == 0 ) { band->TimeCredits = band->MaxTimeCredits; } return dutyCycle; } static uint16_t UpdateTimeCredits( Band_t* band, bool joined, bool dutyCycleEnabled, bool lastTxIsJoinRequest, SysTime_t elapsedTimeSinceStartup, TimerTime_t currentTime ) { uint16_t dutyCycle = SetMaxTimeCredits( band, joined, elapsedTimeSinceStartup ); if( joined == false ) { if( ( dutyCycleEnabled == false ) && ( 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. band->TimeCredits = band->MaxTimeCredits; } } else { if( dutyCycleEnabled == false ) { band->TimeCredits = band->MaxTimeCredits; } } // Get the difference between now and the last update band->TimeCredits += TimerGetElapsedTime( band->LastBandUpdateTime ); // Limit band credits to maximum if( band->TimeCredits > band->MaxTimeCredits ) { band->TimeCredits = band->MaxTimeCredits; } // Synchronize update time band->LastBandUpdateTime = currentTime; return dutyCycle; } 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; } uint16_t RegionCommonGetJoinDc( SysTime_t elapsedTime ) { uint16_t dutyCycle = 0; if( elapsedTime.Seconds < 3600 ) { dutyCycle = BACKOFF_DC_1_HOUR; } else if( elapsedTime.Seconds < ( 3600 + 36000 ) ) { dutyCycle = BACKOFF_DC_10_HOURS; } else { dutyCycle = BACKOFF_DC_24_HOURS; } return dutyCycle; } bool RegionCommonChanVerifyDr( uint8_t nbChannels, uint16_t* channelsMask, int8_t dr, int8_t minDr, int8_t maxDr, ChannelParams_t* channels ) { if( RegionCommonValueInRange( 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( RegionCommonValueInRange( 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 RegionCommonValueInRange( int8_t value, int8_t min, int8_t max ) { if( ( value >= min ) && ( value <= max ) ) { return 1; } return 0; } bool RegionCommonChanDisable( 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 RegionCommonCountChannels( 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 RegionCommonChanMaskCopy( 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 RegionCommonSetBandTxDone( Band_t* band, TimerTime_t lastTxAirTime, bool joined, SysTime_t elapsedTimeSinceStartup ) { // Get the band duty cycle. If not joined, the function either returns the join duty cycle // or the band duty cycle, whichever is more restrictive. uint16_t dutyCycle = GetDutyCycle( band, joined, elapsedTimeSinceStartup ); // Reduce with transmission time if( band->TimeCredits > ( lastTxAirTime * dutyCycle ) ) { // Reduce time credits by the time of air band->TimeCredits -= ( lastTxAirTime * dutyCycle ); } else { band->TimeCredits = 0; } } TimerTime_t RegionCommonUpdateBandTimeOff( bool joined, Band_t* bands, uint8_t nbBands, bool dutyCycleEnabled, bool lastTxIsJoinRequest, SysTime_t elapsedTimeSinceStartup, TimerTime_t expectedTimeOnAir ) { TimerTime_t minTimeToWait = TIMERTIME_T_MAX; TimerTime_t currentTime = TimerGetCurrentTime( ); TimerTime_t creditCosts = 0; uint16_t dutyCycle = 1; uint8_t validBands = 0; for( uint8_t i = 0; i < nbBands; i++ ) { // Synchronization of bands and credits dutyCycle = UpdateTimeCredits( &bands[i], joined, dutyCycleEnabled, lastTxIsJoinRequest, elapsedTimeSinceStartup, currentTime ); // Calculate the credit costs for the next transmission // with the duty cycle and the expected time on air creditCosts = expectedTimeOnAir * dutyCycle; // Check if the band is ready for transmission. Its ready, // when the duty cycle is off, or the TimeCredits of the band // is higher than the credit costs for the transmission. if( ( bands[i].TimeCredits > creditCosts ) || ( dutyCycleEnabled == false ) ) { bands[i].ReadyForTransmission = true; // This band is a potential candidate for an // upcoming transmission, so increase the counter. validBands++; } else { // In this case, the band has not enough credits // for the next transmission. bands[i].ReadyForTransmission = false; if( bands[i].MaxTimeCredits > creditCosts ) { // The band can only be taken into account, if the maximum credits // of the band are higher than the credit costs. // We calculate the minTimeToWait among the bands which are not // ready for transmission and which are potentially available // for a transmission in the future. minTimeToWait = MIN( minTimeToWait, ( creditCosts - bands[i].TimeCredits ) ); // This band is a potential candidate for an // upcoming transmission (even if its time credits are not enough // at the moment), so increase the counter. validBands++; } } } if( validBands == 0 ) { // There is no valid band available to handle a transmission // in the given DUTY_CYCLE_TIME_PERIOD. return TIMERTIME_T_MAX; } return minTimeToWait; } uint8_t RegionCommonParseLinkAdrReq( 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 RegionCommonLinkAdrReqVerifyParams( 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 nbRepetitions = verifyParams->CurrentNbRep; datarate = verifyParams->CurrentDatarate; txPower = verifyParams->CurrentTxPower; } if( status != 0 ) { // Verify datarate. The variable phyParam. Value contains the minimum allowed datarate. if( RegionCommonChanVerifyDr( verifyParams->NbChannels, verifyParams->ChannelsMask, datarate, verifyParams->MinDatarate, verifyParams->MaxDatarate, verifyParams->Channels ) == false ) { status &= 0xFD; // Datarate KO } // Verify tx power if( RegionCommonValueInRange( 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 ) { // Restore the default value according to the LoRaWAN specification nbRepetitions = 1; } } // Apply changes *dr = datarate; *txPow = txPower; *nbRep = nbRepetitions; return status; } /* ST_WORKAROUND_BEGIN: remove float/double */ uint32_t RegionCommonComputeSymbolTimeLoRa( uint8_t phyDr, uint32_t bandwidth ) { return (1000000000UL/bandwidth) * (1 << phyDr); } uint32_t RegionCommonComputeSymbolTimeFsk( uint8_t phyDr ) { // ((8 * 1000000) / 50); return 160000UL; } void RegionCommonComputeRxWindowParameters( uint32_t tSymbol, uint8_t minRxSymbols, uint32_t rxError, uint32_t wakeUpTime, uint32_t* windowTimeout, int32_t* windowOffset ) { *windowTimeout = MAX( (uint32_t)2 * minRxSymbols - 8 + DIVC(2 * rxError * 1000000UL, tSymbol ), minRxSymbols); *windowOffset = DIVC((int32_t)(4 * tSymbol - ((*windowTimeout * tSymbol) >> 1)), 1000000L) - 1 - wakeUpTime; } /* ST_WORKAROUND_END */ int8_t RegionCommonComputeTxPower( int8_t txPowerIndex, float maxEirp, float antennaGain ) { int8_t phyTxPower = 0; phyTxPower = ( int8_t )floor( ( maxEirp - ( txPowerIndex * 2U ) ) - antennaGain ); return phyTxPower; } void RegionCommonRxBeaconSetup( RegionCommonRxBeaconSetupParams_t* rxBeaconSetupParams ) { bool rxContinuous = true; uint8_t datarate; // Set the radio into sleep mode Radio.Sleep( ); // Setup frequency and payload length Radio.SetChannel( rxBeaconSetupParams->Frequency ); Radio.SetMaxPayloadLength( MODEM_LORA, rxBeaconSetupParams->BeaconSize ); // Check the RX continuous mode if( rxBeaconSetupParams->RxTime != 0 ) { rxContinuous = false; } // Get region specific datarate datarate = rxBeaconSetupParams->Datarates[rxBeaconSetupParams->BeaconDatarate]; // Setup radio Radio.SetRxConfig( MODEM_LORA, rxBeaconSetupParams->BeaconChannelBW, datarate, 1, 0, 10, rxBeaconSetupParams->SymbolTimeout, true, rxBeaconSetupParams->BeaconSize, false, 0, 0, false, rxContinuous ); Radio.Rx( rxBeaconSetupParams->RxTime ); MW_LOG(TS_ON, VLEVEL_M, "RX_BC on freq %d Hz at DR %d\r\n", rxBeaconSetupParams->Frequency, rxBeaconSetupParams->BeaconDatarate ); } void RegionCommonCountNbOfEnabledChannels( RegionCommonCountNbOfEnabledChannelsParams_t* countNbOfEnabledChannelsParams, uint8_t* enabledChannels, uint8_t* nbEnabledChannels, uint8_t* nbRestrictedChannels ) { uint8_t nbChannelCount = 0; uint8_t nbRestrictedChannelsCount = 0; for( uint8_t i = 0, k = 0; i < countNbOfEnabledChannelsParams->MaxNbChannels; i += 16, k++ ) { for( uint8_t j = 0; j < 16; j++ ) { if( ( countNbOfEnabledChannelsParams->ChannelsMask[k] & ( 1 << j ) ) != 0 ) { if( countNbOfEnabledChannelsParams->Channels[i + j].Frequency == 0 ) { // Check if the channel is enabled continue; } if( ( countNbOfEnabledChannelsParams->Joined == false ) && ( countNbOfEnabledChannelsParams->JoinChannels > 0 ) ) { if( ( countNbOfEnabledChannelsParams->JoinChannels & ( 1 << j ) ) == 0 ) { continue; } } if( RegionCommonValueInRange( countNbOfEnabledChannelsParams->Datarate, countNbOfEnabledChannelsParams->Channels[i + j].DrRange.Fields.Min, countNbOfEnabledChannelsParams->Channels[i + j].DrRange.Fields.Max ) == false ) { // Check if the current channel selection supports the given datarate continue; } if( countNbOfEnabledChannelsParams->Bands[countNbOfEnabledChannelsParams->Channels[i + j].Band].ReadyForTransmission == false ) { // Check if the band is available for transmission nbRestrictedChannelsCount++; continue; } enabledChannels[nbChannelCount++] = i + j; } } } *nbEnabledChannels = nbChannelCount; *nbRestrictedChannels = nbRestrictedChannelsCount; } LoRaMacStatus_t RegionCommonIdentifyChannels( RegionCommonIdentifyChannelsParam_t* identifyChannelsParam, TimerTime_t* aggregatedTimeOff, uint8_t* enabledChannels, uint8_t* nbEnabledChannels, uint8_t* nbRestrictedChannels, TimerTime_t* nextTxDelay ) { TimerTime_t elapsed = TimerGetElapsedTime( identifyChannelsParam->LastAggrTx ); *nextTxDelay = identifyChannelsParam->AggrTimeOff - elapsed; *nbRestrictedChannels = 1; *nbEnabledChannels = 0; if( ( identifyChannelsParam->LastAggrTx == 0 ) || ( identifyChannelsParam->AggrTimeOff <= elapsed ) ) { // Reset Aggregated time off *aggregatedTimeOff = 0; // Update bands Time OFF *nextTxDelay = RegionCommonUpdateBandTimeOff( identifyChannelsParam->CountNbOfEnabledChannelsParam->Joined, identifyChannelsParam->CountNbOfEnabledChannelsParam->Bands, identifyChannelsParam->MaxBands, identifyChannelsParam->DutyCycleEnabled, identifyChannelsParam->LastTxIsJoinRequest, identifyChannelsParam->ElapsedTimeSinceStartUp, identifyChannelsParam->ExpectedTimeOnAir ); RegionCommonCountNbOfEnabledChannels( identifyChannelsParam->CountNbOfEnabledChannelsParam, enabledChannels, nbEnabledChannels, nbRestrictedChannels ); } if( *nbEnabledChannels > 0 ) { *nextTxDelay = 0; return LORAMAC_STATUS_OK; } else if( *nbRestrictedChannels > 0 ) { return LORAMAC_STATUS_DUTYCYCLE_RESTRICTED; } else { return LORAMAC_STATUS_NO_CHANNEL_FOUND; } } void RegionCommonRxConfigPrint(LoRaMacRxSlot_t rxSlot, uint32_t frequency, int8_t dr) { const char *slotStrings[] = { "1", "2", "C", "Multi_C", "P", "Multi_P" }; if ( rxSlot < RX_SLOT_NONE ) { MW_LOG(TS_ON, VLEVEL_M, "RX_%s on freq %d Hz at DR %d\r\n", slotStrings[rxSlot], frequency, dr ); } else { MW_LOG(TS_ON, VLEVEL_M, "RX on freq %d Hz at DR %d\r\n", frequency, dr ); } } void RegionCommonTxConfigPrint(uint32_t frequency, int8_t dr) { MW_LOG(TS_ON, VLEVEL_M, "TX on freq %d Hz at DR %d\r\n", frequency, dr ); }