commit
03d4b502d6
@ -8,8 +8,9 @@
|
||||
|
||||
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
|
||||
#define TIME_SYNC_FRAME_LENGTH 0x05 // timeserver answer frame length [bytes]
|
||||
#define TIME_SYNC_FIXUP 6 // calibration to fixup processing time [milliseconds]
|
||||
#define TIME_SYNC_FIXUP 4 // calibration to fixup processing time [milliseconds]
|
||||
|
||||
void timesync_init(void);
|
||||
void send_timesync_req(void);
|
||||
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len);
|
||||
void process_timesync_req(void *taskparameter);
|
||||
|
@ -46,7 +46,7 @@ board_build.partitions = min_spiffs.csv
|
||||
monitor_speed = 115200
|
||||
lib_deps_lora =
|
||||
;MCCI LoRaWAN LMIC library@>=2.3.2
|
||||
https://github.com/mcci-catena/arduino-lmic.git#6e5ebbe
|
||||
https://github.com/mcci-catena/arduino-lmic.git#e5503ff
|
||||
lib_deps_display =
|
||||
U8g2@>=2.25.7
|
||||
lib_deps_rgbled =
|
||||
|
@ -170,8 +170,6 @@ void bme_loop(void *pvParameters) {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
ESP_LOGE(TAG, "BME task ended");
|
||||
vTaskDelete(BmeTask); // should never be reached
|
||||
|
||||
} // bme_loop()
|
||||
|
||||
|
@ -40,7 +40,7 @@ void doHousekeeping() {
|
||||
ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d",
|
||||
uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask));
|
||||
#endif
|
||||
#ifdef HAS_DCF77
|
||||
#if (defined HAS_DCF77 || defined HAS_IF482)
|
||||
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
|
||||
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
|
||||
#endif
|
||||
|
@ -26,8 +26,8 @@ void DCF77_Pulse(time_t t, uint8_t const *DCFpulse) {
|
||||
ESP_LOGD(TAG, "[%02d:%02d:%02d.%03d] DCF second %d", hour(t), minute(t),
|
||||
second(t), millisecond(), sec);
|
||||
|
||||
// induce 10 pulses
|
||||
for (uint8_t pulse = 0; pulse <= 9; pulse++) {
|
||||
// induce a DCF Pulse
|
||||
for (uint8_t pulse = 0; pulse <= 2; pulse++) {
|
||||
|
||||
switch (pulse) {
|
||||
|
||||
@ -45,9 +45,6 @@ void DCF77_Pulse(time_t t, uint8_t const *DCFpulse) {
|
||||
digitalWrite(HAS_DCF77, dcf_high);
|
||||
break;
|
||||
|
||||
case 9: // 900ms after start -> last pulse
|
||||
break;
|
||||
|
||||
} // switch
|
||||
|
||||
// pulse pause
|
||||
|
@ -123,8 +123,6 @@ void gps_loop(void *pvParameters) {
|
||||
|
||||
} // end of infinite loop
|
||||
|
||||
vTaskDelete(GpsTask); // shoud never be reached
|
||||
|
||||
} // gps_loop()
|
||||
|
||||
#endif // HAS_GPS
|
@ -9,6 +9,7 @@
|
||||
/* Hardware related definitions for TTGO V2.1 Board
|
||||
// ATTENTION: check your board version!
|
||||
// This settings are for boards without label on pcb, or labeled v1.5 on pcb
|
||||
// see https://github.com/manuelbl/ttn-esp32/wiki/Boards-and-Pins
|
||||
*/
|
||||
|
||||
#define HAS_LORA 1 // comment out if device shall not send data via LoRa
|
||||
@ -32,6 +33,7 @@
|
||||
#define LORA_MISO (19)
|
||||
#define LORA_MOSI (27)
|
||||
#define LORA_RST LMIC_UNUSED_PIN
|
||||
// #define LORA_RST (12) // v1.5 labelled with pcb date 20180523
|
||||
#define LORA_IRQ (26)
|
||||
#define LORA_IO1 (33)
|
||||
#define LORA_IO2 (32)
|
||||
|
@ -56,7 +56,6 @@ void irqHandler(void *pvParameters) {
|
||||
if (InterruptStatus & SENDCYCLE_IRQ)
|
||||
sendCounter();
|
||||
}
|
||||
vTaskDelete(NULL); // shoud never be reached
|
||||
}
|
||||
|
||||
// esp32 hardware timer triggered interrupt service routines
|
||||
|
@ -212,7 +212,6 @@ void ledLoop(void *parameter) {
|
||||
// give yield to CPU
|
||||
delay(2);
|
||||
} // while(1)
|
||||
vTaskDelete(ledLoopTask); // shoud never be reached
|
||||
}; // ledloop()
|
||||
|
||||
#endif // #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)
|
||||
|
@ -223,6 +223,10 @@ void onEvent(ev_t ev) {
|
||||
// show effective LoRa parameters after join
|
||||
ESP_LOGI(TAG, "ADR=%d, SF=%d, TXPOWER=%d", cfg.adrmode, cfg.lorasf,
|
||||
cfg.txpower);
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// kickoff first time sync
|
||||
send_timesync_req();
|
||||
#endif
|
||||
break;
|
||||
|
||||
case EV_RFU1:
|
||||
@ -241,9 +245,8 @@ void onEvent(ev_t ev) {
|
||||
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// if last packet sent was a timesync request, store TX timestamp
|
||||
if (LMIC.pendTxPort == TIMEPORT) {
|
||||
if (LMIC.pendTxPort == TIMEPORT)
|
||||
store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
|
||||
}
|
||||
#endif
|
||||
|
||||
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK")
|
||||
@ -263,10 +266,10 @@ void onEvent(ev_t ev) {
|
||||
rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
|
||||
break;
|
||||
|
||||
default: // unknown port -> display info
|
||||
default:
|
||||
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// timesync answer -> call timesync processor
|
||||
// timesync answer -> call timesync processor
|
||||
if ((LMIC.frame[LMIC.dataBeg - 1] >= TIMEANSWERPORT_MIN) &&
|
||||
(LMIC.frame[LMIC.dataBeg - 1] <= TIMEANSWERPORT_MAX)) {
|
||||
recv_timesync_ans(LMIC.frame[LMIC.dataBeg - 1],
|
||||
@ -274,6 +277,7 @@ void onEvent(ev_t ev) {
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
// unknown port -> display info
|
||||
ESP_LOGI(TAG, "Received data on unsupported port #%d",
|
||||
LMIC.frame[LMIC.dataBeg - 1]);
|
||||
break;
|
||||
@ -309,7 +313,14 @@ void onEvent(ev_t ev) {
|
||||
|
||||
case EV_TXSTART:
|
||||
if (!(LMIC.opmode & OP_JOINING))
|
||||
strcpy_P(buff, PSTR("TX START"));
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// if last packet sent was a timesync request, store TX time
|
||||
// if ((LMIC.pendTxPort == TIMEPORT) && timeSyncPending)
|
||||
if (LMIC.pendTxPort == TIMEPORT)
|
||||
strcpy_P(buff, PSTR("TX TIMESYNC"));
|
||||
else
|
||||
#endif
|
||||
strcpy_P(buff, PSTR("TX START"));
|
||||
break;
|
||||
|
||||
case EV_TXCANCELED:
|
||||
|
48
src/main.cpp
48
src/main.cpp
@ -17,7 +17,7 @@ Copyright 2018 Klaus Wilting <verkehrsrot@arcor.de>
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
NOTICE:
|
||||
NOTE:
|
||||
Parts of the source files in this repository are made available under different
|
||||
licenses. Refer to LICENSE.txt file in repository for more details.
|
||||
|
||||
@ -31,12 +31,12 @@ ledloop 0 3 blinks LEDs
|
||||
spiloop 0 2 reads/writes data on spi interface
|
||||
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
|
||||
|
||||
clockloop 1 3 generates realtime telegrams for external clock
|
||||
looptask 1 1 arduino core -> runs the LMIC LoRa stack
|
||||
irqhandler 1 1 executes tasks triggered by timer irq
|
||||
clockloop 1 4 generates realtime telegrams for external clock
|
||||
timesync_req 1 3 processes realtime time sync requests
|
||||
irqhandler 1 2 display, timesync, etc. tasks triggered by timer
|
||||
gpsloop 1 2 reads data from GPS via serial or i2c
|
||||
bmeloop 1 1 reads data from BME sensor via i2c
|
||||
timesync_req 1 2 temporary task for processing time sync requests
|
||||
looptask 1 1 runs the LMIC LoRa stack (arduino loop)
|
||||
IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
|
||||
|
||||
Low priority numbers denote low priority tasks.
|
||||
@ -44,6 +44,9 @@ Low priority numbers denote low priority tasks.
|
||||
Tasks using i2c bus all must have same priority, because using mutex semaphore
|
||||
(irqhandler, bmeloop)
|
||||
|
||||
NOTE: Changing any timings will have impact on time accuracy of whole code.
|
||||
So don't do it if you do not own a digital oscilloscope.
|
||||
|
||||
// ESP32 hardware timers
|
||||
-------------------------------------------------------------------------------
|
||||
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS)
|
||||
@ -83,7 +86,7 @@ uint16_t volatile macs_total = 0, macs_wifi = 0, macs_ble = 0,
|
||||
|
||||
hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL;
|
||||
|
||||
TaskHandle_t irqHandlerTask, ClockTask;
|
||||
TaskHandle_t irqHandlerTask = NULL, ClockTask = NULL;
|
||||
SemaphoreHandle_t I2Caccess;
|
||||
bool volatile TimePulseTick = false;
|
||||
time_t userUTCTime = 0;
|
||||
@ -360,7 +363,7 @@ void setup() {
|
||||
"irqhandler", // name of task
|
||||
4096, // stack size of task
|
||||
(void *)1, // parameter of the task
|
||||
1, // priority of the task
|
||||
2, // priority of the task
|
||||
&irqHandlerTask, // task handle
|
||||
1); // CPU core
|
||||
|
||||
@ -411,35 +414,40 @@ void setup() {
|
||||
#endif // HAS_BUTTON
|
||||
|
||||
#if (TIME_SYNC_INTERVAL)
|
||||
#if (!defined(TIME_SYNC_LORAWAN) && !defined(TIME_SYNC_LORASERVER) && \
|
||||
!defined HAS_GPS && !defined HAS_RTC)
|
||||
|
||||
#if (!(TIME_SYNC_LORAWAN) && !(TIME_SYNC_LORASERVER) && !defined HAS_GPS && \
|
||||
!defined HAS_RTC)
|
||||
#warning you did not specify a time source, time will not be synched
|
||||
#endif
|
||||
|
||||
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||
ESP_LOGI(TAG, "Starting Clock Controller...");
|
||||
clock_init();
|
||||
#endif
|
||||
|
||||
#if (TIME_SYNC_LORASERVER)
|
||||
// create time sync task
|
||||
timesync_init();
|
||||
#endif
|
||||
|
||||
// start pps timepulse
|
||||
ESP_LOGI(TAG, "Starting Timekeeper...");
|
||||
assert(timepulse_init()); // setup timepulse
|
||||
timepulse_start();
|
||||
timeSync(); // init systime
|
||||
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
|
||||
#endif
|
||||
|
||||
#if defined HAS_IF482 || defined HAS_DCF77
|
||||
#if (!TIME_SYNC_INTERVAL)
|
||||
#error for clock controller function TIME_SNYC_INTERVAL must be defined in paxcounter.conf
|
||||
#endif
|
||||
ESP_LOGI(TAG, "Starting Clock Controller...");
|
||||
clock_init();
|
||||
#endif
|
||||
#endif // TIME_SYNC_INTERVAL
|
||||
|
||||
} // setup()
|
||||
|
||||
void loop() {
|
||||
|
||||
while (1) {
|
||||
#if (HAS_LORA)
|
||||
os_runloop_once(); // execute lmic scheduled jobs and events
|
||||
#endif
|
||||
#else
|
||||
delay(2); // yield to CPU
|
||||
#endif
|
||||
}
|
||||
|
||||
vTaskDelete(NULL); // shoud never be reached
|
||||
}
|
||||
|
@ -73,9 +73,9 @@
|
||||
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0]
|
||||
|
||||
// settings for syncing time with timeserver applications
|
||||
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging
|
||||
#define TIME_SYNC_CYCLE 20 // delay between two time samples [seconds]
|
||||
#define TIME_SYNC_TIMEOUT 120 // timeout waiting for timeserver answer [seconds]
|
||||
#define TIME_SYNC_SAMPLES 2 // number of time requests for averaging
|
||||
#define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds]
|
||||
#define TIME_SYNC_TIMEOUT 600 // timeout waiting for timeserver answer [seconds]
|
||||
|
||||
// time zone, see https://github.com/JChristensen/Timezone/blob/master/examples/WorldClock/WorldClock.ino
|
||||
#define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time
|
||||
|
@ -20,7 +20,9 @@ HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
|
||||
|
||||
Ticker timesyncer;
|
||||
|
||||
void timeSync() { xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL); }
|
||||
void timeSync() {
|
||||
xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL);
|
||||
}
|
||||
|
||||
time_t timeProvider(void) {
|
||||
|
||||
@ -123,9 +125,10 @@ void IRAM_ATTR CLOCKIRQ(void) {
|
||||
|
||||
SyncToPPS(); // calibrates UTC systime and advances it +1, see microTime.h
|
||||
|
||||
if (ClockTask != NULL)
|
||||
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
|
||||
&xHigherPriorityTaskWoken);
|
||||
#if (defined HAS_IF482 || defined HAS_DCF77)
|
||||
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
|
||||
&xHigherPriorityTaskWoken);
|
||||
#endif
|
||||
|
||||
#ifdef HAS_DISPLAY
|
||||
#if (defined GPS_INT || defined RTC_INT)
|
||||
@ -196,7 +199,7 @@ void clock_init(void) {
|
||||
"clockloop", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)&userUTCTime, // start time as task parameter
|
||||
3, // priority of the task
|
||||
4, // priority of the task
|
||||
&ClockTask, // task handle
|
||||
1); // CPU core
|
||||
|
||||
@ -213,7 +216,6 @@ void clock_loop(void *taskparameter) { // ClockTask
|
||||
static bool led1_state = false;
|
||||
uint32_t printtime;
|
||||
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
|
||||
TickType_t startTime;
|
||||
|
||||
#ifdef HAS_DCF77
|
||||
uint8_t *DCFpulse; // pointer on array with DCF pulse bits
|
||||
@ -225,20 +227,39 @@ void clock_loop(void *taskparameter) { // ClockTask
|
||||
|
||||
// output the next second's pulse after timepulse arrived
|
||||
for (;;) {
|
||||
// ensure the notification state is not already pending
|
||||
xTaskNotifyWait(0x00, ULONG_MAX, &printtime,
|
||||
portMAX_DELAY); // wait for timepulse
|
||||
|
||||
startTime = xTaskGetTickCount();
|
||||
|
||||
t = time_t(printtime); // UTC time seconds
|
||||
// wait for timepulse and store UTC time in seconds got
|
||||
xTaskNotifyWait(0x00, ULONG_MAX, &printtime, portMAX_DELAY);
|
||||
t = time_t(printtime);
|
||||
|
||||
// no confident or no recent time -> suppress clock output
|
||||
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
|
||||
(t == last_printtime))
|
||||
continue;
|
||||
|
||||
last_printtime = t;
|
||||
#if defined HAS_IF482
|
||||
|
||||
// wait until moment to fire. Normally we won't get notified during this
|
||||
// timespan, except when next pps pulse arrives while waiting, because pps
|
||||
// was adjusted by recent time sync
|
||||
if (xTaskNotifyWait(0x00, ULONG_MAX, &printtime, txDelay) == pdTRUE)
|
||||
t = time_t(printtime); // new adjusted UTC time seconds
|
||||
|
||||
// send IF482 telegram
|
||||
IF482.print(IF482_Frame(t + 1)); // note: telegram is for *next* second
|
||||
|
||||
#elif defined HAS_DCF77
|
||||
|
||||
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
|
||||
DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute
|
||||
|
||||
if (minute(nextmin(t)) == // do we still have a recent frame?
|
||||
DCFpulse[DCF77_FRAME_SIZE]) // (timepulses could be missed!)
|
||||
DCF77_Pulse(t, DCFpulse); // then output current second's pulse
|
||||
|
||||
// else we have no recent frame, thus suppressing clock output
|
||||
|
||||
#endif
|
||||
|
||||
// pps blink on secondary LED if we have one
|
||||
#ifdef HAS_TWO_LED
|
||||
@ -249,23 +270,7 @@ void clock_loop(void *taskparameter) { // ClockTask
|
||||
led1_state = !led1_state;
|
||||
#endif
|
||||
|
||||
#if defined HAS_IF482
|
||||
|
||||
vTaskDelayUntil(&startTime, txDelay); // wait until moment to fire
|
||||
IF482.print(IF482_Frame(t + 1)); // note: if482 telegram for *next* second
|
||||
|
||||
#elif defined HAS_DCF77
|
||||
|
||||
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
|
||||
DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute
|
||||
|
||||
if (minute(nextmin(t)) == // do we still have a recent frame?
|
||||
DCFpulse[DCF77_FRAME_SIZE]) // (timepulses could be missed!)
|
||||
DCF77_Pulse(t, DCFpulse); // then output current second's pulse
|
||||
else
|
||||
continue; // no recent frame -> we suppress clock output
|
||||
|
||||
#endif
|
||||
last_printtime = t;
|
||||
|
||||
} // for
|
||||
} // clock_loop()
|
||||
|
200
src/timesync.cpp
200
src/timesync.cpp
@ -13,152 +13,142 @@ algorithm in applications without granted license by the patent holder.
|
||||
|
||||
#include "timesync.h"
|
||||
|
||||
using namespace std::chrono;
|
||||
|
||||
// Local logging tag
|
||||
static const char TAG[] = __FILE__;
|
||||
|
||||
TaskHandle_t timeSyncReqTask;
|
||||
|
||||
static uint8_t time_sync_seqNo = TIMEANSWERPORT_MIN;
|
||||
static bool lora_time_sync_pending = false;
|
||||
using namespace std::chrono;
|
||||
|
||||
typedef std::chrono::system_clock myClock;
|
||||
typedef myClock::time_point myClock_timepoint;
|
||||
typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
|
||||
myClock_msecTick;
|
||||
|
||||
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
||||
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
||||
TaskHandle_t timeSyncReqTask = NULL;
|
||||
|
||||
static uint8_t time_sync_seqNo = random(TIMEANSWERPORT_MIN, TIMEANSWERPORT_MAX);
|
||||
static bool timeSyncPending = false;
|
||||
static myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES];
|
||||
static myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
|
||||
|
||||
// send time request message
|
||||
void send_timesync_req() {
|
||||
|
||||
// if a timesync handshake is pending then exit
|
||||
if (lora_time_sync_pending) {
|
||||
// ESP_LOGI(TAG, "Timeserver sync request already pending");
|
||||
// if a timesync handshake is pending or we are not joined then exit
|
||||
if (timeSyncPending || !LMIC.devaddr)
|
||||
return;
|
||||
} else {
|
||||
// else unblock timesync task
|
||||
else {
|
||||
timeSyncPending = true;
|
||||
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
|
||||
|
||||
lora_time_sync_pending = true;
|
||||
|
||||
// clear timestamp array
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++)
|
||||
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint();
|
||||
|
||||
// kick off temporary task for timeserver handshake processing
|
||||
if (!timeSyncReqTask)
|
||||
xTaskCreatePinnedToCore(process_timesync_req, // task function
|
||||
"timesync_req", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)1, // task parameter
|
||||
2, // priority of the task
|
||||
&timeSyncReqTask, // task handle
|
||||
1); // CPU core
|
||||
xTaskNotifyGive(timeSyncReqTask);
|
||||
}
|
||||
}
|
||||
|
||||
// task for sending time sync requests
|
||||
void process_timesync_req(void *taskparameter) {
|
||||
|
||||
uint8_t k = 0;
|
||||
uint8_t k;
|
||||
uint16_t time_to_set_fraction_msec;
|
||||
uint32_t seq_no = 0, time_to_set;
|
||||
uint32_t seq_no, time_to_set;
|
||||
auto time_offset_ms = myClock_msecTick::zero();
|
||||
|
||||
// wait until we are joined
|
||||
while (!LMIC.devaddr) {
|
||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||
}
|
||||
while (1) {
|
||||
|
||||
// enqueue timestamp samples in lora sendqueue
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
||||
// reset all timestamps before next sync run
|
||||
time_offset_ms = myClock_msecTick::zero();
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++)
|
||||
time_sync_tx[i] = time_sync_rx[i] = myClock_timepoint();
|
||||
|
||||
// send sync request to server
|
||||
payload.reset();
|
||||
payload.addByte(time_sync_seqNo);
|
||||
SendPayload(TIMEPORT, prio_high);
|
||||
// wait for kickoff
|
||||
ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
|
||||
|
||||
// process answer, wait for notification from recv_timesync_ans()
|
||||
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) ||
|
||||
(seq_no != time_sync_seqNo))
|
||||
goto error; // no valid sequence received before timeout
|
||||
// collect timestamp samples
|
||||
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
|
||||
// send sync request to server
|
||||
payload.reset();
|
||||
payload.addByte(time_sync_seqNo);
|
||||
SendPayload(TIMEPORT, prio_high);
|
||||
|
||||
else { // calculate time diff from collected timestamps
|
||||
k = seq_no % TIME_SYNC_SAMPLES;
|
||||
// wait for notification from recv_timesync_ans()
|
||||
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no,
|
||||
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) ==
|
||||
pdFALSE) ||
|
||||
(seq_no != time_sync_seqNo))
|
||||
goto error; // no valid sequence received before timeout
|
||||
|
||||
// cumulate timepoint diffs
|
||||
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
|
||||
time_point_cast<milliseconds>(time_sync_tx[k]);
|
||||
// process answer
|
||||
else {
|
||||
k = seq_no % TIME_SYNC_SAMPLES;
|
||||
|
||||
// wrap around seqNo keeping it in time port range
|
||||
time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX)
|
||||
? time_sync_seqNo + 1
|
||||
: TIMEANSWERPORT_MIN;
|
||||
// calculate time diff from collected timestamps
|
||||
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
|
||||
time_point_cast<milliseconds>(time_sync_tx[k]);
|
||||
|
||||
if (i < TIME_SYNC_SAMPLES - 1) {
|
||||
// wait until next cycle
|
||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||
} else { // before sending last time sample...
|
||||
// ...send flush to open a receive window for last time_sync_answer
|
||||
// payload.reset();
|
||||
// payload.addByte(0x99);
|
||||
// SendPayload(RCMDPORT, prio_high);
|
||||
// ...send a alive open a receive window for last time_sync_answer
|
||||
LMIC_sendAlive();
|
||||
// wrap around seqNo, keeping it in time port range
|
||||
time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX)
|
||||
? time_sync_seqNo + 1
|
||||
: TIMEANSWERPORT_MIN;
|
||||
|
||||
if (i < TIME_SYNC_SAMPLES - 1) {
|
||||
// wait until next cycle
|
||||
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
|
||||
} else { // before sending last time sample...
|
||||
// ...send flush to open a receive window for last time_sync_answer
|
||||
payload.reset();
|
||||
payload.addByte(0x99);
|
||||
SendPayload(RCMDPORT, prio_high);
|
||||
// ...send a alive open a receive window for last time_sync_answer
|
||||
// LMIC_sendAlive();
|
||||
}
|
||||
}
|
||||
}
|
||||
} // for
|
||||
} // end of for loop to collect timestamp samples
|
||||
|
||||
// begin of time critical section: lock I2C bus to ensure accurate timing
|
||||
if (!mask_user_IRQ())
|
||||
goto error; // failure
|
||||
// begin of time critical section: lock app irq's and I2C bus
|
||||
if (!mask_user_IRQ())
|
||||
goto error; // failure
|
||||
|
||||
// average time offset from collected diffs
|
||||
time_offset_ms /= TIME_SYNC_SAMPLES;
|
||||
// average time offset over all collected diffs
|
||||
time_offset_ms /= TIME_SYNC_SAMPLES;
|
||||
|
||||
// calculate time offset with millisecond precision using LMIC's time base,
|
||||
// since we use LMIC's ostime_t txEnd as tx timestamp.
|
||||
// Finally apply calibration const for processing time.
|
||||
time_offset_ms +=
|
||||
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
|
||||
// calculate time offset with millisecond precision using LMIC's time base,
|
||||
// since we use LMIC's ostime_t txEnd as tx timestamp.
|
||||
// Also apply calibration const to compensate processing time.
|
||||
time_offset_ms +=
|
||||
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
|
||||
|
||||
// calculate absolute time in UTC epoch: convert to whole seconds, round to
|
||||
// ceil, and calculate fraction milliseconds
|
||||
time_to_set = (uint32_t)(time_offset_ms.count() / 1000) + 1;
|
||||
// calculate fraction milliseconds
|
||||
time_to_set_fraction_msec = (uint16_t)(time_offset_ms.count() % 1000);
|
||||
// calculate absolute time in UTC epoch: convert to whole seconds, round to
|
||||
// ceil, and calculate fraction milliseconds
|
||||
time_to_set = (uint32_t)(time_offset_ms.count() / 1000) + 1;
|
||||
// calculate fraction milliseconds
|
||||
time_to_set_fraction_msec = (uint16_t)(time_offset_ms.count() % 1000);
|
||||
|
||||
setMyTime(time_to_set, time_to_set_fraction_msec);
|
||||
setMyTime(time_to_set, time_to_set_fraction_msec);
|
||||
|
||||
// end of time critical section: release I2C bus
|
||||
unmask_user_IRQ();
|
||||
// end of time critical section: release I2C bus and re-enable app irq's
|
||||
unmask_user_IRQ();
|
||||
|
||||
finish:
|
||||
goto finish;
|
||||
|
||||
lora_time_sync_pending = false;
|
||||
timeSyncReqTask = NULL;
|
||||
vTaskDelete(NULL); // end task
|
||||
error:
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
|
||||
millis() / 1000.0);
|
||||
|
||||
error:
|
||||
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
|
||||
millis() / 1000.0);
|
||||
goto finish; // end task
|
||||
finish:
|
||||
timeSyncPending = false;
|
||||
|
||||
} // infinite while(1)
|
||||
}
|
||||
|
||||
// called from lorawan.cpp after time_sync_req was sent
|
||||
void store_time_sync_req(uint32_t timestamp) {
|
||||
|
||||
if (lora_time_sync_pending) {
|
||||
if (timeSyncPending) {
|
||||
|
||||
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
|
||||
time_sync_tx[k] += milliseconds(timestamp);
|
||||
|
||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d",
|
||||
millis() / 1000.0, time_sync_seqNo, timestamp / 1000,
|
||||
timestamp % 1000);
|
||||
millis() / 1000.0, k, timestamp / 1000, timestamp % 1000);
|
||||
}
|
||||
}
|
||||
|
||||
@ -166,7 +156,7 @@ void store_time_sync_req(uint32_t timestamp) {
|
||||
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) {
|
||||
|
||||
// if no timesync handshake is pending then exit
|
||||
if (!lora_time_sync_pending)
|
||||
if (!timeSyncPending)
|
||||
return 0; // failure
|
||||
|
||||
// if no time is available or spurious buffer then exit
|
||||
@ -198,14 +188,13 @@ int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) {
|
||||
// construct the timepoint when message was seen on gateway
|
||||
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec);
|
||||
|
||||
// guess timepoint is recent if newer than code compile date
|
||||
// we guess timepoint is recent if it newer than code compile date
|
||||
if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) {
|
||||
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d",
|
||||
millis() / 1000.0, seq_no, timestamp_sec, timestamp_msec);
|
||||
millis() / 1000.0, k, timestamp_sec, timestamp_msec);
|
||||
|
||||
// inform processing task
|
||||
if (timeSyncReqTask)
|
||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
|
||||
|
||||
return 1; // success
|
||||
} else {
|
||||
@ -252,4 +241,15 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec) {
|
||||
millis() / 1000.0);
|
||||
}
|
||||
|
||||
void timesync_init() {
|
||||
// create task for timeserver handshake processing, called from main.cpp
|
||||
xTaskCreatePinnedToCore(process_timesync_req, // task function
|
||||
"timesync_req", // name of task
|
||||
2048, // stack size of task
|
||||
(void *)1, // task parameter
|
||||
3, // priority of the task
|
||||
&timeSyncReqTask, // task handle
|
||||
1); // CPU core
|
||||
}
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user