2019-02-24 01:44:55 +01:00
|
|
|
#include "timekeeper.h"
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-31 15:56:56 +02:00
|
|
|
#if !(HAS_LORA)
|
|
|
|
#if (TIME_SYNC_LORASERVER)
|
|
|
|
#error TIME_SYNC_LORASERVER defined, but device has no LORA configured
|
2019-03-09 22:08:57 +01:00
|
|
|
#elif (TIME_SYNC_LORAWAN)
|
|
|
|
#error TIME_SYNC_LORAWAN defined, but device has no LORA configured
|
|
|
|
#endif
|
|
|
|
#endif
|
|
|
|
|
2019-02-21 23:17:01 +01:00
|
|
|
// Local logging tag
|
2019-02-27 00:52:27 +01:00
|
|
|
static const char TAG[] = __FILE__;
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-02-24 15:08:41 +01:00
|
|
|
// symbol to display current time source
|
|
|
|
const char timeSetSymbols[] = {'G', 'R', 'L', '?'};
|
|
|
|
|
2019-03-23 15:12:11 +01:00
|
|
|
#ifdef HAS_IF482
|
|
|
|
HardwareSerial IF482(2); // use UART #2 (#1 may be in use for serial GPS)
|
|
|
|
#endif
|
|
|
|
|
2019-03-03 00:30:57 +01:00
|
|
|
Ticker timesyncer;
|
2019-02-24 23:13:15 +01:00
|
|
|
|
2019-04-14 14:35:48 +02:00
|
|
|
void timeSync() { xTaskNotify(irqHandlerTask, TIMESYNC_IRQ, eSetBits); }
|
2019-03-02 13:32:02 +01:00
|
|
|
|
2019-03-03 00:30:57 +01:00
|
|
|
time_t timeProvider(void) {
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-02-25 00:26:46 +01:00
|
|
|
time_t t = 0;
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-16 21:01:43 +01:00
|
|
|
#if (HAS_GPS)
|
2019-04-14 22:54:27 +02:00
|
|
|
t = gps_pps_time; // fetch recent time from last NEMA record
|
2019-02-27 22:40:58 +01:00
|
|
|
if (t) {
|
2019-02-25 20:22:03 +01:00
|
|
|
#ifdef HAS_RTC
|
2019-03-24 16:20:39 +01:00
|
|
|
set_rtctime(t, do_mutex); // calibrate RTC
|
2019-02-25 20:22:03 +01:00
|
|
|
#endif
|
2019-02-27 22:40:58 +01:00
|
|
|
timeSource = _gps;
|
2019-03-03 17:35:08 +01:00
|
|
|
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync); // regular repeat
|
2019-03-02 13:32:02 +01:00
|
|
|
return t;
|
2019-02-27 22:40:58 +01:00
|
|
|
}
|
2019-02-21 23:17:01 +01:00
|
|
|
#endif
|
2019-02-23 23:39:45 +01:00
|
|
|
|
2019-02-24 13:47:18 +01:00
|
|
|
// no GPS -> fallback to RTC time while trying lora sync
|
2019-02-24 01:44:55 +01:00
|
|
|
#ifdef HAS_RTC
|
2019-02-27 22:40:58 +01:00
|
|
|
t = get_rtctime();
|
|
|
|
if (t) {
|
|
|
|
timeSource = _rtc;
|
2019-03-24 22:02:19 +01:00
|
|
|
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync); // short retry
|
2019-02-27 22:40:58 +01:00
|
|
|
}
|
2019-02-22 23:17:28 +01:00
|
|
|
#endif
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-09 22:08:57 +01:00
|
|
|
// kick off asychronous Lora timeserver timesync if we have
|
2019-03-31 15:56:56 +02:00
|
|
|
#if (HAS_LORA) && (TIME_SYNC_LORASERVER)
|
2019-03-12 23:50:02 +01:00
|
|
|
send_timesync_req();
|
2019-03-09 22:08:57 +01:00
|
|
|
// kick off asychronous lora network sync if we have
|
2019-03-31 15:56:56 +02:00
|
|
|
#elif (HAS_LORA) && (TIME_SYNC_LORAWAN)
|
2019-02-27 22:40:58 +01:00
|
|
|
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
|
2019-02-21 23:17:01 +01:00
|
|
|
#endif
|
|
|
|
|
2019-03-03 17:35:08 +01:00
|
|
|
if (!t) {
|
2019-02-27 22:40:58 +01:00
|
|
|
timeSource = _unsynced;
|
2019-03-24 22:02:19 +01:00
|
|
|
timesyncer.attach(TIME_SYNC_INTERVAL_RETRY * 60, timeSync); // short retry
|
2019-03-03 17:35:08 +01:00
|
|
|
}
|
2019-02-24 23:13:15 +01:00
|
|
|
|
2019-03-02 13:32:02 +01:00
|
|
|
return t;
|
|
|
|
|
|
|
|
} // timeProvider()
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
// helper function to setup a pulse per second for time synchronisation
|
2019-02-24 01:44:55 +01:00
|
|
|
uint8_t timepulse_init() {
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
// use time pulse from GPS as time base with fixed 1Hz frequency
|
|
|
|
#ifdef GPS_INT
|
|
|
|
|
2019-02-27 22:40:58 +01:00
|
|
|
// setup external interupt pin for rising edge GPS INT
|
2019-02-21 23:17:01 +01:00
|
|
|
pinMode(GPS_INT, INPUT_PULLDOWN);
|
|
|
|
// setup external rtc 1Hz clock as pulse per second clock
|
|
|
|
ESP_LOGI(TAG, "Timepulse: external (GPS)");
|
|
|
|
return 1; // success
|
|
|
|
|
|
|
|
// use pulse from on board RTC chip as time base with fixed frequency
|
|
|
|
#elif defined RTC_INT
|
|
|
|
|
2019-02-27 22:40:58 +01:00
|
|
|
// setup external interupt pin for falling edge RTC INT
|
2019-02-21 23:17:01 +01:00
|
|
|
pinMode(RTC_INT, INPUT_PULLUP);
|
|
|
|
|
|
|
|
// setup external rtc 1Hz clock as pulse per second clock
|
|
|
|
if (I2C_MUTEX_LOCK()) {
|
|
|
|
Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz);
|
|
|
|
Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock);
|
|
|
|
I2C_MUTEX_UNLOCK();
|
|
|
|
ESP_LOGI(TAG, "Timepulse: external (RTC)");
|
|
|
|
return 1; // success
|
|
|
|
} else {
|
2019-02-22 22:28:35 +01:00
|
|
|
ESP_LOGE(TAG, "RTC initialization error, I2C bus busy");
|
2019-02-21 23:17:01 +01:00
|
|
|
return 0; // failure
|
|
|
|
}
|
|
|
|
return 1; // success
|
|
|
|
|
|
|
|
#else
|
|
|
|
// use ESP32 hardware timer as time base with adjustable frequency
|
2019-03-03 17:35:08 +01:00
|
|
|
ppsIRQ = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
|
2019-03-03 12:57:00 +01:00
|
|
|
timerAlarmWrite(ppsIRQ, 10000, true); // 1000ms
|
2019-02-21 23:17:01 +01:00
|
|
|
ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)");
|
|
|
|
return 1; // success
|
|
|
|
|
|
|
|
#endif
|
|
|
|
} // timepulse_init
|
|
|
|
|
|
|
|
void timepulse_start(void) {
|
2019-04-14 22:54:27 +02:00
|
|
|
|
2019-02-21 23:17:01 +01:00
|
|
|
#ifdef GPS_INT // start external clock gps pps line
|
|
|
|
attachInterrupt(digitalPinToInterrupt(GPS_INT), CLOCKIRQ, RISING);
|
|
|
|
#elif defined RTC_INT // start external clock rtc
|
|
|
|
attachInterrupt(digitalPinToInterrupt(RTC_INT), CLOCKIRQ, FALLING);
|
|
|
|
#else // start internal clock esp32 hardware timer
|
2019-03-03 12:57:00 +01:00
|
|
|
timerAttachInterrupt(ppsIRQ, &CLOCKIRQ, true);
|
|
|
|
timerAlarmEnable(ppsIRQ);
|
2019-02-21 23:17:01 +01:00
|
|
|
#endif
|
2019-04-14 22:54:27 +02:00
|
|
|
|
|
|
|
#if (HAS_GPS)
|
|
|
|
gps_read();
|
|
|
|
gps_pps_time = gps_status.utctime;
|
|
|
|
#endif
|
2019-04-13 13:59:30 +02:00
|
|
|
|
|
|
|
// start cyclic time sync
|
2019-04-14 22:54:27 +02:00
|
|
|
now(); // ensure sysTime is ßrecent
|
2019-04-13 13:59:30 +02:00
|
|
|
timeSync(); // init systime by RTC or GPS or LORA
|
|
|
|
timesyncer.attach(TIME_SYNC_INTERVAL * 60, timeSync);
|
2019-02-21 23:17:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// interrupt service routine triggered by either pps or esp32 hardware timer
|
|
|
|
void IRAM_ATTR CLOCKIRQ(void) {
|
2019-02-27 22:40:58 +01:00
|
|
|
|
2019-03-24 00:15:04 +01:00
|
|
|
BaseType_t xHigherPriorityTaskWoken = pdFALSE;
|
2019-03-02 13:32:02 +01:00
|
|
|
|
2019-04-14 22:54:27 +02:00
|
|
|
SyncToPPS(); // advance systime, see microTime.h
|
|
|
|
|
|
|
|
// store recent gps time, if we have
|
|
|
|
#if (HAS_GPS)
|
|
|
|
gps_pps_time = gps_status.utctime + 1;
|
|
|
|
#endif
|
2019-03-23 15:12:11 +01:00
|
|
|
|
2019-04-14 22:54:27 +02:00
|
|
|
// advance wall clock, if we have
|
2019-04-10 21:38:17 +02:00
|
|
|
#if (defined HAS_IF482 || defined HAS_DCF77)
|
|
|
|
xTaskNotifyFromISR(ClockTask, uint32_t(now()), eSetBits,
|
|
|
|
&xHigherPriorityTaskWoken);
|
|
|
|
#endif
|
2019-02-27 22:40:58 +01:00
|
|
|
|
2019-04-14 22:54:27 +02:00
|
|
|
// flip time pulse ticker, if needed
|
2019-03-24 01:05:13 +01:00
|
|
|
#ifdef HAS_DISPLAY
|
2019-03-23 15:12:11 +01:00
|
|
|
#if (defined GPS_INT || defined RTC_INT)
|
|
|
|
TimePulseTick = !TimePulseTick; // flip pulse ticker
|
2019-03-24 01:05:13 +01:00
|
|
|
#endif
|
2019-02-21 23:17:01 +01:00
|
|
|
#endif
|
2019-02-27 22:40:58 +01:00
|
|
|
|
2019-03-02 20:58:06 +01:00
|
|
|
// yield only if we should
|
|
|
|
if (xHigherPriorityTaskWoken)
|
|
|
|
portYIELD_FROM_ISR();
|
2019-02-21 23:17:01 +01:00
|
|
|
}
|
|
|
|
|
2019-02-22 22:28:35 +01:00
|
|
|
// helper function to check plausibility of a time
|
2019-03-02 13:32:02 +01:00
|
|
|
time_t timeIsValid(time_t const t) {
|
2019-02-22 22:28:35 +01:00
|
|
|
// is it a time in the past? we use compile date to guess
|
2019-02-25 20:22:03 +01:00
|
|
|
return (t >= compiledUTC() ? t : 0);
|
2019-02-22 22:28:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// helper function to convert compile time to UTC time
|
|
|
|
time_t compiledUTC(void) {
|
2019-03-02 13:32:02 +01:00
|
|
|
static time_t t = myTZ.toUTC(RtcDateTime(__DATE__, __TIME__).Epoch32Time());
|
|
|
|
return t;
|
2019-02-22 22:28:35 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// helper function to convert gps date/time into time_t
|
2019-02-23 23:39:45 +01:00
|
|
|
time_t tmConvert(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh, uint8_t mm,
|
|
|
|
uint8_t ss) {
|
2019-02-22 22:28:35 +01:00
|
|
|
tmElements_t tm;
|
2019-03-12 23:50:02 +01:00
|
|
|
tm.Year = CalendarYrToTm(YYYY); // year offset from 1970 in microTime.h
|
2019-02-22 22:28:35 +01:00
|
|
|
tm.Month = MM;
|
|
|
|
tm.Day = DD;
|
|
|
|
tm.Hour = hh;
|
|
|
|
tm.Minute = mm;
|
|
|
|
tm.Second = ss;
|
|
|
|
return makeTime(tm);
|
|
|
|
}
|
|
|
|
|
2019-02-25 20:22:03 +01:00
|
|
|
// helper function to calculate serial transmit time
|
|
|
|
TickType_t tx_Ticks(uint32_t framesize, unsigned long baud, uint32_t config,
|
|
|
|
int8_t rxPin, int8_t txPins) {
|
|
|
|
|
|
|
|
uint32_t databits = ((config & 0x0c) >> 2) + 5;
|
|
|
|
uint32_t stopbits = ((config & 0x20) >> 5) + 1;
|
2019-03-23 15:12:11 +01:00
|
|
|
uint32_t txTime = (databits + stopbits + 1) * framesize * 1000.0 / baud;
|
|
|
|
// +1 for the startbit
|
2019-02-25 20:22:03 +01:00
|
|
|
|
|
|
|
return round(txTime);
|
|
|
|
}
|
|
|
|
|
2019-03-31 19:13:06 +02:00
|
|
|
#if (defined HAS_IF482 || defined HAS_DCF77)
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-31 19:13:06 +02:00
|
|
|
#if (defined HAS_DCF77 && defined HAS_IF482)
|
2019-02-21 23:17:01 +01:00
|
|
|
#error You must define at most one of IF482 or DCF77!
|
|
|
|
#endif
|
|
|
|
|
|
|
|
void clock_init(void) {
|
|
|
|
|
|
|
|
// setup clock output interface
|
|
|
|
#ifdef HAS_IF482
|
|
|
|
IF482.begin(HAS_IF482);
|
|
|
|
#elif defined HAS_DCF77
|
|
|
|
pinMode(HAS_DCF77, OUTPUT);
|
|
|
|
#endif
|
|
|
|
|
2019-03-02 20:01:27 +01:00
|
|
|
userUTCTime = now();
|
|
|
|
|
|
|
|
xTaskCreatePinnedToCore(clock_loop, // task function
|
|
|
|
"clockloop", // name of task
|
|
|
|
2048, // stack size of task
|
|
|
|
(void *)&userUTCTime, // start time as task parameter
|
2019-04-07 21:54:19 +02:00
|
|
|
4, // priority of the task
|
2019-03-02 20:01:27 +01:00
|
|
|
&ClockTask, // task handle
|
|
|
|
1); // CPU core
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
assert(ClockTask); // has clock task started?
|
|
|
|
} // clock_init
|
|
|
|
|
2019-03-02 20:01:27 +01:00
|
|
|
void clock_loop(void *taskparameter) { // ClockTask
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-02 20:01:27 +01:00
|
|
|
// caveat: don't use now() in this task, it will cause a race condition
|
2019-04-14 14:35:48 +02:00
|
|
|
// due to concurrent access to i2c bus for setting rtc!
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-02 21:25:22 +01:00
|
|
|
#define nextmin(t) (t + DCF77_FRAME_SIZE + 1) // next minute
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-24 01:05:13 +01:00
|
|
|
static bool led1_state = false;
|
2019-03-02 20:01:27 +01:00
|
|
|
uint32_t printtime;
|
2019-03-24 00:15:04 +01:00
|
|
|
time_t t = *((time_t *)taskparameter), last_printtime = 0; // UTC time seconds
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
#ifdef HAS_DCF77
|
2019-03-23 15:12:11 +01:00
|
|
|
uint8_t *DCFpulse; // pointer on array with DCF pulse bits
|
|
|
|
DCFpulse = DCF77_Frame(nextmin(t)); // load first DCF frame before start
|
|
|
|
#elif defined HAS_IF482
|
2019-03-24 16:20:39 +01:00
|
|
|
static TickType_t txDelay = pdMS_TO_TICKS(1000 - IF482_SYNC_FIXUP) -
|
|
|
|
tx_Ticks(IF482_FRAME_SIZE, HAS_IF482);
|
2019-02-21 23:17:01 +01:00
|
|
|
#endif
|
|
|
|
|
2019-04-13 13:59:30 +02:00
|
|
|
// output the next second's pulse/telegram after pps arrived
|
2019-02-21 23:17:01 +01:00
|
|
|
for (;;) {
|
|
|
|
|
2019-04-10 21:38:17 +02:00
|
|
|
// wait for timepulse and store UTC time in seconds got
|
|
|
|
xTaskNotifyWait(0x00, ULONG_MAX, &printtime, portMAX_DELAY);
|
|
|
|
t = time_t(printtime);
|
2019-04-13 13:59:30 +02:00
|
|
|
|
2019-03-24 00:15:04 +01:00
|
|
|
// no confident or no recent time -> suppress clock output
|
|
|
|
if ((timeStatus() == timeNotSet) || !(timeIsValid(t)) ||
|
|
|
|
(t == last_printtime))
|
2019-03-16 21:01:43 +01:00
|
|
|
continue;
|
|
|
|
|
2019-02-21 23:17:01 +01:00
|
|
|
#if defined HAS_IF482
|
|
|
|
|
2019-04-08 21:22:24 +02:00
|
|
|
// 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
|
2019-04-10 21:38:17 +02:00
|
|
|
if (xTaskNotifyWait(0x00, ULONG_MAX, &printtime, txDelay) == pdTRUE)
|
2019-04-08 21:22:24 +02:00
|
|
|
t = time_t(printtime); // new adjusted UTC time seconds
|
|
|
|
|
|
|
|
// send IF482 telegram
|
|
|
|
IF482.print(IF482_Frame(t + 1)); // note: telegram is for *next* second
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
#elif defined HAS_DCF77
|
|
|
|
|
|
|
|
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
|
2019-03-02 20:01:27 +01:00
|
|
|
DCFpulse = DCF77_Frame(nextmin(t)); // generate frame for next minute
|
2019-02-21 23:17:01 +01:00
|
|
|
|
2019-03-02 20:58:06 +01:00
|
|
|
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
|
2019-04-10 21:38:17 +02:00
|
|
|
|
|
|
|
// else we have no recent frame, thus suppressing clock output
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
#endif
|
|
|
|
|
2019-04-10 21:38:17 +02:00
|
|
|
// pps blink on secondary LED if we have one
|
|
|
|
#ifdef HAS_TWO_LED
|
|
|
|
if (led1_state)
|
|
|
|
switch_LED1(LED_OFF);
|
|
|
|
else
|
|
|
|
switch_LED1(LED_ON);
|
|
|
|
led1_state = !led1_state;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
last_printtime = t;
|
|
|
|
|
2019-02-21 23:17:01 +01:00
|
|
|
} // for
|
|
|
|
} // clock_loop()
|
|
|
|
|
2019-03-11 18:09:01 +01:00
|
|
|
#endif // HAS_IF482 || defined HAS_DCF77
|