473 lines
16 KiB
C
473 lines
16 KiB
C
/*!
|
|
* \file LmhpClockSync.c
|
|
*
|
|
* \brief Implements the LoRa-Alliance clock synchronization package
|
|
* Specification V1.0.0: https://resources.lora-alliance.org/technical-specifications/lorawan-application-layer-clock-synchronization-specification-v1-0-0
|
|
* Specification V2.0.0: https://resources.lora-alliance.org/technical-specifications/ts003-2-0-0-application-layer-clock-synchronization
|
|
*
|
|
* \copyright Revised BSD License, see section \ref LICENSE.
|
|
*
|
|
* \code
|
|
* ______ _
|
|
* / _____) _ | |
|
|
* ( (____ _____ ____ _| |_ _____ ____| |__
|
|
* \____ \| ___ | (_ _) ___ |/ ___) _ \
|
|
* _____) ) ____| | | || |_| ____( (___| | | |
|
|
* (______/|_____)_|_|_| \__)_____)\____)_| |_|
|
|
* (C)2013-2018 Semtech
|
|
*
|
|
* \endcode
|
|
*
|
|
* \author Miguel Luis ( Semtech )
|
|
*/
|
|
/**
|
|
******************************************************************************
|
|
*
|
|
* Portions COPYRIGHT 2020 STMicroelectronics
|
|
*
|
|
* @file LmhpClockSync.c
|
|
* @author MCD Application Team
|
|
* @brief Clock Synchronisation Package definition
|
|
******************************************************************************
|
|
*/
|
|
#include "LoRaMac.h"
|
|
#include "LmHandler.h"
|
|
#include "LmhpClockSync.h"
|
|
#include "utilities.h"
|
|
|
|
/*!
|
|
* LoRaWAN Application Layer Clock Synchronization Specification
|
|
*/
|
|
#define CLOCK_SYNC_PORT 202
|
|
|
|
#define CLOCK_SYNC_ID 1
|
|
|
|
#if (LORAWAN_PACKAGES_VERSION == 1)
|
|
#define CLOCK_SYNC_VERSION 1
|
|
#elif (LORAWAN_PACKAGES_VERSION == 2)
|
|
#define CLOCK_SYNC_VERSION 2
|
|
#endif /* LORAWAN_PACKAGES_VERSION */
|
|
|
|
/*!
|
|
* Package current context
|
|
*/
|
|
typedef struct LmhpClockSyncState_s
|
|
{
|
|
bool Initialized;
|
|
bool IsTxPending;
|
|
uint8_t DataBufferMaxSize;
|
|
uint8_t *DataBuffer;
|
|
union
|
|
{
|
|
uint8_t Value;
|
|
struct
|
|
{
|
|
uint8_t TokenReq: 4;
|
|
uint8_t AnsRequired: 1;
|
|
uint8_t RFU: 3;
|
|
} Fields;
|
|
} TimeReqParam;
|
|
bool AppTimeReqPending;
|
|
#if ( CLOCK_SYNC_VERSION == 2 )
|
|
bool SysTimeNotSync;
|
|
#endif /* CLOCK_SYNC_VERSION */
|
|
bool AdrEnabledPrev;
|
|
uint8_t NbTransPrev;
|
|
uint8_t DataratePrev;
|
|
uint8_t NbTransmissions;
|
|
} LmhpClockSyncState_t;
|
|
|
|
typedef enum LmhpClockSyncMoteCmd_e
|
|
{
|
|
CLOCK_SYNC_PKG_VERSION_ANS = 0x00,
|
|
CLOCK_SYNC_APP_TIME_REQ = 0x01,
|
|
CLOCK_SYNC_APP_TIME_PERIOD_ANS = 0x02,
|
|
CLOCK_SYNC_FORCE_RESYNC_ANS = 0x03,
|
|
} LmhpClockSyncMoteCmd_t;
|
|
|
|
typedef enum LmhpClockSyncSrvCmd_e
|
|
{
|
|
CLOCK_SYNC_PKG_VERSION_REQ = 0x00,
|
|
CLOCK_SYNC_APP_TIME_ANS = 0x01,
|
|
CLOCK_SYNC_APP_TIME_PERIOD_REQ = 0x02,
|
|
CLOCK_SYNC_FORCE_RESYNC_REQ = 0x03,
|
|
} LmhpClockSyncSrvCmd_t;
|
|
|
|
/*!
|
|
* Initializes the package with provided parameters
|
|
*
|
|
* \param [in] params Pointer to the package parameters
|
|
* \param [in] dataBuffer Pointer to main application buffer
|
|
* \param [in] dataBufferMaxSize Main application buffer maximum size
|
|
*/
|
|
static void LmhpClockSyncInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize );
|
|
|
|
/*!
|
|
* Returns the current package initialization status.
|
|
*
|
|
* \retval status Package initialization status
|
|
* [true: Initialized, false: Not initialized]
|
|
*/
|
|
static bool LmhpClockSyncIsInitialized( void );
|
|
|
|
/*!
|
|
* Returns if a package transmission is pending or not.
|
|
*
|
|
* \retval status Package transmission status
|
|
* [true: pending, false: Not pending]
|
|
*/
|
|
static bool LmhpClockSyncIsTxPending( void );
|
|
|
|
/*!
|
|
* Processes the internal package events.
|
|
*/
|
|
static void LmhpClockSyncProcess( void );
|
|
|
|
/*!
|
|
* Processes the MCSP Confirm
|
|
*
|
|
* \param [in] mcpsConfirm MCPS confirmation primitive data
|
|
*/
|
|
static void LmhpClockSyncOnMcpsConfirm( McpsConfirm_t *mcpsConfirm );
|
|
|
|
/*!
|
|
* Processes the MCPS Indication
|
|
*
|
|
* \param [in] mcpsIndication MCPS indication primitive data
|
|
*/
|
|
static void LmhpClockSyncOnMcpsIndication( McpsIndication_t *mcpsIndication );
|
|
|
|
static void OnPeriodicTimeStartTimer( void *context );
|
|
|
|
static LmhpClockSyncState_t LmhpClockSyncState =
|
|
{
|
|
.Initialized = false,
|
|
.IsTxPending = false,
|
|
.TimeReqParam.Value = 0,
|
|
.AppTimeReqPending = false,
|
|
#if ( CLOCK_SYNC_VERSION == 2 )
|
|
.SysTimeNotSync = false,
|
|
#endif /* CLOCK_SYNC_VERSION */
|
|
.AdrEnabledPrev = false,
|
|
.NbTransPrev = 0,
|
|
.NbTransmissions = 0,
|
|
};
|
|
|
|
static LmhPackage_t LmhpClockSyncPackage =
|
|
{
|
|
.Port = CLOCK_SYNC_PORT,
|
|
.Init = LmhpClockSyncInit,
|
|
.IsInitialized = LmhpClockSyncIsInitialized,
|
|
.IsTxPending = LmhpClockSyncIsTxPending,
|
|
.Process = LmhpClockSyncProcess,
|
|
.OnMcpsConfirmProcess = LmhpClockSyncOnMcpsConfirm,
|
|
.OnMcpsIndicationProcess = LmhpClockSyncOnMcpsIndication,
|
|
.OnMlmeConfirmProcess = NULL, /* Not used in this package */
|
|
.OnMlmeIndicationProcess = NULL, /* Not used in this package */
|
|
.OnJoinRequest = NULL, /* To be initialized by LmHandler */
|
|
.OnDeviceTimeRequest = NULL, /* To be initialized by LmHandler */
|
|
.OnSysTimeUpdate = NULL, /* To be initialized by LmHandler */
|
|
.OnPackageProcessEvent = NULL, /* To be initialized by LmHandler */
|
|
};
|
|
|
|
/*!
|
|
* Periodic Time start timer
|
|
*/
|
|
static TimerEvent_t PeriodicTimeStartTimer;
|
|
|
|
LmhPackage_t *LmhpClockSyncPackageFactory( void )
|
|
{
|
|
return &LmhpClockSyncPackage;
|
|
}
|
|
|
|
static void LmhpClockSyncInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize )
|
|
{
|
|
if( dataBuffer != NULL )
|
|
{
|
|
LmhpClockSyncState.DataBuffer = dataBuffer;
|
|
LmhpClockSyncState.DataBufferMaxSize = dataBufferMaxSize;
|
|
LmhpClockSyncState.Initialized = true;
|
|
TimerInit( &PeriodicTimeStartTimer, OnPeriodicTimeStartTimer );
|
|
}
|
|
else
|
|
{
|
|
LmhpClockSyncState.Initialized = false;
|
|
}
|
|
LmhpClockSyncState.IsTxPending = false;
|
|
}
|
|
|
|
static bool LmhpClockSyncIsInitialized( void )
|
|
{
|
|
return LmhpClockSyncState.Initialized;
|
|
}
|
|
|
|
static bool LmhpClockSyncIsTxPending( void )
|
|
{
|
|
return LmhpClockSyncState.IsTxPending;
|
|
}
|
|
|
|
static void LmhpClockSyncProcess( void )
|
|
{
|
|
if( LmhpClockSyncState.NbTransmissions > 0 )
|
|
{
|
|
if( LmhpClockSyncAppTimeReq( ) == LORAMAC_HANDLER_SUCCESS )
|
|
{
|
|
LmhpClockSyncState.NbTransmissions--;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void LmhpClockSyncOnMcpsConfirm( McpsConfirm_t *mcpsConfirm )
|
|
{
|
|
MibRequestConfirm_t mibReq;
|
|
|
|
if( LmhpClockSyncState.AppTimeReqPending == true )
|
|
{
|
|
/* Revert ADR setting */
|
|
mibReq.Type = MIB_ADR;
|
|
mibReq.Param.AdrEnable = LmhpClockSyncState.AdrEnabledPrev;
|
|
LoRaMacMibSetRequestConfirm( &mibReq );
|
|
|
|
/* Revert NbTrans setting */
|
|
mibReq.Type = MIB_CHANNELS_NB_TRANS;
|
|
mibReq.Param.ChannelsNbTrans = LmhpClockSyncState.NbTransPrev;
|
|
LoRaMacMibSetRequestConfirm( &mibReq );
|
|
|
|
/* Revert data rate setting */
|
|
mibReq.Type = MIB_CHANNELS_DATARATE;
|
|
mibReq.Param.ChannelsDatarate = LmhpClockSyncState.DataratePrev;
|
|
LoRaMacMibSetRequestConfirm( &mibReq );
|
|
|
|
LmhpClockSyncState.AppTimeReqPending = false;
|
|
}
|
|
}
|
|
|
|
static void LmhpClockSyncOnMcpsIndication( McpsIndication_t *mcpsIndication )
|
|
{
|
|
uint8_t cmdIndex = 0;
|
|
uint8_t dataBufferIndex = 0;
|
|
|
|
if( mcpsIndication->Port != CLOCK_SYNC_PORT )
|
|
{
|
|
return;
|
|
}
|
|
|
|
while( cmdIndex < mcpsIndication->BufferSize )
|
|
{
|
|
switch( mcpsIndication->Buffer[cmdIndex++] )
|
|
{
|
|
case CLOCK_SYNC_PKG_VERSION_REQ:
|
|
{
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_PKG_VERSION_ANS;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_ID;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_VERSION;
|
|
break;
|
|
}
|
|
case CLOCK_SYNC_APP_TIME_ANS:
|
|
{
|
|
LmhpClockSyncState.NbTransmissions = 0;
|
|
|
|
/* Check if a more precise time correction has been received. */
|
|
/* If yes then don't process and ignore this answer. */
|
|
if( mcpsIndication->DeviceTimeAnsReceived == true )
|
|
{
|
|
cmdIndex += 5;
|
|
break;
|
|
}
|
|
int32_t timeCorrection = 0;
|
|
timeCorrection = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF;
|
|
timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00;
|
|
timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000;
|
|
timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000;
|
|
if( ( mcpsIndication->Buffer[cmdIndex++] & 0x0F ) == LmhpClockSyncState.TimeReqParam.Fields.TokenReq )
|
|
{
|
|
SysTime_t curTime = { .Seconds = 0, .SubSeconds = 0 };
|
|
curTime = SysTimeGet( );
|
|
#if ( CLOCK_SYNC_VERSION == 1 )
|
|
curTime.Seconds += timeCorrection;
|
|
SysTimeSet( curTime );
|
|
LmhpClockSyncState.TimeReqParam.Fields.TokenReq = ( LmhpClockSyncState.TimeReqParam.Fields.TokenReq + 1 ) & 0x0F;
|
|
if( LmhpClockSyncPackage.OnSysTimeUpdate != NULL )
|
|
{
|
|
if( ( timeCorrection >= -1 ) && ( timeCorrection <= 1 ) )
|
|
{
|
|
LmhpClockSyncPackage.OnSysTimeUpdate( );
|
|
}
|
|
}
|
|
#elif ( CLOCK_SYNC_VERSION == 2 )
|
|
if( LmhpClockSyncState.SysTimeNotSync == true )
|
|
{
|
|
curTime.Seconds += UNIX_GPS_EPOCH_OFFSET;
|
|
}
|
|
|
|
curTime.Seconds += timeCorrection;
|
|
SysTimeSet( curTime );
|
|
LmhpClockSyncState.TimeReqParam.Fields.TokenReq = ( LmhpClockSyncState.TimeReqParam.Fields.TokenReq + 1 ) & 0x0F;
|
|
|
|
if( timeCorrection == ( int32_t )0x7FFFFFFF )
|
|
{
|
|
LmhpClockSyncState.NbTransmissions = 1;
|
|
}
|
|
else if( LmhpClockSyncPackage.OnSysTimeUpdate != NULL )
|
|
{
|
|
LmhpClockSyncPackage.OnSysTimeUpdate( );
|
|
}
|
|
#endif /* CLOCK_SYNC_VERSION */
|
|
}
|
|
break;
|
|
}
|
|
case CLOCK_SYNC_APP_TIME_PERIOD_REQ:
|
|
{
|
|
/* Increment index */
|
|
cmdIndex++;
|
|
|
|
uint32_t periodTime = mcpsIndication->Buffer[cmdIndex++] & 0x0F;
|
|
periodTime = ( 128 << periodTime ) + randr( 0, 30 );
|
|
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_APP_TIME_PERIOD_ANS;
|
|
/* Answer status supported. */
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = 0x00;
|
|
|
|
SysTime_t curTime = SysTimeGet( );
|
|
/* Subtract Unix to Gps epoch offset. The system time is based on Unix time. */
|
|
if( curTime.Seconds > UNIX_GPS_EPOCH_OFFSET )
|
|
{
|
|
curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
|
|
}
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 0 ) & 0xFF;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 8 ) & 0xFF;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 16 ) & 0xFF;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 24 ) & 0xFF;
|
|
|
|
/* Start Periodic timer */
|
|
TimerSetValue( &PeriodicTimeStartTimer, periodTime * 1000 );
|
|
TimerStart( &PeriodicTimeStartTimer );
|
|
|
|
break;
|
|
}
|
|
case CLOCK_SYNC_FORCE_RESYNC_REQ:
|
|
{
|
|
LmhpClockSyncState.NbTransmissions = mcpsIndication->Buffer[cmdIndex++] & 0X07;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if( dataBufferIndex != 0 )
|
|
{
|
|
/* Answer commands */
|
|
LmHandlerAppData_t appData =
|
|
{
|
|
.Buffer = LmhpClockSyncState.DataBuffer,
|
|
.BufferSize = dataBufferIndex,
|
|
.Port = CLOCK_SYNC_PORT
|
|
};
|
|
|
|
bool current_dutycycle;
|
|
LmHandlerGetDutyCycleEnable( ¤t_dutycycle );
|
|
|
|
/* force Duty Cycle OFF to this Send */
|
|
LmHandlerSetDutyCycleEnable( false );
|
|
LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG, true );
|
|
|
|
/* restore initial Duty Cycle */
|
|
LmHandlerSetDutyCycleEnable( current_dutycycle );
|
|
}
|
|
}
|
|
|
|
LmHandlerErrorStatus_t LmhpClockSyncAppTimeReq( void )
|
|
{
|
|
if( LmHandlerIsBusy( ) == true )
|
|
{
|
|
return LORAMAC_HANDLER_ERROR;
|
|
}
|
|
|
|
if( LmhpClockSyncState.AppTimeReqPending == false )
|
|
{
|
|
MibRequestConfirm_t mibReq;
|
|
|
|
/* Disable ADR */
|
|
mibReq.Type = MIB_ADR;
|
|
LoRaMacMibGetRequestConfirm( &mibReq );
|
|
LmhpClockSyncState.AdrEnabledPrev = mibReq.Param.AdrEnable;
|
|
mibReq.Param.AdrEnable = false;
|
|
LoRaMacMibSetRequestConfirm( &mibReq );
|
|
|
|
/* Set NbTrans = 1 */
|
|
mibReq.Type = MIB_CHANNELS_NB_TRANS;
|
|
LoRaMacMibGetRequestConfirm( &mibReq );
|
|
LmhpClockSyncState.NbTransPrev = mibReq.Param.ChannelsNbTrans;
|
|
mibReq.Param.ChannelsNbTrans = 1;
|
|
LoRaMacMibSetRequestConfirm( &mibReq );
|
|
|
|
/* Store data rate */
|
|
mibReq.Type = MIB_CHANNELS_DATARATE;
|
|
LoRaMacMibGetRequestConfirm( &mibReq );
|
|
LmhpClockSyncState.DataratePrev = mibReq.Param.ChannelsDatarate;
|
|
|
|
#if ( CLOCK_SYNC_VERSION == 1 )
|
|
/* Add DeviceTimeReq MAC command. */
|
|
/* In case the network server supports this more precise command */
|
|
/* this package will use DeviceTimeAns answer as clock synchronization */
|
|
/* mechanism. */
|
|
if( LmhpClockSyncPackage.OnDeviceTimeRequest != NULL )
|
|
{
|
|
LmhpClockSyncPackage.OnDeviceTimeRequest( );
|
|
}
|
|
#endif /* CLOCK_SYNC_VERSION */
|
|
}
|
|
|
|
SysTime_t curTime = SysTimeGet( );
|
|
uint8_t dataBufferIndex = 0;
|
|
|
|
/* Subtract Unix to Gps epoch offset. The system time is based on Unix time. */
|
|
curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
|
|
if( curTime.Seconds > UNIX_GPS_EPOCH_OFFSET )
|
|
{
|
|
curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
|
|
}
|
|
#if ( CLOCK_SYNC_VERSION == 2 )
|
|
else
|
|
{
|
|
LmhpClockSyncState.SysTimeNotSync = true;
|
|
}
|
|
#endif /* CLOCK_SYNC_VERSION */
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_APP_TIME_REQ;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 0 ) & 0xFF;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 8 ) & 0xFF;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 16 ) & 0xFF;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 24 ) & 0xFF;
|
|
LmhpClockSyncState.TimeReqParam.Fields.AnsRequired = 0;
|
|
LmhpClockSyncState.DataBuffer[dataBufferIndex++] = LmhpClockSyncState.TimeReqParam.Value;
|
|
|
|
LmHandlerAppData_t appData =
|
|
{
|
|
.Buffer = LmhpClockSyncState.DataBuffer,
|
|
.BufferSize = dataBufferIndex,
|
|
.Port = CLOCK_SYNC_PORT
|
|
};
|
|
LmhpClockSyncState.AppTimeReqPending = true;
|
|
|
|
bool current_dutycycle;
|
|
LmHandlerGetDutyCycleEnable( ¤t_dutycycle );
|
|
|
|
/* force Duty Cycle OFF to this Send */
|
|
LmHandlerSetDutyCycleEnable( false );
|
|
LmHandlerErrorStatus_t status = LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG, true );
|
|
|
|
/* restore initial Duty Cycle */
|
|
LmHandlerSetDutyCycleEnable( current_dutycycle );
|
|
|
|
return status;
|
|
}
|
|
|
|
static void OnPeriodicTimeStartTimer( void *context )
|
|
{
|
|
LmhpClockSyncState.NbTransmissions = 1;
|
|
TimerStart( &PeriodicTimeStartTimer );
|
|
if( LmhpClockSyncPackage.OnPackageProcessEvent != NULL )
|
|
{
|
|
LmhpClockSyncPackage.OnPackageProcessEvent();
|
|
}
|
|
}
|