1 /*
2  / _____)             _              | |
3 ( (____  _____ ____ _| |_ _____  ____| |__
4  \____ \| ___ |    (_   _) ___ |/ ___)  _ \
5  _____) ) ____| | | || |_| ____( (___| | | |
6 (______/|_____)_|_|_| \__)_____)\____)_| |_|
7     (C)2013 Semtech
8  ___ _____ _   ___ _  _____ ___  ___  ___ ___
9 / __|_   _/_\ / __| |/ / __/ _ \| _ \/ __| __|
10 \__ \ | |/ _ \ (__| ' <| _| (_) |   / (__| _|
11 |___/ |_/_/ \_\___|_|\_\_| \___/|_|_\\___|___|
12 embedded.connectivity.solutions===============
13 
14 Description: LoRa MAC Class B layer implementation
15 
16 License: Revised BSD License, see LICENSE.TXT file include in the project
17 
18 Maintainer: Miguel Luis ( Semtech ), Gregory Cristian ( Semtech ) and Daniel Jaeckle ( STACKFORCE )
19 */
20 #include <math.h>
21 #include "utilities.h"
22 #include "secure-element.h"
23 #include "LoRaMac.h"
24 #include "LoRaMacClassB.h"
25 #include "LoRaMacClassBNvm.h"
26 #include "LoRaMacClassBConfig.h"
27 #include "LoRaMacCrypto.h"
28 #include "LoRaMacConfirmQueue.h"
29 #include "radio.h"
30 #include "region/Region.h"
31 
32 #ifdef LORAMAC_CLASSB_ENABLED
33 
34 
35 /*
36  * LoRaMac Class B Context structure
37  */
38 typedef struct sLoRaMacClassBCtx
39 {
40     /*!
41     * Class B ping slot context
42     */
43     PingSlotContext_t PingSlotCtx;
44     /*!
45     * Class B beacon context
46     */
47     BeaconContext_t BeaconCtx;
48     /*!
49     * State of the beaconing mechanism
50     */
51     BeaconState_t BeaconState;
52     /*!
53     * State of the ping slot mechanism
54     */
55     PingSlotState_t PingSlotState;
56     /*!
57     * State of the multicast slot mechanism
58     */
59     PingSlotState_t MulticastSlotState;
60     /*!
61     * Timer for CLASS B beacon acquisition and tracking.
62     */
63     TimerEvent_t BeaconTimer;
64     /*!
65     * Timer for CLASS B ping slot timer.
66     */
67     TimerEvent_t PingSlotTimer;
68     /*!
69     * Timer for CLASS B multicast ping slot timer.
70     */
71     TimerEvent_t MulticastSlotTimer;
72     /*!
73     * Container for the callbacks related to class b.
74     */
75     LoRaMacClassBCallback_t LoRaMacClassBCallbacks;
76     /*!
77     * Data structure which holds the parameters which needs to be set
78     * in class b operation.
79     */
80     LoRaMacClassBParams_t LoRaMacClassBParams;
81 } LoRaMacClassBCtx_t;
82 
83 /*!
84  * Defines the LoRaMac radio events status
85  */
86 typedef union uLoRaMacClassBEvents
87 {
88     uint32_t Value;
89     struct sEvents
90     {
91         uint32_t Beacon        : 1;
92         uint32_t PingSlot      : 1;
93         uint32_t MulticastSlot : 1;
94     }Events;
95 }LoRaMacClassBEvents_t;
96 
97 LoRaMacClassBEvents_t LoRaMacClassBEvents = { .Value = 0 };
98 
99 /*
100  * Module context.
101  */
102 static LoRaMacClassBCtx_t Ctx;
103 
104 /*
105  * Beacon transmit time precision in milliseconds.
106  * The usage of these values shall be determined by the
107  * prec value in param field received in a beacon frame.
108  * As the time base is milli seconds, the precision will be either 0 ms or 1 ms.
109  */
110 static const uint8_t BeaconPrecTimeValue[4] = { 0, 1, 1, 1 };
111 
112 /*!
113  * Data structure which holds the parameters which needs to be stored
114  * in the NVM.
115  */
116 static LoRaMacClassBNvmData_t* ClassBNvm;
117 
118 /*!
119  * Computes the Ping Offset
120  *
121  * \param [IN]  beaconTime      - Time of the recent received beacon
122  * \param [IN]  address         - Frame address
123  * \param [IN]  pingPeriod      - Ping period of the node
124  * \param [OUT] pingOffset      - Pseudo random ping offset
125  */
ComputePingOffset(uint64_t beaconTime,uint32_t address,uint16_t pingPeriod,uint16_t * pingOffset)126 static void ComputePingOffset( uint64_t beaconTime, uint32_t address, uint16_t pingPeriod, uint16_t *pingOffset )
127 {
128     uint8_t buffer[16];
129     uint8_t cipher[16];
130     uint32_t result = 0;
131     /* Refer to chapter 15.2 of the LoRaWAN specification v1.1. The beacon time
132      * GPS time in seconds modulo 2^32
133      */
134     uint32_t time = ( beaconTime % ( ( ( uint64_t ) 1 ) << 32 ) );
135 
136     memset1( buffer, 0, 16 );
137     memset1( cipher, 0, 16 );
138 
139     buffer[0] = ( time ) & 0xFF;
140     buffer[1] = ( time >> 8 ) & 0xFF;
141     buffer[2] = ( time >> 16 ) & 0xFF;
142     buffer[3] = ( time >> 24 ) & 0xFF;
143 
144     buffer[4] = ( address ) & 0xFF;
145     buffer[5] = ( address >> 8 ) & 0xFF;
146     buffer[6] = ( address >> 16 ) & 0xFF;
147     buffer[7] = ( address >> 24 ) & 0xFF;
148 
149     SecureElementAesEncrypt( buffer, 16, SLOT_RAND_ZERO_KEY, cipher );
150 
151     result = ( ( ( uint32_t ) cipher[0] ) + ( ( ( uint32_t ) cipher[1] ) * 256 ) );
152 
153     *pingOffset = ( uint16_t )( result % pingPeriod );
154 }
155 
156 /*!
157  * \brief Calculates the downlink frequency for a given channel.
158  *
159  * \param [IN] channel The channel according to the channel plan.
160  *
161  * \param [IN] isBeacon Set to true, if the function shall
162  *                      calculate the frequency for a beacon.
163  *
164  * \retval The downlink frequency
165  */
CalcDownlinkFrequency(uint8_t channel,bool isBeacon)166 static uint32_t CalcDownlinkFrequency( uint8_t channel, bool isBeacon )
167 {
168     GetPhyParams_t getPhy;
169     PhyParam_t phyParam;
170 
171     getPhy.Attribute = PHY_PING_SLOT_CHANNEL_FREQ;
172 
173     if( isBeacon == true )
174     {
175         getPhy.Attribute = PHY_BEACON_CHANNEL_FREQ;
176     }
177     getPhy.Channel = channel;
178     phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
179 
180     return phyParam.Value;
181 }
182 
183 /*!
184  * \brief Calculates the downlink channel for the beacon and for
185  *        ping slot downlinks.
186  *
187  * \param [IN] devAddr The address of the device. Assign 0 if its a beacon.
188  *
189  * \param [IN] beaconTime The beacon time of the beacon.
190  *
191  * \param [IN] beaconInterval The beacon interval.
192  *
193  * \param [IN] isBeacon Set to true, if the function shall
194  *                      calculate the frequency for a beacon.
195  *
196  * \retval The downlink channel
197  */
CalcDownlinkChannelAndFrequency(uint32_t devAddr,TimerTime_t beaconTime,TimerTime_t beaconInterval,bool isBeacon)198 static uint32_t CalcDownlinkChannelAndFrequency( uint32_t devAddr, TimerTime_t beaconTime,
199                                                  TimerTime_t beaconInterval, bool isBeacon )
200 {
201     GetPhyParams_t getPhy;
202     PhyParam_t phyParam;
203     uint32_t channel = 0;
204     uint8_t nbChannels = 0;
205     uint8_t offset = 0;
206 
207     // Default initialization - ping slot channels
208     getPhy.Attribute = PHY_PING_SLOT_NB_CHANNELS;
209 
210     if( isBeacon == true )
211     {
212         // Beacon channels
213         getPhy.Attribute = PHY_BEACON_NB_CHANNELS;
214     }
215     phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
216     nbChannels = ( uint8_t ) phyParam.Value;
217 
218     // nbChannels is > 1, when the channel plan requires more than one possible channel
219     // defined by the calculation below.
220     if( nbChannels > 1 )
221     {
222         getPhy.Attribute = PHY_BEACON_CHANNEL_OFFSET;
223         phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
224         offset = ( uint8_t ) phyParam.Value;
225 
226         // Calculate the channel for the next downlink
227         channel = devAddr + ( beaconTime / ( beaconInterval / 1000 ) );
228         channel = channel % nbChannels;
229         channel += offset;
230     }
231 
232     // Calculate the frequency for the next downlink. This holds
233     // for beacons and ping slots.
234     return CalcDownlinkFrequency( channel, isBeacon );
235 }
236 
237 /*!
238  * \brief Calculates the correct frequency and opens up the beacon reception window. Please
239  *        note that the variable WindowTimeout and WindowOffset will be updated according
240  *        to the current settings. Also, the function perform a calculation only, when
241  *        Ctx.BeaconCtx.Ctrl.BeaconAcquired OR Ctx.BeaconCtx.Ctrl.AcquisitionPending is
242  *        set to 1.
243  *
244  * \param [IN] rxConfig Reception parameters for the beacon window.
245  *
246  * \param [IN] currentSymbolTimeout Current symbol timeout.
247  */
CalculateBeaconRxWindowConfig(RxConfigParams_t * rxConfig,uint16_t currentSymbolTimeout)248 static void CalculateBeaconRxWindowConfig( RxConfigParams_t* rxConfig, uint16_t currentSymbolTimeout )
249 {
250     GetPhyParams_t getPhy;
251     PhyParam_t phyParam;
252     uint32_t maxRxError = 0;
253 
254     rxConfig->WindowTimeout = currentSymbolTimeout;
255     rxConfig->WindowOffset = 0;
256 
257     if( ( Ctx.BeaconCtx.Ctrl.BeaconAcquired == 1 ) || ( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 ) )
258     {
259         // Apply the symbol timeout only if we have acquired the beacon
260         // Otherwise, take the window enlargement into account
261         // Read beacon datarate
262         getPhy.Attribute = PHY_BEACON_CHANNEL_DR;
263         phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
264 
265         // Compare and assign the maximum between the region specific rx error window time
266         // and time precision received from beacon frame format.
267         maxRxError = MAX( Ctx.LoRaMacClassBParams.LoRaMacParams->SystemMaxRxError,
268                           ( uint32_t ) Ctx.BeaconCtx.BeaconTimePrecision.SubSeconds );
269 
270         // Calculate downlink symbols
271         RegionComputeRxWindowParameters( *Ctx.LoRaMacClassBParams.LoRaMacRegion,
272                                         ( int8_t )phyParam.Value, // datarate
273                                         Ctx.LoRaMacClassBParams.LoRaMacParams->MinRxSymbols,
274                                         maxRxError,
275                                         rxConfig );
276     }
277 }
278 
279 /*!
280  * \brief Calculates the correct frequency and opens up the beacon reception window.
281  *
282  * \param [IN] rxTime The reception time which should be setup
283  *
284  * \param [IN] activateDefaultChannel Set to true, if the function shall setup the default channel
285  *
286  * \param [IN] symbolTimeout Symbol timeout
287  */
RxBeaconSetup(TimerTime_t rxTime,bool activateDefaultChannel,uint16_t symbolTimeout)288 static void RxBeaconSetup( TimerTime_t rxTime, bool activateDefaultChannel, uint16_t symbolTimeout )
289 {
290     RxBeaconSetup_t rxBeaconSetup;
291     uint32_t frequency = 0;
292 
293     if( activateDefaultChannel == true )
294     {
295         // This is the default frequency in case we don't know when the next
296         // beacon will be transmitted. We select channel 0 as default.
297         frequency = CalcDownlinkFrequency( 0, true );
298     }
299     else
300     {
301         // This is the frequency according to the channel plan
302         frequency = CalcDownlinkChannelAndFrequency( 0, Ctx.BeaconCtx.BeaconTime.Seconds + ( CLASSB_BEACON_INTERVAL / 1000 ),
303                                                      CLASSB_BEACON_INTERVAL, true );
304     }
305 
306     if( ClassBNvm->BeaconCtx.Ctrl.CustomFreq == 1 )
307     {
308         // Set the frequency from the BeaconFreqReq
309         frequency = ClassBNvm->BeaconCtx.Frequency;
310     }
311 
312     if( Ctx.BeaconCtx.Ctrl.BeaconChannelSet == 1 )
313     {
314         // Set the frequency which was provided by BeaconTimingAns MAC command
315         Ctx.BeaconCtx.Ctrl.BeaconChannelSet = 0;
316         frequency = CalcDownlinkFrequency( Ctx.BeaconCtx.BeaconTimingChannel, true );
317     }
318 
319     rxBeaconSetup.SymbolTimeout = symbolTimeout;
320     rxBeaconSetup.RxTime = rxTime;
321     rxBeaconSetup.Frequency = frequency;
322 
323     RegionRxBeaconSetup( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &rxBeaconSetup, &Ctx.LoRaMacClassBParams.McpsIndication->RxDatarate );
324 
325     Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Frequency = frequency;
326     Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Datarate = Ctx.LoRaMacClassBParams.McpsIndication->RxDatarate;
327 }
328 
329 /*!
330  * \brief Calculates the next ping slot time.
331  *
332  * \param [IN] slotOffset The ping slot offset
333  * \param [IN] pingPeriod The ping period
334  * \param [OUT] timeOffset Time offset of the next slot, based on current time
335  *
336  * \retval [true: ping slot found, false: no ping slot found]
337  */
CalcNextSlotTime(uint16_t slotOffset,uint16_t pingPeriod,uint16_t pingNb,TimerTime_t * timeOffset)338 static bool CalcNextSlotTime( uint16_t slotOffset, uint16_t pingPeriod, uint16_t pingNb, TimerTime_t* timeOffset )
339 {
340     uint8_t currentPingSlot = 0;
341     TimerTime_t slotTime = 0;
342     TimerTime_t currentTime = TimerGetCurrentTime( );
343 
344     // Calculate the point in time of the last beacon even if we missed it
345     slotTime = ( ( currentTime - SysTimeToMs( Ctx.BeaconCtx.LastBeaconRx ) ) % CLASSB_BEACON_INTERVAL );
346     slotTime = currentTime - slotTime;
347 
348     // Add the reserved time and the ping offset
349     slotTime += CLASSB_BEACON_RESERVED;
350     slotTime += slotOffset * CLASSB_PING_SLOT_WINDOW;
351 
352     if( slotTime < currentTime )
353     {
354         currentPingSlot = ( ( currentTime - slotTime ) /
355                           ( pingPeriod * CLASSB_PING_SLOT_WINDOW ) ) + 1;
356         slotTime += ( ( TimerTime_t )( currentPingSlot * pingPeriod ) *
357                     CLASSB_PING_SLOT_WINDOW );
358     }
359 
360     if( currentPingSlot < pingNb )
361     {
362         if( slotTime <= ( SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) - CLASSB_BEACON_GUARD - CLASSB_PING_SLOT_WINDOW ) )
363         {
364             // Calculate the relative ping slot time
365             slotTime -= currentTime;
366             slotTime -= Radio.GetWakeupTime( );
367             slotTime = TimerTempCompensation( slotTime, Ctx.BeaconCtx.Temperature );
368             *timeOffset = slotTime;
369             return true;
370         }
371     }
372     return false;
373 }
374 
375 /*!
376  * \brief Calculates CRC's of the beacon frame
377  *
378  * \param [IN] buffer Pointer to the data
379  * \param [IN] length Length of the data
380  *
381  * \retval CRC
382  */
BeaconCrc(uint8_t * buffer,uint16_t length)383 static uint16_t BeaconCrc( uint8_t *buffer, uint16_t length )
384 {
385     // The CRC calculation follows CCITT
386     const uint16_t polynom = 0x1021;
387     // CRC initial value
388     uint16_t crc = 0x0000;
389 
390     if( buffer == NULL )
391     {
392         return 0;
393     }
394 
395     for( uint16_t i = 0; i < length; ++i )
396     {
397         crc ^= ( uint16_t ) buffer[i] << 8;
398         for( uint16_t j = 0; j < 8; ++j )
399         {
400             crc = ( crc & 0x8000 ) ? ( crc << 1 ) ^ polynom : ( crc << 1 );
401         }
402     }
403 
404     return crc;
405 }
406 
GetTemperatureLevel(LoRaMacClassBCallback_t * callbacks,BeaconContext_t * beaconCtx)407 static void GetTemperatureLevel( LoRaMacClassBCallback_t *callbacks, BeaconContext_t *beaconCtx )
408 {
409     // Measure temperature, if available
410     if( ( callbacks != NULL ) && ( callbacks->GetTemperatureLevel != NULL ) )
411     {
412         beaconCtx->Temperature = callbacks->GetTemperatureLevel( );
413     }
414 }
415 
InitClassB(void)416 static void InitClassB( void )
417 {
418     GetPhyParams_t getPhy;
419     PhyParam_t phyParam;
420 
421     // Init events
422     LoRaMacClassBEvents.Value = 0;
423 
424     // Init variables to default
425     memset1( ( uint8_t* ) ClassBNvm, 0, sizeof( LoRaMacClassBNvmData_t ) );
426     memset1( ( uint8_t* ) &Ctx.PingSlotCtx, 0, sizeof( PingSlotContext_t ) );
427     memset1( ( uint8_t* ) &Ctx.BeaconCtx, 0, sizeof( BeaconContext_t ) );
428 
429     // Setup default temperature
430     Ctx.BeaconCtx.Temperature = 25.0;
431     GetTemperatureLevel( &Ctx.LoRaMacClassBCallbacks, &Ctx.BeaconCtx );
432 
433     // Setup default ping slot datarate
434     getPhy.Attribute = PHY_PING_SLOT_CHANNEL_DR;
435     phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
436     ClassBNvm->PingSlotCtx.Datarate = ( int8_t )( phyParam.Value );
437 
438     // Setup default FPending bit
439     ClassBNvm->PingSlotCtx.FPendingSet = 0;
440 
441     // Setup default states
442     Ctx.BeaconState = BEACON_STATE_ACQUISITION;
443     Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
444     Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
445 }
446 
InitClassBDefaults(void)447 static void InitClassBDefaults( void )
448 {
449     // This function shall reset the Class B settings to default,
450     // but should keep important configurations
451     LoRaMacClassBBeaconNvmData_t beaconCtx = ClassBNvm->BeaconCtx;
452     LoRaMacClassBPingSlotNvmData_t pingSlotCtx = ClassBNvm->PingSlotCtx;
453 
454     InitClassB( );
455 
456     // Parameters from BeaconFreqReq
457     ClassBNvm->BeaconCtx.Frequency = beaconCtx.Frequency;
458     ClassBNvm->BeaconCtx.Ctrl.CustomFreq = beaconCtx.Ctrl.CustomFreq;
459 
460     // Parameters from PingSlotChannelReq
461     ClassBNvm->PingSlotCtx.Ctrl.CustomFreq = pingSlotCtx.Ctrl.CustomFreq;
462     ClassBNvm->PingSlotCtx.Frequency = pingSlotCtx.Frequency;
463     ClassBNvm->PingSlotCtx.Datarate = pingSlotCtx.Datarate;
464 }
465 
EnlargeWindowTimeout(void)466 static void EnlargeWindowTimeout( void )
467 {
468     // Update beacon movement
469     Ctx.BeaconCtx.BeaconWindowMovement *= CLASSB_WINDOW_MOVE_EXPANSION_FACTOR;
470     if( Ctx.BeaconCtx.BeaconWindowMovement > CLASSB_WINDOW_MOVE_EXPANSION_MAX )
471     {
472         Ctx.BeaconCtx.BeaconWindowMovement = CLASSB_WINDOW_MOVE_EXPANSION_MAX;
473     }
474     // Update symbol timeout
475     Ctx.BeaconCtx.SymbolTimeout *= CLASSB_BEACON_SYMBOL_TO_EXPANSION_FACTOR;
476     if( Ctx.BeaconCtx.SymbolTimeout > CLASSB_BEACON_SYMBOL_TO_EXPANSION_MAX )
477     {
478         Ctx.BeaconCtx.SymbolTimeout = CLASSB_BEACON_SYMBOL_TO_EXPANSION_MAX;
479     }
480     Ctx.PingSlotCtx.SymbolTimeout *= CLASSB_BEACON_SYMBOL_TO_EXPANSION_FACTOR;
481     if( Ctx.PingSlotCtx.SymbolTimeout > CLASSB_PING_SLOT_SYMBOL_TO_EXPANSION_MAX )
482     {
483         Ctx.PingSlotCtx.SymbolTimeout = CLASSB_PING_SLOT_SYMBOL_TO_EXPANSION_MAX;
484     }
485 }
486 
ResetWindowTimeout(void)487 static void ResetWindowTimeout( void )
488 {
489     Ctx.BeaconCtx.SymbolTimeout = CLASSB_BEACON_SYMBOL_TO_DEFAULT;
490     Ctx.PingSlotCtx.SymbolTimeout = CLASSB_BEACON_SYMBOL_TO_DEFAULT;
491     Ctx.BeaconCtx.BeaconWindowMovement  = CLASSB_WINDOW_MOVE_DEFAULT;
492 }
493 
CalcDelayForNextBeacon(TimerTime_t currentTime,TimerTime_t lastBeaconRx)494 static TimerTime_t CalcDelayForNextBeacon( TimerTime_t currentTime, TimerTime_t lastBeaconRx )
495 {
496     TimerTime_t nextBeaconRxTime = 0;
497 
498     // Calculate the point in time of the next beacon
499     nextBeaconRxTime = ( ( currentTime - lastBeaconRx ) % CLASSB_BEACON_INTERVAL );
500     return ( CLASSB_BEACON_INTERVAL - nextBeaconRxTime );
501 }
502 
IndicateBeaconStatus(LoRaMacEventInfoStatus_t status)503 static void IndicateBeaconStatus( LoRaMacEventInfoStatus_t status )
504 {
505     if( Ctx.BeaconCtx.Ctrl.ResumeBeaconing == 0 )
506     {
507         Ctx.LoRaMacClassBParams.MlmeIndication->MlmeIndication = MLME_BEACON;
508         Ctx.LoRaMacClassBParams.MlmeIndication->Status = status;
509         Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeInd = 1;
510 
511         Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MacDone = 1;
512     }
513     Ctx.BeaconCtx.Ctrl.ResumeBeaconing = 0;
514 }
515 
ApplyGuardTime(TimerTime_t beaconEventTime)516 static TimerTime_t ApplyGuardTime( TimerTime_t beaconEventTime )
517 {
518     TimerTime_t timeGuard = beaconEventTime;
519 
520     if( timeGuard > CLASSB_BEACON_GUARD )
521     {
522         timeGuard -= CLASSB_BEACON_GUARD;
523     }
524     return timeGuard;
525 }
526 
UpdateBeaconState(LoRaMacEventInfoStatus_t status,TimerTime_t windowMovement,TimerTime_t currentTime)527 static TimerTime_t UpdateBeaconState( LoRaMacEventInfoStatus_t status,
528                                       TimerTime_t windowMovement, TimerTime_t currentTime )
529 
530 {
531     TimerTime_t beaconEventTime = 0;
532 
533     // Calculate the next beacon RX time
534     beaconEventTime = CalcDelayForNextBeacon( currentTime, SysTimeToMs( Ctx.BeaconCtx.LastBeaconRx ) );
535     Ctx.BeaconCtx.NextBeaconRx = SysTimeFromMs( currentTime + beaconEventTime );
536 
537     // Take temperature compensation into account
538     beaconEventTime = TimerTempCompensation( beaconEventTime, Ctx.BeaconCtx.Temperature );
539 
540     // Move the window
541     if( beaconEventTime > windowMovement )
542     {
543         beaconEventTime -= windowMovement;
544     }
545     Ctx.BeaconCtx.NextBeaconRxAdjusted = currentTime + beaconEventTime;
546 
547     // Start the RX slot state machine for ping and multicast slots
548     LoRaMacClassBStartRxSlots( );
549 
550     // Setup an MLME_BEACON indication to inform the upper layer
551     IndicateBeaconStatus( status );
552 
553     // Apply guard time
554     return ApplyGuardTime( beaconEventTime );
555 }
556 
CalcPingNb(uint16_t periodicity)557 static uint8_t CalcPingNb( uint16_t periodicity )
558 {
559     return 128 / ( 1 << periodicity );
560 }
561 
CalcPingPeriod(uint8_t pingNb)562 static uint16_t CalcPingPeriod( uint8_t pingNb )
563 {
564     return CLASSB_BEACON_WINDOW_SLOTS / pingNb;
565 }
566 
CheckSlotPriority(uint32_t currentAddress,uint8_t currentFPendingSet,uint8_t currentIsMulticast,uint32_t address,uint8_t fPendingSet,uint8_t isMulticast)567 static bool CheckSlotPriority( uint32_t currentAddress, uint8_t currentFPendingSet, uint8_t currentIsMulticast,
568                                uint32_t address, uint8_t fPendingSet, uint8_t isMulticast )
569 {
570     if( currentFPendingSet != fPendingSet )
571     {
572         if( currentFPendingSet < fPendingSet )
573         {
574             // New slot sequence has priority. It does not matter
575             // which type it is
576             return true;
577         }
578         return false;
579     }
580     else
581     {
582         // FPendingSet has the same priority level, decide
583         // based on multicast or unicast setting
584         if( currentIsMulticast != isMulticast )
585         {
586             if( currentIsMulticast < isMulticast )
587             {
588                 // New slot sequence has priority. Multicasts have
589                 // more priority than unicasts
590                 return true;
591             }
592             return false;
593         }
594         else
595         {
596             // IsMulticast has the same priority level, decide
597             // based on the highest address
598             if( currentAddress < address )
599             {
600                 // New slot sequence has priority. The sequence with
601                 // the highest address has priority
602                 return true;
603             }
604         }
605     }
606     return false;
607 }
608 
609 #endif // LORAMAC_CLASSB_ENABLED
610 
LoRaMacClassBInit(LoRaMacClassBParams_t * classBParams,LoRaMacClassBCallback_t * callbacks,LoRaMacClassBNvmData_t * nvm)611 void LoRaMacClassBInit( LoRaMacClassBParams_t *classBParams, LoRaMacClassBCallback_t *callbacks, LoRaMacClassBNvmData_t* nvm )
612 {
613 #ifdef LORAMAC_CLASSB_ENABLED
614     // Assign non-volatile context
615     if( nvm == NULL )
616     {
617         return;
618     }
619     ClassBNvm = nvm;
620 
621     // Store callbacks
622     Ctx.LoRaMacClassBCallbacks = *callbacks;
623 
624     // Store parameter pointers
625     Ctx.LoRaMacClassBParams = *classBParams;
626 
627     // Initialize timers
628     TimerInit( &Ctx.BeaconTimer, LoRaMacClassBBeaconTimerEvent );
629     TimerInit( &Ctx.PingSlotTimer, LoRaMacClassBPingSlotTimerEvent );
630     TimerInit( &Ctx.MulticastSlotTimer, LoRaMacClassBMulticastSlotTimerEvent );
631 
632     InitClassB( );
633 #endif // LORAMAC_CLASSB_ENABLED
634 }
635 
LoRaMacClassBSetBeaconState(BeaconState_t beaconState)636 void LoRaMacClassBSetBeaconState( BeaconState_t beaconState )
637 {
638 #ifdef LORAMAC_CLASSB_ENABLED
639     if( beaconState == BEACON_STATE_ACQUISITION )
640     {
641         // If the MAC has received a time reference for the beacon,
642         // apply the state BEACON_STATE_ACQUISITION_BY_TIME.
643         if( ( Ctx.BeaconCtx.Ctrl.BeaconDelaySet == 1 ) &&
644             ( LoRaMacClassBIsAcquisitionPending( ) == false ) )
645         {
646             Ctx.BeaconState = BEACON_STATE_ACQUISITION_BY_TIME;
647         }
648         else
649         {
650            Ctx.BeaconState = beaconState;
651         }
652     }
653     else
654     {
655         if( ( Ctx.BeaconState != BEACON_STATE_ACQUISITION ) &&
656             ( Ctx.BeaconState != BEACON_STATE_ACQUISITION_BY_TIME ) )
657         {
658             Ctx.BeaconState = beaconState;
659         }
660     }
661 #endif // LORAMAC_CLASSB_ENABLED
662 }
663 
LoRaMacClassBSetPingSlotState(PingSlotState_t pingSlotState)664 void LoRaMacClassBSetPingSlotState( PingSlotState_t pingSlotState )
665 {
666 #ifdef LORAMAC_CLASSB_ENABLED
667     Ctx.PingSlotState = pingSlotState;
668 #endif // LORAMAC_CLASSB_ENABLED
669 }
670 
LoRaMacClassBSetMulticastSlotState(PingSlotState_t multicastSlotState)671 void LoRaMacClassBSetMulticastSlotState( PingSlotState_t multicastSlotState )
672 {
673 #ifdef LORAMAC_CLASSB_ENABLED
674     Ctx.MulticastSlotState = multicastSlotState;
675 #endif // LORAMAC_CLASSB_ENABLED
676 }
677 
LoRaMacClassBIsAcquisitionInProgress(void)678 bool LoRaMacClassBIsAcquisitionInProgress( void )
679 {
680 #ifdef LORAMAC_CLASSB_ENABLED
681     if( Ctx.BeaconState == BEACON_STATE_ACQUISITION_BY_TIME )
682     {
683         // In this case the acquisition is in progress, as the MAC has
684         // a time reference for the next beacon RX.
685         return true;
686     }
687     if( LoRaMacClassBIsAcquisitionPending( ) == true )
688     {
689         // In this case the acquisition is in progress, as the MAC
690         // searches for a beacon.
691         return true;
692     }
693     return false;
694 #else
695     return false;
696 #endif // LORAMAC_CLASSB_ENABLED
697 }
698 
LoRaMacClassBBeaconTimerEvent(void * context)699 void LoRaMacClassBBeaconTimerEvent( void* context )
700 {
701 #ifdef LORAMAC_CLASSB_ENABLED
702     Ctx.BeaconCtx.TimeStamp = TimerGetCurrentTime( );
703     TimerStop( &Ctx.BeaconTimer );
704     LoRaMacClassBEvents.Events.Beacon = 1;
705 
706     if( Ctx.LoRaMacClassBCallbacks.MacProcessNotify != NULL )
707     {
708         Ctx.LoRaMacClassBCallbacks.MacProcessNotify( );
709     }
710 #endif // LORAMAC_CLASSB_ENABLED
711 }
712 
713 #ifdef LORAMAC_CLASSB_ENABLED
LoRaMacClassBProcessBeacon(void)714 static void LoRaMacClassBProcessBeacon( void )
715 {
716     bool activateTimer = false;
717     TimerTime_t beaconEventTime = 1;
718     RxConfigParams_t beaconRxConfig;
719     TimerTime_t currentTime = Ctx.BeaconCtx.TimeStamp;
720 
721     // Beacon state machine
722     switch( Ctx.BeaconState )
723     {
724         case BEACON_STATE_ACQUISITION_BY_TIME:
725         {
726             activateTimer = true;
727 
728             if( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 )
729             {
730                 Radio.Sleep();
731                 Ctx.BeaconState = BEACON_STATE_LOST;
732             }
733             else
734             {
735                 // Default symbol timeouts
736                 ResetWindowTimeout( );
737 
738                 if( Ctx.BeaconCtx.Ctrl.BeaconDelaySet == 1 )
739                 {
740                     // The goal is to calculate beaconRxConfig.WindowTimeout
741                     CalculateBeaconRxWindowConfig( &beaconRxConfig, Ctx.BeaconCtx.SymbolTimeout );
742 
743                     if( Ctx.BeaconCtx.BeaconTimingDelay > 0 )
744                     {
745                         if( SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) > currentTime )
746                         {
747                             // Calculate the time when we expect the next beacon
748                             beaconEventTime = TimerTempCompensation( SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) - currentTime, Ctx.BeaconCtx.Temperature );
749 
750                             if( ( int32_t ) beaconEventTime > beaconRxConfig.WindowOffset )
751                             {
752                                 // Apply the offset of the system error respectively beaconing precision setting
753                                 beaconEventTime += beaconRxConfig.WindowOffset;
754                             }
755                         }
756                         else
757                         {
758                             // Reset status provides by BeaconTimingAns
759                             Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 0;
760                             Ctx.BeaconCtx.Ctrl.BeaconChannelSet = 0;
761                             Ctx.BeaconState = BEACON_STATE_ACQUISITION;
762                         }
763                         Ctx.BeaconCtx.BeaconTimingDelay = 0;
764                     }
765                     else
766                     {
767                         activateTimer = false;
768 
769                         // Reset status provides by BeaconTimingAns
770                         Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 0;
771                         // Set the node into acquisition mode
772                         Ctx.BeaconCtx.Ctrl.AcquisitionPending = 1;
773 
774                         // Don't use the default channel. We know on which
775                         // channel the next beacon will be transmitted
776                         RxBeaconSetup( CLASSB_BEACON_RESERVED, false, beaconRxConfig.WindowTimeout );
777                     }
778                 }
779                 else
780                 {
781                     Ctx.BeaconCtx.NextBeaconRx.Seconds = 0;
782                     Ctx.BeaconCtx.NextBeaconRx.SubSeconds = 0;
783                     Ctx.BeaconCtx.BeaconTimingDelay = 0;
784 
785                     Ctx.BeaconState = BEACON_STATE_ACQUISITION;
786                 }
787             }
788             break;
789         }
790         case BEACON_STATE_ACQUISITION:
791         {
792             activateTimer = true;
793 
794             if( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 )
795             {
796                 Radio.Sleep();
797                 Ctx.BeaconState = BEACON_STATE_LOST;
798             }
799             else
800             {
801                 // Default symbol timeouts
802                 ResetWindowTimeout( );
803 
804                 Ctx.BeaconCtx.Ctrl.AcquisitionPending = 1;
805                 beaconEventTime = CLASSB_BEACON_INTERVAL;
806 
807                 // The goal is to calculate beaconRxConfig.WindowTimeout
808                 CalculateBeaconRxWindowConfig( &beaconRxConfig, Ctx.BeaconCtx.SymbolTimeout );
809 
810                 // Start the beacon acquisition. When the MAC has received a beacon in function
811                 // RxBeacon successfully, the next state is BEACON_STATE_LOCKED. If the MAC does not
812                 // find a beacon, the state machine will stay in state BEACON_STATE_ACQUISITION.
813                 // This state detects that a acquisition was pending previously and will change the next
814                 // state to BEACON_STATE_LOST.
815                 RxBeaconSetup( 0, true, beaconRxConfig.WindowTimeout );
816             }
817             break;
818         }
819         case BEACON_STATE_TIMEOUT:
820         {
821             // We have to update the beacon time, since we missed a beacon
822             Ctx.BeaconCtx.BeaconTime.Seconds += ( CLASSB_BEACON_INTERVAL / 1000 );
823             Ctx.BeaconCtx.BeaconTime.SubSeconds = 0;
824 
825             // Enlarge window timeouts to increase the chance to receive the next beacon
826             EnlargeWindowTimeout( );
827 
828             // Setup next state
829             Ctx.BeaconState = BEACON_STATE_REACQUISITION;
830         }
831             // Intentional fall through
832         case BEACON_STATE_REACQUISITION:
833         {
834             activateTimer = true;
835 
836             // The beacon is no longer acquired
837             Ctx.BeaconCtx.Ctrl.BeaconAcquired = 0;
838 
839             // Verify if the maximum beacon less period has been elapsed
840             if( ( currentTime - SysTimeToMs( Ctx.BeaconCtx.LastBeaconRx ) ) > CLASSB_MAX_BEACON_LESS_PERIOD )
841             {
842                 Ctx.BeaconState = BEACON_STATE_LOST;
843             }
844             else
845             {
846                 // Handle beacon miss
847                 beaconEventTime = UpdateBeaconState( LORAMAC_EVENT_INFO_STATUS_BEACON_LOST,
848                                                      Ctx.BeaconCtx.BeaconWindowMovement, currentTime );
849 
850                 // Setup next state
851                 Ctx.BeaconState = BEACON_STATE_IDLE;
852             }
853             break;
854         }
855         case BEACON_STATE_LOCKED:
856         {
857             activateTimer = true;
858 
859             // We have received a beacon. Acquisition is no longer pending.
860             Ctx.BeaconCtx.Ctrl.AcquisitionPending = 0;
861 
862             // Handle beacon reception
863             beaconEventTime = UpdateBeaconState( LORAMAC_EVENT_INFO_STATUS_BEACON_LOCKED,
864                                                  0, currentTime );
865 
866             // Setup the MLME confirm for the MLME_BEACON_ACQUISITION
867             if( Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeReq == 1 )
868             {
869                 if( LoRaMacConfirmQueueIsCmdActive( MLME_BEACON_ACQUISITION ) == true )
870                 {
871                     LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_BEACON_ACQUISITION );
872                     Ctx.LoRaMacClassBParams.MlmeConfirm->TxTimeOnAir = 0;
873                 }
874             }
875 
876             // Setup next state
877             Ctx.BeaconState = BEACON_STATE_IDLE;
878             break;
879         }
880         case BEACON_STATE_IDLE:
881         {
882             activateTimer = true;
883             GetTemperatureLevel( &Ctx.LoRaMacClassBCallbacks, &Ctx.BeaconCtx );
884             beaconEventTime = Ctx.BeaconCtx.NextBeaconRxAdjusted - Radio.GetWakeupTime( );
885             currentTime = TimerGetCurrentTime( );
886 
887             // The goal is to calculate beaconRxConfig.WindowTimeout and beaconRxConfig.WindowOffset
888             CalculateBeaconRxWindowConfig( &beaconRxConfig, Ctx.BeaconCtx.SymbolTimeout );
889 
890             if( beaconEventTime > currentTime )
891             {
892                 Ctx.BeaconState = BEACON_STATE_GUARD;
893                 beaconEventTime -= currentTime;
894                 beaconEventTime = TimerTempCompensation( beaconEventTime, Ctx.BeaconCtx.Temperature );
895 
896                 if( ( int32_t ) beaconEventTime > beaconRxConfig.WindowOffset )
897                 {
898                     // Apply the offset of the system error respectively beaconing precision setting
899                     beaconEventTime += beaconRxConfig.WindowOffset;
900                 }
901             }
902             else
903             {
904                 Ctx.BeaconState = BEACON_STATE_REACQUISITION;
905                 beaconEventTime = 1;
906             }
907             break;
908         }
909         case BEACON_STATE_GUARD:
910         {
911             Ctx.BeaconState = BEACON_STATE_RX;
912 
913             // Stop slot timers
914             LoRaMacClassBStopRxSlots( );
915 
916             // Don't use the default channel. We know on which
917             // channel the next beacon will be transmitted
918             RxBeaconSetup( CLASSB_BEACON_RESERVED, false, beaconRxConfig.WindowTimeout );
919             break;
920         }
921         case BEACON_STATE_LOST:
922         {
923             // Handle events
924             if( Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeReq == 1 )
925             {
926                 if( LoRaMacConfirmQueueIsCmdActive( MLME_BEACON_ACQUISITION ) == true )
927                 {
928                     LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND, MLME_BEACON_ACQUISITION );
929                 }
930             }
931             else
932             {
933                 Ctx.LoRaMacClassBParams.MlmeIndication->MlmeIndication = MLME_BEACON_LOST;
934                 Ctx.LoRaMacClassBParams.MlmeIndication->Status = LORAMAC_EVENT_INFO_STATUS_OK;
935                 Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeInd = 1;
936             }
937 
938             // Stop slot timers
939             LoRaMacClassBStopRxSlots( );
940 
941             // Initialize default state for class b
942             InitClassBDefaults( );
943 
944             Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MacDone = 1;
945 
946             break;
947         }
948         default:
949         {
950             Ctx.BeaconState = BEACON_STATE_ACQUISITION;
951             break;
952         }
953     }
954 
955     if( activateTimer == true )
956     {
957         TimerSetValue( &Ctx.BeaconTimer, beaconEventTime );
958         TimerStart( &Ctx.BeaconTimer );
959     }
960 }
961 #endif // LORAMAC_CLASSB_ENABLED
962 
LoRaMacClassBPingSlotTimerEvent(void * context)963 void LoRaMacClassBPingSlotTimerEvent( void* context )
964 {
965 #ifdef LORAMAC_CLASSB_ENABLED
966     LoRaMacClassBEvents.Events.PingSlot = 1;
967 
968     if( Ctx.LoRaMacClassBCallbacks.MacProcessNotify != NULL )
969     {
970         Ctx.LoRaMacClassBCallbacks.MacProcessNotify( );
971     }
972 #endif // LORAMAC_CLASSB_ENABLED
973 }
974 
975 #ifdef LORAMAC_CLASSB_ENABLED
LoRaMacClassBProcessPingSlot(void)976 static void LoRaMacClassBProcessPingSlot( void )
977 {
978     static RxConfigParams_t pingSlotRxConfig;
979     TimerTime_t pingSlotTime = 0;
980     uint32_t maxRxError = 0;
981     bool slotHasPriority = false;
982 
983     switch( Ctx.PingSlotState )
984     {
985         case PINGSLOT_STATE_CALC_PING_OFFSET:
986         {
987             ComputePingOffset( Ctx.BeaconCtx.BeaconTime.Seconds,
988                                *Ctx.LoRaMacClassBParams.LoRaMacDevAddr,
989                                ClassBNvm->PingSlotCtx.PingPeriod,
990                                &( Ctx.PingSlotCtx.PingOffset ) );
991             Ctx.PingSlotState = PINGSLOT_STATE_SET_TIMER;
992         }
993             // Intentional fall through
994         case PINGSLOT_STATE_SET_TIMER:
995         {
996             if( CalcNextSlotTime( Ctx.PingSlotCtx.PingOffset, ClassBNvm->PingSlotCtx.PingPeriod, ClassBNvm->PingSlotCtx.PingNb, &pingSlotTime ) == true )
997             {
998                 if( Ctx.BeaconCtx.Ctrl.BeaconAcquired == 1 )
999                 {
1000                     // Compare and assign the maximum between the region specific rx error window time
1001                     // and time precision received from beacon frame format.
1002                     maxRxError = MAX( Ctx.LoRaMacClassBParams.LoRaMacParams->SystemMaxRxError ,
1003                                       ( uint32_t ) Ctx.BeaconCtx.BeaconTimePrecision.SubSeconds );
1004 
1005                     // Compute the symbol timeout. Apply it only, if the beacon is acquired
1006                     // Otherwise, take the enlargement of the symbols into account.
1007                     RegionComputeRxWindowParameters( *Ctx.LoRaMacClassBParams.LoRaMacRegion,
1008                                                      ClassBNvm->PingSlotCtx.Datarate,
1009                                                      Ctx.LoRaMacClassBParams.LoRaMacParams->MinRxSymbols,
1010                                                      maxRxError,
1011                                                      &pingSlotRxConfig );
1012                     Ctx.PingSlotCtx.SymbolTimeout = pingSlotRxConfig.WindowTimeout;
1013 
1014                     if( ( int32_t )pingSlotTime > pingSlotRxConfig.WindowOffset )
1015                     {// Apply the window offset
1016                         pingSlotTime += pingSlotRxConfig.WindowOffset;
1017                     }
1018                 }
1019 
1020                 // Start the timer if the ping slot time is in range
1021                 Ctx.PingSlotState = PINGSLOT_STATE_IDLE;
1022                 TimerSetValue( &Ctx.PingSlotTimer, pingSlotTime );
1023                 TimerStart( &Ctx.PingSlotTimer );
1024             }
1025             break;
1026         }
1027         case PINGSLOT_STATE_IDLE:
1028         {
1029             uint32_t frequency = ClassBNvm->PingSlotCtx.Frequency;
1030 
1031             // Apply a custom frequency if the following bit is set
1032             if( ClassBNvm->PingSlotCtx.Ctrl.CustomFreq == 0 )
1033             {
1034                 // Restore floor plan
1035                 frequency = CalcDownlinkChannelAndFrequency( *Ctx.LoRaMacClassBParams.LoRaMacDevAddr, Ctx.BeaconCtx.BeaconTime.Seconds,
1036                                                              CLASSB_BEACON_INTERVAL, false );
1037             }
1038 
1039             if( Ctx.PingSlotCtx.NextMulticastChannel != NULL )
1040             {
1041                 // Verify, if the unicast has priority.
1042                 slotHasPriority = CheckSlotPriority( *Ctx.LoRaMacClassBParams.LoRaMacDevAddr, ClassBNvm->PingSlotCtx.FPendingSet, 0,
1043                                                      Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.Address, Ctx.PingSlotCtx.NextMulticastChannel->FPendingSet, 1 );
1044             }
1045 
1046             // Open the ping slot window only, if there is no multicast ping slot
1047             // open or if the unicast has priority.
1048             if( ( Ctx.MulticastSlotState != PINGSLOT_STATE_RX ) || ( slotHasPriority == true ) )
1049             {
1050                 if( Ctx.MulticastSlotState == PINGSLOT_STATE_RX )
1051                 {
1052                     // Close multicast slot window, if necessary. Multicast slots have priority
1053                     Radio.Standby( );
1054                     Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1055                     TimerSetValue( &Ctx.MulticastSlotTimer, CLASSB_PING_SLOT_WINDOW );
1056                     TimerStart( &Ctx.MulticastSlotTimer );
1057                 }
1058 
1059                 Ctx.PingSlotState = PINGSLOT_STATE_RX;
1060 
1061                 pingSlotRxConfig.Datarate = ClassBNvm->PingSlotCtx.Datarate;
1062                 pingSlotRxConfig.DownlinkDwellTime = Ctx.LoRaMacClassBParams.LoRaMacParams->DownlinkDwellTime;
1063                 pingSlotRxConfig.Frequency = frequency;
1064                 pingSlotRxConfig.RxContinuous = false;
1065                 pingSlotRxConfig.RxSlot = RX_SLOT_WIN_CLASS_B_PING_SLOT;
1066                 pingSlotRxConfig.NetworkActivation = *Ctx.LoRaMacClassBParams.NetworkActivation;
1067 
1068                 RegionRxConfig( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &pingSlotRxConfig, ( int8_t* )&Ctx.LoRaMacClassBParams.McpsIndication->RxDatarate );
1069 
1070                 if( pingSlotRxConfig.RxContinuous == false )
1071                 {
1072                     Radio.Rx( Ctx.LoRaMacClassBParams.LoRaMacParams->MaxRxWindow );
1073                 }
1074                 else
1075                 {
1076                     Radio.Rx( 0 ); // Continuous mode
1077                 }
1078             }
1079             else
1080             {
1081                 // Multicast slots have priority. Skip Rx
1082                 Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1083                 TimerSetValue( &Ctx.PingSlotTimer, CLASSB_PING_SLOT_WINDOW );
1084                 TimerStart( &Ctx.PingSlotTimer );
1085             }
1086             break;
1087         }
1088         default:
1089         {
1090             Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1091             break;
1092         }
1093     }
1094 }
1095 #endif // LORAMAC_CLASSB_ENABLED
1096 
LoRaMacClassBMulticastSlotTimerEvent(void * context)1097 void LoRaMacClassBMulticastSlotTimerEvent( void* context )
1098 {
1099 #ifdef LORAMAC_CLASSB_ENABLED
1100     LoRaMacClassBEvents.Events.MulticastSlot = 1;
1101 
1102     if( Ctx.LoRaMacClassBCallbacks.MacProcessNotify != NULL )
1103     {
1104         Ctx.LoRaMacClassBCallbacks.MacProcessNotify( );
1105     }
1106 #endif // LORAMAC_CLASSB_ENABLED
1107 }
1108 
1109 #ifdef LORAMAC_CLASSB_ENABLED
LoRaMacClassBProcessMulticastSlot(void)1110 static void LoRaMacClassBProcessMulticastSlot( void )
1111 {
1112     static RxConfigParams_t multicastSlotRxConfig;
1113     TimerTime_t multicastSlotTime = 0;
1114     TimerTime_t slotTime = 0;
1115     uint32_t maxRxError = 0;
1116     MulticastCtx_t *cur = Ctx.LoRaMacClassBParams.MulticastChannels;
1117     bool slotHasPriority = false;
1118 
1119     if( cur == NULL )
1120     {
1121         return;
1122     }
1123 
1124     if( Ctx.MulticastSlotState == PINGSLOT_STATE_RX )
1125     {
1126         // A multicast slot is already open
1127         return;
1128     }
1129 
1130     switch( Ctx.MulticastSlotState )
1131     {
1132         case PINGSLOT_STATE_CALC_PING_OFFSET:
1133         {
1134             // Compute all offsets for every multicast slots
1135             for( uint8_t i = 0; i < 4; i++ )
1136             {
1137                 ComputePingOffset( Ctx.BeaconCtx.BeaconTime.Seconds,
1138                                    cur->ChannelParams.Address,
1139                                    cur->PingPeriod,
1140                                    &( cur->PingOffset ) );
1141                 cur++;
1142             }
1143             Ctx.MulticastSlotState = PINGSLOT_STATE_SET_TIMER;
1144         }
1145             // Intentional fall through
1146         case PINGSLOT_STATE_SET_TIMER:
1147         {
1148             cur = Ctx.LoRaMacClassBParams.MulticastChannels;
1149             Ctx.PingSlotCtx.NextMulticastChannel = NULL;
1150 
1151             for( uint8_t i = 0; i < LORAMAC_MAX_MC_CTX; i++ )
1152             {
1153                 // Calculate the next slot time for every multicast slot
1154                 if( CalcNextSlotTime( cur->PingOffset, cur->PingPeriod, cur->PingNb, &slotTime ) == true )
1155                 {
1156                     if( ( multicastSlotTime == 0 ) || ( multicastSlotTime > slotTime ) )
1157                     {
1158                         // Update the slot time and the next multicast channel
1159                         multicastSlotTime = slotTime;
1160                         Ctx.PingSlotCtx.NextMulticastChannel = cur;
1161                     }
1162                 }
1163                 cur++;
1164             }
1165 
1166             // Schedule the next multicast slot
1167             if( Ctx.PingSlotCtx.NextMulticastChannel != NULL )
1168             {
1169                 if( Ctx.BeaconCtx.Ctrl.BeaconAcquired == 1 )
1170                 {
1171 
1172                     // Compare and assign the maximum between the region specific rx error window time
1173                     // and time precision received from beacon frame format.
1174                     maxRxError = MAX( Ctx.LoRaMacClassBParams.LoRaMacParams->SystemMaxRxError ,
1175                                       ( uint32_t ) Ctx.BeaconCtx.BeaconTimePrecision.SubSeconds );
1176 
1177                     RegionComputeRxWindowParameters( *Ctx.LoRaMacClassBParams.LoRaMacRegion,
1178                                                     ClassBNvm->PingSlotCtx.Datarate,
1179                                                     Ctx.LoRaMacClassBParams.LoRaMacParams->MinRxSymbols,
1180                                                     maxRxError,
1181                                                     &multicastSlotRxConfig );
1182                     Ctx.PingSlotCtx.SymbolTimeout = multicastSlotRxConfig.WindowTimeout;
1183                 }
1184 
1185                 if( ( int32_t )multicastSlotTime > multicastSlotRxConfig.WindowOffset )
1186                 {// Apply the window offset
1187                     multicastSlotTime += multicastSlotRxConfig.WindowOffset;
1188                 }
1189 
1190                 // Start the timer if the ping slot time is in range
1191                 Ctx.MulticastSlotState = PINGSLOT_STATE_IDLE;
1192                 TimerSetValue( &Ctx.MulticastSlotTimer, multicastSlotTime );
1193                 TimerStart( &Ctx.MulticastSlotTimer );
1194             }
1195             break;
1196         }
1197         case PINGSLOT_STATE_IDLE:
1198         {
1199             uint32_t frequency = 0;
1200 
1201             // Verify if the multicast channel is valid
1202             if( Ctx.PingSlotCtx.NextMulticastChannel == NULL )
1203             {
1204                 Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1205                 TimerSetValue( &Ctx.MulticastSlotTimer, 1 );
1206                 TimerStart( &Ctx.MulticastSlotTimer );
1207                 break;
1208             }
1209 
1210             // Apply frequency
1211             frequency = Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.RxParams.ClassB.Frequency;
1212 
1213             // Restore the floor plan frequency if there is no individual frequency assigned
1214             if( frequency == 0 )
1215             {
1216                 // Restore floor plan
1217                 frequency = CalcDownlinkChannelAndFrequency( Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.Address,
1218                                                              Ctx.BeaconCtx.BeaconTime.Seconds, CLASSB_BEACON_INTERVAL, false );
1219             }
1220 
1221             // Verify, if the unicast has priority.
1222             slotHasPriority = CheckSlotPriority( Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.Address, Ctx.PingSlotCtx.NextMulticastChannel->FPendingSet, 1,
1223                                                  *Ctx.LoRaMacClassBParams.LoRaMacDevAddr, ClassBNvm->PingSlotCtx.FPendingSet, 0 );
1224 
1225             // Open the ping slot window only, if there is no multicast ping slot
1226             // open or if the unicast has priority.
1227             if( ( Ctx.PingSlotState != PINGSLOT_STATE_RX ) || ( slotHasPriority == true ) )
1228             {
1229                 if( Ctx.PingSlotState == PINGSLOT_STATE_RX )
1230                 {
1231                     // Close ping slot window, if necessary. Multicast slots have priority
1232                     Radio.Standby( );
1233                     Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1234                     TimerSetValue( &Ctx.PingSlotTimer, CLASSB_PING_SLOT_WINDOW );
1235                     TimerStart( &Ctx.PingSlotTimer );
1236                 }
1237 
1238                 Ctx.MulticastSlotState = PINGSLOT_STATE_RX;
1239 
1240                 multicastSlotRxConfig.Datarate = Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.RxParams.ClassB.Datarate;
1241                 multicastSlotRxConfig.DownlinkDwellTime = Ctx.LoRaMacClassBParams.LoRaMacParams->DownlinkDwellTime;
1242                 multicastSlotRxConfig.Frequency = frequency;
1243                 multicastSlotRxConfig.RxContinuous = false;
1244                 multicastSlotRxConfig.RxSlot = RX_SLOT_WIN_CLASS_B_MULTICAST_SLOT;
1245                 multicastSlotRxConfig.NetworkActivation = *Ctx.LoRaMacClassBParams.NetworkActivation;
1246 
1247                 RegionRxConfig( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &multicastSlotRxConfig, ( int8_t* )&Ctx.LoRaMacClassBParams.McpsIndication->RxDatarate );
1248 
1249                 if( multicastSlotRxConfig.RxContinuous == false )
1250                 {
1251                     Radio.Rx( Ctx.LoRaMacClassBParams.LoRaMacParams->MaxRxWindow );
1252                 }
1253                 else
1254                 {
1255                     Radio.Rx( 0 ); // Continuous mode
1256                 }
1257             }
1258             else
1259             {
1260                 // Unicast slots have priority. Skip Rx
1261                 Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1262                 TimerSetValue( &Ctx.MulticastSlotTimer, CLASSB_PING_SLOT_WINDOW );
1263                 TimerStart( &Ctx.MulticastSlotTimer );
1264             }
1265             break;
1266         }
1267         default:
1268         {
1269             Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1270             break;
1271         }
1272     }
1273 }
1274 #endif // LORAMAC_CLASSB_ENABLED
1275 
LoRaMacClassBRxBeacon(uint8_t * payload,uint16_t size)1276 bool LoRaMacClassBRxBeacon( uint8_t *payload, uint16_t size )
1277 {
1278 #ifdef LORAMAC_CLASSB_ENABLED
1279     GetPhyParams_t getPhy;
1280     PhyParam_t phyParam;
1281     bool beaconProcessed = false;
1282     uint16_t crc0 = 0;
1283     uint16_t crc1 = 0;
1284     uint16_t beaconCrc0 = 0;
1285     uint16_t beaconCrc1 = 0;
1286 
1287     getPhy.Attribute = PHY_BEACON_FORMAT;
1288     phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1289 
1290     // Verify if we are in the state where we expect a beacon
1291     if( ( Ctx.BeaconState == BEACON_STATE_RX ) || ( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 ) )
1292     {
1293         if( size == phyParam.BeaconFormat.BeaconSize )
1294         {
1295             // A beacon frame is defined as:
1296             // Bytes: |  x   |   1   |  4   |  2   |     7      |  y   |  2   |
1297             //        |------|-------|------|------|------------|------|------|
1298             // Field: | RFU1 | Param | Time | CRC1 | GwSpecific | RFU2 | CRC2 |
1299             //
1300             // Field RFU1 and RFU2 have variable sizes. It depends on the region specific implementation
1301 
1302             // Read CRC1 field from the frame
1303             beaconCrc0 = ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4] ) & 0x00FF;
1304             beaconCrc0 |= ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 1] << 8 ) & 0xFF00;
1305             crc0 = BeaconCrc( payload, phyParam.BeaconFormat.Rfu1Size + 1 + 4 );
1306 
1307             // Validate the first crc of the beacon frame
1308             if( crc0 == beaconCrc0 )
1309             {
1310                 // Copy the param field for app layer
1311                 Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Param = ( payload[phyParam.BeaconFormat.Rfu1Size] );
1312                 // Fetch the precise time value in milliseconds that will be used for Rx ping slot delay.
1313                 Ctx.BeaconCtx.BeaconTimePrecision.SubSeconds = BeaconPrecTimeValue[Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Param];
1314 
1315                 // Read Time field from the frame
1316                 Ctx.BeaconCtx.BeaconTime.Seconds  = ( ( uint32_t )payload[phyParam.BeaconFormat.Rfu1Size + 1] ) & 0x000000FF;
1317                 Ctx.BeaconCtx.BeaconTime.Seconds |= ( ( uint32_t )( payload[phyParam.BeaconFormat.Rfu1Size + 2] << 8 ) ) & 0x0000FF00;
1318                 Ctx.BeaconCtx.BeaconTime.Seconds |= ( ( uint32_t )( payload[phyParam.BeaconFormat.Rfu1Size + 3] << 16 ) ) & 0x00FF0000;
1319                 Ctx.BeaconCtx.BeaconTime.Seconds |= ( ( uint32_t )( payload[phyParam.BeaconFormat.Rfu1Size + 4] << 24 ) ) & 0xFF000000;
1320                 Ctx.BeaconCtx.BeaconTime.SubSeconds = 0;
1321                 Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Time = Ctx.BeaconCtx.BeaconTime;
1322                 beaconProcessed = true;
1323             }
1324 
1325             // Read CRC2 field from the frame
1326             beaconCrc1 = ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2 + 7 + phyParam.BeaconFormat.Rfu2Size] ) & 0x00FF;
1327             beaconCrc1 |= ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2 + 7 + phyParam.BeaconFormat.Rfu2Size + 1] << 8 ) & 0xFF00;
1328             crc1 = BeaconCrc( &payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2], 7 + phyParam.BeaconFormat.Rfu2Size );
1329 
1330             // Validate the second crc of the beacon frame
1331             if( crc1 == beaconCrc1 )
1332             {
1333                 // Read GwSpecific field from the frame
1334                 // The GwSpecific field contains 1 byte InfoDesc and 6 bytes Info
1335                 Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.GwSpecific.InfoDesc = payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2];
1336                 memcpy1( Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.GwSpecific.Info, &payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2 + 1], 6 );
1337             }
1338 
1339             // Reset beacon variables, if one of the crc is valid
1340             if( beaconProcessed == true )
1341             {
1342                 uint32_t spreadingFactor = 0;
1343                 uint32_t bandwith = 0;
1344 
1345                 getPhy.Attribute = PHY_BEACON_CHANNEL_DR;
1346                 phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1347 
1348                 getPhy.Attribute = PHY_SF_FROM_DR;
1349                 getPhy.Datarate = phyParam.Value;
1350                 phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1351                 spreadingFactor = phyParam.Value;
1352 
1353                 getPhy.Attribute = PHY_BW_FROM_DR;
1354                 phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1355                 bandwith = phyParam.Value;
1356 
1357                 TimerTime_t time = Radio.TimeOnAir( MODEM_LORA, bandwith, spreadingFactor, 1, 10, true, size, false );
1358                 SysTime_t timeOnAir;
1359                 timeOnAir.Seconds = time / 1000;
1360                 timeOnAir.SubSeconds = time - timeOnAir.Seconds * 1000;
1361 
1362                 Ctx.BeaconCtx.LastBeaconRx = Ctx.BeaconCtx.BeaconTime;
1363                 Ctx.BeaconCtx.LastBeaconRx.Seconds += UNIX_GPS_EPOCH_OFFSET;
1364 
1365                 // Update system time.
1366                 SysTimeSet( SysTimeAdd( Ctx.BeaconCtx.LastBeaconRx, timeOnAir ) );
1367 
1368                 Ctx.BeaconCtx.Ctrl.BeaconAcquired = 1;
1369                 Ctx.BeaconCtx.Ctrl.BeaconMode = 1;
1370                 ResetWindowTimeout( );
1371                 Ctx.BeaconState = BEACON_STATE_LOCKED;
1372 
1373                 LoRaMacClassBBeaconTimerEvent( NULL );
1374             }
1375         }
1376 
1377         if( Ctx.BeaconState == BEACON_STATE_RX )
1378         {
1379             Ctx.BeaconState = BEACON_STATE_TIMEOUT;
1380             LoRaMacClassBBeaconTimerEvent( NULL );
1381         }
1382         // When the MAC listens for a beacon, it is not allowed to process any other
1383         // downlink except the beacon frame itself. The reason for this is that no valid downlink window is open.
1384         // If it receives a frame which is
1385         // 1. not a beacon or
1386         // 2. a beacon with a crc fail
1387         // the MAC shall ignore the frame completely. Thus, the function must always return true, even if no
1388         // valid beacon has been received.
1389         beaconProcessed = true;
1390     }
1391     return beaconProcessed;
1392 #else
1393     return false;
1394 #endif // LORAMAC_CLASSB_ENABLED
1395 }
1396 
LoRaMacClassBIsBeaconExpected(void)1397 bool LoRaMacClassBIsBeaconExpected( void )
1398 {
1399 #ifdef LORAMAC_CLASSB_ENABLED
1400     if( ( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 ) ||
1401         ( Ctx.BeaconState == BEACON_STATE_RX ) )
1402     {
1403         return true;
1404     }
1405     return false;
1406 #else
1407     return false;
1408 #endif // LORAMAC_CLASSB_ENABLED
1409 }
1410 
LoRaMacClassBIsPingExpected(void)1411 bool LoRaMacClassBIsPingExpected( void )
1412 {
1413 #ifdef LORAMAC_CLASSB_ENABLED
1414     if( Ctx.PingSlotState == PINGSLOT_STATE_RX )
1415     {
1416         return true;
1417     }
1418     return false;
1419 #else
1420     return false;
1421 #endif // LORAMAC_CLASSB_ENABLED
1422 }
1423 
LoRaMacClassBIsMulticastExpected(void)1424 bool LoRaMacClassBIsMulticastExpected( void )
1425 {
1426 #ifdef LORAMAC_CLASSB_ENABLED
1427     if( Ctx.MulticastSlotState == PINGSLOT_STATE_RX )
1428     {
1429         return true;
1430     }
1431     return false;
1432 #else
1433     return false;
1434 #endif // LORAMAC_CLASSB_ENABLED
1435 }
1436 
LoRaMacClassBIsAcquisitionPending(void)1437 bool LoRaMacClassBIsAcquisitionPending( void )
1438 {
1439 #ifdef LORAMAC_CLASSB_ENABLED
1440     if( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 )
1441     {
1442         return true;
1443     }
1444     return false;
1445 #else
1446     return false;
1447 #endif // LORAMAC_CLASSB_ENABLED
1448 }
1449 
LoRaMacClassBIsBeaconModeActive(void)1450 bool LoRaMacClassBIsBeaconModeActive( void )
1451 {
1452 #ifdef LORAMAC_CLASSB_ENABLED
1453     if( ( Ctx.BeaconCtx.Ctrl.BeaconMode == 1 ) ||
1454         ( Ctx.BeaconState == BEACON_STATE_ACQUISITION_BY_TIME ) )
1455     {
1456         return true;
1457     }
1458     return false;
1459 #else
1460     return false;
1461 #endif // LORAMAC_CLASSB_ENABLED
1462 }
1463 
LoRaMacClassBSetPingSlotInfo(uint8_t periodicity)1464 void LoRaMacClassBSetPingSlotInfo( uint8_t periodicity )
1465 {
1466 #ifdef LORAMAC_CLASSB_ENABLED
1467     ClassBNvm->PingSlotCtx.PingNb = CalcPingNb( periodicity );
1468     ClassBNvm->PingSlotCtx.PingPeriod = CalcPingPeriod( ClassBNvm->PingSlotCtx.PingNb );
1469 #endif // LORAMAC_CLASSB_ENABLED
1470 }
1471 
LoRaMacClassBHaltBeaconing(void)1472 void LoRaMacClassBHaltBeaconing( void )
1473 {
1474 #ifdef LORAMAC_CLASSB_ENABLED
1475     if( Ctx.BeaconCtx.Ctrl.BeaconMode == 1 )
1476     {
1477         if( ( Ctx.BeaconState == BEACON_STATE_TIMEOUT ) ||
1478             ( Ctx.BeaconState == BEACON_STATE_LOST ) )
1479         {
1480             // Update the state machine before halt
1481             LoRaMacClassBBeaconTimerEvent( NULL );
1482         }
1483 
1484         CRITICAL_SECTION_BEGIN( );
1485         LoRaMacClassBEvents.Events.Beacon = 0;
1486         CRITICAL_SECTION_END( );
1487 
1488         // Halt ping slot state machine
1489         TimerStop( &Ctx.BeaconTimer );
1490 
1491         // Halt beacon state machine
1492         Ctx.BeaconState = BEACON_STATE_HALT;
1493 
1494         // Halt ping and multicast slot state machines
1495         LoRaMacClassBStopRxSlots( );
1496     }
1497 #endif // LORAMAC_CLASSB_ENABLED
1498 }
1499 
LoRaMacClassBResumeBeaconing(void)1500 void LoRaMacClassBResumeBeaconing( void )
1501 {
1502 #ifdef LORAMAC_CLASSB_ENABLED
1503     if( Ctx.BeaconState == BEACON_STATE_HALT )
1504     {
1505         Ctx.BeaconCtx.Ctrl.ResumeBeaconing = 1;
1506 
1507         // Set default state
1508         Ctx.BeaconState = BEACON_STATE_LOCKED;
1509 
1510         if( Ctx.BeaconCtx.Ctrl.BeaconAcquired == 0 )
1511         {
1512             // Set the default state for beacon less operation
1513             Ctx.BeaconState = BEACON_STATE_REACQUISITION;
1514         }
1515 
1516         LoRaMacClassBBeaconTimerEvent( NULL );
1517     }
1518 #endif // LORAMAC_CLASSB_ENABLED
1519 }
1520 
LoRaMacClassBSwitchClass(DeviceClass_t nextClass)1521 LoRaMacStatus_t LoRaMacClassBSwitchClass( DeviceClass_t nextClass )
1522 {
1523 #ifdef LORAMAC_CLASSB_ENABLED
1524     if( nextClass == CLASS_B )
1525     {// Switch to from class a to class b
1526         if( ( Ctx.BeaconCtx.Ctrl.BeaconMode == 1 ) && ( ClassBNvm->PingSlotCtx.Ctrl.Assigned == 1 ) )
1527         {
1528             return LORAMAC_STATUS_OK;
1529         }
1530     }
1531     if( nextClass == CLASS_A )
1532     {// Switch from class b to class a
1533         LoRaMacClassBHaltBeaconing( );
1534 
1535         // Initialize default state for class b
1536         InitClassBDefaults( );
1537 
1538         return LORAMAC_STATUS_OK;
1539     }
1540     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1541 #else
1542     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1543 #endif // LORAMAC_CLASSB_ENABLED
1544 }
1545 
LoRaMacClassBMibGetRequestConfirm(MibRequestConfirm_t * mibGet)1546 LoRaMacStatus_t LoRaMacClassBMibGetRequestConfirm( MibRequestConfirm_t *mibGet )
1547 {
1548 #ifdef LORAMAC_CLASSB_ENABLED
1549     LoRaMacStatus_t status;
1550 
1551     switch( mibGet->Type )
1552     {
1553         case MIB_PING_SLOT_DATARATE:
1554         {
1555             mibGet->Param.PingSlotDatarate = ClassBNvm->PingSlotCtx.Datarate;
1556             break;
1557         }
1558         default:
1559         {
1560             status = LORAMAC_STATUS_SERVICE_UNKNOWN;
1561             break;
1562         }
1563     }
1564     return status;
1565 #else
1566     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1567 #endif // LORAMAC_CLASSB_ENABLED
1568 }
1569 
LoRaMacMibClassBSetRequestConfirm(MibRequestConfirm_t * mibSet)1570 LoRaMacStatus_t LoRaMacMibClassBSetRequestConfirm( MibRequestConfirm_t *mibSet )
1571 {
1572 #ifdef LORAMAC_CLASSB_ENABLED
1573     LoRaMacStatus_t status;
1574 
1575     switch( mibSet->Type )
1576     {
1577         case MIB_PING_SLOT_DATARATE:
1578         {
1579             ClassBNvm->PingSlotCtx.Datarate = mibSet->Param.PingSlotDatarate;
1580             break;
1581         }
1582         default:
1583         {
1584             status = LORAMAC_STATUS_SERVICE_UNKNOWN;
1585             break;
1586         }
1587     }
1588     return status;
1589 #else
1590     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1591 #endif // LORAMAC_CLASSB_ENABLED
1592 }
1593 
LoRaMacClassBPingSlotInfoAns(void)1594 void LoRaMacClassBPingSlotInfoAns( void )
1595 {
1596 #ifdef LORAMAC_CLASSB_ENABLED
1597     if( LoRaMacConfirmQueueIsCmdActive( MLME_PING_SLOT_INFO ) == true )
1598     {
1599         LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_PING_SLOT_INFO );
1600         ClassBNvm->PingSlotCtx.Ctrl.Assigned = 1;
1601     }
1602 #endif // LORAMAC_CLASSB_ENABLED
1603 }
1604 
LoRaMacClassBPingSlotChannelReq(uint8_t datarate,uint32_t frequency)1605 uint8_t LoRaMacClassBPingSlotChannelReq( uint8_t datarate, uint32_t frequency )
1606 {
1607 #ifdef LORAMAC_CLASSB_ENABLED
1608     uint8_t status = 0x03;
1609     VerifyParams_t verify;
1610     bool isCustomFreq = false;
1611 
1612     if( frequency != 0 )
1613     {
1614         isCustomFreq = true;
1615         verify.Frequency = frequency;
1616         if( RegionVerify( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &verify, PHY_FREQUENCY ) == false )
1617         {
1618             status &= 0xFE; // Channel frequency KO
1619         }
1620     }
1621 
1622     verify.DatarateParams.Datarate = datarate;
1623     verify.DatarateParams.DownlinkDwellTime = Ctx.LoRaMacClassBParams.LoRaMacParams->DownlinkDwellTime;
1624 
1625     if( RegionVerify( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &verify, PHY_RX_DR ) == false )
1626     {
1627         status &= 0xFD; // Datarate range KO
1628     }
1629 
1630     if( status == 0x03 )
1631     {
1632         if( isCustomFreq == true )
1633         {
1634             ClassBNvm->PingSlotCtx.Ctrl.CustomFreq = 1;
1635             ClassBNvm->PingSlotCtx.Frequency = frequency;
1636         }
1637         else
1638         {
1639             ClassBNvm->PingSlotCtx.Ctrl.CustomFreq = 0;
1640             ClassBNvm->PingSlotCtx.Frequency = 0;
1641         }
1642         ClassBNvm->PingSlotCtx.Datarate = datarate;
1643     }
1644 
1645     return status;
1646 #else
1647     return 0;
1648 #endif // LORAMAC_CLASSB_ENABLED
1649 }
1650 
LoRaMacClassBBeaconTimingAns(uint16_t beaconTimingDelay,uint8_t beaconTimingChannel,TimerTime_t lastRxDone)1651 void LoRaMacClassBBeaconTimingAns( uint16_t beaconTimingDelay, uint8_t beaconTimingChannel, TimerTime_t lastRxDone )
1652 {
1653 #ifdef LORAMAC_CLASSB_ENABLED
1654     Ctx.BeaconCtx.BeaconTimingDelay = ( CLASSB_BEACON_DELAY_BEACON_TIMING_ANS * beaconTimingDelay );
1655     Ctx.BeaconCtx.BeaconTimingChannel = beaconTimingChannel;
1656 
1657     if( LoRaMacConfirmQueueIsCmdActive( MLME_BEACON_TIMING ) == true )
1658     {
1659         if( Ctx.BeaconCtx.BeaconTimingDelay > CLASSB_BEACON_INTERVAL )
1660         {
1661             // We missed the beacon already
1662             Ctx.BeaconCtx.BeaconTimingDelay = 0;
1663             Ctx.BeaconCtx.BeaconTimingChannel = 0;
1664             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND, MLME_BEACON_TIMING );
1665         }
1666         else
1667         {
1668             Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 1;
1669             Ctx.BeaconCtx.Ctrl.BeaconChannelSet = 1;
1670             Ctx.BeaconCtx.NextBeaconRx = SysTimeFromMs( lastRxDone + Ctx.BeaconCtx.BeaconTimingDelay );
1671             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_BEACON_TIMING );
1672         }
1673 
1674         Ctx.LoRaMacClassBParams.MlmeConfirm->BeaconTimingDelay = Ctx.BeaconCtx.BeaconTimingDelay;
1675         Ctx.LoRaMacClassBParams.MlmeConfirm->BeaconTimingChannel = Ctx.BeaconCtx.BeaconTimingChannel;
1676     }
1677 #endif // LORAMAC_CLASSB_ENABLED
1678 }
1679 
LoRaMacClassBDeviceTimeAns(void)1680 void LoRaMacClassBDeviceTimeAns( void )
1681 {
1682 #ifdef LORAMAC_CLASSB_ENABLED
1683 
1684     SysTime_t nextBeacon = SysTimeGet( );
1685     uint32_t currentTimeMs = SysTimeToMs( nextBeacon );
1686 
1687     nextBeacon.Seconds = nextBeacon.Seconds + ( 128 - ( nextBeacon.Seconds % 128 ) );
1688     nextBeacon.SubSeconds = 0;
1689 
1690     Ctx.BeaconCtx.NextBeaconRx = nextBeacon;
1691     Ctx.BeaconCtx.LastBeaconRx = SysTimeSub( Ctx.BeaconCtx.NextBeaconRx, ( SysTime_t ){ .Seconds = CLASSB_BEACON_INTERVAL / 1000, .SubSeconds = 0 } );
1692 
1693     if( LoRaMacConfirmQueueIsCmdActive( MLME_DEVICE_TIME ) == true )
1694     {
1695         if( currentTimeMs > SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) )
1696         {
1697             // We missed the beacon already
1698             Ctx.BeaconCtx.LastBeaconRx.Seconds = 0;
1699             Ctx.BeaconCtx.LastBeaconRx.SubSeconds = 0;
1700             Ctx.BeaconCtx.NextBeaconRx.Seconds = 0;
1701             Ctx.BeaconCtx.NextBeaconRx.SubSeconds = 0;
1702             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND, MLME_DEVICE_TIME );
1703         }
1704         else
1705         {
1706             Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 1;
1707             Ctx.BeaconCtx.BeaconTimingDelay = SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) - currentTimeMs;
1708             Ctx.BeaconCtx.BeaconTime.Seconds = nextBeacon.Seconds - UNIX_GPS_EPOCH_OFFSET - 128;
1709             Ctx.BeaconCtx.BeaconTime.SubSeconds = 0;
1710             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_DEVICE_TIME );
1711         }
1712     }
1713 #endif // LORAMAC_CLASSB_ENABLED
1714 }
1715 
LoRaMacClassBBeaconFreqReq(uint32_t frequency)1716 bool LoRaMacClassBBeaconFreqReq( uint32_t frequency )
1717 {
1718 #ifdef LORAMAC_CLASSB_ENABLED
1719     VerifyParams_t verify;
1720 
1721     if( frequency != 0 )
1722     {
1723         verify.Frequency = frequency;
1724 
1725         if( RegionVerify( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &verify, PHY_FREQUENCY ) == true )
1726         {
1727             ClassBNvm->BeaconCtx.Ctrl.CustomFreq = 1;
1728             ClassBNvm->BeaconCtx.Frequency = frequency;
1729             return true;
1730         }
1731     }
1732     else
1733     {
1734         ClassBNvm->BeaconCtx.Ctrl.CustomFreq = 0;
1735         return true;
1736     }
1737     return false;
1738 #else
1739     return false;
1740 #endif // LORAMAC_CLASSB_ENABLED
1741 }
1742 
LoRaMacClassBIsUplinkCollision(TimerTime_t txTimeOnAir)1743 TimerTime_t LoRaMacClassBIsUplinkCollision( TimerTime_t txTimeOnAir )
1744 {
1745 #ifdef LORAMAC_CLASSB_ENABLED
1746     TimerTime_t currentTime = TimerGetCurrentTime( );
1747     TimerTime_t beaconReserved = 0;
1748     TimerTime_t nextBeacon = SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx );
1749 
1750     beaconReserved = nextBeacon -
1751                      CLASSB_BEACON_GUARD -
1752                      Ctx.LoRaMacClassBParams.LoRaMacParams->ReceiveDelay1 -
1753                      Ctx.LoRaMacClassBParams.LoRaMacParams->ReceiveDelay2 -
1754                      txTimeOnAir;
1755 
1756     // Check if the next beacon will be received during the next uplink.
1757     if( ( currentTime >= beaconReserved ) && ( currentTime < ( nextBeacon + CLASSB_BEACON_RESERVED ) ) )
1758     {// Next beacon will be sent during the next uplink.
1759         return CLASSB_BEACON_RESERVED;
1760     }
1761     return 0;
1762 #else
1763     return 0;
1764 #endif // LORAMAC_CLASSB_ENABLED
1765 }
1766 
LoRaMacClassBStopRxSlots(void)1767 void LoRaMacClassBStopRxSlots( void )
1768 {
1769 #ifdef LORAMAC_CLASSB_ENABLED
1770     TimerStop( &Ctx.PingSlotTimer );
1771     TimerStop( &Ctx.MulticastSlotTimer );
1772 
1773     CRITICAL_SECTION_BEGIN( );
1774     LoRaMacClassBEvents.Events.PingSlot = 0;
1775     LoRaMacClassBEvents.Events.MulticastSlot = 0;
1776     CRITICAL_SECTION_END( );
1777 #endif // LORAMAC_CLASSB_ENABLED
1778 }
1779 
LoRaMacClassBStartRxSlots(void)1780 void LoRaMacClassBStartRxSlots( void )
1781 {
1782 #ifdef LORAMAC_CLASSB_ENABLED
1783     if( ClassBNvm->PingSlotCtx.Ctrl.Assigned == 1 )
1784     {
1785         Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1786         TimerSetValue( &Ctx.PingSlotTimer, 1 );
1787         TimerStart( &Ctx.PingSlotTimer );
1788 
1789         Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1790         TimerSetValue( &Ctx.MulticastSlotTimer, 1 );
1791         TimerStart( &Ctx.MulticastSlotTimer );
1792     }
1793 #endif // LORAMAC_CLASSB_ENABLED
1794 }
1795 
LoRaMacClassBSetMulticastPeriodicity(MulticastCtx_t * multicastChannel)1796 void LoRaMacClassBSetMulticastPeriodicity( MulticastCtx_t* multicastChannel )
1797 {
1798 #ifdef LORAMAC_CLASSB_ENABLED
1799     if( multicastChannel != NULL )
1800     {
1801         multicastChannel->PingNb = CalcPingNb( multicastChannel->ChannelParams.RxParams.ClassB.Periodicity );
1802         multicastChannel->PingPeriod = CalcPingPeriod( multicastChannel->PingNb );
1803     }
1804 #endif // LORAMAC_CLASSB_ENABLED
1805 }
1806 
LoRaMacClassBSetFPendingBit(uint32_t address,uint8_t fPendingSet)1807 void LoRaMacClassBSetFPendingBit( uint32_t address, uint8_t fPendingSet )
1808 {
1809 #ifdef LORAMAC_CLASSB_ENABLED
1810     MulticastCtx_t *cur = Ctx.LoRaMacClassBParams.MulticastChannels;
1811 
1812     if( address == *Ctx.LoRaMacClassBParams.LoRaMacDevAddr )
1813     {
1814         // Unicast
1815         ClassBNvm->PingSlotCtx.FPendingSet = fPendingSet;
1816     }
1817     else
1818     {
1819         for( uint8_t i = 0; i < LORAMAC_MAX_MC_CTX; i++ )
1820         {
1821             if( cur != NULL )
1822             {
1823                 // Set the fPending bit, if its a multicast
1824                 if( address == cur->ChannelParams.Address )
1825                 {
1826                     cur->FPendingSet = fPendingSet;
1827                 }
1828             }
1829             cur++;
1830         }
1831     }
1832 #endif
1833 }
1834 
LoRaMacClassBProcess(void)1835 void LoRaMacClassBProcess( void )
1836 {
1837 #ifdef LORAMAC_CLASSB_ENABLED
1838     LoRaMacClassBEvents_t events;
1839 
1840     CRITICAL_SECTION_BEGIN( );
1841     events = LoRaMacClassBEvents;
1842     LoRaMacClassBEvents.Value = 0;
1843     CRITICAL_SECTION_END( );
1844 
1845     if( events.Value != 0 )
1846     {
1847         if( events.Events.Beacon == 1 )
1848         {
1849             LoRaMacClassBProcessBeacon( );
1850         }
1851         if( events.Events.PingSlot == 1 )
1852         {
1853             LoRaMacClassBProcessPingSlot( );
1854         }
1855         if( events.Events.MulticastSlot == 1 )
1856         {
1857             LoRaMacClassBProcessMulticastSlot( );
1858         }
1859     }
1860 #endif // LORAMAC_CLASSB_ENABLED
1861 }
1862