Merge pull request #347 from cyberman54/development

Development
This commit is contained in:
Verkehrsrot 2019-04-10 21:44:23 +02:00 committed by GitHub
commit 03d4b502d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 191 additions and 173 deletions

View File

@ -8,8 +8,9 @@
//#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds] //#define TIME_SYNC_TRIGGER 100 // threshold for time sync [milliseconds]
#define TIME_SYNC_FRAME_LENGTH 0x05 // timeserver answer frame length [bytes] #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); void send_timesync_req(void);
int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len); int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len);
void process_timesync_req(void *taskparameter); void process_timesync_req(void *taskparameter);

View File

@ -46,7 +46,7 @@ board_build.partitions = min_spiffs.csv
monitor_speed = 115200 monitor_speed = 115200
lib_deps_lora = lib_deps_lora =
;MCCI LoRaWAN LMIC library@>=2.3.2 ;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 = lib_deps_display =
U8g2@>=2.25.7 U8g2@>=2.25.7
lib_deps_rgbled = lib_deps_rgbled =

View File

@ -170,8 +170,6 @@ void bme_loop(void *pvParameters) {
} }
} }
#endif #endif
ESP_LOGE(TAG, "BME task ended");
vTaskDelete(BmeTask); // should never be reached
} // bme_loop() } // bme_loop()

View File

@ -40,7 +40,7 @@ void doHousekeeping() {
ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d", ESP_LOGD(TAG, "Bmeloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask)); uxTaskGetStackHighWaterMark(BmeTask), eTaskGetState(BmeTask));
#endif #endif
#ifdef HAS_DCF77 #if (defined HAS_DCF77 || defined HAS_IF482)
ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d", ESP_LOGD(TAG, "Clockloop %d bytes left | Taskstate = %d",
uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask)); uxTaskGetStackHighWaterMark(ClockTask), eTaskGetState(ClockTask));
#endif #endif

View File

@ -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), ESP_LOGD(TAG, "[%02d:%02d:%02d.%03d] DCF second %d", hour(t), minute(t),
second(t), millisecond(), sec); second(t), millisecond(), sec);
// induce 10 pulses // induce a DCF Pulse
for (uint8_t pulse = 0; pulse <= 9; pulse++) { for (uint8_t pulse = 0; pulse <= 2; pulse++) {
switch (pulse) { switch (pulse) {
@ -45,9 +45,6 @@ void DCF77_Pulse(time_t t, uint8_t const *DCFpulse) {
digitalWrite(HAS_DCF77, dcf_high); digitalWrite(HAS_DCF77, dcf_high);
break; break;
case 9: // 900ms after start -> last pulse
break;
} // switch } // switch
// pulse pause // pulse pause

View File

@ -123,8 +123,6 @@ void gps_loop(void *pvParameters) {
} // end of infinite loop } // end of infinite loop
vTaskDelete(GpsTask); // shoud never be reached
} // gps_loop() } // gps_loop()
#endif // HAS_GPS #endif // HAS_GPS

View File

@ -9,6 +9,7 @@
/* Hardware related definitions for TTGO V2.1 Board /* Hardware related definitions for TTGO V2.1 Board
// ATTENTION: check your board version! // ATTENTION: check your board version!
// This settings are for boards without label on pcb, or labeled v1.5 on pcb // 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 #define HAS_LORA 1 // comment out if device shall not send data via LoRa
@ -32,6 +33,7 @@
#define LORA_MISO (19) #define LORA_MISO (19)
#define LORA_MOSI (27) #define LORA_MOSI (27)
#define LORA_RST LMIC_UNUSED_PIN #define LORA_RST LMIC_UNUSED_PIN
// #define LORA_RST (12) // v1.5 labelled with pcb date 20180523
#define LORA_IRQ (26) #define LORA_IRQ (26)
#define LORA_IO1 (33) #define LORA_IO1 (33)
#define LORA_IO2 (32) #define LORA_IO2 (32)

View File

@ -56,7 +56,6 @@ void irqHandler(void *pvParameters) {
if (InterruptStatus & SENDCYCLE_IRQ) if (InterruptStatus & SENDCYCLE_IRQ)
sendCounter(); sendCounter();
} }
vTaskDelete(NULL); // shoud never be reached
} }
// esp32 hardware timer triggered interrupt service routines // esp32 hardware timer triggered interrupt service routines

View File

@ -212,7 +212,6 @@ void ledLoop(void *parameter) {
// give yield to CPU // give yield to CPU
delay(2); delay(2);
} // while(1) } // while(1)
vTaskDelete(ledLoopTask); // shoud never be reached
}; // ledloop() }; // ledloop()
#endif // #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED) #endif // #if (HAS_LED != NOT_A_PIN) || defined(HAS_RGB_LED)

View File

@ -223,6 +223,10 @@ void onEvent(ev_t ev) {
// show effective LoRa parameters after join // show effective LoRa parameters after join
ESP_LOGI(TAG, "ADR=%d, SF=%d, TXPOWER=%d", cfg.adrmode, cfg.lorasf, ESP_LOGI(TAG, "ADR=%d, SF=%d, TXPOWER=%d", cfg.adrmode, cfg.lorasf,
cfg.txpower); cfg.txpower);
#if (TIME_SYNC_LORASERVER)
// kickoff first time sync
send_timesync_req();
#endif
break; break;
case EV_RFU1: case EV_RFU1:
@ -241,9 +245,8 @@ void onEvent(ev_t ev) {
#if (TIME_SYNC_LORASERVER) #if (TIME_SYNC_LORASERVER)
// if last packet sent was a timesync request, store TX timestamp // 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 store_time_sync_req(osticks2ms(LMIC.txend)); // milliseconds
}
#endif #endif
strcpy_P(buff, (LMIC.txrxFlags & TXRX_ACK) ? PSTR("RECEIVED ACK") 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); rcommand(LMIC.frame + LMIC.dataBeg, LMIC.dataLen);
break; break;
default: // unknown port -> display info default:
#if (TIME_SYNC_LORASERVER) #if (TIME_SYNC_LORASERVER)
// timesync answer -> call timesync processor // timesync answer -> call timesync processor
if ((LMIC.frame[LMIC.dataBeg - 1] >= TIMEANSWERPORT_MIN) && if ((LMIC.frame[LMIC.dataBeg - 1] >= TIMEANSWERPORT_MIN) &&
(LMIC.frame[LMIC.dataBeg - 1] <= TIMEANSWERPORT_MAX)) { (LMIC.frame[LMIC.dataBeg - 1] <= TIMEANSWERPORT_MAX)) {
recv_timesync_ans(LMIC.frame[LMIC.dataBeg - 1], recv_timesync_ans(LMIC.frame[LMIC.dataBeg - 1],
@ -274,6 +277,7 @@ void onEvent(ev_t ev) {
break; break;
} }
#endif #endif
// unknown port -> display info
ESP_LOGI(TAG, "Received data on unsupported port #%d", ESP_LOGI(TAG, "Received data on unsupported port #%d",
LMIC.frame[LMIC.dataBeg - 1]); LMIC.frame[LMIC.dataBeg - 1]);
break; break;
@ -309,7 +313,14 @@ void onEvent(ev_t ev) {
case EV_TXSTART: case EV_TXSTART:
if (!(LMIC.opmode & OP_JOINING)) 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; break;
case EV_TXCANCELED: case EV_TXCANCELED:

View File

@ -17,7 +17,7 @@ Copyright 2018 Klaus Wilting <verkehrsrot@arcor.de>
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
NOTICE: NOTE:
Parts of the source files in this repository are made available under different Parts of the source files in this repository are made available under different
licenses. Refer to LICENSE.txt file in repository for more details. 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 spiloop 0 2 reads/writes data on spi interface
IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer IDLE 0 0 ESP32 arduino scheduler -> runs wifi sniffer
clockloop 1 3 generates realtime telegrams for external clock clockloop 1 4 generates realtime telegrams for external clock
looptask 1 1 arduino core -> runs the LMIC LoRa stack timesync_req 1 3 processes realtime time sync requests
irqhandler 1 1 executes tasks triggered by timer irq irqhandler 1 2 display, timesync, etc. tasks triggered by timer
gpsloop 1 2 reads data from GPS via serial or i2c gpsloop 1 2 reads data from GPS via serial or i2c
bmeloop 1 1 reads data from BME sensor via 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 IDLE 1 0 ESP32 arduino scheduler -> runs wifi channel rotator
Low priority numbers denote low priority tasks. 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 Tasks using i2c bus all must have same priority, because using mutex semaphore
(irqhandler, bmeloop) (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 // ESP32 hardware timers
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
0 displayIRQ -> display refresh -> 40ms (DISPLAYREFRESH_MS) 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; hw_timer_t *ppsIRQ = NULL, *displayIRQ = NULL;
TaskHandle_t irqHandlerTask, ClockTask; TaskHandle_t irqHandlerTask = NULL, ClockTask = NULL;
SemaphoreHandle_t I2Caccess; SemaphoreHandle_t I2Caccess;
bool volatile TimePulseTick = false; bool volatile TimePulseTick = false;
time_t userUTCTime = 0; time_t userUTCTime = 0;
@ -360,7 +363,7 @@ void setup() {
"irqhandler", // name of task "irqhandler", // name of task
4096, // stack size of task 4096, // stack size of task
(void *)1, // parameter of the task (void *)1, // parameter of the task
1, // priority of the task 2, // priority of the task
&irqHandlerTask, // task handle &irqHandlerTask, // task handle
1); // CPU core 1); // CPU core
@ -411,35 +414,40 @@ void setup() {
#endif // HAS_BUTTON #endif // HAS_BUTTON
#if (TIME_SYNC_INTERVAL) #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 #warning you did not specify a time source, time will not be synched
#endif #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 // start pps timepulse
ESP_LOGI(TAG, "Starting Timekeeper..."); ESP_LOGI(TAG, "Starting Timekeeper...");
assert(timepulse_init()); // setup timepulse assert(timepulse_init()); // setup timepulse
timepulse_start(); timepulse_start();
timeSync(); // init systime timeSync(); // init systime
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
#endif
#if defined HAS_IF482 || defined HAS_DCF77 #endif // TIME_SYNC_INTERVAL
#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
} // setup() } // setup()
void loop() { void loop() {
while (1) { while (1) {
#if (HAS_LORA) #if (HAS_LORA)
os_runloop_once(); // execute lmic scheduled jobs and events os_runloop_once(); // execute lmic scheduled jobs and events
#endif #else
delay(2); // yield to CPU delay(2); // yield to CPU
#endif
} }
vTaskDelete(NULL); // shoud never be reached
} }

View File

@ -73,9 +73,9 @@
#define TIME_SYNC_LORASERVER 0 // set to 1 to use LORA timeserver as time source, 0 means off [default = 0] #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 // settings for syncing time with timeserver applications
#define TIME_SYNC_SAMPLES 1 // number of time requests for averaging #define TIME_SYNC_SAMPLES 2 // number of time requests for averaging
#define TIME_SYNC_CYCLE 20 // delay between two time samples [seconds] #define TIME_SYNC_CYCLE 60 // delay between two time samples [seconds]
#define TIME_SYNC_TIMEOUT 120 // timeout waiting for timeserver answer [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 // 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 #define DAYLIGHT_TIME {"CEST", Last, Sun, Mar, 2, 120} // Central European Summer Time

View File

@ -20,7 +20,9 @@ HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
Ticker timesyncer; Ticker timesyncer;
void timeSync() { xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL); } void timeSync() {
xTaskNotifyFromISR(irqHandlerTask, TIMESYNC_IRQ, eSetBits, NULL);
}
time_t timeProvider(void) { time_t timeProvider(void) {
@ -123,9 +125,10 @@ void IRAM_ATTR CLOCKIRQ(void) {
SyncToPPS(); // calibrates UTC systime and advances it +1, see microTime.h SyncToPPS(); // calibrates UTC systime and advances it +1, see microTime.h
if (ClockTask != NULL) #if (defined HAS_IF482 || defined HAS_DCF77)
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits, xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
&xHigherPriorityTaskWoken); &xHigherPriorityTaskWoken);
#endif
#ifdef HAS_DISPLAY #ifdef HAS_DISPLAY
#if (defined GPS_INT || defined RTC_INT) #if (defined GPS_INT || defined RTC_INT)
@ -196,7 +199,7 @@ void clock_init(void) {
"clockloop", // name of task "clockloop", // name of task
2048, // stack size of task 2048, // stack size of task
(void *)&userUTCTime, // start time as task parameter (void *)&userUTCTime, // start time as task parameter
3, // priority of the task 4, // priority of the task
&ClockTask, // task handle &ClockTask, // task handle
1); // CPU core 1); // CPU core
@ -213,7 +216,6 @@ void clock_loop(void *taskparameter) { // ClockTask
static bool led1_state = false; static bool led1_state = false;
uint32_t printtime; uint32_t printtime;
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
TickType_t startTime;
#ifdef HAS_DCF77 #ifdef HAS_DCF77
uint8_t *DCFpulse; // pointer on array with DCF pulse bits 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 // output the next second's pulse after timepulse arrived
for (;;) { for (;;) {
// ensure the notification state is not already pending
xTaskNotifyWait(0x00, ULONG_MAX, &printtime,
portMAX_DELAY); // wait for timepulse
startTime = xTaskGetTickCount(); // wait for timepulse and store UTC time in seconds got
xTaskNotifyWait(0x00, ULONG_MAX, &printtime, portMAX_DELAY);
t = time_t(printtime); // UTC time seconds t = time_t(printtime);
// no confident or no recent time -> suppress clock output // no confident or no recent time -> suppress clock output
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) || if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
(t == last_printtime)) (t == last_printtime))
continue; 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 // pps blink on secondary LED if we have one
#ifdef HAS_TWO_LED #ifdef HAS_TWO_LED
@ -249,23 +270,7 @@ void clock_loop(void *taskparameter) { // ClockTask
led1_state = !led1_state; led1_state = !led1_state;
#endif #endif
#if defined HAS_IF482 last_printtime = t;
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
} // for } // for
} // clock_loop() } // clock_loop()

View File

@ -13,152 +13,142 @@ algorithm in applications without granted license by the patent holder.
#include "timesync.h" #include "timesync.h"
using namespace std::chrono;
// Local logging tag // Local logging tag
static const char TAG[] = __FILE__; static const char TAG[] = __FILE__;
TaskHandle_t timeSyncReqTask; using namespace std::chrono;
static uint8_t time_sync_seqNo = TIMEANSWERPORT_MIN;
static bool lora_time_sync_pending = false;
typedef std::chrono::system_clock myClock; typedef std::chrono::system_clock myClock;
typedef myClock::time_point myClock_timepoint; typedef myClock::time_point myClock_timepoint;
typedef std::chrono::duration<long long int, std::ratio<1, 1000>> typedef std::chrono::duration<long long int, std::ratio<1, 1000>>
myClock_msecTick; myClock_msecTick;
myClock_timepoint time_sync_tx[TIME_SYNC_SAMPLES]; TaskHandle_t timeSyncReqTask = NULL;
myClock_timepoint time_sync_rx[TIME_SYNC_SAMPLES];
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 // send time request message
void send_timesync_req() { void send_timesync_req() {
// if a timesync handshake is pending then exit // if a timesync handshake is pending or we are not joined then exit
if (lora_time_sync_pending) { if (timeSyncPending || !LMIC.devaddr)
// ESP_LOGI(TAG, "Timeserver sync request already pending");
return; return;
} else { // else unblock timesync task
else {
timeSyncPending = true;
ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0); ESP_LOGI(TAG, "[%0.3f] Timeserver sync request started", millis() / 1000.0);
xTaskNotifyGive(timeSyncReqTask);
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
} }
} }
// task for sending time sync requests // task for sending time sync requests
void process_timesync_req(void *taskparameter) { void process_timesync_req(void *taskparameter) {
uint8_t k = 0; uint8_t k;
uint16_t time_to_set_fraction_msec; 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(); auto time_offset_ms = myClock_msecTick::zero();
// wait until we are joined while (1) {
while (!LMIC.devaddr) {
vTaskDelay(pdMS_TO_TICKS(2000));
}
// enqueue timestamp samples in lora sendqueue // reset all timestamps before next sync run
for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) { 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 // wait for kickoff
payload.reset(); ulTaskNotifyTake(pdFALSE, portMAX_DELAY);
payload.addByte(time_sync_seqNo);
SendPayload(TIMEPORT, prio_high);
// process answer, wait for notification from recv_timesync_ans() // collect timestamp samples
if ((xTaskNotifyWait(0x00, ULONG_MAX, &seq_no, for (uint8_t i = 0; i < TIME_SYNC_SAMPLES; i++) {
pdMS_TO_TICKS(TIME_SYNC_TIMEOUT * 1000)) == pdFALSE) || // send sync request to server
(seq_no != time_sync_seqNo)) payload.reset();
goto error; // no valid sequence received before timeout payload.addByte(time_sync_seqNo);
SendPayload(TIMEPORT, prio_high);
else { // calculate time diff from collected timestamps // wait for notification from recv_timesync_ans()
k = seq_no % TIME_SYNC_SAMPLES; 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 // process answer
time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) - else {
time_point_cast<milliseconds>(time_sync_tx[k]); k = seq_no % TIME_SYNC_SAMPLES;
// wrap around seqNo keeping it in time port range // calculate time diff from collected timestamps
time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX) time_offset_ms += time_point_cast<milliseconds>(time_sync_rx[k]) -
? time_sync_seqNo + 1 time_point_cast<milliseconds>(time_sync_tx[k]);
: TIMEANSWERPORT_MIN;
if (i < TIME_SYNC_SAMPLES - 1) { // wrap around seqNo, keeping it in time port range
// wait until next cycle time_sync_seqNo = (time_sync_seqNo < TIMEANSWERPORT_MAX)
vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000)); ? time_sync_seqNo + 1
} else { // before sending last time sample... : TIMEANSWERPORT_MIN;
// ...send flush to open a receive window for last time_sync_answer
// payload.reset(); if (i < TIME_SYNC_SAMPLES - 1) {
// payload.addByte(0x99); // wait until next cycle
// SendPayload(RCMDPORT, prio_high); vTaskDelay(pdMS_TO_TICKS(TIME_SYNC_CYCLE * 1000));
// ...send a alive open a receive window for last time_sync_answer } else { // before sending last time sample...
LMIC_sendAlive(); // ...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();
}
} }
} } // end of for loop to collect timestamp samples
} // for
// begin of time critical section: lock I2C bus to ensure accurate timing // begin of time critical section: lock app irq's and I2C bus
if (!mask_user_IRQ()) if (!mask_user_IRQ())
goto error; // failure goto error; // failure
// average time offset from collected diffs // average time offset over all collected diffs
time_offset_ms /= TIME_SYNC_SAMPLES; time_offset_ms /= TIME_SYNC_SAMPLES;
// calculate time offset with millisecond precision using LMIC's time base, // calculate time offset with millisecond precision using LMIC's time base,
// since we use LMIC's ostime_t txEnd as tx timestamp. // since we use LMIC's ostime_t txEnd as tx timestamp.
// Finally apply calibration const for processing time. // Also apply calibration const to compensate processing time.
time_offset_ms += time_offset_ms +=
milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP); milliseconds(osticks2ms(os_getTime())) + milliseconds(TIME_SYNC_FIXUP);
// calculate absolute time in UTC epoch: convert to whole seconds, round to // calculate absolute time in UTC epoch: convert to whole seconds, round to
// ceil, and calculate fraction milliseconds // ceil, and calculate fraction milliseconds
time_to_set = (uint32_t)(time_offset_ms.count() / 1000) + 1; time_to_set = (uint32_t)(time_offset_ms.count() / 1000) + 1;
// calculate fraction milliseconds // calculate fraction milliseconds
time_to_set_fraction_msec = (uint16_t)(time_offset_ms.count() % 1000); 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 // end of time critical section: release I2C bus and re-enable app irq's
unmask_user_IRQ(); unmask_user_IRQ();
finish: goto finish;
lora_time_sync_pending = false; error:
timeSyncReqTask = NULL; ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out",
vTaskDelete(NULL); // end task millis() / 1000.0);
error: finish:
ESP_LOGW(TAG, "[%0.3f] Timeserver error: handshake timed out", timeSyncPending = false;
millis() / 1000.0);
goto finish; // end task } // infinite while(1)
} }
// called from lorawan.cpp after time_sync_req was sent // called from lorawan.cpp after time_sync_req was sent
void store_time_sync_req(uint32_t timestamp) { void store_time_sync_req(uint32_t timestamp) {
if (lora_time_sync_pending) { if (timeSyncPending) {
uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES; uint8_t k = time_sync_seqNo % TIME_SYNC_SAMPLES;
time_sync_tx[k] += milliseconds(timestamp); time_sync_tx[k] += milliseconds(timestamp);
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d", ESP_LOGD(TAG, "[%0.3f] Timesync request #%d sent at %d.%03d",
millis() / 1000.0, time_sync_seqNo, timestamp / 1000, millis() / 1000.0, k, timestamp / 1000, 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) { int recv_timesync_ans(uint8_t seq_no, uint8_t buf[], uint8_t buf_len) {
// if no timesync handshake is pending then exit // if no timesync handshake is pending then exit
if (!lora_time_sync_pending) if (!timeSyncPending)
return 0; // failure return 0; // failure
// if no time is available or spurious buffer then exit // 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 // construct the timepoint when message was seen on gateway
time_sync_rx[k] += seconds(timestamp_sec) + milliseconds(timestamp_msec); 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]))) { if (timeIsValid(myClock::to_time_t(time_sync_rx[k]))) {
ESP_LOGD(TAG, "[%0.3f] Timesync request #%d rcvd at %d.%03d", 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 // inform processing task
if (timeSyncReqTask) xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
xTaskNotify(timeSyncReqTask, seq_no, eSetBits);
return 1; // success return 1; // success
} else { } else {
@ -252,4 +241,15 @@ void IRAM_ATTR setMyTime(uint32_t t_sec, uint16_t t_msec) {
millis() / 1000.0); 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 #endif