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