1 /*!
2 * \file LmhpClockSync.c
3 *
4 * \brief Implements the LoRa-Alliance clock synchronization package
5 * Specification: https://lora-alliance.org/sites/default/files/2018-09/application_layer_clock_synchronization_v1.0.0.pdf
6 *
7 * \copyright Revised BSD License, see section \ref LICENSE.
8 *
9 * \code
10 * ______ _
11 * / _____) _ | |
12 * ( (____ _____ ____ _| |_ _____ ____| |__
13 * \____ \| ___ | (_ _) ___ |/ ___) _ \
14 * _____) ) ____| | | || |_| ____( (___| | | |
15 * (______/|_____)_|_|_| \__)_____)\____)_| |_|
16 * (C)2013-2018 Semtech
17 *
18 * \endcode
19 *
20 * \author Miguel Luis ( Semtech )
21 */
22 #include "LmHandler.h"
23 #include "LmhpClockSync.h"
24
25 /*!
26 * LoRaWAN Application Layer Clock Synchronization Specification
27 */
28 #define CLOCK_SYNC_PORT 202
29
30 #define CLOCK_SYNC_ID 1
31 #define CLOCK_SYNC_VERSION 1
32
33 /*!
34 * Package current context
35 */
36 typedef struct LmhpClockSyncState_s
37 {
38 bool Initialized;
39 bool IsTxPending;
40 uint8_t DataBufferMaxSize;
41 uint8_t *DataBuffer;
42 union
43 {
44 uint8_t Value;
45 struct
46 {
47 uint8_t TokenReq: 4;
48 uint8_t AnsRequired: 1;
49 uint8_t RFU: 3;
50 }Fields;
51 }TimeReqParam;
52 bool AppTimeReqPending;
53 bool AdrEnabledPrev;
54 uint8_t NbTransPrev;
55 uint8_t DataratePrev;
56 uint8_t NbTransmissions;
57 }LmhpClockSyncState_t;
58
59 typedef enum LmhpClockSyncMoteCmd_e
60 {
61 CLOCK_SYNC_PKG_VERSION_ANS = 0x00,
62 CLOCK_SYNC_APP_TIME_REQ = 0x01,
63 CLOCK_SYNC_APP_TIME_PERIOD_ANS = 0x02,
64 CLOCK_SYNC_FORCE_RESYNC_ANS = 0x03,
65 }LmhpClockSyncMoteCmd_t;
66
67 typedef enum LmhpClockSyncSrvCmd_e
68 {
69 CLOCK_SYNC_PKG_VERSION_REQ = 0x00,
70 CLOCK_SYNC_APP_TIME_ANS = 0x01,
71 CLOCK_SYNC_APP_TIME_PERIOD_REQ = 0x02,
72 CLOCK_SYNC_FORCE_RESYNC_REQ = 0x03,
73 }LmhpClockSyncSrvCmd_t;
74
75 /*!
76 * Initializes the package with provided parameters
77 *
78 * \param [IN] params Pointer to the package parameters
79 * \param [IN] dataBuffer Pointer to main application buffer
80 * \param [IN] dataBufferMaxSize Main application buffer maximum size
81 */
82 static void LmhpClockSyncInit( void *params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize );
83
84 /*!
85 * Returns the current package initialization status.
86 *
87 * \retval status Package initialization status
88 * [true: Initialized, false: Not initialized]
89 */
90 static bool LmhpClockSyncIsInitialized( void );
91
92 /*!
93 * Returns if a package transmission is pending or not.
94 *
95 * \retval status Package transmission status
96 * [true: pending, false: Not pending]
97 */
98 static bool LmhpClockSyncIsTxPending( void );
99
100 /*!
101 * Processes the internal package events.
102 */
103 static void LmhpClockSyncProcess( void );
104
105 /*!
106 * Processes the MCSP Confirm
107 *
108 * \param [IN] mcpsConfirm MCPS confirmation primitive data
109 */
110 static void LmhpClockSyncOnMcpsConfirm( McpsConfirm_t *mcpsConfirm );
111
112 /*!
113 * Processes the MCPS Indication
114 *
115 * \param [IN] mcpsIndication MCPS indication primitive data
116 */
117 static void LmhpClockSyncOnMcpsIndication( McpsIndication_t *mcpsIndication );
118
119 static LmhpClockSyncState_t LmhpClockSyncState =
120 {
121 .Initialized = false,
122 .IsTxPending = false,
123 .TimeReqParam.Value = 0,
124 .AppTimeReqPending = false,
125 .AdrEnabledPrev = false,
126 .NbTransPrev = 0,
127 .NbTransmissions = 0,
128 };
129
130 static LmhPackage_t LmhpClockSyncPackage =
131 {
132 .Port = CLOCK_SYNC_PORT,
133 .Init = LmhpClockSyncInit,
134 .IsInitialized = LmhpClockSyncIsInitialized,
135 .IsTxPending = LmhpClockSyncIsTxPending,
136 .Process = LmhpClockSyncProcess,
137 .OnMcpsConfirmProcess = LmhpClockSyncOnMcpsConfirm,
138 .OnMcpsIndicationProcess = LmhpClockSyncOnMcpsIndication,
139 .OnMlmeConfirmProcess = NULL, // Not used in this package
140 .OnMlmeIndicationProcess = NULL, // Not used in this package
141 .OnMacMcpsRequest = NULL, // To be initialized by LmHandler
142 .OnMacMlmeRequest = NULL, // To be initialized by LmHandler
143 .OnJoinRequest = NULL, // To be initialized by LmHandler
144 .OnDeviceTimeRequest = NULL, // To be initialized by LmHandler
145 .OnSysTimeUpdate = NULL, // To be initialized by LmHandler
146 };
147
LmphClockSyncPackageFactory(void)148 LmhPackage_t *LmphClockSyncPackageFactory( void )
149 {
150 return &LmhpClockSyncPackage;
151 }
152
LmhpClockSyncInit(void * params,uint8_t * dataBuffer,uint8_t dataBufferMaxSize)153 static void LmhpClockSyncInit( void * params, uint8_t *dataBuffer, uint8_t dataBufferMaxSize )
154 {
155 if( dataBuffer != NULL )
156 {
157 LmhpClockSyncState.DataBuffer = dataBuffer;
158 LmhpClockSyncState.DataBufferMaxSize = dataBufferMaxSize;
159 LmhpClockSyncState.Initialized = true;
160 }
161 else
162 {
163 LmhpClockSyncState.Initialized = false;
164 }
165 LmhpClockSyncState.IsTxPending = false;
166 }
167
LmhpClockSyncIsInitialized(void)168 static bool LmhpClockSyncIsInitialized( void )
169 {
170 return LmhpClockSyncState.Initialized;
171 }
172
LmhpClockSyncIsTxPending(void)173 static bool LmhpClockSyncIsTxPending( void )
174 {
175 return LmhpClockSyncState.IsTxPending;
176 }
177
LmhpClockSyncProcess(void)178 static void LmhpClockSyncProcess( void )
179 {
180 if( LmhpClockSyncState.NbTransmissions > 0 )
181 {
182 if( LmhpClockSyncAppTimeReq( ) == LORAMAC_HANDLER_SUCCESS )
183 {
184 LmhpClockSyncState.NbTransmissions--;
185 }
186 }
187 }
188
LmhpClockSyncOnMcpsConfirm(McpsConfirm_t * mcpsConfirm)189 static void LmhpClockSyncOnMcpsConfirm( McpsConfirm_t *mcpsConfirm )
190 {
191 MibRequestConfirm_t mibReq;
192
193 if( LmhpClockSyncState.AppTimeReqPending == true )
194 {
195 // Revert ADR setting
196 mibReq.Type = MIB_ADR;
197 mibReq.Param.AdrEnable = LmhpClockSyncState.AdrEnabledPrev;
198 LoRaMacMibSetRequestConfirm( &mibReq );
199
200 // Revert NbTrans setting
201 mibReq.Type = MIB_CHANNELS_NB_TRANS;
202 mibReq.Param.ChannelsNbTrans = LmhpClockSyncState.NbTransPrev;
203 LoRaMacMibSetRequestConfirm( &mibReq );
204
205 // Revert data rate setting
206 mibReq.Type = MIB_CHANNELS_DATARATE;
207 mibReq.Param.ChannelsDatarate = LmhpClockSyncState.DataratePrev;
208 LoRaMacMibSetRequestConfirm( &mibReq );
209
210 LmhpClockSyncState.AppTimeReqPending = false;
211 }
212 }
213
LmhpClockSyncOnMcpsIndication(McpsIndication_t * mcpsIndication)214 static void LmhpClockSyncOnMcpsIndication( McpsIndication_t *mcpsIndication )
215 {
216 uint8_t cmdIndex = 0;
217 uint8_t dataBufferIndex = 0;
218
219 if( mcpsIndication->Port != CLOCK_SYNC_PORT )
220 {
221 return;
222 }
223
224 while( cmdIndex < mcpsIndication->BufferSize )
225 {
226 switch( mcpsIndication->Buffer[cmdIndex++] )
227 {
228 case CLOCK_SYNC_PKG_VERSION_REQ:
229 {
230 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_PKG_VERSION_ANS;
231 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_ID;
232 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_VERSION;
233 break;
234 }
235 case CLOCK_SYNC_APP_TIME_ANS:
236 {
237 LmhpClockSyncState.NbTransmissions = 0;
238
239 // Check if a more precise time correction has been received.
240 // If yes then don't process and ignore this answer.
241 if( mcpsIndication->DeviceTimeAnsReceived == true )
242 {
243 cmdIndex += 5;
244 break;
245 }
246 int32_t timeCorrection = 0;
247 timeCorrection = ( mcpsIndication->Buffer[cmdIndex++] << 0 ) & 0x000000FF;
248 timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 8 ) & 0x0000FF00;
249 timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 16 ) & 0x00FF0000;
250 timeCorrection += ( mcpsIndication->Buffer[cmdIndex++] << 24 ) & 0xFF000000;
251 if( ( mcpsIndication->Buffer[cmdIndex++] & 0x0F ) == LmhpClockSyncState.TimeReqParam.Fields.TokenReq )
252 {
253 SysTime_t curTime = { .Seconds = 0, .SubSeconds = 0 };
254 curTime = SysTimeGet( );
255 curTime.Seconds += timeCorrection;
256 SysTimeSet( curTime );
257 LmhpClockSyncState.TimeReqParam.Fields.TokenReq = ( LmhpClockSyncState.TimeReqParam.Fields.TokenReq + 1 ) & 0x0F;
258 if( LmhpClockSyncPackage.OnSysTimeUpdate != NULL )
259 {
260 #if( LMH_SYS_TIME_UPDATE_NEW_API == 1 )
261 LmhpClockSyncPackage.OnSysTimeUpdate(
262 ( timeCorrection >= -1 ) && ( timeCorrection <= 1 ),
263 timeCorrection );
264 #else
265 if( ( timeCorrection >= -1 ) && ( timeCorrection <= 1 ) )
266 {
267 LmhpClockSyncPackage.OnSysTimeUpdate( );
268 }
269 #endif
270 }
271 }
272 break;
273 }
274 case CLOCK_SYNC_APP_TIME_PERIOD_REQ:
275 {
276 // Increment index
277 cmdIndex++;
278 // TODO implement command prosessing and handling
279 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_APP_TIME_PERIOD_ANS;
280 // Answer status not supported.
281 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = 0x01;
282
283 SysTime_t curTime = SysTimeGet( );
284 // Substract Unix to Gps epcoh offset. The system time is based on Unix time.
285 curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
286 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 0 ) & 0xFF;
287 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 8 ) & 0xFF;
288 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 16 ) & 0xFF;
289 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 24 ) & 0xFF;
290 break;
291 }
292 case CLOCK_SYNC_FORCE_RESYNC_REQ:
293 {
294 LmhpClockSyncState.NbTransmissions = mcpsIndication->Buffer[cmdIndex++] & 0X07;
295 break;
296 }
297 }
298 }
299
300 if( dataBufferIndex != 0 )
301 {
302 // Answer commands
303 LmHandlerAppData_t appData =
304 {
305 .Buffer = LmhpClockSyncState.DataBuffer,
306 .BufferSize = dataBufferIndex,
307 .Port = CLOCK_SYNC_PORT
308 };
309 LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG );
310 }
311 }
312
LmhpClockSyncAppTimeReq(void)313 LmHandlerErrorStatus_t LmhpClockSyncAppTimeReq( void )
314 {
315 if( LmHandlerIsBusy( ) == true )
316 {
317 return LORAMAC_HANDLER_ERROR;
318 }
319
320 if( LmhpClockSyncState.AppTimeReqPending == false )
321 {
322 MibRequestConfirm_t mibReq;
323
324 // Disable ADR
325 mibReq.Type = MIB_ADR;
326 LoRaMacMibGetRequestConfirm( &mibReq );
327 LmhpClockSyncState.AdrEnabledPrev = mibReq.Param.AdrEnable;
328 mibReq.Param.AdrEnable = false;
329 LoRaMacMibSetRequestConfirm( &mibReq );
330
331 // Set NbTrans = 1
332 mibReq.Type = MIB_CHANNELS_NB_TRANS;
333 LoRaMacMibGetRequestConfirm( &mibReq );
334 LmhpClockSyncState.NbTransPrev = mibReq.Param.ChannelsNbTrans;
335 mibReq.Param.ChannelsNbTrans = 1;
336 LoRaMacMibSetRequestConfirm( &mibReq );
337
338 // Store data rate
339 mibReq.Type = MIB_CHANNELS_DATARATE;
340 LoRaMacMibGetRequestConfirm( &mibReq );
341 LmhpClockSyncState.DataratePrev = mibReq.Param.ChannelsDatarate;
342
343 // Add DeviceTimeReq MAC command.
344 // In case the network server supports this more precise command
345 // this package will use DeviceTimeAns answer as clock synchronization
346 // mechanism.
347 LmhpClockSyncPackage.OnDeviceTimeRequest( );
348 }
349
350 SysTime_t curTime = SysTimeGet( );
351 uint8_t dataBufferIndex = 0;
352
353 // Substract Unix to Gps epcoh offset. The system time is based on Unix time.
354 curTime.Seconds -= UNIX_GPS_EPOCH_OFFSET;
355
356 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = CLOCK_SYNC_APP_TIME_REQ;
357 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 0 ) & 0xFF;
358 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 8 ) & 0xFF;
359 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 16 ) & 0xFF;
360 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = ( curTime.Seconds >> 24 ) & 0xFF;
361 LmhpClockSyncState.TimeReqParam.Fields.AnsRequired = 0;
362 LmhpClockSyncState.DataBuffer[dataBufferIndex++] = LmhpClockSyncState.TimeReqParam.Value;
363
364 LmHandlerAppData_t appData =
365 {
366 .Buffer = LmhpClockSyncState.DataBuffer,
367 .BufferSize = dataBufferIndex,
368 .Port = CLOCK_SYNC_PORT
369 };
370 LmhpClockSyncState.AppTimeReqPending = true;
371 return LmHandlerSend( &appData, LORAMAC_HANDLER_UNCONFIRMED_MSG );
372 }
373