1 /*!
2  * \file      RegionCommon.c
3  *
4  * \brief     LoRa MAC common region implementation
5  *
6  * \copyright Revised BSD License, see section \ref LICENSE.
7  *
8  * \code
9  *                ______                              _
10  *               / _____)             _              | |
11  *              ( (____  _____ ____ _| |_ _____  ____| |__
12  *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
13  *               _____) ) ____| | | || |_| ____( (___| | | |
14  *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
15  *              (C)2013-2017 Semtech
16  *
17  *               ___ _____ _   ___ _  _____ ___  ___  ___ ___
18  *              / __|_   _/_\ / __| |/ / __/ _ \| _ \/ __| __|
19  *              \__ \ | |/ _ \ (__| ' <| _| (_) |   / (__| _|
20  *              |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___|
21  *              embedded.connectivity.solutions===============
22  *
23  * \endcode
24  *
25  * \author    Miguel Luis ( Semtech )
26  *
27  * \author    Gregory Cristian ( Semtech )
28  *
29  * \author    Daniel Jaeckle ( STACKFORCE )
30  */
31 #include <math.h>
32 #include "radio.h"
33 #include "utilities.h"
34 #include "RegionCommon.h"
35 #include "systime.h"
36 
37 #define BACKOFF_DC_1_HOUR                   100
38 
39 #define BACKOFF_DUTY_CYCLE_1_HOUR_IN_S      3600
40 #define BACKOFF_DUTY_CYCLE_10_HOURS_IN_S    ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S + ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 10 ) )
41 #define BACKOFF_DUTY_CYCLE_24_HOURS_IN_S    ( BACKOFF_DUTY_CYCLE_10_HOURS_IN_S + ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 24 ) )
42 #define BACKOFF_24_HOURS_IN_S               ( BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 24 )
43 
44 #ifndef DUTY_CYCLE_TIME_PERIOD
45 /*!
46  * Default duty cycle observation time period is 1 hour (3600000 ms) according to ETSI.
47  */
48 #define DUTY_CYCLE_TIME_PERIOD              3600000
49 #endif
50 
51 #ifndef DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H
52 /*!
53  * Time credits for the join backoff algorithm for the 24H period.
54  */
55 #define DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H 870000
56 #endif
57 
58 /*!
59  * \brief Returns `N / D` rounded to the smallest integer value greater than or equal to `N / D`
60  *
61  * \warning when `D == 0`, the result is undefined
62  *
63  * \remark `N` and `D` can be signed or unsigned
64  *
65  * \param [IN] N the numerator, which can have any sign
66  * \param [IN] D the denominator, which can have any sign
67  * \retval N / D with any fractional part rounded to the smallest integer value greater than or equal to `N / D`
68  */
69 #define DIV_CEIL( N, D )                                                       \
70     (                                                                          \
71         ( N > 0 ) ?                                                            \
72         ( ( ( N ) + ( D ) - 1 ) / ( D ) ) :                                    \
73         ( ( N ) / ( D ) )                                                      \
74     )
75 
GetDutyCycle(Band_t * band,bool joined,SysTime_t elapsedTimeSinceStartup)76 static uint16_t GetDutyCycle( Band_t* band, bool joined, SysTime_t elapsedTimeSinceStartup )
77 {
78     uint16_t dutyCycle = band->DCycle;
79 
80     if( joined == false )
81     {
82         uint16_t joinDutyCycle = BACKOFF_DC_1_HOUR;
83 
84         // Take the most restrictive duty cycle
85         dutyCycle = MAX( dutyCycle, joinDutyCycle );
86     }
87 
88     // Prevent value of 0
89     if( dutyCycle == 0 )
90     {
91         dutyCycle = 1;
92     }
93 
94     return dutyCycle;
95 }
96 
SetMaxTimeCredits(Band_t * band,bool joined,SysTime_t elapsedTimeSinceStartup,bool dutyCycleEnabled,bool lastTxIsJoinRequest)97 static uint16_t SetMaxTimeCredits( Band_t* band, bool joined, SysTime_t elapsedTimeSinceStartup,
98                                    bool dutyCycleEnabled, bool lastTxIsJoinRequest )
99 {
100     uint16_t dutyCycle = band->DCycle;
101     TimerTime_t maxCredits = DUTY_CYCLE_TIME_PERIOD;
102 
103     // Get the band duty cycle. If not joined, the function either returns the join duty cycle
104     // or the band duty cycle, whichever is more restrictive.
105     dutyCycle = GetDutyCycle( band, joined, elapsedTimeSinceStartup );
106 
107     if( joined == false )
108     {
109         if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_1_HOUR_IN_S )
110         {
111             maxCredits = DUTY_CYCLE_TIME_PERIOD;
112         }
113         else if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_10_HOURS_IN_S )
114         {
115             maxCredits = DUTY_CYCLE_TIME_PERIOD;
116         }
117         else
118         {
119             maxCredits = DUTY_CYCLE_TIME_PERIOD_JOIN_BACKOFF_24H;
120         }
121     }
122     else
123     {
124         if( dutyCycleEnabled == false )
125         {
126             // Assign max credits when the duty cycle is disabled.
127             band->TimeCredits = maxCredits;
128         }
129     }
130 
131     // Setup the maximum allowed credits. We can assign them
132     // safely all the time.
133     band->MaxTimeCredits = maxCredits;
134 
135     return dutyCycle;
136 }
137 
UpdateTimeCredits(Band_t * band,bool joined,bool dutyCycleEnabled,bool lastTxIsJoinRequest,SysTime_t elapsedTimeSinceStartup,TimerTime_t currentTime,TimerTime_t lastBandUpdateTime)138 static uint16_t UpdateTimeCredits( Band_t* band, bool joined, bool dutyCycleEnabled,
139                                    bool lastTxIsJoinRequest, SysTime_t elapsedTimeSinceStartup,
140                                    TimerTime_t currentTime, TimerTime_t lastBandUpdateTime )
141 {
142     uint16_t dutyCycle = SetMaxTimeCredits( band, joined, elapsedTimeSinceStartup,
143                                             dutyCycleEnabled, lastTxIsJoinRequest );
144     TimerTime_t observation = DUTY_CYCLE_TIME_PERIOD;
145 
146     if( joined == false )
147     {
148         if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_1_HOUR_IN_S )
149         {
150             observation = BACKOFF_DUTY_CYCLE_1_HOUR_IN_S * 1000;
151         }
152         else if( elapsedTimeSinceStartup.Seconds < BACKOFF_DUTY_CYCLE_10_HOURS_IN_S )
153         {
154             observation = ( BACKOFF_DUTY_CYCLE_10_HOURS_IN_S * 1000 );
155         }
156         else
157         {
158             observation = ( BACKOFF_DUTY_CYCLE_24_HOURS_IN_S * 1000 );
159         }
160     }
161 
162     // Apply new credits only if the observation period has been elapsed.
163     if( ( observation <= lastBandUpdateTime ) ||
164         ( band->LastMaxCreditAssignTime != observation ) ||
165         ( band->LastBandUpdateTime == 0 ) )
166     {
167         band->TimeCredits = band->MaxTimeCredits;
168         band->LastBandUpdateTime = currentTime;
169         band->LastMaxCreditAssignTime = observation;
170     }
171     return dutyCycle;
172 }
173 
CountChannels(uint16_t mask,uint8_t nbBits)174 static uint8_t CountChannels( uint16_t mask, uint8_t nbBits )
175 {
176     uint8_t nbActiveBits = 0;
177 
178     for( uint8_t j = 0; j < nbBits; j++ )
179     {
180         if( ( mask & ( 1 << j ) ) == ( 1 << j ) )
181         {
182             nbActiveBits++;
183         }
184     }
185     return nbActiveBits;
186 }
187 
RegionCommonChanVerifyDr(uint8_t nbChannels,uint16_t * channelsMask,int8_t dr,int8_t minDr,int8_t maxDr,ChannelParams_t * channels)188 bool RegionCommonChanVerifyDr( uint8_t nbChannels, uint16_t* channelsMask, int8_t dr, int8_t minDr, int8_t maxDr, ChannelParams_t* channels )
189 {
190     if( RegionCommonValueInRange( dr, minDr, maxDr ) == 0 )
191     {
192         return false;
193     }
194 
195     for( uint8_t i = 0, k = 0; i < nbChannels; i += 16, k++ )
196     {
197         for( uint8_t j = 0; j < 16; j++ )
198         {
199             if( ( ( channelsMask[k] & ( 1 << j ) ) != 0 ) )
200             {// Check datarate validity for enabled channels
201                 if( RegionCommonValueInRange( dr, ( channels[i + j].DrRange.Fields.Min & 0x0F ),
202                                                   ( channels[i + j].DrRange.Fields.Max & 0x0F ) ) == 1 )
203                 {
204                     // At least 1 channel has been found we can return OK.
205                     return true;
206                 }
207             }
208         }
209     }
210     return false;
211 }
212 
RegionCommonValueInRange(int8_t value,int8_t min,int8_t max)213 uint8_t RegionCommonValueInRange( int8_t value, int8_t min, int8_t max )
214 {
215     if( ( value >= min ) && ( value <= max ) )
216     {
217         return 1;
218     }
219     return 0;
220 }
221 
RegionCommonChanDisable(uint16_t * channelsMask,uint8_t id,uint8_t maxChannels)222 bool RegionCommonChanDisable( uint16_t* channelsMask, uint8_t id, uint8_t maxChannels )
223 {
224     uint8_t index = id / 16;
225 
226     if( ( index > ( maxChannels / 16 ) ) || ( id >= maxChannels ) )
227     {
228         return false;
229     }
230 
231     // Deactivate channel
232     channelsMask[index] &= ~( 1 << ( id % 16 ) );
233 
234     return true;
235 }
236 
RegionCommonCountChannels(uint16_t * channelsMask,uint8_t startIdx,uint8_t stopIdx)237 uint8_t RegionCommonCountChannels( uint16_t* channelsMask, uint8_t startIdx, uint8_t stopIdx )
238 {
239     uint8_t nbChannels = 0;
240 
241     if( channelsMask == NULL )
242     {
243         return 0;
244     }
245 
246     for( uint8_t i = startIdx; i < stopIdx; i++ )
247     {
248         nbChannels += CountChannels( channelsMask[i], 16 );
249     }
250 
251     return nbChannels;
252 }
253 
RegionCommonChanMaskCopy(uint16_t * channelsMaskDest,uint16_t * channelsMaskSrc,uint8_t len)254 void RegionCommonChanMaskCopy( uint16_t* channelsMaskDest, uint16_t* channelsMaskSrc, uint8_t len )
255 {
256     if( ( channelsMaskDest != NULL ) && ( channelsMaskSrc != NULL ) )
257     {
258         for( uint8_t i = 0; i < len; i++ )
259         {
260             channelsMaskDest[i] = channelsMaskSrc[i];
261         }
262     }
263 }
264 
RegionCommonSetBandTxDone(Band_t * band,TimerTime_t lastTxAirTime,bool joined,SysTime_t elapsedTimeSinceStartup)265 void RegionCommonSetBandTxDone( Band_t* band, TimerTime_t lastTxAirTime, bool joined, SysTime_t elapsedTimeSinceStartup )
266 {
267     // Get the band duty cycle. If not joined, the function either returns the join duty cycle
268     // or the band duty cycle, whichever is more restrictive.
269     uint16_t dutyCycle = GetDutyCycle( band, joined, elapsedTimeSinceStartup );
270 
271     // Reduce with transmission time
272     if( band->TimeCredits > ( lastTxAirTime * dutyCycle ) )
273     {
274         // Reduce time credits by the time of air
275         band->TimeCredits -= ( lastTxAirTime * dutyCycle );
276     }
277     else
278     {
279         band->TimeCredits = 0;
280     }
281 }
282 
RegionCommonUpdateBandTimeOff(bool joined,Band_t * bands,uint8_t nbBands,bool dutyCycleEnabled,bool lastTxIsJoinRequest,SysTime_t elapsedTimeSinceStartup,TimerTime_t expectedTimeOnAir)283 TimerTime_t RegionCommonUpdateBandTimeOff( bool joined, Band_t* bands,
284                                            uint8_t nbBands, bool dutyCycleEnabled,
285                                            bool lastTxIsJoinRequest, SysTime_t elapsedTimeSinceStartup,
286                                            TimerTime_t expectedTimeOnAir )
287 {
288     TimerTime_t minTimeToWait = TIMERTIME_T_MAX;
289     TimerTime_t currentTime = TimerGetCurrentTime( );
290     TimerTime_t creditCosts = 0;
291     uint16_t dutyCycle = 1;
292     uint8_t validBands = 0;
293 
294     for( uint8_t i = 0; i < nbBands; i++ )
295     {
296         TimerTime_t elapsedTime = TimerGetElapsedTime( bands[i].LastBandUpdateTime );
297 
298         // Synchronization of bands and credits
299         dutyCycle = UpdateTimeCredits( &bands[i], joined, dutyCycleEnabled,
300                                        lastTxIsJoinRequest, elapsedTimeSinceStartup,
301                                        currentTime, elapsedTime );
302 
303         // Calculate the credit costs for the next transmission
304         // with the duty cycle and the expected time on air
305         creditCosts = expectedTimeOnAir * dutyCycle;
306 
307         // Check if the band is ready for transmission. Its ready,
308         // when the duty cycle is off, or the TimeCredits of the band
309         // is higher than the credit costs for the transmission.
310         if( ( bands[i].TimeCredits > creditCosts ) ||
311             ( ( dutyCycleEnabled == false ) && ( joined == true ) ) )
312         {
313             bands[i].ReadyForTransmission = true;
314             // This band is a potential candidate for an
315             // upcoming transmission, so increase the counter.
316             validBands++;
317         }
318         else
319         {
320             // In this case, the band has not enough credits
321             // for the next transmission.
322             bands[i].ReadyForTransmission = false;
323 
324             if( bands[i].MaxTimeCredits > creditCosts )
325             {
326                 // The band can only be taken into account, if the maximum credits
327                 // of the band are higher than the credit costs.
328                 // We calculate the minTimeToWait among the bands which are not
329                 // ready for transmission and which are potentially available
330                 // for a transmission in the future.
331                 TimerTime_t observationTimeDiff = 0;
332                 if( bands[i].LastMaxCreditAssignTime >= elapsedTime )
333                 {
334                     observationTimeDiff = bands[i].LastMaxCreditAssignTime - elapsedTime;
335                 }
336                 minTimeToWait = MIN( minTimeToWait, observationTimeDiff );
337                 // This band is a potential candidate for an
338                 // upcoming transmission (even if its time credits are not enough
339                 // at the moment), so increase the counter.
340                 validBands++;
341             }
342         }
343     }
344 
345 
346     if( validBands == 0 )
347     {
348         // There is no valid band available to handle a transmission
349         // in the given DUTY_CYCLE_TIME_PERIOD.
350         return TIMERTIME_T_MAX;
351     }
352     return minTimeToWait;
353 }
354 
RegionCommonParseLinkAdrReq(uint8_t * payload,RegionCommonLinkAdrParams_t * linkAdrParams)355 uint8_t RegionCommonParseLinkAdrReq( uint8_t* payload, RegionCommonLinkAdrParams_t* linkAdrParams )
356 {
357     uint8_t retIndex = 0;
358 
359     if( payload[0] == SRV_MAC_LINK_ADR_REQ )
360     {
361         // Parse datarate and tx power
362         linkAdrParams->Datarate = payload[1];
363         linkAdrParams->TxPower = linkAdrParams->Datarate & 0x0F;
364         linkAdrParams->Datarate = ( linkAdrParams->Datarate >> 4 ) & 0x0F;
365         // Parse ChMask
366         linkAdrParams->ChMask = ( uint16_t )payload[2];
367         linkAdrParams->ChMask |= ( uint16_t )payload[3] << 8;
368         // Parse ChMaskCtrl and nbRep
369         linkAdrParams->NbRep = payload[4];
370         linkAdrParams->ChMaskCtrl = ( linkAdrParams->NbRep >> 4 ) & 0x07;
371         linkAdrParams->NbRep &= 0x0F;
372 
373         // LinkAdrReq has 4 bytes length + 1 byte CMD
374         retIndex = 5;
375     }
376     return retIndex;
377 }
378 
RegionCommonLinkAdrReqVerifyParams(RegionCommonLinkAdrReqVerifyParams_t * verifyParams,int8_t * dr,int8_t * txPow,uint8_t * nbRep)379 uint8_t RegionCommonLinkAdrReqVerifyParams( RegionCommonLinkAdrReqVerifyParams_t* verifyParams, int8_t* dr, int8_t* txPow, uint8_t* nbRep )
380 {
381     uint8_t status = verifyParams->Status;
382     int8_t datarate = verifyParams->Datarate;
383     int8_t txPower = verifyParams->TxPower;
384     int8_t nbRepetitions = verifyParams->NbRep;
385 
386     // Handle the case when ADR is off.
387     if( verifyParams->AdrEnabled == false )
388     {
389         // When ADR is off, we are allowed to change the channels mask
390         nbRepetitions = verifyParams->CurrentNbRep;
391         datarate =  verifyParams->CurrentDatarate;
392         txPower =  verifyParams->CurrentTxPower;
393     }
394 
395     if( status != 0 )
396     {
397         // Verify datarate. The variable phyParam. Value contains the minimum allowed datarate.
398         if( datarate == 0x0F )
399         { // 0xF means that the device MUST ignore that field, and keep the current parameter value.
400             datarate =  verifyParams->CurrentDatarate;
401         }
402         else if( RegionCommonChanVerifyDr( verifyParams->NbChannels, verifyParams->ChannelsMask, datarate,
403                                       verifyParams->MinDatarate, verifyParams->MaxDatarate, verifyParams->Channels  ) == false )
404         {
405             status &= 0xFD; // Datarate KO
406         }
407 
408         // Verify tx power
409         if( txPower == 0x0F )
410         { // 0xF means that the device MUST ignore that field, and keep the current parameter value.
411             txPower =  verifyParams->CurrentTxPower;
412         }
413         else if( RegionCommonValueInRange( txPower, verifyParams->MaxTxPower, verifyParams->MinTxPower ) == 0 )
414         {
415             // Verify if the maximum TX power is exceeded
416             if( verifyParams->MaxTxPower > txPower )
417             { // Apply maximum TX power. Accept TX power.
418                 txPower = verifyParams->MaxTxPower;
419             }
420             else
421             {
422                 status &= 0xFB; // TxPower KO
423             }
424         }
425     }
426 
427     // If the status is ok, verify the NbRep
428     if( status == 0x07 )
429     {
430         if( nbRepetitions == 0 )
431         { // Set nbRep to the default value of 1.
432             nbRepetitions = 1;
433         }
434     }
435 
436     // Apply changes
437     *dr = datarate;
438     *txPow = txPower;
439     *nbRep = nbRepetitions;
440 
441     return status;
442 }
443 
RegionCommonComputeSymbolTimeLoRa(uint8_t phyDr,uint32_t bandwidthInHz)444 uint32_t RegionCommonComputeSymbolTimeLoRa( uint8_t phyDr, uint32_t bandwidthInHz )
445 {
446     return ( 1 << phyDr ) * 1000000 / bandwidthInHz;
447 }
448 
RegionCommonComputeSymbolTimeFsk(uint8_t phyDrInKbps)449 uint32_t RegionCommonComputeSymbolTimeFsk( uint8_t phyDrInKbps )
450 {
451     return 8000 / ( uint32_t )phyDrInKbps; // 1 symbol equals 1 byte
452 }
453 
RegionCommonComputeRxWindowParameters(uint32_t tSymbolInUs,uint8_t minRxSymbols,uint32_t rxErrorInMs,uint32_t wakeUpTimeInMs,uint32_t * windowTimeoutInSymbols,int32_t * windowOffsetInMs)454 void RegionCommonComputeRxWindowParameters( uint32_t tSymbolInUs, uint8_t minRxSymbols, uint32_t rxErrorInMs, uint32_t wakeUpTimeInMs, uint32_t* windowTimeoutInSymbols, int32_t* windowOffsetInMs )
455 {
456     *windowTimeoutInSymbols = MAX( DIV_CEIL( ( ( 2 * minRxSymbols - 8 ) * tSymbolInUs + 2 * ( rxErrorInMs * 1000 ) ),  tSymbolInUs ), minRxSymbols ); // Computed number of symbols
457     *windowOffsetInMs = ( int32_t )DIV_CEIL( ( int32_t )( 4 * tSymbolInUs ) -
458                                                ( int32_t )DIV_CEIL( ( *windowTimeoutInSymbols * tSymbolInUs ), 2 ) -
459                                                ( int32_t )( wakeUpTimeInMs * 1000 ), 1000 );
460 }
461 
RegionCommonComputeTxPower(int8_t txPowerIndex,float maxEirp,float antennaGain)462 int8_t RegionCommonComputeTxPower( int8_t txPowerIndex, float maxEirp, float antennaGain )
463 {
464     int8_t phyTxPower = 0;
465 
466     phyTxPower = ( int8_t )floorf( ( maxEirp - ( txPowerIndex * 2U ) ) - antennaGain );
467 
468     return phyTxPower;
469 }
470 
RegionCommonRxBeaconSetup(RegionCommonRxBeaconSetupParams_t * rxBeaconSetupParams)471 void RegionCommonRxBeaconSetup( RegionCommonRxBeaconSetupParams_t* rxBeaconSetupParams )
472 {
473     bool rxContinuous = true;
474     uint8_t datarate;
475 
476     // Set the radio into sleep mode
477     Radio.Sleep( );
478 
479     // Setup frequency and payload length
480     Radio.SetChannel( rxBeaconSetupParams->Frequency );
481     Radio.SetMaxPayloadLength( MODEM_LORA, rxBeaconSetupParams->BeaconSize );
482 
483     // Check the RX continuous mode
484     if( rxBeaconSetupParams->RxTime != 0 )
485     {
486         rxContinuous = false;
487     }
488 
489     // Get region specific datarate
490     datarate = rxBeaconSetupParams->Datarates[rxBeaconSetupParams->BeaconDatarate];
491 
492     // Setup radio
493     Radio.SetRxConfig( MODEM_LORA, rxBeaconSetupParams->BeaconChannelBW, datarate,
494                        1, 0, 10, rxBeaconSetupParams->SymbolTimeout, true, rxBeaconSetupParams->BeaconSize, false, 0, 0, false, rxContinuous );
495 
496     Radio.Rx( rxBeaconSetupParams->RxTime );
497 }
498 
RegionCommonCountNbOfEnabledChannels(RegionCommonCountNbOfEnabledChannelsParams_t * countNbOfEnabledChannelsParams,uint8_t * enabledChannels,uint8_t * nbEnabledChannels,uint8_t * nbRestrictedChannels)499 void RegionCommonCountNbOfEnabledChannels( RegionCommonCountNbOfEnabledChannelsParams_t* countNbOfEnabledChannelsParams,
500                                            uint8_t* enabledChannels, uint8_t* nbEnabledChannels, uint8_t* nbRestrictedChannels )
501 {
502     uint8_t nbChannelCount = 0;
503     uint8_t nbRestrictedChannelsCount = 0;
504 
505     for( uint8_t i = 0, k = 0; i < countNbOfEnabledChannelsParams->MaxNbChannels; i += 16, k++ )
506     {
507         for( uint8_t j = 0; j < 16; j++ )
508         {
509             if( ( countNbOfEnabledChannelsParams->ChannelsMask[k] & ( 1 << j ) ) != 0 )
510             {
511                 if( countNbOfEnabledChannelsParams->Channels[i + j].Frequency == 0 )
512                 { // Check if the channel is enabled
513                     continue;
514                 }
515                 if( ( countNbOfEnabledChannelsParams->Joined == false ) &&
516                     ( countNbOfEnabledChannelsParams->JoinChannels != NULL ) )
517                 {
518                     if( ( countNbOfEnabledChannelsParams->JoinChannels[k] & ( 1 << j ) ) == 0 )
519                     {
520                         continue;
521                     }
522                 }
523                 if( RegionCommonValueInRange( countNbOfEnabledChannelsParams->Datarate,
524                                               countNbOfEnabledChannelsParams->Channels[i + j].DrRange.Fields.Min,
525                                               countNbOfEnabledChannelsParams->Channels[i + j].DrRange.Fields.Max ) == false )
526                 { // Check if the current channel selection supports the given datarate
527                     continue;
528                 }
529                 if( countNbOfEnabledChannelsParams->Bands[countNbOfEnabledChannelsParams->Channels[i + j].Band].ReadyForTransmission == false )
530                 { // Check if the band is available for transmission
531                     nbRestrictedChannelsCount++;
532                     continue;
533                 }
534                 enabledChannels[nbChannelCount++] = i + j;
535             }
536         }
537     }
538     *nbEnabledChannels = nbChannelCount;
539     *nbRestrictedChannels = nbRestrictedChannelsCount;
540 }
541 
RegionCommonIdentifyChannels(RegionCommonIdentifyChannelsParam_t * identifyChannelsParam,TimerTime_t * aggregatedTimeOff,uint8_t * enabledChannels,uint8_t * nbEnabledChannels,uint8_t * nbRestrictedChannels,TimerTime_t * nextTxDelay)542 LoRaMacStatus_t RegionCommonIdentifyChannels( RegionCommonIdentifyChannelsParam_t* identifyChannelsParam,
543                                               TimerTime_t* aggregatedTimeOff, uint8_t* enabledChannels,
544                                               uint8_t* nbEnabledChannels, uint8_t* nbRestrictedChannels,
545                                               TimerTime_t* nextTxDelay )
546 {
547     TimerTime_t elapsed = TimerGetElapsedTime( identifyChannelsParam->LastAggrTx );
548     *nextTxDelay = identifyChannelsParam->AggrTimeOff - elapsed;
549     *nbRestrictedChannels = 1;
550     *nbEnabledChannels = 0;
551 
552     if( ( identifyChannelsParam->LastAggrTx == 0 ) ||
553         ( identifyChannelsParam->AggrTimeOff <= elapsed ) )
554     {
555         // Reset Aggregated time off
556         *aggregatedTimeOff = 0;
557 
558         // Update bands Time OFF
559         *nextTxDelay = RegionCommonUpdateBandTimeOff( identifyChannelsParam->CountNbOfEnabledChannelsParam->Joined,
560                                                       identifyChannelsParam->CountNbOfEnabledChannelsParam->Bands,
561                                                       identifyChannelsParam->MaxBands,
562                                                       identifyChannelsParam->DutyCycleEnabled,
563                                                       identifyChannelsParam->LastTxIsJoinRequest,
564                                                       identifyChannelsParam->ElapsedTimeSinceStartUp,
565                                                       identifyChannelsParam->ExpectedTimeOnAir );
566 
567         RegionCommonCountNbOfEnabledChannels( identifyChannelsParam->CountNbOfEnabledChannelsParam, enabledChannels,
568                                               nbEnabledChannels, nbRestrictedChannels );
569     }
570 
571     if( *nbEnabledChannels > 0 )
572     {
573         *nextTxDelay = 0;
574         return LORAMAC_STATUS_OK;
575     }
576     else if( *nbRestrictedChannels > 0 )
577     {
578         return LORAMAC_STATUS_DUTYCYCLE_RESTRICTED;
579     }
580     else
581     {
582         return LORAMAC_STATUS_NO_CHANNEL_FOUND;
583     }
584 }
585 
RegionCommonGetNextLowerTxDr(RegionCommonGetNextLowerTxDrParams_t * params)586 int8_t RegionCommonGetNextLowerTxDr( RegionCommonGetNextLowerTxDrParams_t *params )
587 {
588     int8_t drLocal = params->CurrentDr;
589 
590     if( params->CurrentDr == params->MinDr )
591     {
592         return params->MinDr;
593     }
594     else
595     {
596         do
597         {
598             drLocal = ( drLocal - 1 );
599         } while( ( drLocal != params->MinDr ) &&
600                  ( RegionCommonChanVerifyDr( params->NbChannels, params->ChannelsMask, drLocal, params->MinDr, params->MaxDr, params->Channels  ) == false ) );
601 
602         return drLocal;
603     }
604 }
605 
RegionCommonLimitTxPower(int8_t txPower,int8_t maxBandTxPower)606 int8_t RegionCommonLimitTxPower( int8_t txPower, int8_t maxBandTxPower )
607 {
608     // Limit tx power to the band max
609     return MAX( txPower, maxBandTxPower );
610 }
611 
RegionCommonGetBandwidth(uint32_t drIndex,const uint32_t * bandwidths)612 uint32_t RegionCommonGetBandwidth( uint32_t drIndex, const uint32_t* bandwidths )
613 {
614     switch( bandwidths[drIndex] )
615     {
616         default:
617         case 125000:
618             return 0;
619         case 250000:
620             return 1;
621         case 500000:
622             return 2;
623     }
624 }
625