2019-02-21 23:17:01 +01:00
|
|
|
#include "timemanager.h"
|
|
|
|
|
|
|
|
// Local logging tag
|
|
|
|
static const char TAG[] = "main";
|
|
|
|
|
|
|
|
void time_sync() {
|
|
|
|
// synchonization of systime with external time source (GPS/LORA)
|
2019-02-22 22:28:35 +01:00
|
|
|
// frequently called from cyclic.cpp
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
#ifdef TIME_SYNC_INTERVAL
|
|
|
|
|
|
|
|
time_t lastTimeSync = now() - LastSyncTime; // check if a sync is due
|
|
|
|
|
2019-02-22 23:17:28 +01:00
|
|
|
if ((lastTimeSync >= (TIME_SYNC_INTERVAL * 60000)) || !LastSyncTime) {
|
|
|
|
// is it time to sync with external source?
|
2019-02-21 23:17:01 +01:00
|
|
|
#ifdef HAS_GPS
|
|
|
|
syncTime(get_gpstime()); // attempt sync with GPS time
|
|
|
|
#endif
|
2019-02-22 23:17:28 +01:00
|
|
|
#if defined HAS_LORA && defined TIME_SYNC_LORA
|
|
|
|
if (!TimeIsSynced) // no GPS sync -> try lora sync
|
|
|
|
LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime);
|
|
|
|
#endif
|
|
|
|
}
|
2019-02-21 23:17:01 +01:00
|
|
|
|
|
|
|
#ifdef HAS_RTC
|
|
|
|
if (TimeIsSynced) { // recalibrate RTC, if we have one
|
|
|
|
set_rtctime(now());
|
2019-02-22 22:28:35 +01:00
|
|
|
} else { // we switch to fallback time after a while
|
2019-02-21 23:17:01 +01:00
|
|
|
if ((lastTimeSync >= (TIME_SYNC_TIMEOUT * 60000)) ||
|
2019-02-22 22:28:35 +01:00
|
|
|
!LastSyncTime) { // sync is still due -> use RTC as fallback source
|
2019-02-21 23:17:01 +01:00
|
|
|
syncTime(get_rtctime()); // sync with RTC time
|
2019-02-22 22:28:35 +01:00
|
|
|
TimeIsSynced = false;
|
2019-02-21 23:17:01 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#endif // TIME_SYNC_INTERVAL
|
|
|
|
} // time_sync()
|
|
|
|
|
|
|
|
// helper function to sync time on start of next second
|
|
|
|
int syncTime(time_t t) {
|
2019-02-22 22:28:35 +01:00
|
|
|
if (TimeIsValid(t)) {
|
2019-02-21 23:17:01 +01:00
|
|
|
TimeIsSynced = wait_for_pulse(); // wait for next 1pps timepulse
|
|
|
|
setTime(t);
|
|
|
|
adjustTime(1); // forward time to next second
|
|
|
|
LastSyncTime = now(); // store time of this sync
|
2019-02-22 22:28:35 +01:00
|
|
|
ESP_LOGD(TAG, "Time was set to %02d:%02d:%02d", hour(t), minute(t),
|
2019-02-21 23:17:01 +01:00
|
|
|
second(t));
|
|
|
|
return 1; // success
|
|
|
|
} else {
|
2019-02-22 22:28:35 +01:00
|
|
|
ESP_LOGD(TAG, "Time sync attempt failed");
|
2019-02-21 23:17:01 +01:00
|
|
|
TimeIsSynced = false;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
// failure
|
|
|
|
}
|
|
|
|
|
2019-02-22 22:28:35 +01:00
|
|
|
int syncTime(uint32_t t) { // t is UTC time in seconds epoch
|
2019-02-21 23:17:01 +01:00
|
|
|
return syncTime(static_cast<time_t>(t));
|
|
|
|
}
|
|
|
|
|
|
|
|
// helper function to sync moment on timepulse
|
2019-02-22 22:28:35 +01:00
|
|
|
int wait_for_pulse(void) {
|
2019-02-21 23:17:01 +01:00
|
|
|
// sync on top of next second with 1pps timepulse
|
|
|
|
if (xSemaphoreTake(TimePulse, pdMS_TO_TICKS(1000)) == pdTRUE)
|
2019-02-22 22:28:35 +01:00
|
|
|
return 1; // success
|
2019-02-21 23:17:01 +01:00
|
|
|
ESP_LOGD(TAG, "Missing timepulse");
|
2019-02-22 22:28:35 +01:00
|
|
|
return 0; // failure
|
2019-02-21 23:17:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// helper function to setup a pulse per second for time synchronisation
|
|
|
|
int timepulse_init() {
|
|
|
|
|
|
|
|
// use time pulse from GPS as time base with fixed 1Hz frequency
|
|
|
|
#ifdef GPS_INT
|
|
|
|
|
|
|
|
// setup external interupt for active low RTC INT pin
|
|
|
|
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
|
|
|
|
|
|
|
|
// setup external interupt for active low RTC INT pin
|
|
|
|
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
|
|
|
|
clockCycle = timerBegin(1, 8000, true); // set 80 MHz prescaler to 1/10000 sec
|
|
|
|
timerAlarmWrite(clockCycle, 10000, true); // 1000ms
|
|
|
|
ESP_LOGI(TAG, "Timepulse: internal (ESP32 hardware timer)");
|
|
|
|
return 1; // success
|
|
|
|
|
|
|
|
#endif
|
|
|
|
} // timepulse_init
|
|
|
|
|
|
|
|
void timepulse_start(void) {
|
|
|
|
#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
|
|
|
|
timerAttachInterrupt(clockCycle, &CLOCKIRQ, true);
|
|
|
|
timerAlarmEnable(clockCycle);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
|
|
|
// interrupt service routine triggered by either pps or esp32 hardware timer
|
|
|
|
void IRAM_ATTR CLOCKIRQ(void) {
|
|
|
|
if (ClockTask != NULL)
|
|
|
|
xTaskNotifyFromISR(ClockTask, xTaskGetTickCountFromISR(), eSetBits, NULL);
|
|
|
|
#if defined GPS_INT || defined RTC_INT
|
|
|
|
xSemaphoreGiveFromISR(TimePulse, NULL);
|
|
|
|
TimePulseTick = !TimePulseTick; // flip ticker
|
|
|
|
#endif
|
|
|
|
portYIELD_FROM_ISR();
|
|
|
|
}
|
|
|
|
|
2019-02-22 22:28:35 +01:00
|
|
|
// helper function to check plausibility of a time
|
|
|
|
int TimeIsValid(time_t t) {
|
|
|
|
// is it a time in the past? we use compile date to guess
|
|
|
|
ESP_LOGD(TAG, "t=%d, tt=%d, valid: %s", t, compiledUTC(),
|
|
|
|
(t >= compiledUTC()) ? "yes" : "no");
|
|
|
|
return (t >= compiledUTC());
|
|
|
|
}
|
|
|
|
|
|
|
|
// helper function to convert compile time to UTC time
|
|
|
|
time_t compiledUTC(void) {
|
|
|
|
time_t t = RtcDateTime(__DATE__, __TIME__).Epoch32Time();
|
|
|
|
return myTZ.toUTC(t);
|
|
|
|
}
|
|
|
|
|
|
|
|
// helper function to convert gps date/time into time_t
|
|
|
|
time_t tmConvert_t(uint16_t YYYY, uint8_t MM, uint8_t DD, uint8_t hh,
|
|
|
|
uint8_t mm, uint8_t ss) {
|
|
|
|
tmElements_t tm;
|
2019-02-22 23:17:28 +01:00
|
|
|
tm.Year = CalendarYrToTm(YYYY); // year offset from 1970 in time.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-21 23:17:01 +01:00
|
|
|
#if defined HAS_IF482 || defined HAS_DCF77
|
|
|
|
|
|
|
|
#if defined HAS_DCF77 && defined HAS_IF482
|
|
|
|
#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
|
|
|
|
|
|
|
|
xTaskCreatePinnedToCore(clock_loop, // task function
|
|
|
|
"clockloop", // name of task
|
|
|
|
2048, // stack size of task
|
|
|
|
(void *)1, // task parameter
|
|
|
|
4, // priority of the task
|
|
|
|
&ClockTask, // task handle
|
|
|
|
0); // CPU core
|
|
|
|
|
|
|
|
assert(ClockTask); // has clock task started?
|
|
|
|
} // clock_init
|
|
|
|
|
|
|
|
void clock_loop(void *pvParameters) { // ClockTask
|
|
|
|
|
|
|
|
configASSERT(((uint32_t)pvParameters) == 1); // FreeRTOS check
|
|
|
|
|
|
|
|
TickType_t wakeTime;
|
|
|
|
time_t t;
|
|
|
|
|
|
|
|
#define t1(t) (t + DCF77_FRAME_SIZE + 1) // future time for next DCF77 frame
|
|
|
|
#define t2(t) (t + 1) // future time for sync with 1pps trigger
|
|
|
|
|
|
|
|
// preload first DCF frame before start
|
|
|
|
#ifdef HAS_DCF77
|
|
|
|
DCF77_Frame(t1(now()));
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// output time telegram for second following sec beginning with timepulse
|
|
|
|
for (;;) {
|
|
|
|
xTaskNotifyWait(0x00, ULONG_MAX, &wakeTime,
|
|
|
|
portMAX_DELAY); // wait for timepulse
|
|
|
|
|
|
|
|
if (timeStatus() == timeNotSet) // do we have valid time?
|
|
|
|
continue;
|
|
|
|
|
|
|
|
t = now(); // payload to send to clock
|
|
|
|
|
|
|
|
#if defined HAS_IF482
|
|
|
|
|
|
|
|
IF482_Pulse(t2(t)); // next second
|
|
|
|
|
|
|
|
#elif defined HAS_DCF77
|
|
|
|
|
|
|
|
if (second(t) == DCF77_FRAME_SIZE - 1) // is it time to load new frame?
|
|
|
|
DCF77_Frame(t1(t)); // generate next frame
|
|
|
|
|
|
|
|
if (DCFpulse[DCF77_FRAME_SIZE] ==
|
|
|
|
minute(t1(t))) // have recent frame? (pulses could be missed!)
|
|
|
|
DCF_Pulse(t2(t)); // then output next second of this frame
|
|
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
} // for
|
|
|
|
} // clock_loop()
|
|
|
|
|
|
|
|
#endif // HAS_IF482 || defined HAS_DCF77
|