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