diff --git a/include/cyclic.h b/include/cyclic.h index 8e578009..13e9419c 100644 --- a/include/cyclic.h +++ b/include/cyclic.h @@ -17,7 +17,6 @@ #endif void doHousekeeping(void); -void do_timesync(void); uint64_t uptime(void); void reset_counters(void); int redirect_log(const char *fmt, va_list args); diff --git a/include/gpsread.h b/include/gpsread.h index affde231..3cd4a931 100644 --- a/include/gpsread.h +++ b/include/gpsread.h @@ -15,5 +15,6 @@ extern TaskHandle_t GpsTask; int gps_init(void); void gps_read(void); void gps_loop(void *pvParameters); +time_t get_gpstime(void); #endif \ No newline at end of file diff --git a/platformio.ini b/platformio.ini index 031ff7bb..3f2f9c0d 100644 --- a/platformio.ini +++ b/platformio.ini @@ -30,7 +30,7 @@ description = Paxcounter is a proof-of-concept ESP32 device for metering passeng [common] ; for release_version use max. 10 chars total, use any decimal format like "a.b.c" -release_version = 1.7.14 +release_version = 1.7.141 ; DEBUG LEVEL: For production run set to 0, otherwise device will leak RAM while running! ; 0=None, 1=Error, 2=Warn, 3=Info, 4=Debug, 5=Verbose debug_level = 3 diff --git a/src/cyclic.cpp b/src/cyclic.cpp index dc9fe12f..543b6e93 100644 --- a/src/cyclic.cpp +++ b/src/cyclic.cpp @@ -7,8 +7,8 @@ // Local logging tag static const char TAG[] = "main"; -uint32_t userUTCTime; // Seconds since the UTC epoch -unsigned long nextTimeSync = millis(); +time_t userUTCTime; // Seconds since the UTC epoch +unsigned long nextLoraTimeSync = millis(); // do all housekeeping void doHousekeeping() { @@ -23,12 +23,14 @@ void doHousekeeping() { spi_housekeeping(); lora_housekeeping(); -// time sync once per TIME_SYNC_INTERVAL -#ifdef TIME_SYNC_INTERVAL - if (millis() >= nextTimeSync) { - nextTimeSync = - millis() + TIME_SYNC_INTERVAL * 60000; // set up next time sync period - do_timesync(); +// do cyclic time sync with LORA network +#ifdef TIME_SYNC_INTERVAL_LORA + if (millis() >= nextLoraTimeSync) { + nextLoraTimeSync = millis() + TIME_SYNC_INTERVAL_LORA * + 60000; // set up next time sync period + // Schedule a network time sync request at the next possible time + LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime); + ESP_LOGI(TAG, "LORAWAN time request scheduled"); } #endif @@ -120,37 +122,6 @@ void reset_counters() { macs_ble = 0; } -void do_timesync() { -#ifdef TIME_SYNC_INTERVAL - -// set system time to time source GPS, if we have valid gps time -#ifdef HAS_GPS - if (gps.time.isValid()) { - setTime(gps.time.hour(), gps.time.minute(), gps.time.second(), - gps.date.day(), gps.date.month(), gps.date.year()); -// set RTC time to time source GPS, if RTC is present -#ifdef HAS_RTC - if (!set_rtctime(RtcDateTime(now()))) - ESP_LOGE(TAG, "RTC set time failure"); -#endif - time_t tt = myTZ.toLocal(now()); - ESP_LOGI(TAG, "GPS has set system time to %02d/%02d/%d %02d:%02d:%02d", - month(tt), day(tt), year(tt), hour(tt), minute(tt), second(tt)); - return; - } else { - ESP_LOGI(TAG, "No valid GPS time"); - } - - // set system time to time source LoRa Network, if network supports DevTimeReq -#elif defined LMIC_ENABLE_DeviceTimeReq - // Schedule a network time sync request at the next possible time - LMIC_requestNetworkTime(user_request_network_time_callback, &userUTCTime); - ESP_LOGI(TAG, "Network time request scheduled"); -#endif // HAS_GPS - -#endif // TIME_SYNC_INTERVAL -} // do_timesync() - #ifndef VERBOSE int redirect_log(const char *fmt, va_list args) { // do nothing diff --git a/src/gpsread.cpp b/src/gpsread.cpp index 55d8ba2d..2aff4947 100644 --- a/src/gpsread.cpp +++ b/src/gpsread.cpp @@ -53,6 +53,32 @@ void gps_read() { gps.passedChecksum(), gps.failedChecksum(), gps.sentencesWithFix()); } +// 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; + tm.Year = YYYY - 1970; // note year argument is offset from 1970 in time.h + tm.Month = MM; + tm.Day = DD; + tm.Hour = hh; + tm.Minute = mm; + tm.Second = ss; + return makeTime(tm); +} + +// function to fetch current time from gps +time_t get_gpstime(void) { + // never call now() in this function, this would cause a recursion! + time_t t = 0; + if (gps.time.age() < 1500) { + t = tmConvert_t(gps.date.year(), gps.date.month(), gps.date.day(), + gps.time.hour(), gps.time.minute(), gps.time.second()); + } else { + ESP_LOGW(TAG, "GPS has no confident time"); + } + return t; +} // get_gpstime() + // GPS serial feed FreeRTos Task void gps_loop(void *pvParameters) { diff --git a/src/hal/generic.h b/src/hal/generic.h index 1c9d3b39..2b34967f 100644 --- a/src/hal/generic.h +++ b/src/hal/generic.h @@ -41,7 +41,7 @@ #define BOARD_HAS_PSRAM // use extra 4MB extern RAM #define HAS_GPS 1 // use if board has GPS -#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_12, GPIO_NUM_15 // UBlox NEO 6M or 7M with default configuration +#define GPS_SERIAL 9600, SERIAL_8N1, GPIO_NUM_17, GPIO_NUM_16 // UBlox NEO 6M or 7M with default configuration // Pins for I2C interface of OLED Display #define MY_OLED_SDA (4) @@ -50,6 +50,10 @@ // Pins for on board DS3231 RTC chip #define HAS_RTC MY_OLED_SDA, MY_OLED_SCL // SDA, SCL +#define RTC_INT GPIO_NUM_34 // interrupt input from rtc + +// Settings for IF482 interface +#define HAS_IF482 9600, SERIAL_7E1, GPIO_NUM_12, GPIO_NUM_14 // IF482 serial port parameters // Pins for LORA chip SPI interface, reset line and interrupt lines #define LORA_SCK (5) diff --git a/src/if482.cpp b/src/if482.cpp index 48d98a47..3914c7d2 100644 --- a/src/if482.cpp +++ b/src/if482.cpp @@ -1,7 +1,19 @@ #if defined HAS_IF482 && defined HAS_RTC -/* +/* NOTE: +The IF482 Generator needs an high precise 1 Hz clock signal which cannot be +acquired in suitable precision on the ESP32 SoC itself. Additional clocking +hardware is required, ususally the clock signal is generated by external RTC or +GPS chip or a GPS chip which can generate a precise clock signal (+/- 2ppm). In +this example code we use a Maxim DS3231 RTC chip, and configure it's interrupt +output as clock output. The clock signal triggers an interrupt on the ESP32, +which controls the realtime output of IF482 telegram. This is why code in +IF482.cpp depends on code in RTCTIME.cpp. +*/ +/////////////////////////////////////////////////////////////////////////////// + +/* IF482 Generator to control clocks with IF482 telegram input (e.g. BÜRK BU190) Example IF482 telegram: "OAL160806F170400" @@ -63,6 +75,7 @@ L: Local Time not evaluated by model BU-190 */ +/////////////////////////////////////////////////////////////////////////////// #include "if482.h" @@ -79,7 +92,7 @@ int if482_init(void) { // open serial interface IF482.begin(HAS_IF482); - // use rtc 1Hz clock for triggering IF482 telegram send + // use external rtc 1Hz clock for triggering IF482 telegram Rtc.SetSquareWavePinClockFrequency(DS3231SquareWaveClock_1Hz); Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeClock); pinMode(RTC_INT, INPUT_PULLUP); @@ -124,7 +137,7 @@ void if482_loop(void *pvParameters) { time_t t, tt; const TickType_t shotTime = pdMS_TO_TICKS(IF482_OFFSET); - // wait until begin of a new second + // wait until begin of a new second to sync clock signal and absolute time t = tt = now(); do { tt = now(); @@ -146,7 +159,7 @@ void if482_loop(void *pvParameters) { // now we're synced to start of second t and wait // until it's time to start transmit telegram for t+1 vTaskDelayUntil(&wakeTime, shotTime); - IF482.print(if482Telegram(t+1)); + IF482.print(if482Telegram(t + 1)); } vTaskDelete(IF482Task); // shoud never be reached } // if482_loop() diff --git a/src/lorawan.cpp b/src/lorawan.cpp index dcf8e3a4..fa53fdd7 100644 --- a/src/lorawan.cpp +++ b/src/lorawan.cpp @@ -453,7 +453,7 @@ void user_request_network_time_callback(void *pVoidUserUTCTime, uint32_t requestDelaySec = osticks2ms(ticksNow - ticksRequestSent) / 1000; *pUserUTCTime += requestDelaySec; - // Update system time with time read from the network + // Update system time with time read from the network setTime(*pUserUTCTime); #ifdef HAS_RTC if (!set_rtctime(*pUserUTCTime)) diff --git a/src/main.cpp b/src/main.cpp index c671f4d0..64c50093 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -192,7 +192,13 @@ void setup() { #ifdef HAS_RTC strcat_P(features, " RTC"); assert(rtc_init()); - sync_rtctime(); + setSyncProvider(&get_rtctime); + if (timeStatus() != timeSet) + ESP_LOGI(TAG, "Unable to sync system time with RTC"); + else + ESP_LOGI(TAG, "RTC has set the system time"); + setSyncInterval(TIME_SYNC_INTERVAL_RTC); + #ifdef HAS_IF482 strcat_P(features, " IF482"); assert(if482_init()); @@ -205,6 +211,7 @@ void setup() { &IF482Task, // task handle 0); // CPU core #endif // HAS_IF482 + #endif // HAS_RTC // initialize wifi antenna @@ -416,6 +423,15 @@ void setup() { #endif #endif // HAS_BUTTON +#ifdef HAS_GPS + setSyncProvider(&get_gpstime); + if (timeStatus() != timeSet) + ESP_LOGI(TAG, "Unable to sync system time with GPS"); + else + ESP_LOGI(TAG, "GPS has set the system time"); + setSyncInterval(TIME_SYNC_INTERVAL_GPS); +#endif + // start RTC interrupt #if defined HAS_IF482 && defined HAS_RTC // setup external interupt for active low RTC INT pin diff --git a/src/paxcounter.conf b/src/paxcounter.conf index 050fe655..11151144 100644 --- a/src/paxcounter.conf +++ b/src/paxcounter.conf @@ -82,9 +82,11 @@ #define RESPONSE_TIMEOUT_MS 60000 // firmware binary server connection timeout [milliseconds] // settings for syncing time of node and external time sources -#define TIME_SYNC_INTERVAL 60 // sync time each ... minutes with external source [default = 60], comment out means off +#define TIME_SYNC_INTERVAL_GPS 5 // sync time each ... minutes with GPS [default = 5], comment out means off #define TIME_SYNC_INTERVAL_RTC 5 // sync time each ... minutes with RTC [default = 5], comment out means off +//#define TIME_SYNC_INTERVAL_LORA 60 // sync time each ... minutes with LORA network [default = 60], comment out means off #define IF482_OFFSET 984 // 1sec minus IF482 serial transmit time [ms]: e.g. 9 bits * 17 bytes * 1/9600 bps = 16ms + // 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 STANDARD_TIME {"CET ", Last, Sun, Oct, 3, 60} // Central European Standard Time diff --git a/src/rtctime.cpp b/src/rtctime.cpp index d0a7b403..044a8a96 100644 --- a/src/rtctime.cpp +++ b/src/rtctime.cpp @@ -57,11 +57,11 @@ error: } // rtc_init() -int set_rtctime(uint32_t UTCTime) { +int set_rtctime(uint32_t t) { // return = 0 -> error / return = 1 -> success // block i2c bus access if (I2C_MUTEX_LOCK()) { - Rtc.SetDateTime(RtcDateTime(UTCTime)); + Rtc.SetDateTime(RtcDateTime(t)); I2C_MUTEX_UNLOCK(); // release i2c bus access return 1; } @@ -81,44 +81,20 @@ int set_rtctime(RtcDateTime t) { time_t get_rtctime(void) { // never call now() in this function, this would cause a recursion! - time_t tt = 0; + time_t t = 0; // block i2c bus access if (I2C_MUTEX_LOCK()) { - if (!Rtc.IsDateTimeValid()) { - ESP_LOGW(TAG, "RTC has no confident time"); + if (Rtc.IsDateTimeValid()) { + RtcDateTime tt = Rtc.GetDateTime(); + t = tt.Epoch32Time(); } else { - RtcDateTime t = Rtc.GetDateTime(); - tt = t.Epoch32Time(); + ESP_LOGW(TAG, "RTC has no confident time"); } I2C_MUTEX_UNLOCK(); // release i2c bus access - return tt; } - return tt; + return t; } // get_rtctime() -void sync_rtctime(void) { - if (timeStatus() != timeSet) { // do we need time sync? - time_t t = get_rtctime(); - if (t) { // have we got a valid time from RTC? - setTime(t); - time_t tt = myTZ.toLocal(t); - ESP_LOGI(TAG, "RTC has set system time to %02d/%02d/%d %02d:%02d:%02d", - month(tt), day(tt), year(tt), hour(tt), minute(tt), second(tt)); - } else - ESP_LOGW(TAG, "System time was not synced"); - } - -#ifdef TIME_SYNC_INTERVAL_RTC - setSyncProvider(&get_rtctime); // does not sync if callback function returns 0 - if (timeStatus() != timeSet) - ESP_LOGI("Unable to sync with the RTC"); - else - ESP_LOGI("RTC has set the system time"); - setSyncInterval(TIME_SYNC_INTERVAL_RTC); -#endif - -} // sync_rtctime; - float get_rtctemp(void) { // block i2c bus access if (I2C_MUTEX_LOCK()) { @@ -127,6 +103,6 @@ float get_rtctemp(void) { return temp.AsFloatDegC(); } // while return 0; -} // get_rtc() +} // get_rtctemp() #endif // HAS_RTC \ No newline at end of file