805 lines
29 KiB
C
805 lines
29 KiB
C
/*!
|
|
* \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 )
|
|
*/
|
|
/**
|
|
******************************************************************************
|
|
*
|
|
* Portions COPYRIGHT 2020 STMicroelectronics
|
|
*
|
|
* @file RegionCommon.c
|
|
* @author MCD Application Team
|
|
* @brief LoRa MAC common region implementation
|
|
******************************************************************************
|
|
*/
|
|
#include <math.h>
|
|
#include "radio.h"
|
|
#include "utilities.h"
|
|
#include "RegionCommon.h"
|
|
#include "systime.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_DUTY_CYCLE_1_HOUR_IN_S 3600
|
|
#define BACKOFF_DUTY_CYCLE_10_HOURS_IN_S ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S + ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 10 ) )
|
|
#define BACKOFF_DUTY_CYCLE_24_HOURS_IN_S ( BACKOFF_DUTY_CYCLE_10_HOURS_IN_S + ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 24 ) )
|
|
#define BACKOFF_24_HOURS_IN_S ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 24 )
|
|
|
|
#ifndef DUTY_CYCLE_TIME_PERIOD
|
|
/*!
|
|
* Default duty cycle observation time period
|
|
*
|
|
* \remark The ETSI observation time period is 1 hour (3600000 ms) but, the implemented algorithm may violate the
|
|
* defined duty-cycle restrictions. In order to ensure that these restrictions never get violated we changed the
|
|
* default duty cycle observation time period to 1/2 hour (1800000 ms).
|
|
*/
|
|
#if (defined( REGION_VERSION ) && ( REGION_VERSION == 0x02010003 ))
|
|
#define DUTY_CYCLE_TIME_PERIOD 3600000
|
|
#else
|
|
#define DUTY_CYCLE_TIME_PERIOD 1800000
|
|
#endif
|
|
#endif
|
|
|
|
#ifndef DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H
|
|
/*!
|
|
* Time credits for the join backoff algorithm for the 24H period.
|
|
*/
|
|
#define DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H 870000
|
|
#endif
|
|
|
|
/*!
|
|
* \brief Returns `N / D` rounded to the smallest integer value greater than or equal to `N / D`
|
|
*
|
|
* \warning when `D == 0`, the result is undefined
|
|
*
|
|
* \remark `N` and `D` can be signed or unsigned
|
|
*
|
|
* \param [in] N the numerator, which can have any sign
|
|
* \param [in] D the denominator, which can have any sign
|
|
* \retval N / D with any fractional part rounded to the smallest integer value greater than or equal to `N / D`
|
|
*/
|
|
#define DIV_CEIL( N, D ) \
|
|
( \
|
|
( N > 0 ) ? \
|
|
( ( ( N ) + ( D ) - 1 ) / ( D ) ) : \
|
|
( ( N ) / ( D ) ) \
|
|
)
|
|
|
|
#ifdef MW_LOG_ENABLED
|
|
static const char *EventRXSlotStrings[] = { "1", "2", "C", "Multi_C", "P", "Multi_P" };
|
|
#endif
|
|
|
|
static uint16_t GetDutyCycle( Band_t* band, bool joined, SysTime_t elapsedTimeSinceStartup )
|
|
{
|
|
uint16_t dutyCycle = band->DCycle;
|
|
|
|
if( joined == false )
|
|
{
|
|
#if (defined( REGION_VERSION ) && ( REGION_VERSION == 0x02010003 ))
|
|
uint16_t joinDutyCycle = BACKOFF_DC_1_HOUR;
|
|
#else
|
|
uint16_t joinDutyCycle = BACKOFF_DC_24_HOURS;
|
|
|
|
if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_1_HOUR_IN_S )
|
|
{
|
|
joinDutyCycle = BACKOFF_DC_1_HOUR;
|
|
}
|
|
else if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_10_HOURS_IN_S )
|
|
{
|
|
joinDutyCycle = BACKOFF_DC_10_HOURS;
|
|
}
|
|
else
|
|
{
|
|
joinDutyCycle = BACKOFF_DC_24_HOURS;
|
|
}
|
|
#endif
|
|
// 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,
|
|
bool dutyCycleEnabled, bool lastTxIsJoinRequest )
|
|
{
|
|
uint16_t dutyCycle = band->DCycle;
|
|
TimerTime_t maxCredits = DUTY_CYCLE_TIME_PERIOD;
|
|
|
|
// 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 )
|
|
{
|
|
#if (defined( REGION_VERSION ) && ( REGION_VERSION == 0x02010003 ))
|
|
if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_1_HOUR_IN_S )
|
|
{
|
|
maxCredits = DUTY_CYCLE_TIME_PERIOD;
|
|
}
|
|
else if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_10_HOURS_IN_S )
|
|
{
|
|
maxCredits = DUTY_CYCLE_TIME_PERIOD;
|
|
}
|
|
else
|
|
{
|
|
maxCredits = DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H;
|
|
}
|
|
#else
|
|
TimerTime_t elapsedTime = SysTimeToMs( elapsedTimeSinceStartup );
|
|
SysTime_t timeDiff = { 0 };
|
|
if( dutyCycle == BACKOFF_DC_1_HOUR )
|
|
{
|
|
maxCredits = DUTY_CYCLE_TIME_PERIOD;
|
|
band->LastMaxCreditAssignTime = elapsedTime;
|
|
}
|
|
else if( dutyCycle == BACKOFF_DC_10_HOURS )
|
|
{
|
|
maxCredits = DUTY_CYCLE_TIME_PERIOD * 10;
|
|
band->LastMaxCreditAssignTime = elapsedTime;
|
|
}
|
|
else
|
|
{
|
|
maxCredits = DUTY_CYCLE_TIME_PERIOD * 24;
|
|
}
|
|
|
|
timeDiff = SysTimeSub( elapsedTimeSinceStartup, SysTimeFromMs( band->LastMaxCreditAssignTime ) );
|
|
|
|
// Verify if we have to assign the maximum credits in cases
|
|
// of the preconditions have changed.
|
|
if( ( ( dutyCycleEnabled == false ) && ( lastTxIsJoinRequest == false ) ) ||
|
|
( band->MaxTimeCredits != maxCredits ) ||
|
|
( timeDiff.Seconds >= BACKOFF_24_HOURS_IN_S ) )
|
|
{
|
|
band->TimeCredits = maxCredits;
|
|
|
|
if( elapsedTimeSinceStartup.Seconds >= BACKOFF_DUTY_CYCLE_24_HOURS_IN_S )
|
|
{
|
|
timeDiff.Seconds = ( elapsedTimeSinceStartup.Seconds - BACKOFF_DUTY_CYCLE_24_HOURS_IN_S ) / BACKOFF_24_HOURS_IN_S;
|
|
timeDiff.Seconds *= BACKOFF_24_HOURS_IN_S;
|
|
timeDiff.Seconds += BACKOFF_DUTY_CYCLE_24_HOURS_IN_S;
|
|
timeDiff.SubSeconds = 0;
|
|
band->LastMaxCreditAssignTime = SysTimeToMs( timeDiff );
|
|
}
|
|
}
|
|
#endif
|
|
|
|
}
|
|
else
|
|
{
|
|
if( dutyCycleEnabled == false )
|
|
{
|
|
// Assign max credits when the duty cycle is disabled.
|
|
band->TimeCredits = maxCredits;
|
|
}
|
|
}
|
|
|
|
#if (defined( REGION_VERSION ) && (( REGION_VERSION == 0x01010003 ) || ( REGION_VERSION == 0x02010001 )))
|
|
// Assign the max credits if its the first time
|
|
if( band->LastBandUpdateTime == 0 )
|
|
{
|
|
band->TimeCredits = maxCredits;
|
|
}
|
|
#endif
|
|
|
|
// Setup the maximum allowed credits. We can assign them
|
|
// safely all the time.
|
|
band->MaxTimeCredits = maxCredits;
|
|
|
|
return dutyCycle;
|
|
}
|
|
|
|
|
|
#if (defined( REGION_VERSION ) && ( REGION_VERSION == 0x02010003 ))
|
|
static uint16_t UpdateTimeCredits( Band_t* band, bool joined, bool dutyCycleEnabled,
|
|
bool lastTxIsJoinRequest, SysTime_t elapsedTimeSinceStartup,
|
|
TimerTime_t currentTime, TimerTime_t lastBandUpdateTime )
|
|
{
|
|
uint16_t dutyCycle = SetMaxTimeCredits( band, joined, elapsedTimeSinceStartup,
|
|
dutyCycleEnabled, lastTxIsJoinRequest );
|
|
TimerTime_t observation = DUTY_CYCLE_TIME_PERIOD;
|
|
|
|
if( joined == false )
|
|
{
|
|
if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_1_HOUR_IN_S )
|
|
{
|
|
observation = BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 1000;
|
|
}
|
|
else if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_10_HOURS_IN_S )
|
|
{
|
|
observation = ( BACKOFF_DUTY_CYCLE_10_HOURS_IN_S * 1000 );
|
|
}
|
|
else
|
|
{
|
|
observation = ( BACKOFF_DUTY_CYCLE_24_HOURS_IN_S * 1000 );
|
|
}
|
|
}
|
|
|
|
// Apply new credits only if the observation period has been elapsed.
|
|
if( ( observation <= lastBandUpdateTime ) ||
|
|
( band->LastMaxCreditAssignTime != observation ) ||
|
|
( band->LastBandUpdateTime == 0 ) )
|
|
{
|
|
band->TimeCredits = band->MaxTimeCredits;
|
|
band->LastBandUpdateTime = currentTime;
|
|
band->LastMaxCreditAssignTime = observation;
|
|
}
|
|
return dutyCycle;
|
|
}
|
|
#else
|
|
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,
|
|
dutyCycleEnabled, lastTxIsJoinRequest );
|
|
|
|
if( joined == true )
|
|
{
|
|
// Apply a sliding window for the duty cycle with collection and speding
|
|
// credits.
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
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++ )
|
|
{
|
|
#if (defined( REGION_VERSION ) && ( REGION_VERSION == 0x02010003 ))
|
|
TimerTime_t elapsedTime = TimerGetElapsedTime( bands[i].LastBandUpdateTime );
|
|
|
|
// Synchronization of bands and credits
|
|
dutyCycle = UpdateTimeCredits( &bands[i], joined, dutyCycleEnabled,
|
|
lastTxIsJoinRequest, elapsedTimeSinceStartup,
|
|
currentTime, elapsedTime );
|
|
#else
|
|
// Synchronization of bands and credits
|
|
dutyCycle = UpdateTimeCredits( &bands[i], joined, dutyCycleEnabled,
|
|
lastTxIsJoinRequest, elapsedTimeSinceStartup,
|
|
currentTime );
|
|
#endif
|
|
|
|
// 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 ) && ( joined == true ) ) )
|
|
{
|
|
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.
|
|
#if (defined( REGION_VERSION ) && ( REGION_VERSION == 0x02010003 ))
|
|
TimerTime_t observationTimeDiff = 0;
|
|
if( bands[i].LastMaxCreditAssignTime >= elapsedTime )
|
|
{
|
|
observationTimeDiff = bands[i].LastMaxCreditAssignTime - elapsedTime;
|
|
}
|
|
minTimeToWait = MIN( minTimeToWait, observationTimeDiff );
|
|
#else
|
|
minTimeToWait = MIN( minTimeToWait, ( creditCosts - bands[i].TimeCredits ) );
|
|
#endif
|
|
|
|
// 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 (defined( REGION_VERSION ) && (( REGION_VERSION == 0x01010003 ) || ( REGION_VERSION == 0x02010001 )))
|
|
// Apply a special calculation if the device is not joined.
|
|
if( joined == false )
|
|
{
|
|
SysTime_t backoffTimeRange = {
|
|
.Seconds = 0,
|
|
.SubSeconds = 0,
|
|
};
|
|
// Get the backoff time range based on the duty cycle definition
|
|
if( dutyCycle == BACKOFF_DC_1_HOUR )
|
|
{
|
|
backoffTimeRange.Seconds = BACKOFF_DUTY_CYCLE_1_HOUR_IN_S;
|
|
}
|
|
else if( dutyCycle == BACKOFF_DC_10_HOURS )
|
|
{
|
|
backoffTimeRange.Seconds = BACKOFF_DUTY_CYCLE_10_HOURS_IN_S;
|
|
}
|
|
else
|
|
{
|
|
backoffTimeRange.Seconds = BACKOFF_DUTY_CYCLE_24_HOURS_IN_S;
|
|
}
|
|
// Calculate the time to wait.
|
|
if( elapsedTimeSinceStartup.Seconds > BACKOFF_DUTY_CYCLE_24_HOURS_IN_S )
|
|
{
|
|
backoffTimeRange.Seconds += BACKOFF_24_HOURS_IN_S * ( ( ( elapsedTimeSinceStartup.Seconds - BACKOFF_DUTY_CYCLE_24_HOURS_IN_S ) / BACKOFF_24_HOURS_IN_S ) + 1 );
|
|
}
|
|
// Calculate the time difference between now and the next range
|
|
backoffTimeRange = SysTimeSub( backoffTimeRange, elapsedTimeSinceStartup );
|
|
minTimeToWait = SysTimeToMs( backoffTimeRange );
|
|
}
|
|
#endif
|
|
|
|
}
|
|
}
|
|
|
|
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( datarate == 0x0F )
|
|
{ // 0xF means that the device MUST ignore that field, and keep the current parameter value.
|
|
datarate = verifyParams->CurrentDatarate;
|
|
}
|
|
else if( RegionCommonChanVerifyDr( verifyParams->NbChannels, verifyParams->ChannelsMask, datarate,
|
|
verifyParams->MinDatarate, verifyParams->MaxDatarate, verifyParams->Channels ) == false )
|
|
{
|
|
status &= 0xFD; // Datarate KO
|
|
}
|
|
|
|
// Verify tx power
|
|
if( txPower == 0x0F )
|
|
{ // 0xF means that the device MUST ignore that field, and keep the current parameter value.
|
|
txPower = verifyParams->CurrentTxPower;
|
|
}
|
|
else 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 )
|
|
{ // Set nbRep to the default value of 1.
|
|
nbRepetitions = 1;
|
|
}
|
|
}
|
|
|
|
// Apply changes
|
|
*dr = datarate;
|
|
*txPow = txPower;
|
|
*nbRep = nbRepetitions;
|
|
|
|
return status;
|
|
}
|
|
|
|
uint32_t RegionCommonComputeSymbolTimeLoRa( uint8_t phyDr, uint32_t bandwidthInHz )
|
|
{
|
|
return ( 1 << phyDr ) * 1000000 / bandwidthInHz;
|
|
}
|
|
|
|
uint32_t RegionCommonComputeSymbolTimeFsk( uint8_t phyDrInKbps )
|
|
{
|
|
return 8000 / ( uint32_t )phyDrInKbps; // 1 symbol equals 1 byte
|
|
}
|
|
|
|
void RegionCommonComputeRxWindowParameters( uint32_t tSymbolInUs, uint8_t minRxSymbols, uint32_t rxErrorInMs, uint32_t wakeUpTimeInMs, uint32_t* windowTimeoutInSymbols, int32_t* windowOffsetInMs )
|
|
{
|
|
*windowTimeoutInSymbols = MAX( DIV_CEIL( ( ( 2 * minRxSymbols - 8 ) * tSymbolInUs + 2 * ( rxErrorInMs * 1000 ) ), tSymbolInUs ), minRxSymbols ); // Computed number of symbols
|
|
*windowOffsetInMs = ( int32_t )DIV_CEIL( ( int32_t )( 4 * tSymbolInUs ) -
|
|
( int32_t )DIV_CEIL( ( *windowTimeoutInSymbols * tSymbolInUs ), 2 ) -
|
|
( int32_t )( wakeUpTimeInMs * 1000 ), 1000 );
|
|
}
|
|
|
|
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 %u Hz at DR %d\r\n", (unsigned)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 != NULL ) )
|
|
{
|
|
if( ( countNbOfEnabledChannelsParams->JoinChannels[k] & ( 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;
|
|
}
|
|
}
|
|
|
|
int8_t RegionCommonGetNextLowerTxDr( RegionCommonGetNextLowerTxDrParams_t *params )
|
|
{
|
|
int8_t drLocal = params->CurrentDr;
|
|
|
|
if( params->CurrentDr == params->MinDr )
|
|
{
|
|
return params->MinDr;
|
|
}
|
|
else
|
|
{
|
|
do
|
|
{
|
|
drLocal = ( drLocal - 1 );
|
|
} while( ( drLocal != params->MinDr ) &&
|
|
( RegionCommonChanVerifyDr( params->NbChannels, params->ChannelsMask, drLocal, params->MinDr, params->MaxDr, params->Channels ) == false ) );
|
|
|
|
return drLocal;
|
|
}
|
|
}
|
|
|
|
int8_t RegionCommonLimitTxPower( int8_t txPower, int8_t maxBandTxPower )
|
|
{
|
|
// Limit tx power to the band max
|
|
return MAX( txPower, maxBandTxPower );
|
|
}
|
|
|
|
uint32_t RegionCommonGetBandwidth( uint32_t drIndex, const uint32_t* bandwidths )
|
|
{
|
|
switch( bandwidths[drIndex] )
|
|
{
|
|
default:
|
|
case 125000:
|
|
return 0;
|
|
case 250000:
|
|
return 1;
|
|
case 500000:
|
|
return 2;
|
|
}
|
|
}
|
|
|
|
void RegionCommonRxConfigPrint(LoRaMacRxSlot_t rxSlot, uint32_t frequency, int8_t dr)
|
|
{
|
|
if ( rxSlot < RX_SLOT_NONE )
|
|
{
|
|
MW_LOG(TS_ON, VLEVEL_M, "RX_%s on freq %u Hz at DR %d\r\n", EventRXSlotStrings[rxSlot], (unsigned)frequency, dr );
|
|
}
|
|
else
|
|
{
|
|
MW_LOG(TS_ON, VLEVEL_M, "RX on freq %u Hz at DR %d\r\n", (unsigned)frequency, dr );
|
|
}
|
|
}
|
|
|
|
void RegionCommonTxConfigPrint(uint32_t frequency, int8_t dr)
|
|
{
|
|
MW_LOG(TS_ON, VLEVEL_M, "TX on freq %u Hz at DR %d\r\n", (unsigned)frequency, dr );
|
|
}
|