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