ESP32-PaxCounter/src/timesync.cpp

280 lines
9.2 KiB
C++
Raw Normal View History

2019-03-09 00:53:11 +01:00
/*
2020-03-04 21:54:26 +01:00
///--> IMPORTANT LICENSE NOTE for timesync option 1 in this file <--///
2019-03-09 00:53:11 +01:00
PLEASE NOTE: There is a patent filed for the time sync algorithm used in the
2020-03-07 23:18:36 +01:00
code of this file for timesync option TIME_SYNC_LORASERVER. The shown
implementation example is covered by the repository's licencse, but you may not
be eligible to deploy the applied algorithm in applications without granted
license by the patent holder.
2019-03-09 00:53:11 +01:00
2020-03-07 23:18:36 +01:00
You may use timesync option TIME_SYNC_LORAWAN if you do not want or cannot
accept this.
2019-03-09 00:53:11 +01:00
2020-03-04 21:54:26 +01:00
*/
2019-03-09 00:53:11 +01:00
2020-03-06 19:24:58 +01:00
#if (HAS_LORA)
2019-03-09 00:53:11 +01:00
2020-03-06 19:24:58 +01:00
#if (TIME_SYNC_LORASERVER) && (TIME_SYNC_LORAWAN)
2020-03-04 21:54:26 +01:00
#error Duplicate timesync method selected. You must select either LORASERVER or LORAWAN timesync.
#endif
2020-03-06 19:24:58 +01:00
#include "timesync.h"
2019-03-09 00:53:11 +01:00
2020-03-06 19:24:58 +01:00
// Local logging tag
static const char TAG[] = __FILE__;
2019-04-07 16:13:04 +02:00
2020-03-04 21:54:26 +01:00
static bool timeSyncPending = false;
2020-03-08 16:15:46 +01:00
static uint8_t time_sync_seqNo = (uint8_t)random(TIME_SYNC_MAX_SEQNO),
2020-03-06 19:24:58 +01:00
sample_idx;
2020-03-07 23:18:36 +01:00
static uint32_t timesync_timestamp[TIME_SYNC_SAMPLES][no_of_timestamps];
2020-03-08 16:15:46 +01:00
static TaskHandle_t timeSyncProcTask;
2020-03-06 19:24:58 +01:00
// create task for timeserver handshake processing, called from main.cpp
void timesync_init() {
xTaskCreatePinnedToCore(timesync_processReq, // task function
2020-03-07 19:41:08 +01:00
"timesync_proc", // name of task
2020-03-06 19:24:58 +01:00
2048, // stack size of task
(void *)1, // task parameter
3, // priority of the task
2020-03-07 19:41:08 +01:00
&timeSyncProcTask, // task handle
2020-03-06 19:24:58 +01:00
1); // CPU core
}
2019-03-09 00:53:11 +01:00
2020-03-06 19:24:58 +01:00
// kickoff asnychronous timesync handshake
2020-03-11 23:49:06 +01:00
void timesync_request(void) {
2020-03-08 16:15:46 +01:00
// exit if a timesync handshake is already running
2019-04-13 13:59:30 +02:00
if (timeSyncPending)
2019-03-09 00:53:11 +01:00
return;
2020-03-08 16:15:46 +01:00
// start timesync handshake
2019-04-07 16:13:04 +02:00
else {
2020-03-06 19:24:58 +01:00
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request seqNo#%d started",
millis() / 1000.0, time_sync_seqNo);
2020-03-08 16:15:46 +01:00
xTaskNotifyGive(timeSyncProcTask); // unblock timesync task
2019-03-09 00:53:11 +01:00
}
}
2020-03-06 19:24:58 +01:00
// task for processing time sync request
void IRAM_ATTR timesync_processReq(void *taskparameter) {
2019-03-09 20:40:21 +01:00
2020-03-15 20:21:26 +01:00
uint32_t rcv_seqNo = TIME_SYNC_END_FLAG;
uint32_t time_offset_sec = 0, time_offset_ms = 0;
// this task is an endless loop, waiting in blocked mode, until it is
2020-03-11 23:49:06 +01:00
// unblocked by timesync_request(). It then waits to be notified from
2020-03-07 23:18:36 +01:00
// timesync_serverAnswer(), which is called from LMIC each time a timestamp
2020-03-07 19:41:08 +01:00
// from the timesource via LORAWAN arrived.
2019-03-16 21:01:43 +01:00
// --- asnychronous part: generate and collect timestamps from gateway ---
2019-04-07 16:13:04 +02:00
while (1) {
2019-04-07 16:13:04 +02:00
// wait for kickoff
2019-04-07 14:06:47 +02:00
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
2019-04-13 13:59:30 +02:00
timeSyncPending = true;
2020-03-08 16:15:46 +01:00
time_offset_ms = sample_idx = 0;
2019-04-13 13:59:30 +02:00
// wait until we are joined if we are not
while (!LMIC.devaddr) {
2020-03-08 16:15:46 +01:00
vTaskDelay(pdMS_TO_TICKS(3000));
2019-07-24 12:37:02 +02:00
}
2019-03-09 20:40:21 +01:00
2020-03-08 16:15:46 +01:00
// collect timestamp samples in timestamp array
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
2020-03-06 19:24:58 +01:00
2020-03-08 16:15:46 +01:00
// send timesync request
#if (TIME_SYNC_LORASERVER) // aks user's timeserver (for LoRAWAN < 1.0.3)
2019-04-07 16:13:04 +02:00
payload.reset();
payload.addByte(time_sync_seqNo);
SendPayload(TIMEPORT, prio_high);
2020-03-08 16:15:46 +01:00
#elif (TIME_SYNC_LORAWAN) // ask network (requires LoRAWAN >= 1.0.3)
2020-03-07 19:41:08 +01:00
LMIC_requestNetworkTime(timesync_serverAnswer, &time_sync_seqNo);
2020-03-15 20:21:26 +01:00
// trigger to immediately get DevTimeAns from class A device
2020-03-07 23:18:36 +01:00
LMIC_sendAlive();
2020-03-06 19:24:58 +01:00
#endif
// wait until a timestamp was received
2020-03-08 16:15:46 +01:00
if (xTaskNotifyWait(0x00, ULONG_MAX, &rcv_seqNo,
2020-03-07 19:41:08 +01:00
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) {
2020-03-15 20:21:26 +01:00
ESP_LOGW(TAG, "[d%0.3f] Timesync aborted: timed out",
millis() / 1000.0);
2020-03-07 23:47:36 +01:00
goto Fail; // no timestamp received before timeout
2020-03-07 19:41:08 +01:00
}
// check if we are in handshake with server
2020-03-08 16:15:46 +01:00
if (rcv_seqNo != time_sync_seqNo) {
2020-03-07 23:18:36 +01:00
ESP_LOGW(TAG, "[%0.3f] Timesync aborted: handshake out of sync",
millis() / 1000.0);
2020-03-07 19:41:08 +01:00
goto Fail;
2019-04-13 13:59:30 +02:00
}
2019-04-07 16:13:04 +02:00
2020-03-08 16:15:46 +01:00
#if (TIME_SYNC_LORASERVER)
2020-03-07 19:41:08 +01:00
// calculate time diff with received timestamp
time_offset_ms += timesync_timestamp[sample_idx][timesync_rx] -
timesync_timestamp[sample_idx][timesync_tx];
2020-03-08 16:15:46 +01:00
#endif
2020-03-08 16:15:46 +01:00
// increment sample_idx and time_sync_seqNo, keeping it in range
if (++time_sync_seqNo > TIME_SYNC_MAX_SEQNO)
time_sync_seqNo = 0;
sample_idx++;
2020-03-07 23:47:36 +01:00
// if we are not in last cycle, pause until next cycle
2020-03-07 19:41:08 +01:00
if (i < TIME_SYNC_SAMPLES - 1)
2019-04-13 13:59:30 +02:00
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
2020-03-08 16:15:46 +01:00
} // for i
2019-04-07 16:13:04 +02:00
// --- time critial part: evaluate timestamps and calculate time ---
// mask application irq to ensure accurate timing
2019-07-23 20:43:10 +02:00
mask_user_IRQ();
2019-03-09 00:53:11 +01:00
2020-03-07 23:47:36 +01:00
// calculate average time offset over the summed up difference
time_offset_ms /= TIME_SYNC_SAMPLES;
2020-03-15 20:21:26 +01:00
// take latest timestamp received from gateway
// and add time difference rounded to whole seconds
time_offset_sec = timesync_timestamp[sample_idx - 1][gwtime_sec];
time_offset_sec += time_offset_ms / 1000;
// add milliseconds from latest gateway time, and apply a compensation
// constant for processing times on node and gateway, strip full seconds
time_offset_ms += timesync_timestamp[sample_idx - 1][gwtime_msec];
time_offset_ms += TIME_SYNC_FIXUP;
time_offset_ms %= 1000;
setMyTime(time_offset_sec, time_offset_ms, _lora);
2019-03-27 20:38:00 +01:00
2020-03-08 16:15:46 +01:00
// send timesync end char to show timesync was successful
2020-03-07 19:41:08 +01:00
payload.reset();
2020-03-08 16:15:46 +01:00
payload.addByte(TIME_SYNC_END_FLAG);
2020-03-07 19:41:08 +01:00
SendPayload(RCMDPORT, prio_high);
goto Finish;
Fail:
// set retry timer
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync);
Finish:
// end of time critical section: release app irq lock
unmask_user_IRQ();
2019-04-07 14:06:47 +02:00
2019-04-07 16:13:04 +02:00
} // infinite while(1)
2019-03-09 00:53:11 +01:00
}
2020-03-06 19:24:58 +01:00
// store incoming timestamps
2020-03-11 23:49:06 +01:00
void timesync_store(uint32_t timestamp, timesync_t timestamp_type) {
2020-03-15 20:21:26 +01:00
ESP_LOGD(TAG, "[%0.3f] seq#%d[%d]: t%d=%d", millis() / 1000.0,
2020-03-03 20:16:36 +01:00
time_sync_seqNo, sample_idx, timestamp_type, timestamp);
timesync_timestamp[sample_idx][timestamp_type] = timestamp;
2019-03-16 21:01:43 +01:00
}
2020-03-07 23:47:36 +01:00
// callback function to receive time answer from network or answer
2020-03-07 19:41:08 +01:00
void IRAM_ATTR timesync_serverAnswer(void *pUserData, int flag) {
2020-03-07 23:18:36 +01:00
2020-03-07 19:41:08 +01:00
// if no timesync handshake is pending then exit
if (!timeSyncPending)
return;
2020-03-11 23:49:06 +01:00
// store LMIC time when we received the timesync answer
ostime_t rxTime = osticks2ms(os_getTime());
2020-03-15 20:21:26 +01:00
// mask application irq to ensure accurate timing
mask_user_IRQ();
2020-03-07 19:41:08 +01:00
int rc = 0;
2020-03-15 20:21:26 +01:00
// cast back void parameter to a pointer
uint8_t *p = (uint8_t *)pUserData, rcv_seqNo = *p;
2020-03-08 16:15:46 +01:00
uint16_t timestamp_msec = 0;
uint32_t timestamp_sec = 0;
2020-03-07 19:41:08 +01:00
2020-03-06 19:24:58 +01:00
#if (TIME_SYNC_LORASERVER)
2020-03-07 19:41:08 +01:00
// pUserData: contains pointer (32bit) to payload buffer
2020-03-07 19:41:08 +01:00
// flag: length of buffer
2019-10-05 13:15:45 +02:00
2020-03-11 23:49:06 +01:00
// Store the instant the time request of the node was received on the gateway
timesync_store(rxTime, timesync_rx);
2019-10-05 13:15:45 +02:00
// parse pUserData:
// p type meaning
// +0 uint8_t sequence number (taken from node's time_sync_req)
// +1 uint32_t current second (from UTC epoch)
// +4 uint8_t 1/250ths fractions of current second
2019-03-16 21:01:43 +01:00
2020-03-07 23:18:36 +01:00
// swap byte order from msb to lsb, note: this is a platform dependent hack
timestamp_sec = __builtin_bswap32(*(uint32_t *)(++p));
2019-03-16 21:01:43 +01:00
2020-03-07 23:18:36 +01:00
// one step being 1/250th sec * 1000 = 4msec
timestamp_msec = *(p += 4) * 4;
2019-10-05 13:15:45 +02:00
2019-03-19 00:02:35 +01:00
// if no time is available or spurious buffer then exit
2020-03-07 19:41:08 +01:00
if (flag != TIME_SYNC_FRAME_LENGTH) {
2020-03-08 16:15:46 +01:00
if (rcv_seqNo == TIME_SYNC_END_FLAG)
2019-03-19 00:02:35 +01:00
ESP_LOGI(TAG, "[%0.3f] Timeserver error: no confident time available",
millis() / 1000.0);
else
ESP_LOGW(TAG, "[%0.3f] Timeserver error: spurious data received",
millis() / 1000.0);
2020-03-07 19:41:08 +01:00
goto Exit; // failure
2019-03-19 00:02:35 +01:00
}
2020-03-07 19:41:08 +01:00
goto Finish;
2019-03-09 15:25:44 +01:00
2020-03-06 19:24:58 +01:00
#elif (TIME_SYNC_LORAWAN)
2020-03-04 21:54:26 +01:00
// pUserData: contains pointer to SeqNo (not needed here)
2020-03-07 23:18:36 +01:00
// flag: indicates if we got a recent time from the network
2020-03-07 19:41:08 +01:00
if (flag != 1) {
ESP_LOGW(TAG, "[%0.3f] Network did not answer time request",
millis() / 1000.0);
goto Exit;
}
2020-03-04 21:54:26 +01:00
// A struct that will be populated by LMIC_getNetworkTimeReference.
// It contains the following fields:
// - tLocal: the value returned by os_GetTime() when the time
// request was sent to the gateway, and
// - tNetwork: the seconds between the GPS epoch and the time
// the gateway received the time request
2020-03-04 23:39:28 +01:00
lmic_time_reference_t lmicTime;
2020-03-04 21:54:26 +01:00
2020-03-06 19:24:58 +01:00
// Populate lmic_time_reference
if ((LMIC_getNetworkTimeReference(&lmicTime)) != 1) {
2020-03-07 19:41:08 +01:00
ESP_LOGW(TAG, "[%0.3f] Network time request failed", millis() / 1000.0);
goto Exit;
}
2020-03-04 21:54:26 +01:00
2020-03-07 19:41:08 +01:00
// Calculate UTCTime, considering the difference between GPS and UTC time
timestamp_sec = lmicTime.tNetwork + GPS_UTC_DIFF;
2020-03-11 23:49:06 +01:00
// Add delay between the instant the time was received on the gateway and the
// current time on the node
timestamp_msec = rxTime - lmicTime.tLocal;
2020-03-15 20:21:26 +01:00
2020-03-07 19:41:08 +01:00
goto Finish;
2020-03-07 19:41:08 +01:00
#endif // (TIME_SYNC_LORAWAN)
Finish:
2020-03-07 23:47:36 +01:00
// check if calculated time is recent
2020-03-07 19:41:08 +01:00
if (timeIsValid(timestamp_sec)) {
// store time received from gateway
2020-03-11 23:49:06 +01:00
timesync_store(timestamp_sec, gwtime_sec);
timesync_store(timestamp_msec, gwtime_msec);
2020-03-07 19:41:08 +01:00
// success
rc = 1;
} else {
ESP_LOGW(TAG, "[%0.3f] Timeserver error: outdated time received",
millis() / 1000.0);
}
2020-03-04 21:54:26 +01:00
2020-03-07 19:41:08 +01:00
Exit:
// end of time critical section: release app irq lock
unmask_user_IRQ();
// inform processing task
2020-03-08 16:15:46 +01:00
xTaskNotify(timeSyncProcTask, (rc ? rcv_seqNo : TIME_SYNC_END_FLAG),
eSetBits);
2020-03-06 19:24:58 +01:00
}
#endif // HAS_LORA