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 
OnClassBMacProcessNotify(void)416 static void OnClassBMacProcessNotify( void )
417 {
418     if( Ctx.LoRaMacClassBCallbacks.MacProcessNotify != NULL )
419     {
420         Ctx.LoRaMacClassBCallbacks.MacProcessNotify( );
421     }
422 }
423 
InitClassB(void)424 static void InitClassB( void )
425 {
426     GetPhyParams_t getPhy;
427     PhyParam_t phyParam;
428 
429     // Init events
430     LoRaMacClassBEvents.Value = 0;
431 
432     // Init variables to default
433     memset1( ( uint8_t* ) ClassBNvm, 0, sizeof( LoRaMacClassBNvmData_t ) );
434     memset1( ( uint8_t* ) &Ctx.PingSlotCtx, 0, sizeof( PingSlotContext_t ) );
435     memset1( ( uint8_t* ) &Ctx.BeaconCtx, 0, sizeof( BeaconContext_t ) );
436 
437     // Setup default temperature
438     Ctx.BeaconCtx.Temperature = 25.0;
439     GetTemperatureLevel( &Ctx.LoRaMacClassBCallbacks, &Ctx.BeaconCtx );
440 
441     // Setup default ping slot datarate
442     getPhy.Attribute = PHY_PING_SLOT_CHANNEL_DR;
443     phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
444     ClassBNvm->PingSlotCtx.Datarate = ( int8_t )( phyParam.Value );
445 
446     // Setup default FPending bit
447     ClassBNvm->PingSlotCtx.FPendingSet = 0;
448 
449     // Setup default states
450     Ctx.BeaconState = BEACON_STATE_ACQUISITION;
451     Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
452     Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
453 }
454 
InitClassBDefaults(void)455 static void InitClassBDefaults( void )
456 {
457     // This function shall reset the Class B settings to default,
458     // but should keep important configurations
459     LoRaMacClassBBeaconNvmData_t beaconCtx = ClassBNvm->BeaconCtx;
460     LoRaMacClassBPingSlotNvmData_t pingSlotCtx = ClassBNvm->PingSlotCtx;
461 
462     InitClassB( );
463 
464     // Parameters from BeaconFreqReq
465     ClassBNvm->BeaconCtx.Frequency = beaconCtx.Frequency;
466     ClassBNvm->BeaconCtx.Ctrl.CustomFreq = beaconCtx.Ctrl.CustomFreq;
467 
468     // Parameters from PingSlotChannelReq
469     ClassBNvm->PingSlotCtx.Ctrl.CustomFreq = pingSlotCtx.Ctrl.CustomFreq;
470     ClassBNvm->PingSlotCtx.Frequency = pingSlotCtx.Frequency;
471     ClassBNvm->PingSlotCtx.Datarate = pingSlotCtx.Datarate;
472 }
473 
EnlargeWindowTimeout(void)474 static void EnlargeWindowTimeout( void )
475 {
476     // Update beacon movement
477     Ctx.BeaconCtx.BeaconWindowMovement *= CLASSB_WINDOW_MOVE_EXPANSION_FACTOR;
478     if( Ctx.BeaconCtx.BeaconWindowMovement > CLASSB_WINDOW_MOVE_EXPANSION_MAX )
479     {
480         Ctx.BeaconCtx.BeaconWindowMovement = CLASSB_WINDOW_MOVE_EXPANSION_MAX;
481     }
482     // Update symbol timeout
483     Ctx.BeaconCtx.SymbolTimeout *= CLASSB_BEACON_SYMBOL_TO_EXPANSION_FACTOR;
484     if( Ctx.BeaconCtx.SymbolTimeout > CLASSB_BEACON_SYMBOL_TO_EXPANSION_MAX )
485     {
486         Ctx.BeaconCtx.SymbolTimeout = CLASSB_BEACON_SYMBOL_TO_EXPANSION_MAX;
487     }
488     Ctx.PingSlotCtx.SymbolTimeout *= CLASSB_BEACON_SYMBOL_TO_EXPANSION_FACTOR;
489     if( Ctx.PingSlotCtx.SymbolTimeout > CLASSB_PING_SLOT_SYMBOL_TO_EXPANSION_MAX )
490     {
491         Ctx.PingSlotCtx.SymbolTimeout = CLASSB_PING_SLOT_SYMBOL_TO_EXPANSION_MAX;
492     }
493 }
494 
ResetWindowTimeout(void)495 static void ResetWindowTimeout( void )
496 {
497     Ctx.BeaconCtx.SymbolTimeout = CLASSB_BEACON_SYMBOL_TO_DEFAULT;
498     Ctx.PingSlotCtx.SymbolTimeout = CLASSB_BEACON_SYMBOL_TO_DEFAULT;
499     Ctx.BeaconCtx.BeaconWindowMovement  = CLASSB_WINDOW_MOVE_DEFAULT;
500 }
501 
CalcDelayForNextBeacon(TimerTime_t currentTime,TimerTime_t lastBeaconRx)502 static TimerTime_t CalcDelayForNextBeacon( TimerTime_t currentTime, TimerTime_t lastBeaconRx )
503 {
504     TimerTime_t nextBeaconRxTime = 0;
505 
506     // Calculate the point in time of the next beacon
507     nextBeaconRxTime = ( ( currentTime - lastBeaconRx ) % CLASSB_BEACON_INTERVAL );
508     return ( CLASSB_BEACON_INTERVAL - nextBeaconRxTime );
509 }
510 
IndicateBeaconStatus(LoRaMacEventInfoStatus_t status)511 static void IndicateBeaconStatus( LoRaMacEventInfoStatus_t status )
512 {
513     if( Ctx.BeaconCtx.Ctrl.ResumeBeaconing == 0 )
514     {
515         Ctx.LoRaMacClassBParams.MlmeIndication->MlmeIndication = MLME_BEACON;
516         Ctx.LoRaMacClassBParams.MlmeIndication->Status = status;
517         Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeInd = 1;
518 
519         Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MacDone = 1;
520     }
521     Ctx.BeaconCtx.Ctrl.ResumeBeaconing = 0;
522 }
523 
ApplyGuardTime(TimerTime_t beaconEventTime)524 static TimerTime_t ApplyGuardTime( TimerTime_t beaconEventTime )
525 {
526     TimerTime_t timeGuard = beaconEventTime;
527 
528     if( timeGuard > CLASSB_BEACON_GUARD )
529     {
530         timeGuard -= CLASSB_BEACON_GUARD;
531     }
532     return timeGuard;
533 }
534 
UpdateBeaconState(LoRaMacEventInfoStatus_t status,TimerTime_t windowMovement,TimerTime_t currentTime)535 static TimerTime_t UpdateBeaconState( LoRaMacEventInfoStatus_t status,
536                                       TimerTime_t windowMovement, TimerTime_t currentTime )
537 
538 {
539     TimerTime_t beaconEventTime = 0;
540 
541     // Calculate the next beacon RX time
542     beaconEventTime = CalcDelayForNextBeacon( currentTime, SysTimeToMs( Ctx.BeaconCtx.LastBeaconRx ) );
543     Ctx.BeaconCtx.NextBeaconRx = SysTimeFromMs( currentTime + beaconEventTime );
544 
545     // Take temperature compensation into account
546     beaconEventTime = TimerTempCompensation( beaconEventTime, Ctx.BeaconCtx.Temperature );
547 
548     // Move the window
549     if( beaconEventTime > windowMovement )
550     {
551         beaconEventTime -= windowMovement;
552     }
553     Ctx.BeaconCtx.NextBeaconRxAdjusted = currentTime + beaconEventTime;
554 
555     // Start the RX slot state machine for ping and multicast slots
556     LoRaMacClassBStartRxSlots( );
557 
558     // Setup an MLME_BEACON indication to inform the upper layer
559     IndicateBeaconStatus( status );
560 
561     // Apply guard time
562     return ApplyGuardTime( beaconEventTime );
563 }
564 
CalcPingNb(uint16_t periodicity)565 static uint8_t CalcPingNb( uint16_t periodicity )
566 {
567     return 128 / ( 1 << periodicity );
568 }
569 
CalcPingPeriod(uint8_t pingNb)570 static uint16_t CalcPingPeriod( uint8_t pingNb )
571 {
572     return CLASSB_BEACON_WINDOW_SLOTS / pingNb;
573 }
574 
CheckSlotPriority(uint32_t currentAddress,uint8_t currentFPendingSet,uint8_t currentIsMulticast,uint32_t address,uint8_t fPendingSet,uint8_t isMulticast)575 static bool CheckSlotPriority( uint32_t currentAddress, uint8_t currentFPendingSet, uint8_t currentIsMulticast,
576                                uint32_t address, uint8_t fPendingSet, uint8_t isMulticast )
577 {
578     if( currentFPendingSet != fPendingSet )
579     {
580         if( currentFPendingSet < fPendingSet )
581         {
582             // New slot sequence has priority. It does not matter
583             // which type it is
584             return true;
585         }
586         return false;
587     }
588     else
589     {
590         // FPendingSet has the same priority level, decide
591         // based on multicast or unicast setting
592         if( currentIsMulticast != isMulticast )
593         {
594             if( currentIsMulticast < isMulticast )
595             {
596                 // New slot sequence has priority. Multicasts have
597                 // more priority than unicasts
598                 return true;
599             }
600             return false;
601         }
602         else
603         {
604             // IsMulticast has the same priority level, decide
605             // based on the highest address
606             if( currentAddress < address )
607             {
608                 // New slot sequence has priority. The sequence with
609                 // the highest address has priority
610                 return true;
611             }
612         }
613     }
614     return false;
615 }
616 
617 #endif // LORAMAC_CLASSB_ENABLED
618 
LoRaMacClassBInit(LoRaMacClassBParams_t * classBParams,LoRaMacClassBCallback_t * callbacks,LoRaMacClassBNvmData_t * nvm)619 void LoRaMacClassBInit( LoRaMacClassBParams_t *classBParams, LoRaMacClassBCallback_t *callbacks, LoRaMacClassBNvmData_t* nvm )
620 {
621 #ifdef LORAMAC_CLASSB_ENABLED
622     // Assign non-volatile context
623     if( nvm == NULL )
624     {
625         return;
626     }
627     ClassBNvm = nvm;
628 
629     // Store callbacks
630     Ctx.LoRaMacClassBCallbacks = *callbacks;
631 
632     // Store parameter pointers
633     Ctx.LoRaMacClassBParams = *classBParams;
634 
635     // Initialize timers
636     TimerInit( &Ctx.BeaconTimer, LoRaMacClassBBeaconTimerEvent );
637     TimerInit( &Ctx.PingSlotTimer, LoRaMacClassBPingSlotTimerEvent );
638     TimerInit( &Ctx.MulticastSlotTimer, LoRaMacClassBMulticastSlotTimerEvent );
639 
640     InitClassB( );
641 #endif // LORAMAC_CLASSB_ENABLED
642 }
643 
LoRaMacClassBSetBeaconState(BeaconState_t beaconState)644 void LoRaMacClassBSetBeaconState( BeaconState_t beaconState )
645 {
646 #ifdef LORAMAC_CLASSB_ENABLED
647     if( beaconState == BEACON_STATE_ACQUISITION )
648     {
649         // If the MAC has received a time reference for the beacon,
650         // apply the state BEACON_STATE_ACQUISITION_BY_TIME.
651         if( ( Ctx.BeaconCtx.Ctrl.BeaconDelaySet == 1 ) &&
652             ( LoRaMacClassBIsAcquisitionPending( ) == false ) )
653         {
654             Ctx.BeaconState = BEACON_STATE_ACQUISITION_BY_TIME;
655         }
656         else
657         {
658            Ctx.BeaconState = beaconState;
659         }
660     }
661     else
662     {
663         if( ( Ctx.BeaconState != BEACON_STATE_ACQUISITION ) &&
664             ( Ctx.BeaconState != BEACON_STATE_ACQUISITION_BY_TIME ) )
665         {
666             Ctx.BeaconState = beaconState;
667         }
668     }
669 #endif // LORAMAC_CLASSB_ENABLED
670 }
671 
LoRaMacClassBSetPingSlotState(PingSlotState_t pingSlotState)672 void LoRaMacClassBSetPingSlotState( PingSlotState_t pingSlotState )
673 {
674 #ifdef LORAMAC_CLASSB_ENABLED
675     Ctx.PingSlotState = pingSlotState;
676 #endif // LORAMAC_CLASSB_ENABLED
677 }
678 
LoRaMacClassBSetMulticastSlotState(PingSlotState_t multicastSlotState)679 void LoRaMacClassBSetMulticastSlotState( PingSlotState_t multicastSlotState )
680 {
681 #ifdef LORAMAC_CLASSB_ENABLED
682     Ctx.MulticastSlotState = multicastSlotState;
683 #endif // LORAMAC_CLASSB_ENABLED
684 }
685 
LoRaMacClassBIsAcquisitionInProgress(void)686 bool LoRaMacClassBIsAcquisitionInProgress( void )
687 {
688 #ifdef LORAMAC_CLASSB_ENABLED
689     if( Ctx.BeaconState == BEACON_STATE_ACQUISITION_BY_TIME )
690     {
691         // In this case the acquisition is in progress, as the MAC has
692         // a time reference for the next beacon RX.
693         return true;
694     }
695     if( LoRaMacClassBIsAcquisitionPending( ) == true )
696     {
697         // In this case the acquisition is in progress, as the MAC
698         // searches for a beacon.
699         return true;
700     }
701     return false;
702 #else
703     return false;
704 #endif // LORAMAC_CLASSB_ENABLED
705 }
706 
LoRaMacClassBBeaconTimerEvent(void * context)707 void LoRaMacClassBBeaconTimerEvent( void* context )
708 {
709 #ifdef LORAMAC_CLASSB_ENABLED
710     Ctx.BeaconCtx.TimeStamp = TimerGetCurrentTime( );
711     TimerStop( &Ctx.BeaconTimer );
712     LoRaMacClassBEvents.Events.Beacon = 1;
713 
714     OnClassBMacProcessNotify( );
715 #endif // LORAMAC_CLASSB_ENABLED
716 }
717 
718 #ifdef LORAMAC_CLASSB_ENABLED
LoRaMacClassBProcessBeacon(void)719 static void LoRaMacClassBProcessBeacon( void )
720 {
721     bool activateTimer = false;
722     TimerTime_t beaconEventTime = 1;
723     RxConfigParams_t beaconRxConfig;
724     TimerTime_t beaconTimestamp = Ctx.BeaconCtx.TimeStamp;
725 
726     // Beacon state machine
727     switch( Ctx.BeaconState )
728     {
729         case BEACON_STATE_ACQUISITION_BY_TIME:
730         {
731             activateTimer = true;
732 
733             if( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 )
734             {
735                 Radio.Sleep();
736                 Ctx.BeaconState = BEACON_STATE_LOST;
737             }
738             else
739             {
740                 // Default symbol timeouts
741                 ResetWindowTimeout( );
742 
743                 if( Ctx.BeaconCtx.Ctrl.BeaconDelaySet == 1 )
744                 {
745                     // The goal is to calculate beaconRxConfig.WindowTimeout
746                     CalculateBeaconRxWindowConfig( &beaconRxConfig, Ctx.BeaconCtx.SymbolTimeout );
747 
748                     if( Ctx.BeaconCtx.BeaconTimingDelay > 0 )
749                     {
750                         uint32_t now = TimerGetCurrentTime( );
751                         if( SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) > now )
752                         {
753                             // Calculate the time when we expect the next beacon
754                             beaconEventTime = TimerTempCompensation( SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) - now, Ctx.BeaconCtx.Temperature );
755 
756                             if( ( int32_t ) beaconEventTime > beaconRxConfig.WindowOffset )
757                             {
758                                 // Apply the offset of the system error respectively beaconing precision setting
759                                 beaconEventTime += beaconRxConfig.WindowOffset;
760                             }
761                         }
762                         else
763                         {
764                             // Reset status provides by BeaconTimingAns
765                             Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 0;
766                             Ctx.BeaconCtx.Ctrl.BeaconChannelSet = 0;
767                             Ctx.BeaconState = BEACON_STATE_ACQUISITION;
768                         }
769                         Ctx.BeaconCtx.BeaconTimingDelay = 0;
770                     }
771                     else
772                     {
773                         activateTimer = false;
774 
775                         // Reset status provides by BeaconTimingAns
776                         Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 0;
777                         // Set the node into acquisition mode
778                         Ctx.BeaconCtx.Ctrl.AcquisitionPending = 1;
779 
780                         // Don't use the default channel. We know on which
781                         // channel the next beacon will be transmitted
782                         RxBeaconSetup( CLASSB_BEACON_RESERVED, false, beaconRxConfig.WindowTimeout );
783                     }
784                 }
785                 else
786                 {
787                     Ctx.BeaconCtx.NextBeaconRx.Seconds = 0;
788                     Ctx.BeaconCtx.NextBeaconRx.SubSeconds = 0;
789                     Ctx.BeaconCtx.BeaconTimingDelay = 0;
790 
791                     Ctx.BeaconState = BEACON_STATE_ACQUISITION;
792                 }
793             }
794             break;
795         }
796         case BEACON_STATE_ACQUISITION:
797         {
798             activateTimer = true;
799 
800             if( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 )
801             {
802                 Radio.Sleep();
803                 Ctx.BeaconState = BEACON_STATE_LOST;
804             }
805             else
806             {
807                 // Default symbol timeouts
808                 ResetWindowTimeout( );
809 
810                 Ctx.BeaconCtx.Ctrl.AcquisitionPending = 1;
811                 beaconEventTime = CLASSB_BEACON_INTERVAL;
812 
813                 // The goal is to calculate beaconRxConfig.WindowTimeout
814                 CalculateBeaconRxWindowConfig( &beaconRxConfig, Ctx.BeaconCtx.SymbolTimeout );
815 
816                 // Start the beacon acquisition. When the MAC has received a beacon in function
817                 // RxBeacon successfully, the next state is BEACON_STATE_LOCKED. If the MAC does not
818                 // find a beacon, the state machine will stay in state BEACON_STATE_ACQUISITION.
819                 // This state detects that a acquisition was pending previously and will change the next
820                 // state to BEACON_STATE_LOST.
821                 RxBeaconSetup( 0, true, beaconRxConfig.WindowTimeout );
822             }
823             break;
824         }
825         case BEACON_STATE_TIMEOUT:
826         {
827             // We have to update the beacon time, since we missed a beacon
828             Ctx.BeaconCtx.BeaconTime.Seconds += ( CLASSB_BEACON_INTERVAL / 1000 );
829             Ctx.BeaconCtx.BeaconTime.SubSeconds = 0;
830 
831             // Enlarge window timeouts to increase the chance to receive the next beacon
832             EnlargeWindowTimeout( );
833 
834             // Setup next state
835             Ctx.BeaconState = BEACON_STATE_REACQUISITION;
836         }
837             // Intentional fall through
838         case BEACON_STATE_REACQUISITION:
839         {
840             activateTimer = true;
841 
842             // The beacon is no longer acquired
843             Ctx.BeaconCtx.Ctrl.BeaconAcquired = 0;
844 
845             // Verify if the maximum beacon less period has been elapsed
846             if( ( beaconTimestamp - SysTimeToMs( Ctx.BeaconCtx.LastBeaconRx ) ) > CLASSB_MAX_BEACON_LESS_PERIOD )
847             {
848                 Ctx.BeaconState = BEACON_STATE_LOST;
849             }
850             else
851             {
852                 // Handle beacon miss
853                 beaconEventTime = UpdateBeaconState( LORAMAC_EVENT_INFO_STATUS_BEACON_LOST,
854                                                      Ctx.BeaconCtx.BeaconWindowMovement, beaconTimestamp );
855 
856                 // Setup next state
857                 Ctx.BeaconState = BEACON_STATE_IDLE;
858             }
859             break;
860         }
861         case BEACON_STATE_LOCKED:
862         {
863             activateTimer = true;
864 
865             // We have received a beacon. Acquisition is no longer pending.
866             Ctx.BeaconCtx.Ctrl.AcquisitionPending = 0;
867 
868             // Handle beacon reception
869             beaconEventTime = UpdateBeaconState( LORAMAC_EVENT_INFO_STATUS_BEACON_LOCKED,
870                                                  0, beaconTimestamp );
871 
872             // Setup the MLME confirm for the MLME_BEACON_ACQUISITION
873             if( Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeReq == 1 )
874             {
875                 if( LoRaMacConfirmQueueIsCmdActive( MLME_BEACON_ACQUISITION ) == true )
876                 {
877                     LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_BEACON_ACQUISITION );
878                     Ctx.LoRaMacClassBParams.MlmeConfirm->TxTimeOnAir = 0;
879                 }
880             }
881 
882             // Setup next state
883             Ctx.BeaconState = BEACON_STATE_IDLE;
884             break;
885         }
886         case BEACON_STATE_IDLE:
887         {
888             activateTimer = true;
889             GetTemperatureLevel( &Ctx.LoRaMacClassBCallbacks, &Ctx.BeaconCtx );
890             beaconEventTime = Ctx.BeaconCtx.NextBeaconRxAdjusted - Radio.GetWakeupTime( );
891             uint32_t now = TimerGetCurrentTime( );
892 
893             // The goal is to calculate beaconRxConfig.WindowTimeout and beaconRxConfig.WindowOffset
894             CalculateBeaconRxWindowConfig( &beaconRxConfig, Ctx.BeaconCtx.SymbolTimeout );
895 
896             if( beaconEventTime > now )
897             {
898                 Ctx.BeaconState = BEACON_STATE_GUARD;
899                 beaconEventTime -= now;
900                 beaconEventTime = TimerTempCompensation( beaconEventTime, Ctx.BeaconCtx.Temperature );
901 
902                 if( ( int32_t ) beaconEventTime > beaconRxConfig.WindowOffset )
903                 {
904                     // Apply the offset of the system error respectively beaconing precision setting
905                     beaconEventTime += beaconRxConfig.WindowOffset;
906                 }
907             }
908             else
909             {
910                 Ctx.BeaconState = BEACON_STATE_REACQUISITION;
911                 beaconEventTime = 1;
912             }
913             break;
914         }
915         case BEACON_STATE_GUARD:
916         {
917             Ctx.BeaconState = BEACON_STATE_RX;
918 
919             // Stop slot timers
920             LoRaMacClassBStopRxSlots( );
921 
922             // Don't use the default channel. We know on which
923             // channel the next beacon will be transmitted
924             RxBeaconSetup( CLASSB_BEACON_RESERVED, false, beaconRxConfig.WindowTimeout );
925             break;
926         }
927         case BEACON_STATE_LOST:
928         {
929             // Handle events
930             if( Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeReq == 1 )
931             {
932                 if( LoRaMacConfirmQueueIsCmdActive( MLME_BEACON_ACQUISITION ) == true )
933                 {
934                     LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND, MLME_BEACON_ACQUISITION );
935                 }
936             }
937             else
938             {
939                 Ctx.LoRaMacClassBParams.MlmeIndication->MlmeIndication = MLME_BEACON_LOST;
940                 Ctx.LoRaMacClassBParams.MlmeIndication->Status = LORAMAC_EVENT_INFO_STATUS_OK;
941                 Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MlmeInd = 1;
942             }
943 
944             // Stop slot timers
945             LoRaMacClassBStopRxSlots( );
946 
947             // Initialize default state for class b
948             InitClassBDefaults( );
949 
950             Ctx.LoRaMacClassBParams.LoRaMacFlags->Bits.MacDone = 1;
951 
952             break;
953         }
954         default:
955         {
956             Ctx.BeaconState = BEACON_STATE_ACQUISITION;
957             break;
958         }
959     }
960 
961     if( activateTimer == true )
962     {
963         TimerSetValue( &Ctx.BeaconTimer, beaconEventTime );
964         TimerStart( &Ctx.BeaconTimer );
965     }
966 }
967 #endif // LORAMAC_CLASSB_ENABLED
968 
LoRaMacClassBPingSlotTimerEvent(void * context)969 void LoRaMacClassBPingSlotTimerEvent( void* context )
970 {
971 #ifdef LORAMAC_CLASSB_ENABLED
972     LoRaMacClassBEvents.Events.PingSlot = 1;
973 
974     OnClassBMacProcessNotify( );
975 #endif // LORAMAC_CLASSB_ENABLED
976 }
977 
978 #ifdef LORAMAC_CLASSB_ENABLED
LoRaMacClassBProcessPingSlot(void)979 static void LoRaMacClassBProcessPingSlot( void )
980 {
981     static RxConfigParams_t pingSlotRxConfig;
982     TimerTime_t pingSlotTime = 0;
983     uint32_t maxRxError = 0;
984     bool slotHasPriority = false;
985 
986     switch( Ctx.PingSlotState )
987     {
988         case PINGSLOT_STATE_CALC_PING_OFFSET:
989         {
990             ComputePingOffset( Ctx.BeaconCtx.BeaconTime.Seconds,
991                                *Ctx.LoRaMacClassBParams.LoRaMacDevAddr,
992                                ClassBNvm->PingSlotCtx.PingPeriod,
993                                &( Ctx.PingSlotCtx.PingOffset ) );
994             Ctx.PingSlotState = PINGSLOT_STATE_SET_TIMER;
995         }
996             // Intentional fall through
997         case PINGSLOT_STATE_SET_TIMER:
998         {
999             if( CalcNextSlotTime( Ctx.PingSlotCtx.PingOffset, ClassBNvm->PingSlotCtx.PingPeriod, ClassBNvm->PingSlotCtx.PingNb, &pingSlotTime ) == true )
1000             {
1001                 if( Ctx.BeaconCtx.Ctrl.BeaconAcquired == 1 )
1002                 {
1003                     // Compare and assign the maximum between the region specific rx error window time
1004                     // and time precision received from beacon frame format.
1005                     maxRxError = MAX( Ctx.LoRaMacClassBParams.LoRaMacParams->SystemMaxRxError ,
1006                                       ( uint32_t ) Ctx.BeaconCtx.BeaconTimePrecision.SubSeconds );
1007 
1008                     // Compute the symbol timeout. Apply it only, if the beacon is acquired
1009                     // Otherwise, take the enlargement of the symbols into account.
1010                     RegionComputeRxWindowParameters( *Ctx.LoRaMacClassBParams.LoRaMacRegion,
1011                                                      ClassBNvm->PingSlotCtx.Datarate,
1012                                                      Ctx.LoRaMacClassBParams.LoRaMacParams->MinRxSymbols,
1013                                                      maxRxError,
1014                                                      &pingSlotRxConfig );
1015                     Ctx.PingSlotCtx.SymbolTimeout = pingSlotRxConfig.WindowTimeout;
1016 
1017                     if( ( int32_t )pingSlotTime > pingSlotRxConfig.WindowOffset )
1018                     {// Apply the window offset
1019                         pingSlotTime += pingSlotRxConfig.WindowOffset;
1020                     }
1021                 }
1022 
1023                 // Start the timer if the ping slot time is in range
1024                 Ctx.PingSlotState = PINGSLOT_STATE_IDLE;
1025                 TimerSetValue( &Ctx.PingSlotTimer, pingSlotTime );
1026                 TimerStart( &Ctx.PingSlotTimer );
1027             }
1028             break;
1029         }
1030         case PINGSLOT_STATE_IDLE:
1031         {
1032             uint32_t frequency = ClassBNvm->PingSlotCtx.Frequency;
1033 
1034             // Apply a custom frequency if the following bit is set
1035             if( ClassBNvm->PingSlotCtx.Ctrl.CustomFreq == 0 )
1036             {
1037                 // Restore floor plan
1038                 frequency = CalcDownlinkChannelAndFrequency( *Ctx.LoRaMacClassBParams.LoRaMacDevAddr, Ctx.BeaconCtx.BeaconTime.Seconds,
1039                                                              CLASSB_BEACON_INTERVAL, false );
1040             }
1041 
1042             if( Ctx.PingSlotCtx.NextMulticastChannel != NULL )
1043             {
1044                 // Verify, if the unicast has priority.
1045                 slotHasPriority = CheckSlotPriority( *Ctx.LoRaMacClassBParams.LoRaMacDevAddr, ClassBNvm->PingSlotCtx.FPendingSet, 0,
1046                                                      Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.Address, Ctx.PingSlotCtx.NextMulticastChannel->FPendingSet, 1 );
1047             }
1048 
1049             // Open the ping slot window only, if there is no multicast ping slot
1050             // open or if the unicast has priority.
1051             if( ( Ctx.MulticastSlotState != PINGSLOT_STATE_RX ) || ( slotHasPriority == true ) )
1052             {
1053                 if( Ctx.MulticastSlotState == PINGSLOT_STATE_RX )
1054                 {
1055                     // Close multicast slot window, if necessary. Multicast slots have priority
1056                     Radio.Standby( );
1057                     Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1058                     TimerSetValue( &Ctx.MulticastSlotTimer, CLASSB_PING_SLOT_WINDOW );
1059                     TimerStart( &Ctx.MulticastSlotTimer );
1060                 }
1061 
1062                 Ctx.PingSlotState = PINGSLOT_STATE_RX;
1063 
1064                 pingSlotRxConfig.Datarate = ClassBNvm->PingSlotCtx.Datarate;
1065                 pingSlotRxConfig.DownlinkDwellTime = Ctx.LoRaMacClassBParams.LoRaMacParams->DownlinkDwellTime;
1066                 pingSlotRxConfig.Frequency = frequency;
1067                 pingSlotRxConfig.RxContinuous = false;
1068                 pingSlotRxConfig.RxSlot = RX_SLOT_WIN_CLASS_B_PING_SLOT;
1069                 pingSlotRxConfig.NetworkActivation = *Ctx.LoRaMacClassBParams.NetworkActivation;
1070 
1071                 RegionRxConfig( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &pingSlotRxConfig, ( int8_t* )&Ctx.LoRaMacClassBParams.McpsIndication->RxDatarate );
1072 
1073                 if( pingSlotRxConfig.RxContinuous == false )
1074                 {
1075                     Radio.Rx( Ctx.LoRaMacClassBParams.LoRaMacParams->MaxRxWindow );
1076                 }
1077                 else
1078                 {
1079                     Radio.Rx( 0 ); // Continuous mode
1080                 }
1081             }
1082             else
1083             {
1084                 // Multicast slots have priority. Skip Rx
1085                 Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1086                 TimerSetValue( &Ctx.PingSlotTimer, CLASSB_PING_SLOT_WINDOW );
1087                 TimerStart( &Ctx.PingSlotTimer );
1088             }
1089             break;
1090         }
1091         default:
1092         {
1093             Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1094             break;
1095         }
1096     }
1097 }
1098 #endif // LORAMAC_CLASSB_ENABLED
1099 
LoRaMacClassBMulticastSlotTimerEvent(void * context)1100 void LoRaMacClassBMulticastSlotTimerEvent( void* context )
1101 {
1102 #ifdef LORAMAC_CLASSB_ENABLED
1103     LoRaMacClassBEvents.Events.MulticastSlot = 1;
1104 
1105     OnClassBMacProcessNotify( );
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 < LORAMAC_MAX_MC_CTX; i++ )
1136             {
1137                 if( cur->ChannelParams.IsEnabled )
1138                 {
1139                     ComputePingOffset( Ctx.BeaconCtx.BeaconTime.Seconds,
1140                                        cur->ChannelParams.Address,
1141                                        cur->PingPeriod,
1142                                        &( cur->PingOffset ) );
1143                 }
1144                 cur++;
1145             }
1146             Ctx.MulticastSlotState = PINGSLOT_STATE_SET_TIMER;
1147         }
1148             // Intentional fall through
1149         case PINGSLOT_STATE_SET_TIMER:
1150         {
1151             cur = Ctx.LoRaMacClassBParams.MulticastChannels;
1152             Ctx.PingSlotCtx.NextMulticastChannel = NULL;
1153 
1154             for( uint8_t i = 0; i < LORAMAC_MAX_MC_CTX; i++ )
1155             {
1156                 if( cur->ChannelParams.IsEnabled )
1157                 {
1158                     // Calculate the next slot time for every multicast slot
1159                     if( CalcNextSlotTime( cur->PingOffset, cur->PingPeriod, cur->PingNb, &slotTime ) == true )
1160                     {
1161                         if( ( multicastSlotTime == 0 ) || ( multicastSlotTime > slotTime ) )
1162                         {
1163                             // Update the slot time and the next multicast channel
1164                             multicastSlotTime = slotTime;
1165                             Ctx.PingSlotCtx.NextMulticastChannel = cur;
1166                         }
1167                     }
1168                 }
1169                 cur++;
1170             }
1171 
1172             // Schedule the next multicast slot
1173             if( Ctx.PingSlotCtx.NextMulticastChannel != NULL )
1174             {
1175                 if( Ctx.BeaconCtx.Ctrl.BeaconAcquired == 1 )
1176                 {
1177 
1178                     // Compare and assign the maximum between the region specific rx error window time
1179                     // and time precision received from beacon frame format.
1180                     maxRxError = MAX( Ctx.LoRaMacClassBParams.LoRaMacParams->SystemMaxRxError ,
1181                                       ( uint32_t ) Ctx.BeaconCtx.BeaconTimePrecision.SubSeconds );
1182 
1183                     RegionComputeRxWindowParameters( *Ctx.LoRaMacClassBParams.LoRaMacRegion,
1184                                                     ClassBNvm->PingSlotCtx.Datarate,
1185                                                     Ctx.LoRaMacClassBParams.LoRaMacParams->MinRxSymbols,
1186                                                     maxRxError,
1187                                                     &multicastSlotRxConfig );
1188                     Ctx.PingSlotCtx.SymbolTimeout = multicastSlotRxConfig.WindowTimeout;
1189                 }
1190 
1191                 if( ( int32_t )multicastSlotTime > multicastSlotRxConfig.WindowOffset )
1192                 {// Apply the window offset
1193                     multicastSlotTime += multicastSlotRxConfig.WindowOffset;
1194                 }
1195 
1196                 // Start the timer if the ping slot time is in range
1197                 Ctx.MulticastSlotState = PINGSLOT_STATE_IDLE;
1198                 TimerSetValue( &Ctx.MulticastSlotTimer, multicastSlotTime );
1199                 TimerStart( &Ctx.MulticastSlotTimer );
1200             }
1201             break;
1202         }
1203         case PINGSLOT_STATE_IDLE:
1204         {
1205             uint32_t frequency = 0;
1206 
1207             // Verify if the multicast channel is valid
1208             if( Ctx.PingSlotCtx.NextMulticastChannel == NULL )
1209             {
1210                 Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1211                 TimerSetValue( &Ctx.MulticastSlotTimer, 1 );
1212                 TimerStart( &Ctx.MulticastSlotTimer );
1213                 break;
1214             }
1215 
1216             // Apply frequency
1217             frequency = Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.RxParams.Params.ClassB.Frequency;
1218 
1219             // Restore the floor plan frequency if there is no individual frequency assigned
1220             if( frequency == 0 )
1221             {
1222                 // Restore floor plan
1223                 frequency = CalcDownlinkChannelAndFrequency( Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.Address,
1224                                                              Ctx.BeaconCtx.BeaconTime.Seconds, CLASSB_BEACON_INTERVAL, false );
1225             }
1226 
1227             // Verify, if the unicast has priority.
1228             slotHasPriority = CheckSlotPriority( Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.Address, Ctx.PingSlotCtx.NextMulticastChannel->FPendingSet, 1,
1229                                                  *Ctx.LoRaMacClassBParams.LoRaMacDevAddr, ClassBNvm->PingSlotCtx.FPendingSet, 0 );
1230 
1231             // Open the ping slot window only, if there is no multicast ping slot
1232             // open or if the unicast has priority.
1233             if( ( Ctx.PingSlotState != PINGSLOT_STATE_RX ) || ( slotHasPriority == true ) )
1234             {
1235                 if( Ctx.PingSlotState == PINGSLOT_STATE_RX )
1236                 {
1237                     // Close ping slot window, if necessary. Multicast slots have priority
1238                     Radio.Standby( );
1239                     Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1240                     TimerSetValue( &Ctx.PingSlotTimer, CLASSB_PING_SLOT_WINDOW );
1241                     TimerStart( &Ctx.PingSlotTimer );
1242                 }
1243 
1244                 Ctx.MulticastSlotState = PINGSLOT_STATE_RX;
1245 
1246                 multicastSlotRxConfig.Datarate = Ctx.PingSlotCtx.NextMulticastChannel->ChannelParams.RxParams.Params.ClassB.Datarate;
1247                 multicastSlotRxConfig.DownlinkDwellTime = Ctx.LoRaMacClassBParams.LoRaMacParams->DownlinkDwellTime;
1248                 multicastSlotRxConfig.Frequency = frequency;
1249                 multicastSlotRxConfig.RxContinuous = false;
1250                 multicastSlotRxConfig.RxSlot = RX_SLOT_WIN_CLASS_B_MULTICAST_SLOT;
1251                 multicastSlotRxConfig.NetworkActivation = *Ctx.LoRaMacClassBParams.NetworkActivation;
1252 
1253                 RegionRxConfig( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &multicastSlotRxConfig, ( int8_t* )&Ctx.LoRaMacClassBParams.McpsIndication->RxDatarate );
1254 
1255                 if( multicastSlotRxConfig.RxContinuous == false )
1256                 {
1257                     Radio.Rx( Ctx.LoRaMacClassBParams.LoRaMacParams->MaxRxWindow );
1258                 }
1259                 else
1260                 {
1261                     Radio.Rx( 0 ); // Continuous mode
1262                 }
1263             }
1264             else
1265             {
1266                 // Unicast slots have priority. Skip Rx
1267                 Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1268                 TimerSetValue( &Ctx.MulticastSlotTimer, CLASSB_PING_SLOT_WINDOW );
1269                 TimerStart( &Ctx.MulticastSlotTimer );
1270             }
1271             break;
1272         }
1273         default:
1274         {
1275             Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1276             break;
1277         }
1278     }
1279 }
1280 #endif // LORAMAC_CLASSB_ENABLED
1281 
LoRaMacClassBRxBeacon(uint8_t * payload,uint16_t size)1282 bool LoRaMacClassBRxBeacon( uint8_t *payload, uint16_t size )
1283 {
1284 #ifdef LORAMAC_CLASSB_ENABLED
1285     GetPhyParams_t getPhy;
1286     PhyParam_t phyParam;
1287     bool beaconProcessed = false;
1288     uint16_t crc0 = 0;
1289     uint16_t crc1 = 0;
1290     uint16_t beaconCrc0 = 0;
1291     uint16_t beaconCrc1 = 0;
1292 
1293     getPhy.Attribute = PHY_BEACON_FORMAT;
1294     phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1295 
1296     // Verify if we are in the state where we expect a beacon
1297     if( ( Ctx.BeaconState == BEACON_STATE_RX ) || ( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 ) )
1298     {
1299         if( size == phyParam.BeaconFormat.BeaconSize )
1300         {
1301             // A beacon frame is defined as:
1302             // Bytes: |  x   |   1   |  4   |  2   |     7      |  y   |  2   |
1303             //        |------|-------|------|------|------------|------|------|
1304             // Field: | RFU1 | Param | Time | CRC1 | GwSpecific | RFU2 | CRC2 |
1305             //
1306             // Field RFU1 and RFU2 have variable sizes. It depends on the region specific implementation
1307 
1308             // Read CRC1 field from the frame
1309             beaconCrc0 = ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4] ) & 0x00FF;
1310             beaconCrc0 |= ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 1] << 8 ) & 0xFF00;
1311             crc0 = BeaconCrc( payload, phyParam.BeaconFormat.Rfu1Size + 1 + 4 );
1312 
1313             // Validate the first crc of the beacon frame
1314             if( crc0 == beaconCrc0 )
1315             {
1316                 // Copy the param field for app layer
1317                 Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Param = ( payload[phyParam.BeaconFormat.Rfu1Size] );
1318                 // Fetch the precise time value in milliseconds that will be used for Rx ping slot delay.
1319                 Ctx.BeaconCtx.BeaconTimePrecision.SubSeconds = BeaconPrecTimeValue[Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Param];
1320 
1321                 // Read Time field from the frame
1322                 Ctx.BeaconCtx.BeaconTime.Seconds  = ( ( uint32_t )payload[phyParam.BeaconFormat.Rfu1Size + 1] ) & 0x000000FF;
1323                 Ctx.BeaconCtx.BeaconTime.Seconds |= ( ( uint32_t )( payload[phyParam.BeaconFormat.Rfu1Size + 2] << 8 ) ) & 0x0000FF00;
1324                 Ctx.BeaconCtx.BeaconTime.Seconds |= ( ( uint32_t )( payload[phyParam.BeaconFormat.Rfu1Size + 3] << 16 ) ) & 0x00FF0000;
1325                 Ctx.BeaconCtx.BeaconTime.Seconds |= ( ( uint32_t )( payload[phyParam.BeaconFormat.Rfu1Size + 4] << 24 ) ) & 0xFF000000;
1326                 Ctx.BeaconCtx.BeaconTime.SubSeconds = 0;
1327                 Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.Time = Ctx.BeaconCtx.BeaconTime;
1328                 beaconProcessed = true;
1329             }
1330 
1331             // Read CRC2 field from the frame
1332             beaconCrc1 = ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2 + 7 + phyParam.BeaconFormat.Rfu2Size] ) & 0x00FF;
1333             beaconCrc1 |= ( ( uint16_t )payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2 + 7 + phyParam.BeaconFormat.Rfu2Size + 1] << 8 ) & 0xFF00;
1334             crc1 = BeaconCrc( &payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2], 7 + phyParam.BeaconFormat.Rfu2Size );
1335 
1336             // Validate the second crc of the beacon frame
1337             if( crc1 == beaconCrc1 )
1338             {
1339                 // Read GwSpecific field from the frame
1340                 // The GwSpecific field contains 1 byte InfoDesc and 6 bytes Info
1341                 Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.GwSpecific.InfoDesc = payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2];
1342                 memcpy1( Ctx.LoRaMacClassBParams.MlmeIndication->BeaconInfo.GwSpecific.Info, &payload[phyParam.BeaconFormat.Rfu1Size + 1 + 4 + 2 + 1], 6 );
1343             }
1344 
1345             // Reset beacon variables, if one of the crc is valid
1346             if( beaconProcessed == true )
1347             {
1348                 uint32_t spreadingFactor = 0;
1349                 uint32_t bandwith = 0;
1350 
1351                 getPhy.Attribute = PHY_BEACON_CHANNEL_DR;
1352                 phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1353 
1354                 getPhy.Attribute = PHY_SF_FROM_DR;
1355                 getPhy.Datarate = phyParam.Value;
1356                 phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1357                 spreadingFactor = phyParam.Value;
1358 
1359                 getPhy.Attribute = PHY_BW_FROM_DR;
1360                 phyParam = RegionGetPhyParam( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &getPhy );
1361                 bandwith = phyParam.Value;
1362 
1363                 TimerTime_t time = Radio.TimeOnAir( MODEM_LORA, bandwith, spreadingFactor, 1, 10, true, size, false );
1364                 SysTime_t timeOnAir;
1365                 timeOnAir.Seconds = time / 1000;
1366                 timeOnAir.SubSeconds = time - timeOnAir.Seconds * 1000;
1367 
1368                 Ctx.BeaconCtx.LastBeaconRx = Ctx.BeaconCtx.BeaconTime;
1369                 Ctx.BeaconCtx.LastBeaconRx.Seconds += UNIX_GPS_EPOCH_OFFSET;
1370 
1371                 // Update system time.
1372                 SysTimeSet( SysTimeAdd( Ctx.BeaconCtx.LastBeaconRx, timeOnAir ) );
1373 
1374                 Ctx.BeaconCtx.Ctrl.BeaconAcquired = 1;
1375                 Ctx.BeaconCtx.Ctrl.BeaconMode = 1;
1376                 ResetWindowTimeout( );
1377                 Ctx.BeaconState = BEACON_STATE_LOCKED;
1378 
1379                 LoRaMacClassBBeaconTimerEvent( NULL );
1380             }
1381         }
1382 
1383         if( Ctx.BeaconState == BEACON_STATE_RX )
1384         {
1385             Ctx.BeaconState = BEACON_STATE_TIMEOUT;
1386             LoRaMacClassBBeaconTimerEvent( NULL );
1387         }
1388         // When the MAC listens for a beacon, it is not allowed to process any other
1389         // downlink except the beacon frame itself. The reason for this is that no valid downlink window is open.
1390         // If it receives a frame which is
1391         // 1. not a beacon or
1392         // 2. a beacon with a crc fail
1393         // the MAC shall ignore the frame completely. Thus, the function must always return true, even if no
1394         // valid beacon has been received.
1395         beaconProcessed = true;
1396     }
1397     return beaconProcessed;
1398 #else
1399     return false;
1400 #endif // LORAMAC_CLASSB_ENABLED
1401 }
1402 
LoRaMacClassBIsBeaconExpected(void)1403 bool LoRaMacClassBIsBeaconExpected( void )
1404 {
1405 #ifdef LORAMAC_CLASSB_ENABLED
1406     if( ( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 ) ||
1407         ( Ctx.BeaconState == BEACON_STATE_RX ) )
1408     {
1409         return true;
1410     }
1411     return false;
1412 #else
1413     return false;
1414 #endif // LORAMAC_CLASSB_ENABLED
1415 }
1416 
LoRaMacClassBIsPingExpected(void)1417 bool LoRaMacClassBIsPingExpected( void )
1418 {
1419 #ifdef LORAMAC_CLASSB_ENABLED
1420     if( Ctx.PingSlotState == PINGSLOT_STATE_RX )
1421     {
1422         return true;
1423     }
1424     return false;
1425 #else
1426     return false;
1427 #endif // LORAMAC_CLASSB_ENABLED
1428 }
1429 
LoRaMacClassBIsMulticastExpected(void)1430 bool LoRaMacClassBIsMulticastExpected( void )
1431 {
1432 #ifdef LORAMAC_CLASSB_ENABLED
1433     if( Ctx.MulticastSlotState == PINGSLOT_STATE_RX )
1434     {
1435         return true;
1436     }
1437     return false;
1438 #else
1439     return false;
1440 #endif // LORAMAC_CLASSB_ENABLED
1441 }
1442 
LoRaMacClassBIsAcquisitionPending(void)1443 bool LoRaMacClassBIsAcquisitionPending( void )
1444 {
1445 #ifdef LORAMAC_CLASSB_ENABLED
1446     if( Ctx.BeaconCtx.Ctrl.AcquisitionPending == 1 )
1447     {
1448         return true;
1449     }
1450     return false;
1451 #else
1452     return false;
1453 #endif // LORAMAC_CLASSB_ENABLED
1454 }
1455 
LoRaMacClassBIsBeaconModeActive(void)1456 bool LoRaMacClassBIsBeaconModeActive( void )
1457 {
1458 #ifdef LORAMAC_CLASSB_ENABLED
1459     if( ( Ctx.BeaconCtx.Ctrl.BeaconMode == 1 ) ||
1460         ( Ctx.BeaconState == BEACON_STATE_ACQUISITION_BY_TIME ) )
1461     {
1462         return true;
1463     }
1464     return false;
1465 #else
1466     return false;
1467 #endif // LORAMAC_CLASSB_ENABLED
1468 }
1469 
LoRaMacClassBSetPingSlotInfo(uint8_t periodicity)1470 void LoRaMacClassBSetPingSlotInfo( uint8_t periodicity )
1471 {
1472 #ifdef LORAMAC_CLASSB_ENABLED
1473     ClassBNvm->PingSlotCtx.PingNb = CalcPingNb( periodicity );
1474     ClassBNvm->PingSlotCtx.PingPeriod = CalcPingPeriod( ClassBNvm->PingSlotCtx.PingNb );
1475 #endif // LORAMAC_CLASSB_ENABLED
1476 }
1477 
LoRaMacClassBHaltBeaconing(void)1478 void LoRaMacClassBHaltBeaconing( void )
1479 {
1480 #ifdef LORAMAC_CLASSB_ENABLED
1481     if( Ctx.BeaconCtx.Ctrl.BeaconMode == 1 )
1482     {
1483         if( ( Ctx.BeaconState == BEACON_STATE_TIMEOUT ) ||
1484             ( Ctx.BeaconState == BEACON_STATE_LOST ) )
1485         {
1486             // Update the state machine before halt
1487             LoRaMacClassBBeaconTimerEvent( NULL );
1488         }
1489 
1490         CRITICAL_SECTION_BEGIN( );
1491         LoRaMacClassBEvents.Events.Beacon = 0;
1492         CRITICAL_SECTION_END( );
1493 
1494         // Halt ping slot state machine
1495         TimerStop( &Ctx.BeaconTimer );
1496 
1497         // Halt beacon state machine
1498         Ctx.BeaconState = BEACON_STATE_HALT;
1499 
1500         // Halt ping and multicast slot state machines
1501         LoRaMacClassBStopRxSlots( );
1502     }
1503 #endif // LORAMAC_CLASSB_ENABLED
1504 }
1505 
LoRaMacClassBResumeBeaconing(void)1506 void LoRaMacClassBResumeBeaconing( void )
1507 {
1508 #ifdef LORAMAC_CLASSB_ENABLED
1509     if( Ctx.BeaconState == BEACON_STATE_HALT )
1510     {
1511         Ctx.BeaconCtx.Ctrl.ResumeBeaconing = 1;
1512 
1513         // Set default state
1514         Ctx.BeaconState = BEACON_STATE_LOCKED;
1515 
1516         if( Ctx.BeaconCtx.Ctrl.BeaconAcquired == 0 )
1517         {
1518             // Set the default state for beacon less operation
1519             Ctx.BeaconState = BEACON_STATE_REACQUISITION;
1520         }
1521 
1522         LoRaMacClassBBeaconTimerEvent( NULL );
1523     }
1524 #endif // LORAMAC_CLASSB_ENABLED
1525 }
1526 
LoRaMacClassBSwitchClass(DeviceClass_t nextClass)1527 LoRaMacStatus_t LoRaMacClassBSwitchClass( DeviceClass_t nextClass )
1528 {
1529 #ifdef LORAMAC_CLASSB_ENABLED
1530     if( nextClass == CLASS_B )
1531     {// Switch to from class a to class b
1532         if( ( Ctx.BeaconCtx.Ctrl.BeaconMode == 1 ) && ( ClassBNvm->PingSlotCtx.Ctrl.Assigned == 1 ) )
1533         {
1534             return LORAMAC_STATUS_OK;
1535         }
1536     }
1537     if( nextClass == CLASS_A )
1538     {// Switch from class b to class a
1539         LoRaMacClassBHaltBeaconing( );
1540 
1541         // Initialize default state for class b
1542         InitClassBDefaults( );
1543 
1544         return LORAMAC_STATUS_OK;
1545     }
1546     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1547 #else
1548     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1549 #endif // LORAMAC_CLASSB_ENABLED
1550 }
1551 
LoRaMacClassBMibGetRequestConfirm(MibRequestConfirm_t * mibGet)1552 LoRaMacStatus_t LoRaMacClassBMibGetRequestConfirm( MibRequestConfirm_t *mibGet )
1553 {
1554 #ifdef LORAMAC_CLASSB_ENABLED
1555     LoRaMacStatus_t status;
1556 
1557     switch( mibGet->Type )
1558     {
1559         case MIB_PING_SLOT_DATARATE:
1560         {
1561             mibGet->Param.PingSlotDatarate = ClassBNvm->PingSlotCtx.Datarate;
1562             break;
1563         }
1564         default:
1565         {
1566             status = LORAMAC_STATUS_SERVICE_UNKNOWN;
1567             break;
1568         }
1569     }
1570     return status;
1571 #else
1572     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1573 #endif // LORAMAC_CLASSB_ENABLED
1574 }
1575 
LoRaMacMibClassBSetRequestConfirm(MibRequestConfirm_t * mibSet)1576 LoRaMacStatus_t LoRaMacMibClassBSetRequestConfirm( MibRequestConfirm_t *mibSet )
1577 {
1578 #ifdef LORAMAC_CLASSB_ENABLED
1579     LoRaMacStatus_t status;
1580 
1581     switch( mibSet->Type )
1582     {
1583         case MIB_PING_SLOT_DATARATE:
1584         {
1585             ClassBNvm->PingSlotCtx.Datarate = mibSet->Param.PingSlotDatarate;
1586             break;
1587         }
1588         default:
1589         {
1590             status = LORAMAC_STATUS_SERVICE_UNKNOWN;
1591             break;
1592         }
1593     }
1594     return status;
1595 #else
1596     return LORAMAC_STATUS_SERVICE_UNKNOWN;
1597 #endif // LORAMAC_CLASSB_ENABLED
1598 }
1599 
LoRaMacClassBPingSlotInfoAns(void)1600 void LoRaMacClassBPingSlotInfoAns( void )
1601 {
1602 #ifdef LORAMAC_CLASSB_ENABLED
1603     if( LoRaMacConfirmQueueIsCmdActive( MLME_PING_SLOT_INFO ) == true )
1604     {
1605         LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_PING_SLOT_INFO );
1606         ClassBNvm->PingSlotCtx.Ctrl.Assigned = 1;
1607     }
1608 #endif // LORAMAC_CLASSB_ENABLED
1609 }
1610 
LoRaMacClassBPingSlotChannelReq(uint8_t datarate,uint32_t frequency)1611 uint8_t LoRaMacClassBPingSlotChannelReq( uint8_t datarate, uint32_t frequency )
1612 {
1613 #ifdef LORAMAC_CLASSB_ENABLED
1614     uint8_t status = 0x03;
1615     VerifyParams_t verify;
1616     bool isCustomFreq = false;
1617 
1618     if( frequency != 0 )
1619     {
1620         isCustomFreq = true;
1621         verify.Frequency = frequency;
1622         if( RegionVerify( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &verify, PHY_FREQUENCY ) == false )
1623         {
1624             status &= 0xFE; // Channel frequency KO
1625         }
1626     }
1627 
1628     verify.DatarateParams.Datarate = datarate;
1629     verify.DatarateParams.DownlinkDwellTime = Ctx.LoRaMacClassBParams.LoRaMacParams->DownlinkDwellTime;
1630 
1631     if( RegionVerify( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &verify, PHY_RX_DR ) == false )
1632     {
1633         status &= 0xFD; // Datarate range KO
1634     }
1635 
1636     if( status == 0x03 )
1637     {
1638         if( isCustomFreq == true )
1639         {
1640             ClassBNvm->PingSlotCtx.Ctrl.CustomFreq = 1;
1641             ClassBNvm->PingSlotCtx.Frequency = frequency;
1642         }
1643         else
1644         {
1645             ClassBNvm->PingSlotCtx.Ctrl.CustomFreq = 0;
1646             ClassBNvm->PingSlotCtx.Frequency = 0;
1647         }
1648         ClassBNvm->PingSlotCtx.Datarate = datarate;
1649     }
1650 
1651     return status;
1652 #else
1653     return 0;
1654 #endif // LORAMAC_CLASSB_ENABLED
1655 }
1656 
LoRaMacClassBBeaconTimingAns(uint16_t beaconTimingDelay,uint8_t beaconTimingChannel,TimerTime_t lastRxDone)1657 void LoRaMacClassBBeaconTimingAns( uint16_t beaconTimingDelay, uint8_t beaconTimingChannel, TimerTime_t lastRxDone )
1658 {
1659 #ifdef LORAMAC_CLASSB_ENABLED
1660     Ctx.BeaconCtx.BeaconTimingDelay = ( CLASSB_BEACON_DELAY_BEACON_TIMING_ANS * beaconTimingDelay );
1661     Ctx.BeaconCtx.BeaconTimingChannel = beaconTimingChannel;
1662 
1663     if( LoRaMacConfirmQueueIsCmdActive( MLME_BEACON_TIMING ) == true )
1664     {
1665         if( Ctx.BeaconCtx.BeaconTimingDelay > CLASSB_BEACON_INTERVAL )
1666         {
1667             // We missed the beacon already
1668             Ctx.BeaconCtx.BeaconTimingDelay = 0;
1669             Ctx.BeaconCtx.BeaconTimingChannel = 0;
1670             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND, MLME_BEACON_TIMING );
1671         }
1672         else
1673         {
1674             Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 1;
1675             Ctx.BeaconCtx.Ctrl.BeaconChannelSet = 1;
1676             Ctx.BeaconCtx.NextBeaconRx = SysTimeFromMs( lastRxDone + Ctx.BeaconCtx.BeaconTimingDelay );
1677             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_BEACON_TIMING );
1678         }
1679 
1680         Ctx.LoRaMacClassBParams.MlmeConfirm->BeaconTimingDelay = Ctx.BeaconCtx.BeaconTimingDelay;
1681         Ctx.LoRaMacClassBParams.MlmeConfirm->BeaconTimingChannel = Ctx.BeaconCtx.BeaconTimingChannel;
1682     }
1683 #endif // LORAMAC_CLASSB_ENABLED
1684 }
1685 
LoRaMacClassBDeviceTimeAns(void)1686 void LoRaMacClassBDeviceTimeAns( void )
1687 {
1688 #ifdef LORAMAC_CLASSB_ENABLED
1689 
1690     SysTime_t nextBeacon = SysTimeGet( );
1691     TimerTime_t currentTimeMs = SysTimeToMs( nextBeacon );
1692 
1693     nextBeacon.Seconds = nextBeacon.Seconds + ( 128 - ( nextBeacon.Seconds % 128 ) );
1694     nextBeacon.SubSeconds = 0;
1695 
1696     Ctx.BeaconCtx.NextBeaconRx = nextBeacon;
1697     Ctx.BeaconCtx.LastBeaconRx = SysTimeSub( Ctx.BeaconCtx.NextBeaconRx, ( SysTime_t ){ .Seconds = CLASSB_BEACON_INTERVAL / 1000, .SubSeconds = 0 } );
1698 
1699     if( LoRaMacConfirmQueueIsCmdActive( MLME_DEVICE_TIME ) == true )
1700     {
1701         if( currentTimeMs > SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) )
1702         {
1703             // We missed the beacon already
1704             Ctx.BeaconCtx.LastBeaconRx.Seconds = 0;
1705             Ctx.BeaconCtx.LastBeaconRx.SubSeconds = 0;
1706             Ctx.BeaconCtx.NextBeaconRx.Seconds = 0;
1707             Ctx.BeaconCtx.NextBeaconRx.SubSeconds = 0;
1708             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_BEACON_NOT_FOUND, MLME_DEVICE_TIME );
1709         }
1710         else
1711         {
1712             Ctx.BeaconCtx.Ctrl.BeaconDelaySet = 1;
1713             Ctx.BeaconCtx.BeaconTimingDelay = SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx ) - currentTimeMs;
1714             Ctx.BeaconCtx.BeaconTime.Seconds = nextBeacon.Seconds - UNIX_GPS_EPOCH_OFFSET - 128;
1715             Ctx.BeaconCtx.BeaconTime.SubSeconds = 0;
1716             LoRaMacConfirmQueueSetStatus( LORAMAC_EVENT_INFO_STATUS_OK, MLME_DEVICE_TIME );
1717         }
1718     }
1719 #endif // LORAMAC_CLASSB_ENABLED
1720 }
1721 
LoRaMacClassBBeaconFreqReq(uint32_t frequency)1722 bool LoRaMacClassBBeaconFreqReq( uint32_t frequency )
1723 {
1724 #ifdef LORAMAC_CLASSB_ENABLED
1725     VerifyParams_t verify;
1726 
1727     if( frequency != 0 )
1728     {
1729         verify.Frequency = frequency;
1730 
1731         if( RegionVerify( *Ctx.LoRaMacClassBParams.LoRaMacRegion, &verify, PHY_FREQUENCY ) == true )
1732         {
1733             ClassBNvm->BeaconCtx.Ctrl.CustomFreq = 1;
1734             ClassBNvm->BeaconCtx.Frequency = frequency;
1735             return true;
1736         }
1737     }
1738     else
1739     {
1740         ClassBNvm->BeaconCtx.Ctrl.CustomFreq = 0;
1741         return true;
1742     }
1743     return false;
1744 #else
1745     return false;
1746 #endif // LORAMAC_CLASSB_ENABLED
1747 }
1748 
LoRaMacClassBIsUplinkCollision(TimerTime_t txTimeOnAir)1749 TimerTime_t LoRaMacClassBIsUplinkCollision( TimerTime_t txTimeOnAir )
1750 {
1751 #ifdef LORAMAC_CLASSB_ENABLED
1752     TimerTime_t currentTime = TimerGetCurrentTime( );
1753     TimerTime_t beaconReserved = 0;
1754     TimerTime_t nextBeacon = SysTimeToMs( Ctx.BeaconCtx.NextBeaconRx );
1755 
1756     beaconReserved = nextBeacon -
1757                      CLASSB_BEACON_GUARD -
1758                      Ctx.LoRaMacClassBParams.LoRaMacParams->ReceiveDelay1 -
1759                      Ctx.LoRaMacClassBParams.LoRaMacParams->ReceiveDelay2 -
1760                      txTimeOnAir;
1761 
1762     // Check if the next beacon will be received during the next uplink.
1763     if( ( currentTime >= beaconReserved ) && ( currentTime < ( nextBeacon + CLASSB_BEACON_RESERVED ) ) )
1764     {// Next beacon will be sent during the next uplink.
1765         return CLASSB_BEACON_RESERVED;
1766     }
1767     return 0;
1768 #else
1769     return 0;
1770 #endif // LORAMAC_CLASSB_ENABLED
1771 }
1772 
LoRaMacClassBStopRxSlots(void)1773 void LoRaMacClassBStopRxSlots( void )
1774 {
1775 #ifdef LORAMAC_CLASSB_ENABLED
1776     TimerStop( &Ctx.PingSlotTimer );
1777     TimerStop( &Ctx.MulticastSlotTimer );
1778 
1779     CRITICAL_SECTION_BEGIN( );
1780     LoRaMacClassBEvents.Events.PingSlot = 0;
1781     LoRaMacClassBEvents.Events.MulticastSlot = 0;
1782     CRITICAL_SECTION_END( );
1783 #endif // LORAMAC_CLASSB_ENABLED
1784 }
1785 
LoRaMacClassBStartRxSlots(void)1786 void LoRaMacClassBStartRxSlots( void )
1787 {
1788 #ifdef LORAMAC_CLASSB_ENABLED
1789     if( ClassBNvm->PingSlotCtx.Ctrl.Assigned == 1 )
1790     {
1791         Ctx.PingSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1792         TimerSetValue( &Ctx.PingSlotTimer, 1 );
1793         TimerStart( &Ctx.PingSlotTimer );
1794 
1795         Ctx.MulticastSlotState = PINGSLOT_STATE_CALC_PING_OFFSET;
1796         TimerSetValue( &Ctx.MulticastSlotTimer, 1 );
1797         TimerStart( &Ctx.MulticastSlotTimer );
1798     }
1799 #endif // LORAMAC_CLASSB_ENABLED
1800 }
1801 
LoRaMacClassBSetMulticastPeriodicity(MulticastCtx_t * multicastChannel)1802 void LoRaMacClassBSetMulticastPeriodicity( MulticastCtx_t* multicastChannel )
1803 {
1804 #ifdef LORAMAC_CLASSB_ENABLED
1805     if( multicastChannel != NULL )
1806     {
1807         multicastChannel->PingNb = CalcPingNb( multicastChannel->ChannelParams.RxParams.Params.ClassB.Periodicity );
1808         multicastChannel->PingPeriod = CalcPingPeriod( multicastChannel->PingNb );
1809     }
1810 #endif // LORAMAC_CLASSB_ENABLED
1811 }
1812 
LoRaMacClassBSetFPendingBit(uint32_t address,uint8_t fPendingSet)1813 void LoRaMacClassBSetFPendingBit( uint32_t address, uint8_t fPendingSet )
1814 {
1815 #ifdef LORAMAC_CLASSB_ENABLED
1816     MulticastCtx_t *cur = Ctx.LoRaMacClassBParams.MulticastChannels;
1817 
1818     if( address == *Ctx.LoRaMacClassBParams.LoRaMacDevAddr )
1819     {
1820         // Unicast
1821         ClassBNvm->PingSlotCtx.FPendingSet = fPendingSet;
1822     }
1823     else
1824     {
1825         for( uint8_t i = 0; i < LORAMAC_MAX_MC_CTX; i++ )
1826         {
1827             if( cur != NULL )
1828             {
1829                 // Set the fPending bit, if its a multicast
1830                 if( address == cur->ChannelParams.Address )
1831                 {
1832                     cur->FPendingSet = fPendingSet;
1833                 }
1834             }
1835             cur++;
1836         }
1837     }
1838 #endif
1839 }
1840 
LoRaMacClassBProcess(void)1841 void LoRaMacClassBProcess( void )
1842 {
1843 #ifdef LORAMAC_CLASSB_ENABLED
1844     LoRaMacClassBEvents_t events;
1845 
1846     CRITICAL_SECTION_BEGIN( );
1847     events = LoRaMacClassBEvents;
1848     LoRaMacClassBEvents.Value = 0;
1849     CRITICAL_SECTION_END( );
1850 
1851     if( events.Value != 0 )
1852     {
1853         if( events.Events.Beacon == 1 )
1854         {
1855             LoRaMacClassBProcessBeacon( );
1856         }
1857         if( events.Events.PingSlot == 1 )
1858         {
1859             LoRaMacClassBProcessPingSlot( );
1860         }
1861         if( events.Events.MulticastSlot == 1 )
1862         {
1863             LoRaMacClassBProcessMulticastSlot( );
1864         }
1865     }
1866 #endif // LORAMAC_CLASSB_ENABLED
1867 }
1868