From b386b73bd2ff72fd76cebf2fcdd53c181d2fad4f Mon Sep 17 00:00:00 2001 From: Gijs Noorlander Date: Tue, 3 Mar 2020 00:01:45 +0100 Subject: [PATCH] [Cleanup] Refactor parseSystemVariables + move time functions to .h/.cpp Trying to make the parseSystemVariables function smaller Moved time related functions to their respective .h/.cpp classes. This will make the code a bit more clean and also reduces the build size. --- src/ESPEasy-Globals.h | 4 + src/ESPEasy.ino | 33 +- src/ESPEasyRules.ino | 1 + src/ESPEasyTimeTypes.h | 225 +---- src/ESPEasyWiFiEvent.cpp | 2 + src/ESPEasyWifi_ProcessEvent.ino | 4 +- src/ESPEasy_fdwdecl.h | 8 + src/Misc.ino | 6 +- src/Scheduler.ino | 21 + src/StringConverter.ino | 178 +--- src/StringProvider.ino | 2 +- src/TimeESPeasy.ino | 964 -------------------- src/TimeZoneESPeasy.ino | 268 ------ src/WebServer_AdvancedConfigPage.ino | 8 +- src/WebServer_DownloadPage.ino | 4 +- src/WebServer_RootPage.ino | 2 +- src/WebServer_SysInfoPage.ino | 4 +- src/WebServer_TimingStats.ino | 4 +- src/_C016.ino | 2 +- src/_P081_Cron.ino | 12 +- src/_P082_GPS.ino | 13 +- src/src/DataStructs/TimeChangeRule.cpp | 31 + src/src/DataStructs/TimeChangeRule.h | 38 + src/src/DataStructs/Web_StreamingBuffer.cpp | 1 + src/src/DataStructs/timer_id_couple.cpp | 16 + src/src/DataStructs/timer_id_couple.h | 23 + src/src/Globals/ESPEasy_time.cpp | 3 + src/src/Globals/ESPEasy_time.h | 10 + src/src/Globals/RTC.h | 2 +- src/src/Globals/TimeZone.cpp | 3 + src/src/Globals/TimeZone.h | 9 + src/src/Helpers/ESPEasy_time.cpp | 633 +++++++++++++ src/src/Helpers/ESPEasy_time.h | 192 ++++ src/src/Helpers/ESPEasy_time_calc.cpp | 291 ++++++ src/src/Helpers/ESPEasy_time_calc.h | 57 ++ src/src/Helpers/ESPEasy_time_zone.cpp | 198 ++++ src/src/Helpers/ESPEasy_time_zone.h | 118 +++ src/src/Helpers/StringConverter.cpp | 42 + src/src/Helpers/StringConverter.h | 12 + src/src/Helpers/SystemVariables.cpp | 277 ++++++ src/src/Helpers/SystemVariables.h | 85 ++ src/src/Helpers/msecTimerHandlerStruct.cpp | 142 +++ src/src/Helpers/msecTimerHandlerStruct.h | 53 ++ 43 files changed, 2351 insertions(+), 1650 deletions(-) create mode 100644 src/src/DataStructs/TimeChangeRule.cpp create mode 100644 src/src/DataStructs/TimeChangeRule.h create mode 100644 src/src/DataStructs/timer_id_couple.cpp create mode 100644 src/src/DataStructs/timer_id_couple.h create mode 100644 src/src/Globals/ESPEasy_time.cpp create mode 100644 src/src/Globals/ESPEasy_time.h create mode 100644 src/src/Globals/TimeZone.cpp create mode 100644 src/src/Globals/TimeZone.h create mode 100644 src/src/Helpers/ESPEasy_time.cpp create mode 100644 src/src/Helpers/ESPEasy_time.h create mode 100644 src/src/Helpers/ESPEasy_time_calc.cpp create mode 100644 src/src/Helpers/ESPEasy_time_calc.h create mode 100644 src/src/Helpers/ESPEasy_time_zone.cpp create mode 100644 src/src/Helpers/ESPEasy_time_zone.h create mode 100644 src/src/Helpers/StringConverter.cpp create mode 100644 src/src/Helpers/StringConverter.h create mode 100644 src/src/Helpers/SystemVariables.cpp create mode 100644 src/src/Helpers/SystemVariables.h create mode 100644 src/src/Helpers/msecTimerHandlerStruct.cpp create mode 100644 src/src/Helpers/msecTimerHandlerStruct.h diff --git a/src/ESPEasy-Globals.h b/src/ESPEasy-Globals.h index 7e6d7d8a5..36bdfc068 100644 --- a/src/ESPEasy-Globals.h +++ b/src/ESPEasy-Globals.h @@ -29,7 +29,11 @@ #include "src/DataStructs/ESPEasyLimits.h" #include "src/DataStructs/EventQueue.h" +#include "src/Helpers/msecTimerHandlerStruct.h" #include "ESPEasy_plugindefs.h" +#include "src/Globals/Device.h" +#include "src/Globals/Settings.h" +#include "src/Globals/ESPEasy_time.h" diff --git a/src/ESPEasy.ino b/src/ESPEasy.ino index 3f87e17e3..ca5dc7965 100644 --- a/src/ESPEasy.ino +++ b/src/ESPEasy.ino @@ -257,7 +257,7 @@ void setup() lastBootCause=BOOT_CAUSE_DEEP_SLEEP; } else { - restoreLastKnownUnixTime(); + node_time.restoreLastKnownUnixTime(RTC.lastSysTime, RTC.deepSleepState); log = F("INIT : Warm boot #"); } @@ -392,8 +392,8 @@ void setup() if (Settings.UDPPort != 0) portUDP.begin(Settings.UDPPort); - if (systemTimePresent()) - initTime(); + if (node_time.systemTimePresent()) + node_time.initTime(); #if FEATURE_ADC_VCC if (!wifiConnectInProgress) { @@ -815,8 +815,31 @@ void runOncePerSecond() cmd_within_mainloop = 0; } // clock events - if (systemTimePresent()) - checkTime(); + if (node_time.reportNewMinute()) { + String dummy; + PluginCall(PLUGIN_CLOCK_IN, 0, dummy); + if (Settings.UseRules) + { + String event; + event.reserve(21); + event = F("Clock#Time="); + event += node_time.weekday_str(); + event += ","; + + if (node_time.hour() < 10) { + event += '0'; + } + event += node_time.hour(); + event += ":"; + + if (node_time.minute() < 10) { + event += '0'; + } + event += node_time.minute(); + // TD-er: Do not add to the eventQueue, but execute right now. + rulesProcessing(event); + } + } // unsigned long start = micros(); String dummy; diff --git a/src/ESPEasyRules.ino b/src/ESPEasyRules.ino index f94f21736..ad08f5fe7 100644 --- a/src/ESPEasyRules.ino +++ b/src/ESPEasyRules.ino @@ -5,6 +5,7 @@ #include "src/Globals/Device.h" #include "src/Globals/Plugins.h" #include "src/Globals/Plugins_other.h" +#include "src/Helpers/ESPEasy_time_calc.h" String EventToFileName(const String& eventName) { int size = eventName.length(); diff --git a/src/ESPEasyTimeTypes.h b/src/ESPEasyTimeTypes.h index d19527eb4..c0b5f43ce 100644 --- a/src/ESPEasyTimeTypes.h +++ b/src/ESPEasyTimeTypes.h @@ -5,14 +5,9 @@ #include #include +#include "src/DataStructs/TimeChangeRule.h" #include "src/Globals/Plugins.h" -#define MAX_SCHEDULER_WAIT_TIME 5 // Max delay used in the scheduler for passing idle time. - -// convenient constants for TimeChangeRules -enum week_t { Last = 0, First, Second, Third, Fourth }; -enum dow_t { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat }; -enum month_t { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }; enum timeSource_t { No_time_source, @@ -21,50 +16,9 @@ enum timeSource_t { GPS_time_source }; -// structure to describe rules for when daylight/summer time begins, -// or when standard time begins. -// For Daylight Saving Time Around the World, see: -// - https://www.timeanddate.com/time/dst/2018.html -// - https://en.wikipedia.org/wiki/Daylight_saving_time_by_country -struct TimeChangeRule { - TimeChangeRule() : week(0), dow(1), month(1), hour(0), offset(0) {} - - TimeChangeRule(uint8_t weeknr, uint8_t downr, uint8_t m, uint8_t h, uint16_t minutesoffset) : - week(weeknr), dow(downr), month(m), hour(h), offset(minutesoffset) {} - - // Construct time change rule from stored values optimized for minimum space. - TimeChangeRule(uint16_t flash_stored_value, int16_t minutesoffset) : offset(minutesoffset) { - hour = flash_stored_value & 0x001f; - month = (flash_stored_value >> 5) & 0x000f; - dow = (flash_stored_value >> 9) & 0x0007; - week = (flash_stored_value >> 12) & 0x0007; - } - - uint16_t toFlashStoredValue() const { - uint16_t value = hour; - - value = value | (month << 5); - value = value | (dow << 9); - value = value | (week << 12); - return value; - } - - bool isValid() const { - return (week <= 4) && (dow != 0) && (dow <= 7) && - (month != 0) && (month <= 12) && (hour <= 23) && - (offset > -720) && (offset < 900); // UTC-12h ... UTC+14h + 1h DSToffset - } - - uint8_t week; // First, Second, Third, Fourth, or Last week of the month - uint8_t dow; // day of week, 1=Sun, 2=Mon, ... 7=Sat - uint8_t month; // 1=Jan, 2=Feb, ... 12=Dec - uint8_t hour; // 0-23 - int16_t offset; // offset from UTC in minutes -}; // Forward declartions -void setExternalTimeSource(double time, timeSource_t source); -void applyTimeZone(uint32_t curTime = 0); + void setTimeZone(const TimeChangeRule& dstStart, const TimeChangeRule& stdStart, uint32_t curTime = 0); @@ -75,12 +29,6 @@ String getTimeString(char delimiter, String getTimeString_ampm(char delimiter, bool show_seconds = true); -long timeDiff(unsigned long prev, - unsigned long next) ICACHE_RAM_ATTR; -long timePassedSince(unsigned long timestamp) ICACHE_RAM_ATTR; -boolean timeOutReached(unsigned long timer) ICACHE_RAM_ATTR; -long usecPassedSince(unsigned long timestamp) ICACHE_RAM_ATTR; -boolean usecTimeOutReached(unsigned long timer) ICACHE_RAM_ATTR; void setPluginTaskTimer(unsigned long msecFromNow, taskIndex_t taskIndex, int Par1, @@ -103,175 +51,6 @@ void setGPIOTimer(unsigned long msecFromNow, int Par5 = 0); -/*********************************************************************************************\ -* TimerHandler Used by the Scheduler -\*********************************************************************************************/ - -struct timer_id_couple { - timer_id_couple(unsigned long id, unsigned long newtimer) : _id(id), _timer(newtimer) {} - - timer_id_couple(unsigned long id) : _id(id) { - _timer = millis(); - } - - bool operator<(const timer_id_couple& other) { - const unsigned long now(millis()); - - // timediff > 0, means timer has already passed - return timeDiff(_timer, now) > timeDiff(other._timer, now); - } - - unsigned long _id; - unsigned long _timer; -}; - -struct msecTimerHandlerStruct { - msecTimerHandlerStruct() : get_called(0), get_called_ret_id(0), max_queue_length(0), - last_exec_time_usec(0), total_idle_time_usec(0), idle_time_pct(0.0), is_idle(false), eco_mode(true) - { - last_log_start_time = millis(); - } - - void setEcoMode(bool enabled) { - eco_mode = enabled; - } - - void registerAt(unsigned long id, unsigned long timer) { - timer_id_couple item(id, timer); - - insert(item); - } - - // Check if timeout has been reached and also return its set timer. - // Return 0 if no item has reached timeout moment. - unsigned long getNextId(unsigned long& timer) { - ++get_called; - - if (_timer_ids.empty()) { - recordIdle(); - - if (eco_mode) { - delay(MAX_SCHEDULER_WAIT_TIME); // Nothing to do, try save some power. - } - return 0; - } - timer_id_couple item = _timer_ids.front(); - const long passed = timePassedSince(item._timer); - - if (passed < 0) { - // No timeOutReached - recordIdle(); - - if (eco_mode) { - long waitTime = (-1 * passed) - 1; // will be non negative - - if (waitTime > MAX_SCHEDULER_WAIT_TIME) { - waitTime = MAX_SCHEDULER_WAIT_TIME; - } else if (waitTime < 0) { - // Should not happen, but just to be sure we will not wait forever. - waitTime = 0; - } - delay(waitTime); - } - return 0; - } - recordRunning(); - unsigned long size = _timer_ids.size(); - - if (size > max_queue_length) { max_queue_length = size; } - _timer_ids.pop_front(); - timer = item._timer; - ++get_called_ret_id; - return item._id; - } - - String getQueueStats() { - String result; - - result += get_called; - result += '/'; - result += get_called_ret_id; - result += '/'; - result += max_queue_length; - result += '/'; - result += idle_time_pct; - get_called = 0; - get_called_ret_id = 0; - - // max_queue_length = 0; - return result; - } - - void updateIdleTimeStats() { - const long duration = timePassedSince(last_log_start_time); - - last_log_start_time = millis(); - idle_time_pct = total_idle_time_usec / duration / 10.0; - total_idle_time_usec = 0; - } - - float getIdleTimePct() { - return idle_time_pct; - } - -private: - - struct match_id { - match_id(unsigned long id) : _id(id) {} - - bool operator()(const timer_id_couple& item) { - return _id == item._id; - } - - unsigned long _id; - }; - - void insert(const timer_id_couple& item) { - if (item._id == 0) { return; } - - // Make sure only one is present with the same id. - _timer_ids.remove_if(match_id(item._id)); - const bool mustSort = !_timer_ids.empty(); - _timer_ids.push_front(item); - - if (mustSort) { - _timer_ids.sort(); // TD-er: Must check if this is an expensive operation. - } - - // It should be a relative light operation, to insert into a sorted list. - // Perhaps it is better to use std::set ???? - // Keep in mind: order is based on timer, uniqueness is based on id. - } - - void recordIdle() { - if (is_idle) { return; } - last_exec_time_usec = micros(); - is_idle = true; - delay(0); // Nothing to do, so leave time for backgroundtasks - } - - void recordRunning() { - if (!is_idle) { return; } - is_idle = false; - total_idle_time_usec += usecPassedSince(last_exec_time_usec); - } - - // Statistics - unsigned long get_called; - unsigned long get_called_ret_id; - unsigned long max_queue_length; - - // Compute idle system time - unsigned long last_exec_time_usec; - unsigned long total_idle_time_usec; - unsigned long last_log_start_time; - float idle_time_pct; - bool is_idle; - bool eco_mode; - - // The list of set timers - std::list_timer_ids; -}; #endif /* ESPEASY_TIMETYPES_H_ */ diff --git a/src/ESPEasyWiFiEvent.cpp b/src/ESPEasyWiFiEvent.cpp index ba2780c26..2664913a0 100644 --- a/src/ESPEasyWiFiEvent.cpp +++ b/src/ESPEasyWiFiEvent.cpp @@ -5,6 +5,8 @@ #include "src/DataStructs/RTCStruct.h" +#include "src/Helpers/ESPEasy_time_calc.h" + #ifdef ESP32 void WiFi_Access_Static_IP::set_use_static_ip(bool enabled) { _useStaticIp = enabled; diff --git a/src/ESPEasyWifi_ProcessEvent.ino b/src/ESPEasyWifi_ProcessEvent.ino index 60d6e2bbc..b40f205a3 100644 --- a/src/ESPEasyWifi_ProcessEvent.ino +++ b/src/ESPEasyWifi_ProcessEvent.ino @@ -256,8 +256,8 @@ void processGotIP() { } // First try to get the time, since that may be used in logs - if (systemTimePresent()) { - initTime(); + if (node_time.systemTimePresent()) { + node_time.initTime(); } #ifdef USES_MQTT mqtt_reconnect_count = 0; diff --git a/src/ESPEasy_fdwdecl.h b/src/ESPEasy_fdwdecl.h index c33643a56..7116ec99e 100644 --- a/src/ESPEasy_fdwdecl.h +++ b/src/ESPEasy_fdwdecl.h @@ -28,6 +28,8 @@ #include +#include + // Forward declaration to give access to global member variables float & getUserVar(unsigned int varIndex); @@ -139,6 +141,7 @@ String boolToString(bool value); bool isInt(const String& tBuf); String formatToHex(unsigned long value, const String& prefix); String formatToHex(unsigned long value); +String getNumerical(const String& tBuf, bool mustBeInteger); float getCPUload(); int getLoopCountPerSec(); @@ -194,4 +197,9 @@ void delayBackground(unsigned long dsdelay); void setIntervalTimerOverride(unsigned long id, unsigned long msecFromNow); //implemented in Scheduler.ino + +byte PluginCall(byte Function, struct EventStruct *event, String& str); +bool beginWiFiUDP_randomPort(WiFiUDP& udp); +String toString(float value, byte decimals); + #endif // ESPEASY_FWD_DECL_H diff --git a/src/Misc.ino b/src/Misc.ino index 2fe8a566d..5be3f0b6c 100644 --- a/src/Misc.ino +++ b/src/Misc.ino @@ -1518,7 +1518,7 @@ void prepareShutdown() saveUserVarToRTC(); SPIFFS.end(); delay(100); // give the node time to flush all before reboot or sleep - now(); + node_time.now(); saveToRTC(); } @@ -2461,9 +2461,9 @@ void SendValueLogger(taskIndex_t TaskIndex) LoadTaskSettings(TaskIndex); for (byte varNr = 0; varNr < Device[DeviceIndex].ValueCount; varNr++) { - logger += getDateString('-'); + logger += node_time.getDateString('-'); logger += ' '; - logger += getTimeString(':'); + logger += node_time.getTimeString(':'); logger += ','; logger += Settings.Unit; logger += ','; diff --git a/src/Scheduler.ino b/src/Scheduler.ino index d9e5d4286..e474398cd 100644 --- a/src/Scheduler.ino +++ b/src/Scheduler.ino @@ -5,6 +5,8 @@ #include "src/Globals/CPlugins.h" #include "src/Globals/NPlugins.h" #include "src/Globals/Plugins.h" +#include "src/Helpers/ESPEasy_time_calc.h" + #include "ESPEasy_plugindefs.h" #define TIMER_ID_SHIFT 28 @@ -145,6 +147,25 @@ void handle_schedule() { * These timers set a new scheduled timer, based on the old value. * This will make their interval as constant as possible. \*********************************************************************************************/ +void setNextTimeInterval(unsigned long& timer, const unsigned long step) { + timer += step; + const long passed = timePassedSince(timer); + + if (passed < 0) { + // Event has not yet happened, which is fine. + return; + } + + if (static_cast(passed) > step) { + // No need to keep running behind, start again. + timer = millis() + step; + return; + } + + // Try to get in sync again. + timer = millis() + (step - passed); +} + void setIntervalTimer(unsigned long id) { setIntervalTimer(id, millis()); } diff --git a/src/StringConverter.ino b/src/StringConverter.ino index fff55181b..dc33275b5 100644 --- a/src/StringConverter.ino +++ b/src/StringConverter.ino @@ -1,9 +1,13 @@ #include "src/Globals/CRCValues.h" #include "src/Globals/Device.h" +#include "src/Globals/ESPEasy_time.h" #include "src/Globals/ESPEasyWiFiEvent.h" #include "src/Globals/MQTT.h" #include "src/Globals/Plugins.h" +#include "src/Helpers/StringConverter.h" +#include "src/Helpers/SystemVariables.h" + /********************************************************************************************\ Convert a char string to integer \*********************************************************************************************/ @@ -501,49 +505,9 @@ void htmlStrongEscape(String& html) html = escaped; } -// ******************************************************************************** -// URNEncode char string to string object -// ******************************************************************************** -String URLEncode(const char *msg) -{ - const char *hex = "0123456789abcdef"; - String encodedMsg; - encodedMsg.reserve(strlen(msg)); - while (*msg != '\0') { - if ((('a' <= *msg) && (*msg <= 'z')) - || (('A' <= *msg) && (*msg <= 'Z')) - || (('0' <= *msg) && (*msg <= '9')) - || ('-' == *msg) || ('_' == *msg) - || ('.' == *msg) || ('~' == *msg)) { - encodedMsg += *msg; - } else { - encodedMsg += '%'; - encodedMsg += hex[*msg >> 4]; - encodedMsg += hex[*msg & 15]; - } - msg++; - } - return encodedMsg; -} -/********************************************************************************************\ - replace other system variables like %sysname%, %systime%, %ip% - \*********************************************************************************************/ -void parseControllerVariables(String& s, struct EventStruct *event, boolean useURLencode) { - s = parseTemplate(s, useURLencode); - parseEventVariables(s, event, useURLencode); -} -void repl(const String& key, const String& val, String& s, boolean useURLencode) -{ - if (useURLencode) { - // URLEncode does take resources, so check first if needed. - if (s.indexOf(key) == -1) return; - s.replace(key, URLEncode(val.c_str())); - } else { - s.replace(key, val); - } -} + void parseSpecialCharacters(String& s, boolean useURLencode) { @@ -624,6 +588,16 @@ void parseSpecialCharacters(String& s, boolean useURLencode) } } + +/********************************************************************************************\ + replace other system variables like %sysname%, %systime%, %ip% + \*********************************************************************************************/ +void parseControllerVariables(String& s, struct EventStruct *event, boolean useURLencode) { + s = parseTemplate(s, useURLencode); + parseEventVariables(s, event, useURLencode); +} + + // Simple macro to create the replacement string only when needed. #define SMART_REPL(T, S) \ if (s.indexOf(T) != -1) { repl((T), (S), s, useURLencode); } @@ -631,127 +605,9 @@ void parseSpecialCharacters(String& s, boolean useURLencode) if (s.indexOf(T) != -1) { (S((T), s, useURLencode)); } void parseSystemVariables(String& s, boolean useURLencode) { - START_TIMER - parseSpecialCharacters(s, useURLencode); + parseSpecialCharacters(s, useURLencode); - if (s.indexOf('%') == -1) { - STOP_TIMER(PARSE_SYSVAR_NOCHANGE); - return; // Nothing to replace - } - #if FEATURE_ADC_VCC - repl(F("%vcc%"), String(vcc), s, useURLencode); - #endif // if FEATURE_ADC_VCC - repl(F("%CR%"), "\r", s, useURLencode); - repl(F("%LF%"), "\n", s, useURLencode); - repl(F("%SP%"), " ", s, useURLencode); // space - repl(F("%R%"), F("\\r"), s, useURLencode); - repl(F("%N%"), F("\\n"), s, useURLencode); - SMART_REPL(F("%ip4%"), WiFi.localIP().toString().substring(WiFi.localIP().toString().lastIndexOf('.') + 1)) // 4th IP octet - SMART_REPL(F("%ip%"), WiFi.localIP().toString()) - SMART_REPL(F("%rssi%"), String((wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? 0 : WiFi.RSSI())) - SMART_REPL(F("%ssid%"), (wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? F("--") : WiFi.SSID()) - SMART_REPL(F("%bssid%"), (wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? F("00:00:00:00:00:00") : WiFi.BSSIDstr()) - SMART_REPL(F("%wi_ch%"), String((wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? 0 : WiFi.channel())) - SMART_REPL(F("%unit%"), String(Settings.Unit)) - SMART_REPL(F("%mac%"), String(WiFi.macAddress())) - #if defined(ESP8266) - SMART_REPL(F("%mac_int%"), String(ESP.getChipId())) // Last 24 bit of MAC address as integer, to be used in rules. - #endif // if defined(ESP8266) - - if (s.indexOf(F("%sys")) != -1) { - SMART_REPL(F("%sysload%"), String(getCPUload())) - SMART_REPL(F("%sysheap%"), String(ESP.getFreeHeap())); - SMART_REPL(F("%sysstack%"), String(getCurrentFreeStack())); - SMART_REPL(F("%systm_hm%"), getTimeString(':', false)) - SMART_REPL(F("%systm_hm_am%"), getTimeString_ampm(':', false)) - SMART_REPL(F("%systime%"), getTimeString(':')) - SMART_REPL(F("%systime_am%"), getTimeString_ampm(':')) - SMART_REPL(F("%sysbuild_date%"), String(CRCValues.compileDate)) - SMART_REPL(F("%sysbuild_time%"), String(CRCValues.compileTime)) - repl(F("%sysname%"), Settings.Name, s, useURLencode); - - // valueString is being used by the macro. - char valueString[5] = { 0 }; - #define SMART_REPL_TIME(T, F, V) \ - if (s.indexOf(T) != -1) { sprintf_P(valueString, (F), (V)); repl((T), valueString, s, useURLencode); } - SMART_REPL_TIME(F("%sysyear%"), PSTR("%d"), year()) - SMART_REPL_TIME(F("%sysmonth%"), PSTR("%d"), month()) - SMART_REPL_TIME(F("%sysday%"), PSTR("%d"), day()) - SMART_REPL_TIME(F("%syshour%"), PSTR("%d"), hour()) - SMART_REPL_TIME(F("%sysmin%"), PSTR("%d"), minute()) - SMART_REPL_TIME(F("%syssec%"), PSTR("%d"), second()) - SMART_REPL_TIME(F("%syssec_d%"), PSTR("%d"), ((hour() * 60) + minute()) * 60 + second()); - SMART_REPL(F("%sysweekday%"), String(weekday())) - SMART_REPL(F("%sysweekday_s%"), weekday_str()) - - // With leading zero - SMART_REPL_TIME(F("%sysyears%"), PSTR("%02d"), year() % 100) - SMART_REPL_TIME(F("%sysyear_0%"), PSTR("%04d"), year()) - SMART_REPL_TIME(F("%syshour_0%"), PSTR("%02d"), hour()) - SMART_REPL_TIME(F("%sysday_0%"), PSTR("%02d"), day()) - SMART_REPL_TIME(F("%sysmin_0%"), PSTR("%02d"), minute()) - SMART_REPL_TIME(F("%syssec_0%"), PSTR("%02d"), second()) - SMART_REPL_TIME(F("%sysmonth_0%"), PSTR("%02d"), month()) - - #undef SMART_REPL_TIME - } - SMART_REPL(F("%lcltime%"), getDateTimeString('-', ':', ' ')) - SMART_REPL(F("%lcltime_am%"), getDateTimeString_ampm('-', ':', ' ')) - SMART_REPL(F("%uptime%"), String(wdcounter / 2)) - SMART_REPL(F("%unixtime%"), String(getUnixTime())) - SMART_REPL(F("%unixday%"), String(getUnixTime() / 86400)) - SMART_REPL(F("%unixday_sec%"), String(getUnixTime() % 86400)) - SMART_REPL_T(F("%sunset"), replSunSetTimeString) - SMART_REPL_T(F("%sunrise"), replSunRiseTimeString) - - if (s.indexOf(F("%is")) != -1) { -#ifdef USES_MQTT - SMART_REPL(F("%ismqtt%"), String(MQTTclient_connected)); -#endif // ifdef USES_MQTT - SMART_REPL(F("%iswifi%"), String(wifiStatus)); // 0=disconnected, 1=connected, 2=got ip, 3=services initialized - SMART_REPL(F("%isntp%"), String(statusNTPInitialized)); - #ifdef USES_P037 - SMART_REPL(F("%ismqttimp%"), String(P037_MQTTImport_connected)); - #endif // USES_P037 - } - const int v_index = s.indexOf("%v"); - - if ((v_index != -1) && isDigit(s[v_index + 2])) { - for (byte i = 0; i < CUSTOM_VARS_MAX; ++i) { - SMART_REPL("%v" + toString(i + 1, 0) + '%', String(customFloatVar[i])) - } - } - STOP_TIMER(PARSE_SYSVAR); -} - -String getReplacementString(const String& format, String& s) { - int startpos = s.indexOf(format); - int endpos = s.indexOf('%', startpos + 1); - String R = s.substring(startpos, endpos + 1); - -#ifndef BUILD_NO_DEBUG - - if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { - String log = F("ReplacementString SunTime: "); - log += R; - log += F(" offset: "); - log += getSecOffset(R); - addLog(LOG_LEVEL_DEBUG, log); - } -#endif // ifndef BUILD_NO_DEBUG - return R; -} - -void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { - String R = getReplacementString(format, s); - - repl(R, getSunriseTimeString(':', getSecOffset(R)), s, useURLencode); -} - -void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { - String R = getReplacementString(format, s); - - repl(R, getSunsetTimeString(':', getSecOffset(R)), s, useURLencode); + SystemVariables::parseSystemVariables(s, useURLencode); } void parseEventVariables(String& s, struct EventStruct *event, boolean useURLencode) diff --git a/src/StringProvider.ino b/src/StringProvider.ino index 728ac7fa7..2627b245f 100644 --- a/src/StringProvider.ino +++ b/src/StringProvider.ino @@ -116,7 +116,7 @@ String getValue(LabelType::Enum label) { return WiFi.hostname(); #endif - case LabelType::LOCAL_TIME: return getDateTimeString('-',':',' '); + case LabelType::LOCAL_TIME: return node_time.getDateTimeString('-',':',' '); case LabelType::UPTIME: return String(wdcounter / 2); case LabelType::LOAD_PCT: return String(getCPUload()); case LabelType::LOOP_COUNT: return String(getLoopCountPerSec()); diff --git a/src/TimeESPeasy.ino b/src/TimeESPeasy.ino index 6ea5b71a8..e69de29bb 100644 --- a/src/TimeESPeasy.ino +++ b/src/TimeESPeasy.ino @@ -1,964 +0,0 @@ - -/********************************************************************************************\ - Time stuff - \*********************************************************************************************/ - -#define SECS_PER_MIN (60UL) -#define SECS_PER_HOUR (3600UL) -#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) -#define DAYS_PER_WEEK (7UL) -#define SECS_PER_WEEK (SECS_PER_DAY * DAYS_PER_WEEK) -#define SECS_PER_YEAR (SECS_PER_WEEK * 52UL) -#define SECS_YR_2000 (946684800UL) // the time at the start of y2k -#define LEAP_YEAR(Y) (((1970 + Y) > 0) && !((1970 + Y) % 4) && (((1970 + Y) % 100) || !((1970 + Y) % 400))) -#include - -struct tm tm; -uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds -double sysTime = 0.0; // Use high resolution double to get better sync between nodes when using NTP -uint32_t prevMillis = 0; -uint32_t nextSyncTime = 0; -double externalTimeSource = -1.0; // Used to set time from a source other than NTP. -struct tm tsRise, tsSet; -struct tm sunRise; -struct tm sunSet; -timeSource_t timeSource = No_time_source; - -byte PrevMinutes = 0; - -float sunDeclination(int doy) { - // Declination of the sun in radians - // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average declinations (2008-2027) - return 0.409526325277017 * sin(0.0169060504029192 * (doy - 80.0856919827619)); -} - -float diurnalArc(float dec, float lat) { - // Duration of the half sun path in hours (time from sunrise to the highest level in the south) - float rad = 0.0174532925; // = pi/180.0 - float height = -50.0 / 60.0 * rad; - float latRad = lat * rad; - - return 12.0 * acos((sin(height) - sin(latRad) * sin(dec)) / (cos(latRad) * cos(dec))) / 3.1415926536; -} - -float equationOfTime(int doy) { - // Difference between apparent and mean solar time - // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average equation of time (2008-2027) - return -0.170869921174742 * sin(0.0336997028793971 * doy + 0.465419984181394) - 0.129890681040717 * sin( - 0.0178674832556871 * doy - 0.167936777524864); -} - -int dayOfYear(int year, int month, int day) { - // Algorithm borrowed from DateToOrdinal by Ritchie Lawrence, www.commandline.co.uk - int z = 14 - month; - - z /= 12; - int y = year + 4800 - z; - int m = month + 12 * z - 3; - int j = 153 * m + 2; - j = j / 5 + day + y * 365 + y / 4 - y / 100 + y / 400 - 32045; - y = year + 4799; - int k = y * 365 + y / 4 - y / 100 + y / 400 - 31738; - return j - k + 1; -} - -void calcSunRiseAndSet() { - int doy = dayOfYear(tm.tm_year, tm.tm_mon, tm.tm_mday); - float eqt = equationOfTime(doy); - float dec = sunDeclination(doy); - float da = diurnalArc(dec, Settings.Latitude); - float rise = 12 - da - eqt; - float set = 12 + da - eqt; - - tsRise.tm_hour = (int)rise; - tsRise.tm_min = (rise - (int)rise) * 60.0; - tsSet.tm_hour = (int)set; - tsSet.tm_min = (set - (int)set) * 60.0; - tsRise.tm_mday = tsSet.tm_mday = tm.tm_mday; - tsRise.tm_mon = tsSet.tm_mon = tm.tm_mon; - tsRise.tm_year = tsSet.tm_year = tm.tm_year; - - // Now apply the longitude - int secOffset_longitude = -1.0 * (Settings.Longitude / 15.0) * 3600; - tsSet = addSeconds(tsSet, secOffset_longitude, false); - tsRise = addSeconds(tsRise, secOffset_longitude, false); - - breakTime(toLocal(makeTime(tsRise)), sunRise); - breakTime(toLocal(makeTime(tsSet)), sunSet); -} - -struct tm getSunRise(int secOffset) { - return addSeconds(tsRise, secOffset, true); -} - -struct tm getSunSet(int secOffset) { - return addSeconds(tsSet, secOffset, true); -} - -struct tm addSeconds(const struct tm& ts, int seconds, bool toLocalTime) { - unsigned long time = makeTime(ts); - - time += seconds; - - if (toLocalTime) { - time = toLocal(time); - } - struct tm result; - breakTime(time, result); - return result; -} - -void breakTime(unsigned long timeInput, struct tm& tm) { - uint8_t year; - uint8_t month, monthLength; - uint32_t time; - unsigned long days; - const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - - time = (uint32_t)timeInput; - tm.tm_sec = time % 60; - time /= 60; // now it is minutes - tm.tm_min = time % 60; - time /= 60; // now it is hours - tm.tm_hour = time % 24; - time /= 24; // now it is days - tm.tm_wday = ((time + 4) % 7) + 1; // Sunday is day 1 - - year = 0; - days = 0; - - while ((unsigned)(days += (LEAP_YEAR(year) ? 366 : 365)) <= time) { - year++; - } - tm.tm_year = year; // year is offset from 1970 - - days -= LEAP_YEAR(year) ? 366 : 365; - time -= days; // now it is days in this year, starting at 0 - - days = 0; - month = 0; - monthLength = 0; - - for (month = 0; month < 12; month++) { - if (month == 1) { // february - if (LEAP_YEAR(year)) { - monthLength = 29; - } else { - monthLength = 28; - } - } else { - monthLength = monthDays[month]; - } - - if (time >= monthLength) { - time -= monthLength; - } else { - break; - } - } - tm.tm_mon = month + 1; // jan is month 1 - tm.tm_mday = time + 1; // day of month -} - -// Restore the last known system time -// This may be useful to get some idea of what time it is. -// This way the unit can do things based on local time even when NTP servers may not respond. -// Do not use this when booting from deep sleep. -// Only call this once during boot. -void restoreLastKnownUnixTime() -{ - static bool firstCall = true; - if (firstCall && RTC.lastSysTime != 0 && RTC.deepSleepState != 1) { - firstCall = false; - timeSource = Restore_RTC_time_source; - externalTimeSource = static_cast(RTC.lastSysTime); - // Do not add the current uptime as offset. This will be done when calling now() - } -} - -void setExternalTimeSource(double time, timeSource_t source) { - timeSource = source; - externalTimeSource = time; -} - -uint32_t getUnixTime() -{ - return static_cast(sysTime); -} - -int getSecOffset(const String& format) { - int position_minus = format.indexOf('-'); - int position_plus = format.indexOf('+'); - - if ((position_minus == -1) && (position_plus == -1)) { - return 0; - } - int sign_position = _max(position_minus, position_plus); - int position_percent = format.indexOf('%', sign_position); - - if (position_percent == -1) { - return 0; - } - String valueStr = getNumerical(format.substring(sign_position, position_percent), true); - - if (!isInt(valueStr)) { return 0; } - int value = valueStr.toInt(); - - switch (format.charAt(position_percent - 1)) { - case 'm': - case 'M': - return value * 60; - case 'h': - case 'H': - return value * 3600; - } - return value; -} - -String getSunriseTimeString(char delimiter) { - return getTimeString(sunRise, delimiter, false, false); -} - -String getSunsetTimeString(char delimiter) { - return getTimeString(sunSet, delimiter, false, false); -} - -String getSunriseTimeString(char delimiter, int secOffset) { - if (secOffset == 0) { - return getSunriseTimeString(delimiter); - } - return getTimeString(getSunRise(secOffset), delimiter, false, false); -} - -String getSunsetTimeString(char delimiter, int secOffset) { - if (secOffset == 0) { - return getSunsetTimeString(delimiter); - } - return getTimeString(getSunSet(secOffset), delimiter, false, false); -} - -unsigned long now() { - // calculate number of seconds passed since last call to now() - bool timeSynced = false; - const long msec_passed = timePassedSince(prevMillis); - - sysTime += static_cast(msec_passed) / 1000.0; - prevMillis += msec_passed; - - if (nextSyncTime <= sysTime) { - // nextSyncTime & sysTime are in seconds - double unixTime_d = -1.0; - - if (externalTimeSource > 0.0) { - unixTime_d = externalTimeSource; - externalTimeSource = -1.0; - } - - if ((unixTime_d > 0.0) || getNtpTime(unixTime_d)) { - prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) - timeSynced = true; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - double time_offset = unixTime_d - sysTime; - String log = F("Time set to "); - log += String(unixTime_d,3); - - if (-86400 < time_offset && time_offset < 86400) { - // Only useful to show adjustment if it is less than a day. - log += F(" Time adjusted by "); - log += String(time_offset * 1000.0); - log += F(" msec. Wander: "); - log += String((time_offset * 1000.0) / syncInterval); - log += F(" msec/second"); - } - addLog(LOG_LEVEL_INFO, log) - } - sysTime = unixTime_d; - - - applyTimeZone(unixTime_d); - nextSyncTime = (uint32_t)unixTime_d + syncInterval; - } - } - RTC.lastSysTime = static_cast(sysTime); - uint32_t localSystime = toLocal(sysTime); - breakTime(localSystime, tm); - - if (timeSynced) { - calcSunRiseAndSet(); - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("Local time: "); - log += getDateTimeString('-', ':', ' '); - addLog(LOG_LEVEL_INFO, log); - } - { - // Notify plugins the time has been set. - String dummy; - PluginCall(PLUGIN_TIME_CHANGE, 0, dummy); - } - - if (Settings.UseRules) { - if (statusNTPInitialized) { - eventQueue.add(F("Time#Set")); - } else { - eventQueue.add(F("Time#Initialized")); - } - } - statusNTPInitialized = true; // @giig1967g: setting system variable %isntp% - } - return (unsigned long)localSystime; -} - -int year(unsigned long t) { - struct tm tmp; - - breakTime(t, tmp); - return 1970 + tmp.tm_year; -} - -int weekday(unsigned long t) { - struct tm tmp; - - breakTime(t, tmp); - return tmp.tm_wday; -} - -int year() -{ - return 1970 + tm.tm_year; -} - -byte month() -{ - return tm.tm_mon; -} - -byte day() -{ - return tm.tm_mday; -} - -byte hour() -{ - return tm.tm_hour; -} - -byte minute() -{ - return tm.tm_min; -} - -byte second() -{ - return tm.tm_sec; -} - -// day of week, sunday is day 1 -int weekday() -{ - return tm.tm_wday; -} - -String weekday_str() -{ - return weekday_str(weekday()-1); -} - -String weekday_str(int wday) -{ - const String weekDays = F("SunMonTueWedThuFriSat"); - return weekDays.substring(wday * 3, wday * 3 + 3); -} - -void initTime() -{ - nextSyncTime = 0; - now(); -} - -bool systemTimePresent() { - switch (timeSource) { - case No_time_source: - break; - case NTP_time_source: - case Restore_RTC_time_source: - case GPS_time_source: - return true; - } - return nextSyncTime > 0 || Settings.UseNTP || externalTimeSource > 0.0; -} - -void checkTime() -{ - now(); - - if (tm.tm_min != PrevMinutes) - { - String dummy; - PluginCall(PLUGIN_CLOCK_IN, 0, dummy); - PrevMinutes = tm.tm_min; - - if (Settings.UseRules) - { - String event; - event.reserve(21); - event = F("Clock#Time="); - event += weekday_str(); - event += ","; - - if (hour() < 10) { - event += '0'; - } - event += hour(); - event += ":"; - - if (minute() < 10) { - event += '0'; - } - event += minute(); - // TD-er: Do not add to the eventQueue, but execute right now. - rulesProcessing(event); - } - } -} - -bool getNtpTime(double& unixTime_d) -{ - if (!Settings.UseNTP || !WiFiConnected(10)) { - return false; - } - IPAddress timeServerIP; - String log = F("NTP : NTP host "); - - bool useNTPpool = false; - - if (Settings.NTPHost[0] != 0) { - resolveHostByName(Settings.NTPHost, timeServerIP); - log += Settings.NTPHost; - - // When single set host fails, retry again in 20 seconds - nextSyncTime = sysTime + 20; - } else { - // Have to do a lookup each time, since the NTP pool always returns another IP - String ntpServerName = String(random(0, 3)); - ntpServerName += F(".pool.ntp.org"); - resolveHostByName(ntpServerName.c_str(), timeServerIP); - log += ntpServerName; - - // When pool host fails, retry can be much sooner - nextSyncTime = sysTime + 5; - useNTPpool = true; - } - - log += " ("; - log += timeServerIP.toString(); - log += ')'; - - if (!hostReachable(timeServerIP)) { - log += F(" unreachable"); - addLog(LOG_LEVEL_INFO, log); - return false; - } - - WiFiUDP udp; - - if (!beginWiFiUDP_randomPort(udp)) { - return false; - } - - const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message - byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming & outgoing packets - - log += F(" queried"); -#ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, log); -#endif // ifndef BUILD_NO_DEBUG - - while (udp.parsePacket() > 0) { // discard any previously received packets - } - memset(packetBuffer, 0, NTP_PACKET_SIZE); - packetBuffer[0] = 0b11100011; // LI, Version, Mode - packetBuffer[1] = 0; // Stratum, or type of clock - packetBuffer[2] = 6; // Polling Interval - packetBuffer[3] = 0xEC; // Peer Clock Precision - packetBuffer[12] = 49; - packetBuffer[13] = 0x4E; - packetBuffer[14] = 49; - packetBuffer[15] = 52; - - if (udp.beginPacket(timeServerIP, 123) == 0) { // NTP requests are to port 123 - udp.stop(); - return false; - } - udp.write(packetBuffer, NTP_PACKET_SIZE); - udp.endPacket(); - - - uint32_t beginWait = millis(); - - while (!timeOutReached(beginWait + 1000)) { - int size = udp.parsePacket(); - int remotePort = udp.remotePort(); - - if ((size >= NTP_PACKET_SIZE) && (remotePort == 123)) { - udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer - - if ((packetBuffer[0] & 0b11000000) == 0b11000000) { - // Leap-Indicator: unknown (clock unsynchronized) - // See: https://github.com/letscontrolit/ESPEasy/issues/2886#issuecomment-586656384 - if (loglevelActiveFor(LOG_LEVEL_ERROR)) { - String log = F("NTP : NTP host ("); - log += timeServerIP.toString(); - log += ") unsynchronized"; - addLog(LOG_LEVEL_ERROR, log); - } - if (!useNTPpool) { - // Does not make sense to try it very often if a single host is used which is not synchronized. - nextSyncTime = sysTime + 120; - } - return false; - } - - // For more detailed info on improving accuracy, see: - // https://github.com/lettier/ntpclient/issues/4#issuecomment-360703503 - // For now, we simply use half the reply time as delay compensation. - - unsigned long secsSince1900; - - // convert four bytes starting at location 40 to a long integer - // TX time is used here. - secsSince1900 = (unsigned long)packetBuffer[40] << 24; - secsSince1900 |= (unsigned long)packetBuffer[41] << 16; - secsSince1900 |= (unsigned long)packetBuffer[42] << 8; - secsSince1900 |= (unsigned long)packetBuffer[43]; - if (secsSince1900 == 0) { - // No time stamp received - - if (!useNTPpool) { - // Retry again in a minute. - nextSyncTime = sysTime + 60; - } - return false; - } - uint32_t txTm = secsSince1900 - 2208988800UL; - - unsigned long txTm_f; - txTm_f = (unsigned long)packetBuffer[44] << 24; - txTm_f |= (unsigned long)packetBuffer[45] << 16; - txTm_f |= (unsigned long)packetBuffer[46] << 8; - txTm_f |= (unsigned long)packetBuffer[47]; - - // Convert seconds to double - unixTime_d = static_cast(txTm); - - // Add fractional part. - unixTime_d += (static_cast(txTm_f) / 4294967295.0); - - long total_delay = timePassedSince(beginWait); - - // compensate for the delay by adding half the total delay - // N.B. unixTime_d is in seconds and delay in msec. - double delay_compensation = static_cast(total_delay) / 2000.0; - unixTime_d += delay_compensation; - - if (loglevelActiveFor(LOG_LEVEL_INFO)) { - String log = F("NTP : NTP replied: delay "); - log += total_delay; - log += F(" mSec"); - log += F(" Accuracy increased by "); - double fractpart, intpart; - fractpart = modf(unixTime_d, &intpart); - - if (fractpart < delay_compensation) { - // We gained more than 1 second in accuracy - fractpart += 1.0; - } - log += String(fractpart, 3); - log += F(" seconds"); - addLog(LOG_LEVEL_INFO, log); - } - udp.stop(); - timeSource = NTP_time_source; - return true; - } - delay(10); - } - // Timeout. - if (!useNTPpool) { - // Retry again in a minute. - nextSyncTime = sysTime + 60; - } - -#ifndef BUILD_NO_DEBUG - addLog(LOG_LEVEL_DEBUG_MORE, F("NTP : No reply")); -#endif // ifndef BUILD_NO_DEBUG - udp.stop(); - return false; -} - -/********************************************************************************************\ - Unsigned long Timer timeOut check - \*********************************************************************************************/ - -// Return the time difference as a signed value, taking into account the timers may overflow. -// Returned timediff is between -24.9 days and +24.9 days. -// Returned value is positive when "next" is after "prev" -long ICACHE_RAM_ATTR timeDiff(const unsigned long prev, const unsigned long next) -{ - long signed_diff = 0; - - // To cast a value to a signed long, the difference may not exceed half the ULONG_MAX - const unsigned long half_max_unsigned_long = 2147483647u; // = 2^31 -1 - - if (next >= prev) { - const unsigned long diff = next - prev; - - if (diff <= half_max_unsigned_long) { - // Normal situation, just return the difference. - // Difference is a positive value. - signed_diff = static_cast(diff); - } else { - // prev has overflow, return a negative difference value - signed_diff = static_cast((ULONG_MAX - next) + prev + 1u); - signed_diff = -1 * signed_diff; - } - } else { - // next < prev - const unsigned long diff = prev - next; - - if (diff <= half_max_unsigned_long) { - // Normal situation, return a negative difference value - signed_diff = static_cast(diff); - signed_diff = -1 * signed_diff; - } else { - // next has overflow, return a positive difference value - signed_diff = static_cast((ULONG_MAX - prev) + next + 1u); - } - } - return signed_diff; -} - -// Compute the number of milliSeconds passed since timestamp given. -// N.B. value can be negative if the timestamp has not yet been reached. -long timePassedSince(unsigned long timestamp) { - return timeDiff(timestamp, millis()); -} - -long usecPassedSince(unsigned long timestamp) { - return timeDiff(timestamp, micros()); -} - -// Check if a certain timeout has been reached. -boolean timeOutReached(unsigned long timer) { - const long passed = timePassedSince(timer); - - return passed >= 0; -} - -boolean usecTimeOutReached(unsigned long timer) { - const long passed = usecPassedSince(timer); - - return passed >= 0; -} - -void setNextTimeInterval(unsigned long& timer, const unsigned long step) { - timer += step; - const long passed = timePassedSince(timer); - - if (passed < 0) { - // Event has not yet happened, which is fine. - return; - } - - if (static_cast(passed) > step) { - // No need to keep running behind, start again. - timer = millis() + step; - return; - } - - // Try to get in sync again. - timer = millis() + (step - passed); -} - -/********************************************************************************************\ - Convert a 32 bit integer into a string like "Sun,12:30" - \*********************************************************************************************/ -String timeLong2String(unsigned long lngTime) -{ - unsigned long x = 0; - String time = ""; - - x = (lngTime >> 16) & 0xf; - - if (x == 0x0f) { - x = 0; - } - String weekDays = F("AllSunMonTueWedThuFriSatWrkWkd"); - time = weekDays.substring(x * 3, x * 3 + 3); - time += ","; - - x = (lngTime >> 12) & 0xf; - - if (x == 0xf) { - time += "*"; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - x = (lngTime >> 8) & 0xf; - - if (x == 0xf) { - time += "*"; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - time += ":"; - - x = (lngTime >> 4) & 0xf; - - if (x == 0xf) { - time += "*"; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - x = (lngTime) & 0xf; - - if (x == 0xf) { - time += "*"; - } - else if (x == 0xe) { - time += '-'; - } - else { - time += x; - } - - return time; -} - -// returns the current Date separated by the given delimiter -// date format example with '-' delimiter: 2016-12-31 (YYYY-MM-DD) -String getDateString(const struct tm& ts, char delimiter) { - char DateString[20]; // 19 digits plus the null char - const int year = 1970 + ts.tm_year; - - sprintf_P(DateString, PSTR("%4d%c%02d%c%02d"), year, delimiter, ts.tm_mon, delimiter, ts.tm_mday); - return DateString; -} - -String getDateString(char delimiter) -{ - return getDateString(tm, delimiter); -} - -String getDateString(const struct tm& ts) -{ - return getDateString(tm, ':'); -} - -// returns the current Date without delimiter -// date format example: 20161231 (YYYYMMDD) -String getDateString() -{ - return getDateString('\0'); -} - -// returns the current Time separated by the given delimiter -// time format example with ':' delimiter: 23:59:59 (HH:MM:SS) -String getTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds) -{ - char TimeString[20]; // 19 digits plus the null char - - if (am_pm) { - uint8_t hour(ts.tm_hour % 12); - - if (hour == 0) { hour = 12; } - const char a_or_p = ts.tm_hour < 12 ? 'A' : 'P'; - - if (show_seconds) { - sprintf_P(TimeString, PSTR("%d%c%02d%c%02d %cM"), - hour, delimiter, ts.tm_min, delimiter, ts.tm_sec, a_or_p); - } else { - sprintf_P(TimeString, PSTR("%d%c%02d %cM"), - hour, delimiter, ts.tm_min, a_or_p); - } - } else { - if (show_seconds) { - sprintf_P(TimeString, PSTR("%02d%c%02d%c%02d"), - ts.tm_hour, delimiter, ts.tm_min, delimiter, ts.tm_sec); - } else { - sprintf_P(TimeString, PSTR("%d%c%02d"), - ts.tm_hour, delimiter, ts.tm_min); - } - } - return TimeString; -} - -String getTimeString(char delimiter, bool show_seconds /*=true*/) -{ - return getTimeString(tm, delimiter, false, show_seconds); -} - -String getTimeString_ampm(char delimiter, bool show_seconds /*=true*/) -{ - return getTimeString(tm, delimiter, true, show_seconds); -} - -// returns the current Time without delimiter -// time format example: 235959 (HHMMSS) -String getTimeString() -{ - return getTimeString('\0'); -} - -String getTimeString_ampm() -{ - return getTimeString_ampm('\0'); -} - -// returns the current Date and Time separated by the given delimiter -// if called like this: getDateTimeString('\0', '\0', '\0'); -// it will give back this: 20161231235959 (YYYYMMDDHHMMSS) -String getDateTimeString(const struct tm& ts, char dateDelimiter, char timeDelimiter, char dateTimeDelimiter, bool am_pm) -{ - String ret = getDateString(ts, dateDelimiter); - - if (dateTimeDelimiter != '\0') { - ret += dateTimeDelimiter; - } - ret += getTimeString(ts, timeDelimiter, am_pm, true); - return ret; -} - -String getDateTimeString(const struct tm& ts) -{ - return getDateTimeString(ts, '-', ':', ' ', false); -} - -String getDateTimeString(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) { - return getDateTimeString(tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, false); -} - -String getDateTimeString_ampm(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) { - return getDateTimeString(tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, true); -} - -/********************************************************************************************\ - Convert a string like "Sun,12:30" into a 32 bit integer - \*********************************************************************************************/ -unsigned long string2TimeLong(const String& str) -{ - // format 0000WWWWAAAABBBBCCCCDDDD - // WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes - - char command[20]; - int w, x, y; - unsigned long a; - { - // Within a scope so the tmpString is only used for copy. - String tmpString(str); - tmpString.toLowerCase(); - tmpString.toCharArray(command, 20); - } - unsigned long lngTime = 0; - String TmpStr1; - - if (GetArgv(command, TmpStr1, 1)) - { - String day = TmpStr1; - String weekDays = F("allsunmontuewedthufrisatwrkwkd"); - y = weekDays.indexOf(TmpStr1) / 3; - - if (y == 0) { - y = 0xf; // wildcard is 0xf - } - lngTime |= (unsigned long)y << 16; - } - - if (GetArgv(command, TmpStr1, 2)) - { - y = 0; - - for (x = TmpStr1.length() - 1; x >= 0; x--) - { - w = TmpStr1[x]; - - if (((w >= '0') && (w <= '9')) || (w == '*')) - { - a = 0xffffffff ^ (0xfUL << y); // create mask to clean nibble position y - lngTime &= a; // maak nibble leeg - - if (w == '*') { - lngTime |= (0xFUL << y); // fill nibble with wildcard value - } - else { - lngTime |= (w - '0') << y; // fill nibble with token - } - y += 4; - } - else - if (w == ':') {} - else - { - break; - } - } - } - #undef TmpStr1Length - return lngTime; -} - -/********************************************************************************************\ - Match clock event - \*********************************************************************************************/ -boolean matchClockEvent(unsigned long clockEvent, unsigned long clockSet) -{ - unsigned long Mask; - - for (byte y = 0; y < 8; y++) - { - if (((clockSet >> (y * 4)) & 0xf) == 0xf) // if nibble y has the wildcard value 0xf - { - Mask = 0xffffffff ^ (0xFUL << (y * 4)); // Mask to wipe nibble position y. - clockEvent &= Mask; // clear nibble - clockEvent |= (0xFUL << (y * 4)); // fill with wildcard value 0xf - } - } - - if (((clockSet >> (16)) & 0xf) == 0x8) { // if weekday nibble has the wildcard value 0x8 (workdays) - if (weekday() >= 2 and weekday() <= 6) // and we have a working day today... - { - Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. - clockEvent &= Mask; // clear nibble - clockEvent |= (0x8UL << (16)); // fill with wildcard value 0x8 - } - } - - if (((clockSet >> (16)) & 0xf) == 0x9) { // if weekday nibble has the wildcard value 0x9 (weekends) - if (weekday() == 1 or weekday() == 7) // and we have a weekend day today... - { - Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. - clockEvent &= Mask; // clear nibble - clockEvent |= (0x9UL << (16)); // fill with wildcard value 0x9 - } - } - - if (clockEvent == clockSet) { - return true; - } - return false; -} diff --git a/src/TimeZoneESPeasy.ino b/src/TimeZoneESPeasy.ino index 1700ca60c..e69de29bb 100644 --- a/src/TimeZoneESPeasy.ino +++ b/src/TimeZoneESPeasy.ino @@ -1,268 +0,0 @@ -#include - -/********************************************************************************************\ - Time zone - \*********************************************************************************************/ - -// Borrowed code from Timezone: https://github.com/JChristensen/Timezon - -TimeChangeRule m_dst; // rule for start of dst or summer time for any year -TimeChangeRule m_std; // rule for start of standard time for any year -uint32_t m_dstUTC = 0; // dst start for given/current year, given in UTC -uint32_t m_stdUTC = 0; // std time start for given/current year, given in UTC -uint32_t m_dstLoc = 0; // dst start for given/current year, given in local time -uint32_t m_stdLoc = 0; // std time start for given/current year, given in local time - -/* - // Examples time zones - // Australia Eastern Time Zone (Sydney, Melbourne) - TimeChangeRule aEDT = {First, Sun, Oct, 2, 660}; // UTC + 11 hours - TimeChangeRule aEST = {First, Sun, Apr, 3, 600}; // UTC + 10 hours - setTimeZone(aEDT, aEST); - - // Central European Time (Frankfurt, Paris) - TimeChangeRule CEST = {Last, Sun, Mar, 2, 120}; // Central European Summer Time - TimeChangeRule CET = {Last, Sun, Oct, 3, 60}; // Central European Standard Time - setTimeZone(CEST, CET); - - // United Kingdom (London, Belfast) - TimeChangeRule BST = {Last, Sun, Mar, 1, 60}; // British Summer Time - TimeChangeRule GMT = {Last, Sun, Oct, 2, 0}; // Standard Time - setTimeZone(BST, GMT); - - // UTC - TimeChangeRule utcRule = {Last, Sun, Mar, 1, 0}; // UTC - setTimeZone(utcRule, utcRule); - - // US Eastern Time Zone (New York, Detroit) - TimeChangeRule usEDT = {Second, Sun, Mar, 2, -240}; // Eastern Daylight Time = UTC - 4 hours - TimeChangeRule usEST = {First, Sun, Nov, 2, -300}; // Eastern Standard Time = UTC - 5 hours - setTimeZone(usEDT, usEST); - - // US Central Time Zone (Chicago, Houston) - TimeChangeRule usCDT = {Second, dowSunday, Mar, 2, -300}; - TimeChangeRule usCST = {First, dowSunday, Nov, 2, -360}; - setTimeZone(usCDT, usCST); - - // US Mountain Time Zone (Denver, Salt Lake City) - TimeChangeRule usMDT = {Second, dowSunday, Mar, 2, -360}; - TimeChangeRule usMST = {First, dowSunday, Nov, 2, -420}; - setTimeZone(usMDT, usMST); - - // Arizona is US Mountain Time Zone but does not use DST - setTimeZone(usMST, usMST); - - // US Pacific Time Zone (Las Vegas, Los Angeles) - TimeChangeRule usPDT = {Second, dowSunday, Mar, 2, -420}; - TimeChangeRule usPST = {First, dowSunday, Nov, 2, -480}; - setTimeZone(usPDT, usPST); - */ -void getDefaultDst_flash_values(uint16_t& start, uint16_t& end) { - // DST start: Last Sunday March 2am => 3am - // DST end: Last Sunday October 3am => 2am - TimeChangeRule CEST(Last, Sun, Mar, 2, Settings.TimeZone); // Summer Time - TimeChangeRule CET(Last, Sun, Oct, 3, Settings.TimeZone); // Standard Time - - start = CEST.toFlashStoredValue(); - end = CET.toFlashStoredValue(); -} - -void applyTimeZone(uint32_t curTime) { - int dst_offset = Settings.DST ? 60 : 0; - uint16_t tmpStart(Settings.DST_Start); - uint16_t tmpEnd(Settings.DST_End); - - for (int i = 0; i < 2; ++i) { - TimeChangeRule start(tmpStart, Settings.TimeZone + dst_offset); // Summer Time - TimeChangeRule end(tmpEnd, Settings.TimeZone); // Standard Time - - if (start.isValid() && end.isValid()) { - setTimeZone(start, end, curTime); - return; - } - getDefaultDst_flash_values(tmpStart, tmpEnd); - } -} - -void setTimeZone(const TimeChangeRule& dstStart, const TimeChangeRule& stdStart, uint32_t curTime) { - m_dst = dstStart; - m_std = stdStart; - - if (calcTimeChanges(year(curTime))) { - logTimeZoneInfo(); - } -} - -void logTimeZoneInfo() { - String log = F("Current Time Zone: "); - - if (m_std.offset != m_dst.offset) { - // Summer time - log += F(" DST time start: "); - - if (m_dstLoc != 0) { - struct tm tmp; - breakTime(m_dstLoc, tmp); - log += getDateTimeString(tmp, '-', ':', ' ', false); - } - log += F(" offset: "); - log += m_dst.offset; - log += F(" min "); - } - - // Standard/Winter time. - log += F("STD time start: "); - - if (m_stdLoc != 0) { - struct tm tmp; - breakTime(m_stdLoc, tmp); - log += getDateTimeString(tmp, '-', ':', ' ', false); - } - log += F(" offset: "); - log += m_std.offset; - log += F(" min"); - addLog(LOG_LEVEL_INFO, log); -} - -uint32_t makeTime(const struct tm& tm) { - // assemble time elements into uint32_t - // note year argument is offset from 1970 (see macros in time.h to convert to other formats) - // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 - const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; - int i; - uint32_t seconds; - - // seconds from 1970 till 1 jan 00:00:00 of the given year - seconds = tm.tm_year * (SECS_PER_DAY * 365); - - for (i = 0; i < tm.tm_year; i++) { - if (LEAP_YEAR(i)) { - seconds += SECS_PER_DAY; // add extra days for leap years - } - } - - // add days for this year, months start from 1 - for (i = 1; i < tm.tm_mon; i++) { - if ((i == 2) && LEAP_YEAR(tm.tm_year)) { - seconds += SECS_PER_DAY * 29; - } else { - seconds += SECS_PER_DAY * monthDays[i - 1]; // monthDay array starts from 0 - } - } - seconds += (tm.tm_mday - 1) * SECS_PER_DAY; - seconds += tm.tm_hour * SECS_PER_HOUR; - seconds += tm.tm_min * SECS_PER_MIN; - seconds += tm.tm_sec; - return (uint32_t)seconds; -} - -///*----------------------------------------------------------------------* -// * Convert the given time change rule to a uint32_t value * -// * for the given year. * -// *----------------------------------------------------------------------*/ -uint32_t calcTimeChangeForRule(const TimeChangeRule& r, int yr) -{ - uint8_t m = r.month; // temp copies of r.month and r.week - uint8_t w = r.week; - - if (w == 0) // is this a "Last week" rule? - { - if (++m > 12) // yes, for "Last", go to the next month - { - m = 1; - ++yr; - } - w = 1; // and treat as first week of next month, subtract 7 days later - } - - // calculate first day of the month, or for "Last" rules, first day of the next month - struct tm tm; - tm.tm_hour = r.hour; - tm.tm_min = 0; - tm.tm_sec = 0; - tm.tm_mday = 1; - tm.tm_mon = m; - tm.tm_year = yr - 1970; - uint32_t t = makeTime(tm); - - // add offset from the first of the month to r.dow, and offset for the given week - t += ((r.dow - weekday(t) + 7) % 7 + (w - 1) * 7) * SECS_PER_DAY; - - // back up a week if this is a "Last" rule - if (r.week == 0) { t -= 7 * SECS_PER_DAY; } - return t; -} - -/*----------------------------------------------------------------------* -* Calculate the DST and standard time change points for the given * -* given year as local and UTC uint32_t values. * -*----------------------------------------------------------------------*/ -bool calcTimeChanges(int yr) -{ - uint32_t dstLoc = calcTimeChangeForRule(m_dst, yr); - uint32_t stdLoc = calcTimeChangeForRule(m_std, yr); - bool changed = (m_dstLoc != dstLoc) || (m_stdLoc != stdLoc); - - m_dstLoc = dstLoc; - m_stdLoc = stdLoc; - m_dstUTC = m_dstLoc - m_std.offset * SECS_PER_MIN; - m_stdUTC = m_stdLoc - m_dst.offset * SECS_PER_MIN; - return changed; -} - -/*----------------------------------------------------------------------* -* Convert the given UTC time to local time, standard or * -* daylight time, as appropriate. * -*----------------------------------------------------------------------*/ -uint32_t toLocal(uint32_t utc) -{ - // recalculate the time change points if needed - if (year(utc) != year(m_dstUTC)) { calcTimeChanges(year(utc)); } - - if (utcIsDST(utc)) { - return utc + m_dst.offset * SECS_PER_MIN; - } - else { - return utc + m_std.offset * SECS_PER_MIN; - } -} - -/*----------------------------------------------------------------------* -* Determine whether the given UTC uint32_t is within the DST interval * -* or the Standard time interval. * -*----------------------------------------------------------------------*/ -bool utcIsDST(uint32_t utc) -{ - // recalculate the time change points if needed - if (year(utc) != year(m_dstUTC)) { calcTimeChanges(year(utc)); } - - if (m_stdUTC == m_dstUTC) { // daylight time not observed in this tz - return false; - } - else if (m_stdUTC > m_dstUTC) { // northern hemisphere - return utc >= m_dstUTC && utc < m_stdUTC; - } - else { // southern hemisphere - return !(utc >= m_stdUTC && utc < m_dstUTC); - } -} - -/*----------------------------------------------------------------------* -* Determine whether the given Local uint32_t is within the DST interval * -* or the Standard time interval. * -*----------------------------------------------------------------------*/ -bool locIsDST(uint32_t local) -{ - // recalculate the time change points if needed - if (year(local) != year(m_dstLoc)) { calcTimeChanges(year(local)); } - - if (m_stdUTC == m_dstUTC) { // daylight time not observed in this tz - return false; - } - else if (m_stdLoc > m_dstLoc) { // northern hemisphere - return local >= m_dstLoc && local < m_stdLoc; - } - else { // southern hemisphere - return !(local >= m_stdLoc && local < m_dstLoc); - } -} diff --git a/src/WebServer_AdvancedConfigPage.ino b/src/WebServer_AdvancedConfigPage.ino index 4324f17eb..d25d058c1 100644 --- a/src/WebServer_AdvancedConfigPage.ino +++ b/src/WebServer_AdvancedConfigPage.ino @@ -1,5 +1,7 @@ #ifdef WEBSERVER_ADVANCED +#include "src/Globals/TimeZone.h" + // ******************************************************************************** // Web Interface config page // ******************************************************************************** @@ -77,8 +79,8 @@ void handle_advanced() { addHtmlError(SaveSettings()); - if (systemTimePresent()) { - initTime(); + if (node_time.systemTimePresent()) { + node_time.initTime(); } } @@ -220,7 +222,7 @@ void addFormDstSelect(bool isStart, uint16_t choice) { uint16_t tmpend(choice); if (!TimeChangeRule(choice, 0).isValid()) { - getDefaultDst_flash_values(tmpstart, tmpend); + time_zone.getDefaultDst_flash_values(tmpstart, tmpend); } TimeChangeRule rule(isStart ? tmpstart : tmpend, 0); addRowLabel(weeklabel); diff --git a/src/WebServer_DownloadPage.ino b/src/WebServer_DownloadPage.ino index c511d6f7b..668c918e2 100644 --- a/src/WebServer_DownloadPage.ino +++ b/src/WebServer_DownloadPage.ino @@ -29,9 +29,9 @@ void handle_download() str += BUILD; str += '_'; - if (systemTimePresent()) + if (node_time.systemTimePresent()) { - str += getDateTimeString('\0', '\0', '\0'); + str += node_time.getDateTimeString('\0', '\0', '\0'); } str += F(".dat"); diff --git a/src/WebServer_RootPage.ino b/src/WebServer_RootPage.ino index 5e3fe874f..a749c6be5 100644 --- a/src/WebServer_RootPage.ino +++ b/src/WebServer_RootPage.ino @@ -57,7 +57,7 @@ void handle_root() { addRowLabelValue(LabelType::GIT_BUILD); addRowLabel(getLabel(LabelType::LOCAL_TIME)); - if (systemTimePresent()) + if (node_time.systemTimePresent()) { addHtml(getValue(LabelType::LOCAL_TIME)); } diff --git a/src/WebServer_SysInfoPage.ino b/src/WebServer_SysInfoPage.ino index 3df906fdf..73add7c82 100644 --- a/src/WebServer_SysInfoPage.ino +++ b/src/WebServer_SysInfoPage.ino @@ -17,7 +17,7 @@ void handle_sysinfo_json() { json_open(); json_open(false, F("general")); json_number(F("unit"), String(Settings.Unit)); - json_prop(F("time"), getDateTimeString('-', ':', ' ')); + json_prop(F("time"), node_time.getDateTimeString('-', ':', ' ')); json_prop(F("uptime"), getExtendedValue(LabelType::UPTIME)); json_number(F("cpu_load"), String(getCPUload())); json_number(F("loop_count"), String(getLoopCountPerSec())); @@ -238,7 +238,7 @@ void handle_sysinfo() { void handle_sysinfo_basicInfo() { addRowLabelValue(LabelType::UNIT_NR); - if (systemTimePresent()) + if (node_time.systemTimePresent()) { addRowLabelValue(LabelType::LOCAL_TIME); } diff --git a/src/WebServer_TimingStats.ino b/src/WebServer_TimingStats.ino index 6e3e69dd0..0f75c08f3 100644 --- a/src/WebServer_TimingStats.ino +++ b/src/WebServer_TimingStats.ino @@ -25,8 +25,8 @@ void handle_timingstats() { const float timespan = timeSinceLastReset / 1000.0; addFormHeader(F("Statistics")); addRowLabel(F("Start Period")); - struct tm startPeriod = addSeconds(tm, -1.0 * timespan, false); - addHtml(getDateTimeString(startPeriod, '-', ':', ' ', false)); + struct tm startPeriod = node_time.addSeconds(node_time.tm, -1.0 * timespan, false); + addHtml(ESPEasy_time::getDateTimeString(startPeriod, '-', ':', ' ', false)); addRowLabelValue(LabelType::LOCAL_TIME); addRowLabel(F("Time span")); addHtml(String(timespan)); diff --git a/src/_C016.ino b/src/_C016.ino index 76062812b..adbb803b2 100644 --- a/src/_C016.ino +++ b/src/_C016.ino @@ -90,7 +90,7 @@ bool CPlugin_016(CPlugin::Function function, struct EventStruct *event, String& { // Collect the values at the same run, to make sure all are from the same sample byte valueCount = getValueCountFromSensorType(event->sensorType); - C016_queue_element element(event, valueCount, getUnixTime()); + C016_queue_element element(event, valueCount, node_time.getUnixTime()); success = ControllerCache.write((uint8_t*)&element, sizeof(element)); /* diff --git a/src/_P081_Cron.ino b/src/_P081_Cron.ino index 50924b383..de097fda0 100644 --- a/src/_P081_Cron.ino +++ b/src/_P081_Cron.ino @@ -137,16 +137,16 @@ void P081_setCronExecTimes(struct EventStruct *event, time_t lastExecTime, time_ time_t P081_getCurrentTime() { - now(); + node_time.now(); // FIXME TD-er: Why work on a deepcopy of tm? - struct tm current = tm; + struct tm current = node_time.tm; return mktime((struct tm *)¤t); } void P081_check_or_init(struct EventStruct *event) { - if (systemTimePresent()) { + if (node_time.systemTimePresent()) { const time_t current_time = P081_getCurrentTime(); time_t last_exec_time = P081_getCronExecTime(LASTEXECUTION); time_t next_exec_time = P081_getCronExecTime(NEXTEXECUTION); @@ -299,7 +299,7 @@ boolean Plugin_081(byte function, struct EventStruct *event, String& string) case PLUGIN_ONCE_A_SECOND: { // code to be executed once a second. Tasks which do not require fast response can be added here - if (systemTimePresent()) { + if (node_time.systemTimePresent()) { P081_check_or_init(event); time_t next_exec_time = P081_getCronExecTime(NEXTEXECUTION); @@ -314,7 +314,7 @@ boolean Plugin_081(byte function, struct EventStruct *event, String& string) next_exec_time = P081_computeNextCronTime(event->TaskIndex, current_time); P081_setCronExecTimes(event, last_exec_time, next_exec_time); - addLog(LOG_LEVEL_DEBUG, String(F("Next execution:")) + getDateTimeString(*gmtime(&next_exec_time))); + addLog(LOG_LEVEL_DEBUG, String(F("Next execution:")) + ESPEasy_time::getDateTimeString(*gmtime(&next_exec_time))); if (function != PLUGIN_TIME_CHANGE) { LoadTaskSettings(event->TaskIndex); @@ -398,7 +398,7 @@ String P081_formatExecTime(float execTime_f) { time_t exec_time = P081_getCronExecTime(execTime_f); if (exec_time != CRON_INVALID_INSTANT) { - return getDateTimeString(*gmtime(&exec_time)); + return ESPEasy_time::getDateTimeString(*gmtime(&exec_time)); } return F("-"); } diff --git a/src/_P082_GPS.ino b/src/_P082_GPS.ino index 2104e9521..d63959db8 100644 --- a/src/_P082_GPS.ino +++ b/src/_P082_GPS.ino @@ -14,6 +14,9 @@ #include #include "ESPEasy_packed_raw_data.h" +#include "src/Globals/ESPEasy_time.h" +#include "src/Helpers/ESPEasy_time_calc.h" + #define PLUGIN_082 #define PLUGIN_ID_082 82 #define PLUGIN_NAME_082 "Position - GPS [TESTING]" @@ -706,8 +709,8 @@ void P082_html_show_stats(struct EventStruct *event) { bool pps_sync; if (P082_data->getDateTime(dateTime, age, pps_sync)) { - dateTime = addSeconds(dateTime, (age / 1000), false); - addHtml(getDateTimeString(dateTime)); + dateTime = node_time.addSeconds(dateTime, (age / 1000), false); + addHtml(ESPEasy_time::getDateTimeString(dateTime)); } else { addHtml(F("-")); } @@ -730,7 +733,7 @@ void P082_setSystemTime(struct EventStruct *event) { // Set the externalTimesource 10 seconds earlier to make sure no call is made // to NTP (if set) - if (nextSyncTime > (sysTime + 10)) { + if (node_time.nextSyncTime > (node_time.sysTime + 10)) { return; } @@ -744,8 +747,8 @@ void P082_setSystemTime(struct EventStruct *event) { // and the given offset in centisecond. double time = makeTime(dateTime); time += static_cast(age) / 1000.0; - setExternalTimeSource(time, GPS_time_source); - initTime(); + node_time.setExternalTimeSource(time, GPS_time_source); + node_time.initTime(); } P082_pps_time = 0; } diff --git a/src/src/DataStructs/TimeChangeRule.cpp b/src/src/DataStructs/TimeChangeRule.cpp new file mode 100644 index 000000000..156f740dd --- /dev/null +++ b/src/src/DataStructs/TimeChangeRule.cpp @@ -0,0 +1,31 @@ +#include "TimeChangeRule.h" + + + +TimeChangeRule::TimeChangeRule() : week(0), dow(1), month(1), hour(0), offset(0) {} + +TimeChangeRule::TimeChangeRule(uint8_t weeknr, uint8_t downr, uint8_t m, uint8_t h, uint16_t minutesoffset) : + week(weeknr), dow(downr), month(m), hour(h), offset(minutesoffset) {} + +// Construct time change rule from stored values optimized for minimum space. +TimeChangeRule::TimeChangeRule(uint16_t flash_stored_value, int16_t minutesoffset) : offset(minutesoffset) { + hour = flash_stored_value & 0x001f; + month = (flash_stored_value >> 5) & 0x000f; + dow = (flash_stored_value >> 9) & 0x0007; + week = (flash_stored_value >> 12) & 0x0007; +} + +uint16_t TimeChangeRule::toFlashStoredValue() const { + uint16_t value = hour; + + value = value | (month << 5); + value = value | (dow << 9); + value = value | (week << 12); + return value; +} + +bool TimeChangeRule::isValid() const { + return (week <= 4) && (dow != 0) && (dow <= 7) && + (month != 0) && (month <= 12) && (hour <= 23) && + (offset > -720) && (offset < 900); // UTC-12h ... UTC+14h + 1h DSToffset +} diff --git a/src/src/DataStructs/TimeChangeRule.h b/src/src/DataStructs/TimeChangeRule.h new file mode 100644 index 000000000..90d5dbb96 --- /dev/null +++ b/src/src/DataStructs/TimeChangeRule.h @@ -0,0 +1,38 @@ +#ifndef DATASTRUCTS_TIMECHANGERULE_H +#define DATASTRUCTS_TIMECHANGERULE_H + +#include + +// structure to describe rules for when daylight/summer time begins, +// or when standard time begins. +// For Daylight Saving Time Around the World, see: +// - https://www.timeanddate.com/time/dst/2018.html +// - https://en.wikipedia.org/wiki/Daylight_saving_time_by_country +struct TimeChangeRule { + + // convenient constants for TimeChangeRules + enum week_t { Last = 0, First, Second, Third, Fourth }; + enum dow_t { Sun = 1, Mon, Tue, Wed, Thu, Fri, Sat }; + enum month_t { Jan = 1, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec }; + + + + TimeChangeRule(); + + TimeChangeRule(uint8_t weeknr, uint8_t downr, uint8_t m, uint8_t h, uint16_t minutesoffset); + + // Construct time change rule from stored values optimized for minimum space. + TimeChangeRule(uint16_t flash_stored_value, int16_t minutesoffset); + + uint16_t toFlashStoredValue() const; + + bool isValid() const; + + uint8_t week; // First, Second, Third, Fourth, or Last week of the month + uint8_t dow; // day of week, 1=Sun, 2=Mon, ... 7=Sat + uint8_t month; // 1=Jan, 2=Feb, ... 12=Dec + uint8_t hour; // 0-23 + int16_t offset; // offset from UTC in minutes +}; + +#endif // DATASTRUCTS_TIMECHANGERULE_H \ No newline at end of file diff --git a/src/src/DataStructs/Web_StreamingBuffer.cpp b/src/src/DataStructs/Web_StreamingBuffer.cpp index 66e01a31d..c07e37f48 100644 --- a/src/src/DataStructs/Web_StreamingBuffer.cpp +++ b/src/src/DataStructs/Web_StreamingBuffer.cpp @@ -1,6 +1,7 @@ #include "../DataStructs/Web_StreamingBuffer.h" #include "../DataStructs/tcp_cleanup.h" +#include "../Helpers/ESPEasy_time_calc.h" #include "../../ESPEasy_Log.h" #include "../../ESPEasyTimeTypes.h" diff --git a/src/src/DataStructs/timer_id_couple.cpp b/src/src/DataStructs/timer_id_couple.cpp new file mode 100644 index 000000000..c6123e19a --- /dev/null +++ b/src/src/DataStructs/timer_id_couple.cpp @@ -0,0 +1,16 @@ +#include "timer_id_couple.h" + +#include "../Helpers/ESPEasy_time_calc.h" + + timer_id_couple::timer_id_couple(unsigned long id, unsigned long newtimer) : _id(id), _timer(newtimer) {} + + timer_id_couple::timer_id_couple(unsigned long id) : _id(id) { + _timer = millis(); + } + + bool timer_id_couple::operator<(const timer_id_couple& other) { + const unsigned long now(millis()); + + // timediff > 0, means timer has already passed + return timeDiff(_timer, now) > timeDiff(other._timer, now); + } diff --git a/src/src/DataStructs/timer_id_couple.h b/src/src/DataStructs/timer_id_couple.h new file mode 100644 index 000000000..ac3be6b8a --- /dev/null +++ b/src/src/DataStructs/timer_id_couple.h @@ -0,0 +1,23 @@ +#ifndef DATASTRUCTS_TIMER_ID_COUPLE_H +#define DATASTRUCTS_TIMER_ID_COUPLE_H + + + + +/*********************************************************************************************\ +* TimerHandler Used by the Scheduler +\*********************************************************************************************/ + +struct timer_id_couple { + timer_id_couple(unsigned long id, unsigned long newtimer); + + timer_id_couple(unsigned long id); + + bool operator<(const timer_id_couple& other); + + unsigned long _id; + unsigned long _timer; +}; + + +#endif // DATASTRUCTS_TIMER_ID_COUPLE_H \ No newline at end of file diff --git a/src/src/Globals/ESPEasy_time.cpp b/src/src/Globals/ESPEasy_time.cpp new file mode 100644 index 000000000..67b8b78e7 --- /dev/null +++ b/src/src/Globals/ESPEasy_time.cpp @@ -0,0 +1,3 @@ +#include "../Globals/ESPEasy_time.h" + +ESPEasy_time node_time; \ No newline at end of file diff --git a/src/src/Globals/ESPEasy_time.h b/src/src/Globals/ESPEasy_time.h new file mode 100644 index 000000000..74d1715ae --- /dev/null +++ b/src/src/Globals/ESPEasy_time.h @@ -0,0 +1,10 @@ +#ifndef GLOBALS_ESPEASY_TIME_H +#define GLOBALS_ESPEASY_TIME_H + +#include "../Helpers/ESPEasy_time.h" + +extern ESPEasy_time node_time; + + + +#endif // GLOBALS_ESPEASY_TIME_H \ No newline at end of file diff --git a/src/src/Globals/RTC.h b/src/src/Globals/RTC.h index c7afbc9be..0d399f201 100644 --- a/src/src/Globals/RTC.h +++ b/src/src/Globals/RTC.h @@ -1,8 +1,8 @@ #ifndef GLOBALS_RTC_H #define GLOBALS_RTC_H +#include "../DataStructs/RTCStruct.h" -struct RTCStruct; extern RTCStruct RTC; #endif // GLOBALS_RTC_H \ No newline at end of file diff --git a/src/src/Globals/TimeZone.cpp b/src/src/Globals/TimeZone.cpp new file mode 100644 index 000000000..e07289d4f --- /dev/null +++ b/src/src/Globals/TimeZone.cpp @@ -0,0 +1,3 @@ +#include "TimeZone.h" + +ESPEasy_time_zone time_zone; \ No newline at end of file diff --git a/src/src/Globals/TimeZone.h b/src/src/Globals/TimeZone.h new file mode 100644 index 000000000..4f9003ca5 --- /dev/null +++ b/src/src/Globals/TimeZone.h @@ -0,0 +1,9 @@ +#ifndef GLOBALS_TIMEZONE_H +#define GLOBALS_TIMEZONE_H + +#include "../Helpers/ESPEasy_time_zone.h" + +extern ESPEasy_time_zone time_zone; + + +#endif // GLOBALS_TIMEZONE_H \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_time.cpp b/src/src/Helpers/ESPEasy_time.cpp new file mode 100644 index 000000000..65386e633 --- /dev/null +++ b/src/src/Helpers/ESPEasy_time.cpp @@ -0,0 +1,633 @@ +#include "ESPEasy_time.h" + +#include "ESPEasy_time_calc.h" + +#include "../Globals/TimeZone.h" +#include "../Globals/RTC.h" +#include "../Globals/Settings.h" + +#include "../../ESPEasy_fdwdecl.h" +#include "../../ESPEasy_Log.h" +#include "../../ESPEasy-Globals.h" + +#include + + +struct tm ESPEasy_time::addSeconds(const struct tm& ts, int seconds, bool toLocalTime) const { + unsigned long time = makeTime(ts); + + time += seconds; + + if (toLocalTime) { + time = time_zone.toLocal(time); + } + struct tm result; + breakTime(time, result); + return result; +} + + +void ESPEasy_time::breakTime(unsigned long timeInput, struct tm& tm) { + uint8_t year; + uint8_t month, monthLength; + uint32_t time; + unsigned long days; + const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + + time = (uint32_t)timeInput; + tm.tm_sec = time % 60; + time /= 60; // now it is minutes + tm.tm_min = time % 60; + time /= 60; // now it is hours + tm.tm_hour = time % 24; + time /= 24; // now it is days + tm.tm_wday = ((time + 4) % 7) + 1; // Sunday is day 1 + + year = 0; + days = 0; + + while ((unsigned)(days += (isLeapYear(year) ? 366 : 365)) <= time) { + year++; + } + tm.tm_year = year; // year is offset from 1970 + + days -= isLeapYear(year) ? 366 : 365; + time -= days; // now it is days in this year, starting at 0 + + days = 0; + month = 0; + monthLength = 0; + + for (month = 0; month < 12; month++) { + if (month == 1) { // february + if (isLeapYear(year)) { + monthLength = 29; + } else { + monthLength = 28; + } + } else { + monthLength = monthDays[month]; + } + + if (time >= monthLength) { + time -= monthLength; + } else { + break; + } + } + tm.tm_mon = month + 1; // jan is month 1 + tm.tm_mday = time + 1; // day of month +} + + +void ESPEasy_time::restoreLastKnownUnixTime(unsigned long lastSysTime, byte deepSleepState) +{ + static bool firstCall = true; + if (firstCall && lastSysTime != 0 && deepSleepState != 1) { + firstCall = false; + timeSource = Restore_RTC_time_source; + externalTimeSource = static_cast(lastSysTime); + // Do not add the current uptime as offset. This will be done when calling now() + } +} + +void ESPEasy_time::setExternalTimeSource(double time, timeSource_t source) { + timeSource = source; + externalTimeSource = time; +} + +uint32_t ESPEasy_time::getUnixTime() const +{ + return static_cast(sysTime); +} + +void ESPEasy_time::initTime() +{ + nextSyncTime = 0; + now(); +} + +unsigned long ESPEasy_time::now() { + // calculate number of seconds passed since last call to now() + bool timeSynced = false; + const long msec_passed = timePassedSince(prevMillis); + + sysTime += static_cast(msec_passed) / 1000.0; + prevMillis += msec_passed; + + if (nextSyncTime <= sysTime) { + // nextSyncTime & sysTime are in seconds + double unixTime_d = -1.0; + + if (externalTimeSource > 0.0) { + unixTime_d = externalTimeSource; + externalTimeSource = -1.0; + } + + if ((unixTime_d > 0.0) || getNtpTime(unixTime_d)) { + prevMillis = millis(); // restart counting from now (thanks to Korman for this fix) + timeSynced = true; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + double time_offset = unixTime_d - sysTime; + String log = F("Time set to "); + log += String(unixTime_d,3); + + if (-86400 < time_offset && time_offset < 86400) { + // Only useful to show adjustment if it is less than a day. + log += F(" Time adjusted by "); + log += String(time_offset * 1000.0); + log += F(" msec. Wander: "); + log += String((time_offset * 1000.0) / syncInterval); + log += F(" msec/second"); + } + addLog(LOG_LEVEL_INFO, log) + } + sysTime = unixTime_d; + + + time_zone.applyTimeZone(unixTime_d); + nextSyncTime = (uint32_t)unixTime_d + syncInterval; + } + } + RTC.lastSysTime = static_cast(sysTime); + uint32_t localSystime = time_zone.toLocal(sysTime); + breakTime(localSystime, tm); + + if (timeSynced) { + calcSunRiseAndSet(); + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("Local time: "); + log += getDateTimeString('-', ':', ' '); + addLog(LOG_LEVEL_INFO, log); + } + { + // Notify plugins the time has been set. + String dummy; + PluginCall(PLUGIN_TIME_CHANGE, 0, dummy); + } + + if (Settings.UseRules) { + if (statusNTPInitialized) { + eventQueue.add(F("Time#Set")); + } else { + eventQueue.add(F("Time#Initialized")); + } + } + statusNTPInitialized = true; // @giig1967g: setting system variable %isntp% + } + return (unsigned long)localSystime; +} + + +bool ESPEasy_time::reportNewMinute() +{ + now(); + + if (!systemTimePresent()) { + return false; + } + + if (tm.tm_min == PrevMinutes) + { + return false; + } + PrevMinutes = tm.tm_min; + return true; +} + + + +bool ESPEasy_time::systemTimePresent() const { + switch (timeSource) { + case No_time_source: + break; + case NTP_time_source: + case Restore_RTC_time_source: + case GPS_time_source: + return true; + } + return nextSyncTime > 0 || Settings.UseNTP || externalTimeSource > 0.0; +} + + + +bool ESPEasy_time::getNtpTime(double& unixTime_d) +{ + if (!Settings.UseNTP || !WiFiConnected(10)) { + return false; + } + IPAddress timeServerIP; + String log = F("NTP : NTP host "); + + bool useNTPpool = false; + + if (Settings.NTPHost[0] != 0) { + resolveHostByName(Settings.NTPHost, timeServerIP); + log += Settings.NTPHost; + + // When single set host fails, retry again in 20 seconds + nextSyncTime = sysTime + 20; + } else { + // Have to do a lookup each time, since the NTP pool always returns another IP + String ntpServerName = String(random(0, 3)); + ntpServerName += F(".pool.ntp.org"); + resolveHostByName(ntpServerName.c_str(), timeServerIP); + log += ntpServerName; + + // When pool host fails, retry can be much sooner + nextSyncTime = sysTime + 5; + useNTPpool = true; + } + + log += " ("; + log += timeServerIP.toString(); + log += ')'; + + if (!hostReachable(timeServerIP)) { + log += F(" unreachable"); + addLog(LOG_LEVEL_INFO, log); + return false; + } + + WiFiUDP udp; + + if (!beginWiFiUDP_randomPort(udp)) { + return false; + } + + const int NTP_PACKET_SIZE = 48; // NTP time is in the first 48 bytes of message + byte packetBuffer[NTP_PACKET_SIZE]; // buffer to hold incoming & outgoing packets + + log += F(" queried"); +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, log); +#endif // ifndef BUILD_NO_DEBUG + + while (udp.parsePacket() > 0) { // discard any previously received packets + } + memset(packetBuffer, 0, NTP_PACKET_SIZE); + packetBuffer[0] = 0b11100011; // LI, Version, Mode + packetBuffer[1] = 0; // Stratum, or type of clock + packetBuffer[2] = 6; // Polling Interval + packetBuffer[3] = 0xEC; // Peer Clock Precision + packetBuffer[12] = 49; + packetBuffer[13] = 0x4E; + packetBuffer[14] = 49; + packetBuffer[15] = 52; + + if (udp.beginPacket(timeServerIP, 123) == 0) { // NTP requests are to port 123 + udp.stop(); + return false; + } + udp.write(packetBuffer, NTP_PACKET_SIZE); + udp.endPacket(); + + + uint32_t beginWait = millis(); + + while (!timeOutReached(beginWait + 1000)) { + int size = udp.parsePacket(); + int remotePort = udp.remotePort(); + + if ((size >= NTP_PACKET_SIZE) && (remotePort == 123)) { + udp.read(packetBuffer, NTP_PACKET_SIZE); // read packet into the buffer + + if ((packetBuffer[0] & 0b11000000) == 0b11000000) { + // Leap-Indicator: unknown (clock unsynchronized) + // See: https://github.com/letscontrolit/ESPEasy/issues/2886#issuecomment-586656384 + if (loglevelActiveFor(LOG_LEVEL_ERROR)) { + String log = F("NTP : NTP host ("); + log += timeServerIP.toString(); + log += ") unsynchronized"; + addLog(LOG_LEVEL_ERROR, log); + } + if (!useNTPpool) { + // Does not make sense to try it very often if a single host is used which is not synchronized. + nextSyncTime = sysTime + 120; + } + return false; + } + + // For more detailed info on improving accuracy, see: + // https://github.com/lettier/ntpclient/issues/4#issuecomment-360703503 + // For now, we simply use half the reply time as delay compensation. + + unsigned long secsSince1900; + + // convert four bytes starting at location 40 to a long integer + // TX time is used here. + secsSince1900 = (unsigned long)packetBuffer[40] << 24; + secsSince1900 |= (unsigned long)packetBuffer[41] << 16; + secsSince1900 |= (unsigned long)packetBuffer[42] << 8; + secsSince1900 |= (unsigned long)packetBuffer[43]; + if (secsSince1900 == 0) { + // No time stamp received + + if (!useNTPpool) { + // Retry again in a minute. + nextSyncTime = sysTime + 60; + } + return false; + } + uint32_t txTm = secsSince1900 - 2208988800UL; + + unsigned long txTm_f; + txTm_f = (unsigned long)packetBuffer[44] << 24; + txTm_f |= (unsigned long)packetBuffer[45] << 16; + txTm_f |= (unsigned long)packetBuffer[46] << 8; + txTm_f |= (unsigned long)packetBuffer[47]; + + // Convert seconds to double + unixTime_d = static_cast(txTm); + + // Add fractional part. + unixTime_d += (static_cast(txTm_f) / 4294967295.0); + + long total_delay = timePassedSince(beginWait); + + // compensate for the delay by adding half the total delay + // N.B. unixTime_d is in seconds and delay in msec. + double delay_compensation = static_cast(total_delay) / 2000.0; + unixTime_d += delay_compensation; + + if (loglevelActiveFor(LOG_LEVEL_INFO)) { + String log = F("NTP : NTP replied: delay "); + log += total_delay; + log += F(" mSec"); + log += F(" Accuracy increased by "); + double fractpart, intpart; + fractpart = modf(unixTime_d, &intpart); + + if (fractpart < delay_compensation) { + // We gained more than 1 second in accuracy + fractpart += 1.0; + } + log += String(fractpart, 3); + log += F(" seconds"); + addLog(LOG_LEVEL_INFO, log); + } + udp.stop(); + timeSource = NTP_time_source; + return true; + } + delay(10); + } + // Timeout. + if (!useNTPpool) { + // Retry again in a minute. + nextSyncTime = sysTime + 60; + } + +#ifndef BUILD_NO_DEBUG + addLog(LOG_LEVEL_DEBUG_MORE, F("NTP : No reply")); +#endif // ifndef BUILD_NO_DEBUG + udp.stop(); + return false; +} + + +/********************************************************************************************\ + Date/Time string formatters + \*********************************************************************************************/ + +String ESPEasy_time::getDateString(char delimiter) const +{ + return getDateString(tm, delimiter); +} + +String ESPEasy_time::getDateString(const struct tm& ts, char delimiter) { + // time format example with ':' delimiter: 23:59:59 (HH:MM:SS) + char DateString[20]; // 19 digits plus the null char + const int year = 1970 + ts.tm_year; + + sprintf_P(DateString, PSTR("%4d%c%02d%c%02d"), year, delimiter, ts.tm_mon, delimiter, ts.tm_mday); + return DateString; +} + +String ESPEasy_time::getTimeString(char delimiter, bool show_seconds /*=true*/) const +{ + return getTimeString(tm, delimiter, false, show_seconds); +} + +String ESPEasy_time::getTimeString_ampm(char delimiter, bool show_seconds /*=true*/) const +{ + return getTimeString(tm, delimiter, true, show_seconds); +} + + +// returns the current Time separated by the given delimiter +// time format example with ':' delimiter: 23:59:59 (HH:MM:SS) +String ESPEasy_time::getTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds) +{ + char TimeString[20]; // 19 digits plus the null char + + if (am_pm) { + uint8_t hour(ts.tm_hour % 12); + + if (hour == 0) { hour = 12; } + const char a_or_p = ts.tm_hour < 12 ? 'A' : 'P'; + + if (show_seconds) { + sprintf_P(TimeString, PSTR("%d%c%02d%c%02d %cM"), + hour, delimiter, ts.tm_min, delimiter, ts.tm_sec, a_or_p); + } else { + sprintf_P(TimeString, PSTR("%d%c%02d %cM"), + hour, delimiter, ts.tm_min, a_or_p); + } + } else { + if (show_seconds) { + sprintf_P(TimeString, PSTR("%02d%c%02d%c%02d"), + ts.tm_hour, delimiter, ts.tm_min, delimiter, ts.tm_sec); + } else { + sprintf_P(TimeString, PSTR("%d%c%02d"), + ts.tm_hour, delimiter, ts.tm_min); + } + } + return TimeString; +} + +String ESPEasy_time::getDateTimeString(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) const { + return getDateTimeString(tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, false); +} + +String ESPEasy_time::getDateTimeString_ampm(char dateDelimiter, char timeDelimiter, char dateTimeDelimiter) const { + return getDateTimeString(tm, dateDelimiter, timeDelimiter, dateTimeDelimiter, true); +} + +String ESPEasy_time::getDateTimeString(const struct tm& ts, char dateDelimiter, char timeDelimiter, char dateTimeDelimiter, bool am_pm) +{ + // if called like this: getDateTimeString('\0', '\0', '\0'); + // it will give back this: 20161231235959 (YYYYMMDDHHMMSS) + String ret = getDateString(ts, dateDelimiter); + + if (dateTimeDelimiter != '\0') { + ret += dateTimeDelimiter; + } + ret += getTimeString(ts, timeDelimiter, am_pm, true); + return ret; +} + + +/********************************************************************************************\ + Get current time/date + \*********************************************************************************************/ + +int ESPEasy_time::year(unsigned long t) + { + struct tm tmp; + + breakTime(t, tmp); + return 1970 + tmp.tm_year; +} + +int ESPEasy_time::weekday(unsigned long t) +{ + struct tm tmp; + + breakTime(t, tmp); + return tmp.tm_wday; +} + +String ESPEasy_time::weekday_str(int wday) +{ + const String weekDays = F("SunMonTueWedThuFriSat"); + return weekDays.substring(wday * 3, wday * 3 + 3); +} + +String ESPEasy_time::weekday_str() const +{ + return weekday_str(weekday()-1); +} + + + + + +/********************************************************************************************\ + Sunrise/Sunset calculations + \*********************************************************************************************/ + +int ESPEasy_time::getSecOffset(const String& format) { + int position_minus = format.indexOf('-'); + int position_plus = format.indexOf('+'); + + if ((position_minus == -1) && (position_plus == -1)) { + return 0; + } + int sign_position = _max(position_minus, position_plus); + int position_percent = format.indexOf('%', sign_position); + + if (position_percent == -1) { + return 0; + } + String valueStr = getNumerical(format.substring(sign_position, position_percent), true); + + if (!isInt(valueStr)) { return 0; } + int value = valueStr.toInt(); + + switch (format.charAt(position_percent - 1)) { + case 'm': + case 'M': + return value * 60; + case 'h': + case 'H': + return value * 3600; + } + return value; +} + + +String ESPEasy_time::getSunriseTimeString(char delimiter) const { + return getTimeString(sunRise, delimiter, false, false); +} + +String ESPEasy_time::getSunsetTimeString(char delimiter) const { + return getTimeString(sunSet, delimiter, false, false); +} + +String ESPEasy_time::getSunriseTimeString(char delimiter, int secOffset) const { + if (secOffset == 0) { + return getSunriseTimeString(delimiter); + } + return getTimeString(getSunRise(secOffset), delimiter, false, false); +} + +String ESPEasy_time::getSunsetTimeString(char delimiter, int secOffset) const { + if (secOffset == 0) { + return getSunsetTimeString(delimiter); + } + return getTimeString(getSunSet(secOffset), delimiter, false, false); +} + + +float ESPEasy_time::sunDeclination(int doy) { + // Declination of the sun in radians + // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average declinations (2008-2027) + return 0.409526325277017 * sin(0.0169060504029192 * (doy - 80.0856919827619)); +} + +float ESPEasy_time::diurnalArc(float dec, float lat) { + // Duration of the half sun path in hours (time from sunrise to the highest level in the south) + float rad = 0.0174532925; // = pi/180.0 + float height = -50.0 / 60.0 * rad; + float latRad = lat * rad; + + return 12.0 * acos((sin(height) - sin(latRad) * sin(dec)) / (cos(latRad) * cos(dec))) / 3.1415926536; +} + +float ESPEasy_time::equationOfTime(int doy) { + // Difference between apparent and mean solar time + // Formula 2008 by Arnold(at)Barmettler.com, fit to 20 years of average equation of time (2008-2027) + return -0.170869921174742 * sin(0.0336997028793971 * doy + 0.465419984181394) - 0.129890681040717 * sin( + 0.0178674832556871 * doy - 0.167936777524864); +} + +int ESPEasy_time::dayOfYear(int year, int month, int day) { + // Algorithm borrowed from DateToOrdinal by Ritchie Lawrence, www.commandline.co.uk + int z = 14 - month; + + z /= 12; + int y = year + 4800 - z; + int m = month + 12 * z - 3; + int j = 153 * m + 2; + j = j / 5 + day + y * 365 + y / 4 - y / 100 + y / 400 - 32045; + y = year + 4799; + int k = y * 365 + y / 4 - y / 100 + y / 400 - 31738; + return j - k + 1; +} + +void ESPEasy_time::calcSunRiseAndSet() { + int doy = dayOfYear(tm.tm_year, tm.tm_mon, tm.tm_mday); + float eqt = equationOfTime(doy); + float dec = sunDeclination(doy); + float da = diurnalArc(dec, Settings.Latitude); + float rise = 12 - da - eqt; + float set = 12 + da - eqt; + + tsRise.tm_hour = (int)rise; + tsRise.tm_min = (rise - (int)rise) * 60.0; + tsSet.tm_hour = (int)set; + tsSet.tm_min = (set - (int)set) * 60.0; + tsRise.tm_mday = tsSet.tm_mday = tm.tm_mday; + tsRise.tm_mon = tsSet.tm_mon = tm.tm_mon; + tsRise.tm_year = tsSet.tm_year = tm.tm_year; + + // Now apply the longitude + int secOffset_longitude = -1.0 * (Settings.Longitude / 15.0) * 3600; + tsSet = addSeconds(tsSet, secOffset_longitude, false); + tsRise = addSeconds(tsRise, secOffset_longitude, false); + + breakTime(time_zone.toLocal(makeTime(tsRise)), sunRise); + breakTime(time_zone.toLocal(makeTime(tsSet)), sunSet); +} + +struct tm ESPEasy_time::getSunRise(int secOffset) const { + return addSeconds(tsRise, secOffset, true); +} + +struct tm ESPEasy_time::getSunSet(int secOffset) const { + return addSeconds(tsSet, secOffset, true); +} + + diff --git a/src/src/Helpers/ESPEasy_time.h b/src/src/Helpers/ESPEasy_time.h new file mode 100644 index 000000000..e94842de6 --- /dev/null +++ b/src/src/Helpers/ESPEasy_time.h @@ -0,0 +1,192 @@ +#ifndef HELPERS_ESPEASY_TIME_H +#define HELPERS_ESPEASY_TIME_H + +#include + +#include "../../ESPEasyTimeTypes.h" + + +class ESPEasy_time { +public: + + +struct tm addSeconds(const struct tm& ts, int seconds, bool toLocalTime) const; +static void breakTime(unsigned long timeInput, struct tm& tm); + + +// Restore the last known system time +// This may be useful to get some idea of what time it is. +// This way the unit can do things based on local time even when NTP servers may not respond. +// Do not use this when booting from deep sleep. +// Only call this once during boot. +void restoreLastKnownUnixTime(unsigned long lastSysTime, byte deepSleepState); + +void setExternalTimeSource(double time, timeSource_t source); + +uint32_t getUnixTime() const; + +void initTime(); + +// Update and get the current systime +unsigned long now(); + +// Update time and return whether the minute has changed since last check. +bool reportNewMinute(); + +bool systemTimePresent() const; + +bool getNtpTime(double& unixTime_d); + + + +/********************************************************************************************\ + Date/Time string formatters + \*********************************************************************************************/ + +public: + +// Format the current Date separated by the given delimiter +// Default date format example: 20161231 (YYYYMMDD) +String getDateString(char delimiter = '\0') const; + +// Format given Date separated by the given delimiter +// date format example with '-' delimiter: 2016-12-31 (YYYY-MM-DD) +static String getDateString(const struct tm& ts, char delimiter); + +// Formats the current Time +// Default time format example: 235959 (HHMMSS) +String getTimeString(char delimiter = '\0', bool show_seconds=true) const; + +String getTimeString_ampm(char delimiter = '\0', bool show_seconds=true) const; + +// returns the current Time separated by the given delimiter +// time format example with ':' delimiter: 23:59:59 (HH:MM:SS) +static String getTimeString(const struct tm& ts, char delimiter, bool am_pm, bool show_seconds); + + + + +String getDateTimeString(char dateDelimiter = '-', char timeDelimiter = ':', char dateTimeDelimiter = ' ') const; +String getDateTimeString_ampm(char dateDelimiter = '-', char timeDelimiter = ':', char dateTimeDelimiter = ' ') const; + +// returns the current Date and Time separated by the given delimiter +// if called like this: getDateTimeString('\0', '\0', '\0'); +// it will give back this: 20161231235959 (YYYYMMDDHHMMSS) +static String getDateTimeString(const struct tm& ts, char dateDelimiter = '-', char timeDelimiter = ':', char dateTimeDelimiter = ' ', bool am_pm = false); + + +/********************************************************************************************\ + Get current time/date + \*********************************************************************************************/ + +// Get the year given a Unix time stamp +static int year(unsigned long t); + +// Get the weekday, given a Unix time stamp +static int weekday(unsigned long t); + +// Convert a weekday number (Sun = 1 ... Sat = 7) to a 3 letter string +static String weekday_str(int wday); + + +// Get current year. +int year() const +{ + return 1970 + tm.tm_year; +} + +// Get current month +byte month() const +{ + return tm.tm_mon; +} + +// Get current day of the month +byte day() const +{ + return tm.tm_mday; +} + +// Get current hour +byte hour() const +{ + return tm.tm_hour; +} + +// Get current minute +byte minute() const +{ + return tm.tm_min; +} + +// Get current second +byte second() const +{ + return tm.tm_sec; +} + +// day of week, sunday is day 1 +int weekday() const +{ + return tm.tm_wday; +} + +String weekday_str() const; + + + + + + +/********************************************************************************************\ + Sunrise/Sunset calculations + \*********************************************************************************************/ + +public: + +// Compute the offset in seconds of the substring +/-[smh] +static int getSecOffset(const String& format) ; +String getSunriseTimeString(char delimiter) const; +String getSunsetTimeString(char delimiter) const; +String getSunriseTimeString(char delimiter, int secOffset) const; +String getSunsetTimeString(char delimiter, int secOffset) const; + + +private: + +static float sunDeclination(int doy); +static float diurnalArc(float dec, float lat); +static float equationOfTime(int doy); +static int dayOfYear(int year, int month, int day); + +void calcSunRiseAndSet(); +struct tm getSunRise(int secOffset) const; +struct tm getSunSet(int secOffset) const; + + + + + + + +public: + +struct tm tm; +uint32_t syncInterval = 3600; // time sync will be attempted after this many seconds +double sysTime = 0.0; // Use high resolution double to get better sync between nodes when using NTP +uint32_t prevMillis = 0; +uint32_t nextSyncTime = 0; +double externalTimeSource = -1.0; // Used to set time from a source other than NTP. +struct tm tsRise, tsSet; +struct tm sunRise; +struct tm sunSet; +timeSource_t timeSource = No_time_source; + +byte PrevMinutes = 0; + + + +}; + + +#endif // HELPERS_ESPEASY_TIME_H \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_time_calc.cpp b/src/src/Helpers/ESPEasy_time_calc.cpp new file mode 100644 index 000000000..830decb6f --- /dev/null +++ b/src/src/Helpers/ESPEasy_time_calc.cpp @@ -0,0 +1,291 @@ +#include "ESPEasy_time_calc.h" + +#include +#include + +#include "../Globals/ESPEasy_time.h" + +// FIXME TD-er: Needed for GetArgv +#include "../../ESPEasy_fdwdecl.h" + + +#define SECS_PER_MIN (60UL) +#define SECS_PER_HOUR (3600UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) +#define LEAP_YEAR(Y) (((1970 + Y) > 0) && !((1970 + Y) % 4) && (((1970 + Y) % 100) || !((1970 + Y) % 400))) + +long ICACHE_RAM_ATTR timeDiff(const unsigned long prev, const unsigned long next) +{ + long signed_diff = 0; + + // To cast a value to a signed long, the difference may not exceed half the ULONG_MAX + const unsigned long half_max_unsigned_long = 2147483647u; // = 2^31 -1 + + if (next >= prev) { + const unsigned long diff = next - prev; + + if (diff <= half_max_unsigned_long) { + // Normal situation, just return the difference. + // Difference is a positive value. + signed_diff = static_cast(diff); + } else { + // prev has overflow, return a negative difference value + signed_diff = static_cast((ULONG_MAX - next) + prev + 1u); + signed_diff = -1 * signed_diff; + } + } else { + // next < prev + const unsigned long diff = prev - next; + + if (diff <= half_max_unsigned_long) { + // Normal situation, return a negative difference value + signed_diff = static_cast(diff); + signed_diff = -1 * signed_diff; + } else { + // next has overflow, return a positive difference value + signed_diff = static_cast((ULONG_MAX - prev) + next + 1u); + } + } + return signed_diff; +} + + +long timePassedSince(unsigned long timestamp) { + return timeDiff(timestamp, millis()); +} + +long usecPassedSince(unsigned long timestamp) { + return timeDiff(timestamp, micros()); +} + +// Check if a certain timeout has been reached. +bool timeOutReached(unsigned long timer) { + const long passed = timePassedSince(timer); + + return passed >= 0; +} + +bool usecTimeOutReached(unsigned long timer) { + const long passed = usecPassedSince(timer); + + return passed >= 0; +} + + +bool isLeapYear(int year) { + return LEAP_YEAR(year); +} + +/********************************************************************************************\ + Unix Time computations + \*********************************************************************************************/ + +uint32_t makeTime(const struct tm& tm) { + // assemble time elements into uint32_t + // note year argument is offset from 1970 (see macros in time.h to convert to other formats) + // previous version used full four digit year (or digits since 2000),i.e. 2009 was 2009 or 9 + const uint8_t monthDays[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; + int i; + uint32_t seconds; + + // seconds from 1970 till 1 jan 00:00:00 of the given year + seconds = tm.tm_year * (SECS_PER_DAY * 365); + + for (i = 0; i < tm.tm_year; i++) { + if (isLeapYear(i)) { + seconds += SECS_PER_DAY; // add extra days for leap years + } + } + + // add days for this year, months start from 1 + for (i = 1; i < tm.tm_mon; i++) { + if ((i == 2) && isLeapYear(tm.tm_year)) { + seconds += SECS_PER_DAY * 29; + } else { + seconds += SECS_PER_DAY * monthDays[i - 1]; // monthDay array starts from 0 + } + } + seconds += (tm.tm_mday - 1) * SECS_PER_DAY; + seconds += tm.tm_hour * SECS_PER_HOUR; + seconds += tm.tm_min * SECS_PER_MIN; + seconds += tm.tm_sec; + return (uint32_t)seconds; +} + + +/********************************************************************************************\ + Time computations for rules. + \*********************************************************************************************/ + +String timeLong2String(unsigned long lngTime) +{ + unsigned long x = 0; + String time = ""; + + x = (lngTime >> 16) & 0xf; + + if (x == 0x0f) { + x = 0; + } + String weekDays = F("AllSunMonTueWedThuFriSatWrkWkd"); + time = weekDays.substring(x * 3, x * 3 + 3); + time += ","; + + x = (lngTime >> 12) & 0xf; + + if (x == 0xf) { + time += "*"; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + x = (lngTime >> 8) & 0xf; + + if (x == 0xf) { + time += "*"; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + time += ":"; + + x = (lngTime >> 4) & 0xf; + + if (x == 0xf) { + time += "*"; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + x = (lngTime) & 0xf; + + if (x == 0xf) { + time += "*"; + } + else if (x == 0xe) { + time += '-'; + } + else { + time += x; + } + + return time; +} + + +unsigned long string2TimeLong(const String& str) +{ + // format 0000WWWWAAAABBBBCCCCDDDD + // WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes + + char command[20]; + int w, x, y; + unsigned long a; + { + // Within a scope so the tmpString is only used for copy. + String tmpString(str); + tmpString.toLowerCase(); + tmpString.toCharArray(command, 20); + } + unsigned long lngTime = 0; + String TmpStr1; + + if (GetArgv(command, TmpStr1, 1)) + { + String day = TmpStr1; + String weekDays = F("allsunmontuewedthufrisatwrkwkd"); + y = weekDays.indexOf(TmpStr1) / 3; + + if (y == 0) { + y = 0xf; // wildcard is 0xf + } + lngTime |= (unsigned long)y << 16; + } + + if (GetArgv(command, TmpStr1, 2)) + { + y = 0; + + for (x = TmpStr1.length() - 1; x >= 0; x--) + { + w = TmpStr1[x]; + + if (((w >= '0') && (w <= '9')) || (w == '*')) + { + a = 0xffffffff ^ (0xfUL << y); // create mask to clean nibble position y + lngTime &= a; // maak nibble leeg + + if (w == '*') { + lngTime |= (0xFUL << y); // fill nibble with wildcard value + } + else { + lngTime |= (w - '0') << y; // fill nibble with token + } + y += 4; + } + else + if (w == ':') {} + else + { + break; + } + } + } + #undef TmpStr1Length + return lngTime; +} + + + +/********************************************************************************************\ + Match clock event + \*********************************************************************************************/ +bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet) +{ + unsigned long Mask; + + for (byte y = 0; y < 8; y++) + { + if (((clockSet >> (y * 4)) & 0xf) == 0xf) // if nibble y has the wildcard value 0xf + { + Mask = 0xffffffff ^ (0xFUL << (y * 4)); // Mask to wipe nibble position y. + clockEvent &= Mask; // clear nibble + clockEvent |= (0xFUL << (y * 4)); // fill with wildcard value 0xf + } + } + + if (((clockSet >> (16)) & 0xf) == 0x8) { // if weekday nibble has the wildcard value 0x8 (workdays) + if (node_time.weekday() >= 2 and node_time.weekday() <= 6) // and we have a working day today... + { + Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. + clockEvent &= Mask; // clear nibble + clockEvent |= (0x8UL << (16)); // fill with wildcard value 0x8 + } + } + + if (((clockSet >> (16)) & 0xf) == 0x9) { // if weekday nibble has the wildcard value 0x9 (weekends) + if (node_time.weekday() == 1 or node_time.weekday() == 7) // and we have a weekend day today... + { + Mask = 0xffffffff ^ (0xFUL << (16)); // Mask to wipe nibble position. + clockEvent &= Mask; // clear nibble + clockEvent |= (0x9UL << (16)); // fill with wildcard value 0x9 + } + } + + if (clockEvent == clockSet) { + return true; + } + return false; +} diff --git a/src/src/Helpers/ESPEasy_time_calc.h b/src/src/Helpers/ESPEasy_time_calc.h new file mode 100644 index 000000000..da1ca7fed --- /dev/null +++ b/src/src/Helpers/ESPEasy_time_calc.h @@ -0,0 +1,57 @@ +#ifndef HELPERS_ESPEASY_TIME_CALC_H +#define HELPERS_ESPEASY_TIME_CALC_H + +#include + +/********************************************************************************************\ + Simple time computations. + \*********************************************************************************************/ + +// Return the time difference as a signed value, taking into account the timers may overflow. +// Returned timediff is between -24.9 days and +24.9 days. +// Returned value is positive when "next" is after "prev" +long ICACHE_RAM_ATTR timeDiff(const unsigned long prev, const unsigned long next); + +// Compute the number of milliSeconds passed since timestamp given. +// N.B. value can be negative if the timestamp has not yet been reached. +long timePassedSince(unsigned long timestamp); + +long usecPassedSince(unsigned long timestamp); + +// Check if a certain timeout has been reached. +bool timeOutReached(unsigned long timer); + +bool usecTimeOutReached(unsigned long timer); + + + + + +/********************************************************************************************\ + Unix Time computations + \*********************************************************************************************/ +bool isLeapYear(int year); + +uint32_t makeTime(const struct tm& tm); + +/********************************************************************************************\ + Time computations for rules. + \*********************************************************************************************/ + +// format 0000WWWWAAAABBBBCCCCDDDD +// WWWW=weekday, AAAA=hours tens digit, BBBB=hours, CCCC=minutes tens digit DDDD=minutes + +// Convert a 32 bit integer into a string like "Sun,12:30" +String timeLong2String(unsigned long lngTime); + +// Convert a string like "Sun,12:30" into a 32 bit integer +unsigned long string2TimeLong(const String& str); + + +/********************************************************************************************\ + Match clock event + \*********************************************************************************************/ +bool matchClockEvent(unsigned long clockEvent, unsigned long clockSet); + + +#endif // HELPERS_ESPEASY_TIME_CALC_H \ No newline at end of file diff --git a/src/src/Helpers/ESPEasy_time_zone.cpp b/src/src/Helpers/ESPEasy_time_zone.cpp new file mode 100644 index 000000000..cb666c6bc --- /dev/null +++ b/src/src/Helpers/ESPEasy_time_zone.cpp @@ -0,0 +1,198 @@ +#include "ESPEasy_time_zone.h" + + +#include + +#include "ESPEasy_time_calc.h" +#include "../DataStructs/TimeChangeRule.h" +#include "../Globals/Settings.h" +#include "../Globals/ESPEasy_time.h" +#include "../../ESPEasy_Log.h" + + +#define SECS_PER_MIN (60UL) +#define SECS_PER_HOUR (3600UL) +#define SECS_PER_DAY (SECS_PER_HOUR * 24UL) + + + +void ESPEasy_time_zone::getDefaultDst_flash_values(uint16_t& start, uint16_t& end) { + // DST start: Last Sunday March 2am => 3am + // DST end: Last Sunday October 3am => 2am + TimeChangeRule CEST(TimeChangeRule::Last, TimeChangeRule::Sun, TimeChangeRule::Mar, 2, Settings.TimeZone); // Summer Time + TimeChangeRule CET(TimeChangeRule::Last, TimeChangeRule::Sun, TimeChangeRule::Oct, 3, Settings.TimeZone); // Standard Time + + start = CEST.toFlashStoredValue(); + end = CET.toFlashStoredValue(); +} + +void ESPEasy_time_zone::applyTimeZone(uint32_t curTime) { + int dst_offset = Settings.DST ? 60 : 0; + uint16_t tmpStart(Settings.DST_Start); + uint16_t tmpEnd(Settings.DST_End); + + for (int i = 0; i < 2; ++i) { + TimeChangeRule start(tmpStart, Settings.TimeZone + dst_offset); // Summer Time + TimeChangeRule end(tmpEnd, Settings.TimeZone); // Standard Time + + if (start.isValid() && end.isValid()) { + setTimeZone(start, end, curTime); + return; + } + getDefaultDst_flash_values(tmpStart, tmpEnd); + } +} + +void ESPEasy_time_zone::setTimeZone(const TimeChangeRule& dstStart, const TimeChangeRule& stdStart, uint32_t curTime) { + m_dst = dstStart; + m_std = stdStart; + + if (calcTimeChanges(ESPEasy_time::year(curTime))) { + logTimeZoneInfo(); + } +} + +void ESPEasy_time_zone::logTimeZoneInfo() { + String log = F("Current Time Zone: "); + + if (m_std.offset != m_dst.offset) { + // Summer time + log += F(" DST time start: "); + + if (m_dstLoc != 0) { + struct tm tmp; + ESPEasy_time::breakTime(m_dstLoc, tmp); + log += ESPEasy_time::getDateTimeString(tmp, '-', ':', ' ', false); + } + log += F(" offset: "); + log += m_dst.offset; + log += F(" min "); + } + + // Standard/Winter time. + log += F("STD time start: "); + + if (m_stdLoc != 0) { + struct tm tmp; + ESPEasy_time::breakTime(m_stdLoc, tmp); + log += ESPEasy_time::getDateTimeString(tmp, '-', ':', ' ', false); + } + log += F(" offset: "); + log += m_std.offset; + log += F(" min"); + addLog(LOG_LEVEL_INFO, log); +} + + +///*----------------------------------------------------------------------* +// * Convert the given time change rule to a uint32_t value * +// * for the given year. * +// *----------------------------------------------------------------------*/ +uint32_t ESPEasy_time_zone::calcTimeChangeForRule(const TimeChangeRule& r, int yr) +{ + uint8_t m = r.month; // temp copies of r.month and r.week + uint8_t w = r.week; + + if (w == 0) // is this a "Last week" rule? + { + if (++m > 12) // yes, for "Last", go to the next month + { + m = 1; + ++yr; + } + w = 1; // and treat as first week of next month, subtract 7 days later + } + + // calculate first day of the month, or for "Last" rules, first day of the next month + struct tm tm; + tm.tm_hour = r.hour; + tm.tm_min = 0; + tm.tm_sec = 0; + tm.tm_mday = 1; + tm.tm_mon = m; + tm.tm_year = yr - 1970; + uint32_t t = makeTime(tm); + + // add offset from the first of the month to r.dow, and offset for the given week + t += ((r.dow - ESPEasy_time::weekday(t) + 7) % 7 + (w - 1) * 7) * SECS_PER_DAY; + + // back up a week if this is a "Last" rule + if (r.week == 0) { t -= 7 * SECS_PER_DAY; } + return t; +} + +/*----------------------------------------------------------------------* +* Calculate the DST and standard time change points for the given * +* given year as local and UTC uint32_t values. * +*----------------------------------------------------------------------*/ +bool ESPEasy_time_zone::calcTimeChanges(int yr) +{ + uint32_t dstLoc = calcTimeChangeForRule(m_dst, yr); + uint32_t stdLoc = calcTimeChangeForRule(m_std, yr); + bool changed = (m_dstLoc != dstLoc) || (m_stdLoc != stdLoc); + + m_dstLoc = dstLoc; + m_stdLoc = stdLoc; + m_dstUTC = m_dstLoc - m_std.offset * SECS_PER_MIN; + m_stdUTC = m_stdLoc - m_dst.offset * SECS_PER_MIN; + return changed; +} + +/*----------------------------------------------------------------------* +* Convert the given UTC time to local time, standard or * +* daylight time, as appropriate. * +*----------------------------------------------------------------------*/ +uint32_t ESPEasy_time_zone::toLocal(uint32_t utc) +{ + // recalculate the time change points if needed + if (ESPEasy_time::year(utc) != ESPEasy_time::year(m_dstUTC)) { calcTimeChanges(ESPEasy_time::year(utc)); } + + if (utcIsDST(utc)) { + return utc + m_dst.offset * SECS_PER_MIN; + } + else { + return utc + m_std.offset * SECS_PER_MIN; + } +} + +/*----------------------------------------------------------------------* +* Determine whether the given UTC uint32_t is within the DST interval * +* or the Standard time interval. * +*----------------------------------------------------------------------*/ +bool ESPEasy_time_zone::utcIsDST(uint32_t utc) +{ + // recalculate the time change points if needed + if (ESPEasy_time::year(utc) != ESPEasy_time::year(m_dstUTC)) { calcTimeChanges(ESPEasy_time::year(utc)); } + + if (m_stdUTC == m_dstUTC) { // daylight time not observed in this tz + return false; + } + else if (m_stdUTC > m_dstUTC) { // northern hemisphere + return utc >= m_dstUTC && utc < m_stdUTC; + } + else { // southern hemisphere + return !(utc >= m_stdUTC && utc < m_dstUTC); + } +} + +/*----------------------------------------------------------------------* +* Determine whether the given Local uint32_t is within the DST interval * +* or the Standard time interval. * +*----------------------------------------------------------------------*/ +bool ESPEasy_time_zone::locIsDST(uint32_t local) +{ + // recalculate the time change points if needed + if (ESPEasy_time::year(local) != ESPEasy_time::year(m_dstLoc)) { calcTimeChanges(ESPEasy_time::year(local)); } + + if (m_stdUTC == m_dstUTC) { // daylight time not observed in this tz + return false; + } + else if (m_stdLoc > m_dstLoc) { // northern hemisphere + return local >= m_dstLoc && local < m_stdLoc; + } + else { // southern hemisphere + return !(local >= m_stdLoc && local < m_dstLoc); + } +} + + diff --git a/src/src/Helpers/ESPEasy_time_zone.h b/src/src/Helpers/ESPEasy_time_zone.h new file mode 100644 index 000000000..8976f7ed3 --- /dev/null +++ b/src/src/Helpers/ESPEasy_time_zone.h @@ -0,0 +1,118 @@ +#ifndef HELPERS_ESPEASY_TIME_ZONE_H +#define HELPERS_ESPEASY_TIME_ZONE_H + +#include + +#include "../DataStructs/TimeChangeRule.h" + +/********************************************************************************************\ + Time zone + \*********************************************************************************************/ + +// Borrowed code from Timezone: https://github.com/JChristensen/Timezon + + + +/* + // Examples time zones + // Australia Eastern Time Zone (Sydney, Melbourne) + TimeChangeRule aEDT = {First, Sun, Oct, 2, 660}; // UTC + 11 hours + TimeChangeRule aEST = {First, Sun, Apr, 3, 600}; // UTC + 10 hours + setTimeZone(aEDT, aEST); + + // Central European Time (Frankfurt, Paris) + TimeChangeRule CEST = {Last, Sun, Mar, 2, 120}; // Central European Summer Time + TimeChangeRule CET = {Last, Sun, Oct, 3, 60}; // Central European Standard Time + setTimeZone(CEST, CET); + + // United Kingdom (London, Belfast) + TimeChangeRule BST = {Last, Sun, Mar, 1, 60}; // British Summer Time + TimeChangeRule GMT = {Last, Sun, Oct, 2, 0}; // Standard Time + setTimeZone(BST, GMT); + + // UTC + TimeChangeRule utcRule = {Last, Sun, Mar, 1, 0}; // UTC + setTimeZone(utcRule, utcRule); + + // US Eastern Time Zone (New York, Detroit) + TimeChangeRule usEDT = {Second, Sun, Mar, 2, -240}; // Eastern Daylight Time = UTC - 4 hours + TimeChangeRule usEST = {First, Sun, Nov, 2, -300}; // Eastern Standard Time = UTC - 5 hours + setTimeZone(usEDT, usEST); + + // US Central Time Zone (Chicago, Houston) + TimeChangeRule usCDT = {Second, dowSunday, Mar, 2, -300}; + TimeChangeRule usCST = {First, dowSunday, Nov, 2, -360}; + setTimeZone(usCDT, usCST); + + // US Mountain Time Zone (Denver, Salt Lake City) + TimeChangeRule usMDT = {Second, dowSunday, Mar, 2, -360}; + TimeChangeRule usMST = {First, dowSunday, Nov, 2, -420}; + setTimeZone(usMDT, usMST); + + // Arizona is US Mountain Time Zone but does not use DST + setTimeZone(usMST, usMST); + + // US Pacific Time Zone (Las Vegas, Los Angeles) + TimeChangeRule usPDT = {Second, dowSunday, Mar, 2, -420}; + TimeChangeRule usPST = {First, dowSunday, Nov, 2, -480}; + setTimeZone(usPDT, usPST); + */ + + +class ESPEasy_time_zone { + +public: + + +void getDefaultDst_flash_values(uint16_t& start, uint16_t& end); + +void applyTimeZone(uint32_t curTime); + +void setTimeZone(const TimeChangeRule& dstStart, const TimeChangeRule& stdStart, uint32_t curTime); + +void logTimeZoneInfo(); + + +///*----------------------------------------------------------------------* +// * Convert the given time change rule to a uint32_t value * +// * for the given year. * +// *----------------------------------------------------------------------*/ +uint32_t calcTimeChangeForRule(const TimeChangeRule& r, int yr); + +/*----------------------------------------------------------------------* +* Calculate the DST and standard time change points for the given * +* given year as local and UTC uint32_t values. * +*----------------------------------------------------------------------*/ +bool calcTimeChanges(int yr); + +/*----------------------------------------------------------------------* +* Convert the given UTC time to local time, standard or * +* daylight time, as appropriate. * +*-----------------------------------------------------------------------*/ +uint32_t toLocal(uint32_t utc); + +/*----------------------------------------------------------------------* +* Determine whether the given UTC uint32_t is within the DST interval * +* or the Standard time interval. * +*-----------------------------------------------------------------------*/ +bool utcIsDST(uint32_t utc); + +/*----------------------------------------------------------------------* +* Determine whether the given Local uint32_t is within the DST interval * +* or the Standard time interval. * +*-----------------------------------------------------------------------*/ +bool locIsDST(uint32_t local); + + +TimeChangeRule m_dst; // rule for start of dst or summer time for any year +TimeChangeRule m_std; // rule for start of standard time for any year +uint32_t m_dstUTC = 0; // dst start for given/current year, given in UTC +uint32_t m_stdUTC = 0; // std time start for given/current year, given in UTC +uint32_t m_dstLoc = 0; // dst start for given/current year, given in local time +uint32_t m_stdLoc = 0; // std time start for given/current year, given in local time + + +}; + + +#endif // HELPERS_ESPEASY_TIME_ZONE_H \ No newline at end of file diff --git a/src/src/Helpers/StringConverter.cpp b/src/src/Helpers/StringConverter.cpp new file mode 100644 index 000000000..a932a032c --- /dev/null +++ b/src/src/Helpers/StringConverter.cpp @@ -0,0 +1,42 @@ +#include "StringConverter.h" + + + + +// ******************************************************************************** +// URNEncode char string to string object +// ******************************************************************************** +String URLEncode(const char *msg) +{ + const char *hex = "0123456789abcdef"; + String encodedMsg; + encodedMsg.reserve(strlen(msg)); + while (*msg != '\0') { + if ((('a' <= *msg) && (*msg <= 'z')) + || (('A' <= *msg) && (*msg <= 'Z')) + || (('0' <= *msg) && (*msg <= '9')) + || ('-' == *msg) || ('_' == *msg) + || ('.' == *msg) || ('~' == *msg)) { + encodedMsg += *msg; + } else { + encodedMsg += '%'; + encodedMsg += hex[*msg >> 4]; + encodedMsg += hex[*msg & 15]; + } + msg++; + } + return encodedMsg; +} + + + +void repl(const String& key, const String& val, String& s, boolean useURLencode) +{ + if (useURLencode) { + // URLEncode does take resources, so check first if needed. + if (s.indexOf(key) == -1) return; + s.replace(key, URLEncode(val.c_str())); + } else { + s.replace(key, val); + } +} \ No newline at end of file diff --git a/src/src/Helpers/StringConverter.h b/src/src/Helpers/StringConverter.h new file mode 100644 index 000000000..8a28f4e68 --- /dev/null +++ b/src/src/Helpers/StringConverter.h @@ -0,0 +1,12 @@ +#ifndef HELPERS_STRINGCONVERTER_H +#define HELPERS_STRINGCONVERTER_H + +#include + + +String URLEncode(const char *msg); +void repl(const String& key, const String& val, String& s, boolean useURLencode); + + + +#endif // HELPERS_STRINGCONVERTER_H \ No newline at end of file diff --git a/src/src/Helpers/SystemVariables.cpp b/src/src/Helpers/SystemVariables.cpp new file mode 100644 index 000000000..0039a427c --- /dev/null +++ b/src/src/Helpers/SystemVariables.cpp @@ -0,0 +1,277 @@ +#include "SystemVariables.h" + +#include "../DataStructs/TimingStats.h" +#include "../Globals/CRCValues.h" +#include "StringConverter.h" +#include "../../ESPEasy-Globals.h" + + +#ifdef USES_MQTT +#include "../Globals/MQTT.h" +#endif + + +String getReplacementString(const String& format, String& s) { + int startpos = s.indexOf(format); + int endpos = s.indexOf('%', startpos + 1); + String R = s.substring(startpos, endpos + 1); + +#ifndef BUILD_NO_DEBUG + + if (loglevelActiveFor(LOG_LEVEL_DEBUG)) { + String log = F("ReplacementString SunTime: "); + log += R; + log += F(" offset: "); + log += ESPEasy_time::getSecOffset(R); + addLog(LOG_LEVEL_DEBUG, log); + } +#endif // ifndef BUILD_NO_DEBUG + return R; +} + +void replSunRiseTimeString(const String& format, String& s, boolean useURLencode) { + String R = getReplacementString(format, s); + + repl(R, node_time.getSunriseTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); +} + +void replSunSetTimeString(const String& format, String& s, boolean useURLencode) { + String R = getReplacementString(format, s); + + repl(R, node_time.getSunsetTimeString(':', ESPEasy_time::getSecOffset(R)), s, useURLencode); +} + + +String timeReplacement_leadZero(int value) +{ + char valueString[5] = { 0 }; + sprintf(valueString, "%02d", value); + return valueString; +} + +#define SMART_REPL_T(T, S) if (s.indexOf(T) != -1) { (S((T), s, useURLencode)); } + +// FIXME TD-er: Try to match these with StringProvider::getValue + +void SystemVariables::parseSystemVariables(String& s, boolean useURLencode) +{ + START_TIMER + + if (s.indexOf('%') == -1) { + STOP_TIMER(PARSE_SYSVAR_NOCHANGE); + return; + } + + SystemVariables::Enum enumval = static_cast(0); + do { + enumval = SystemVariables::nextReplacementEnum(s, enumval); + String value; + switch (enumval) + { + case BSSID: value = String((wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? F("00:00:00:00:00:00") : WiFi.BSSIDstr()); break; + case CR: value = "\r"; break; + case IP: value = getValue(LabelType::IP_ADDRESS); break; + case IP4: value = WiFi.localIP().toString().substring(WiFi.localIP().toString().lastIndexOf('.') + 1); break; // 4th IP octet + #ifdef USES_MQTT + case ISMQTT: value = String(MQTTclient_connected); break; + #else + case ISMQTT: value = "0"; break; + #endif // ifdef USES_MQTT + + #ifdef USES_P037 + case ISMQTTIMP: value = String(P037_MQTTImport_connected); break; + #else + case ISMQTTIMP: value = "0"; break; + #endif // USES_P037 + + + case ISNTP: value = String(statusNTPInitialized); break; + case ISWIFI: value = String(wifiStatus); break; // 0=disconnected, 1=connected, 2=got ip, 3=services initialized + case LCLTIME: value = getValue(LabelType::LOCAL_TIME); break; + case LCLTIME_AM: value = node_time.getDateTimeString_ampm('-', ':', ' '); break; + case LF: value = "\n"; break; + case MAC: value = getValue(LabelType::STA_MAC); break; + case MAC_INT: value = String(ESP.getChipId()); break; // Last 24 bit of MAC address as integer, to be used in rules. + case RSSI: value = getValue(LabelType::WIFI_RSSI); break; + case SPACE: value = " "; break; + case SSID: value = (wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? F("--") : WiFi.SSID(); break; + case SUNRISE: SMART_REPL_T(SystemVariables::toString(enumval), replSunRiseTimeString); break; + case SUNSET: SMART_REPL_T(SystemVariables::toString(enumval), replSunSetTimeString); break; + case SYSBUILD_DATE: value = String(CRCValues.compileDate); break; + case SYSBUILD_TIME: value = String(CRCValues.compileTime); break; + case SYSDAY: value = String(node_time.day()); break; + case SYSDAY_0: value = timeReplacement_leadZero(node_time.day()); break; + case SYSHEAP: value = String(ESP.getFreeHeap()); break; + case SYSHOUR: value = String(node_time.hour()); break; + case SYSHOUR_0: value = timeReplacement_leadZero(node_time.hour()); break; + case SYSLOAD: value = String(getCPUload()); break; + case SYSMIN: value = String(node_time.minute()); break; + case SYSMIN_0: value = timeReplacement_leadZero(node_time.minute()); break; + case SYSMONTH: value = String(node_time.month()); break; + case SYSNAME: value = Settings.Name; break; + case SYSSEC: value = String(node_time.second()); break; + case SYSSEC_0: value = timeReplacement_leadZero(node_time.second()); break; + case SYSSEC_D: value = String(((node_time.hour() * 60) + node_time.minute()) * 60 + node_time.second()); break; + case SYSSTACK: value = String(getCurrentFreeStack()); break; + case SYSTIME: value = node_time.getTimeString(':'); break; + case SYSTIME_AM: value = node_time.getTimeString_ampm(':', false); break; + case SYSTM_HM: value = node_time.getTimeString(':', false); break; + case SYSTM_HM_AM: value = node_time.getTimeString_ampm(':', false); break; + case SYSWEEKDAY: value = String(node_time.weekday()); break; + case SYSWEEKDAY_S: value = node_time.weekday_str(); break; + case SYSYEAR: value = String(node_time.year()); break; + case SYSYEARS: value = timeReplacement_leadZero(node_time.year() % 100); break; + case SYSYEAR_0: value = String(node_time.year()); break; + case SYS_MONTH_0: value = timeReplacement_leadZero(node_time.month()); break; + case S_CR: value = F("\\r"); break; + case S_LF: value = F("\\n"); break; + case UNIT_sysvar: value = getValue(LabelType::UNIT_NR); break; + case UNIXDAY: value = String(node_time.getUnixTime() / 86400); break; + case UNIXDAY_SEC: value = String(node_time.getUnixTime() % 86400); break; + case UNIXTIME: value = String(node_time.getUnixTime()); break; + case UPTIME: value = String(wdcounter / 2); break; + #if FEATURE_ADC_VCC + case VCC: value = String(vcc); break; + #else + case VCC: value = String(-1); break; + #endif // if FEATURE_ADC_VCC + case WI_CH: value = String((wifiStatus == ESPEASY_WIFI_DISCONNECTED) ? 0 : WiFi.channel()); break; + + case UNKNOWN: + break; + } + + switch(enumval) + { + case SUNRISE: + case SUNSET: + case UNKNOWN: + // Do not replace + break; + default: + if (useURLencode) { + value = URLEncode(value.c_str()); + } + s.replace(SystemVariables::toString(enumval), value); + break; + } + } + while (enumval != SystemVariables::Enum::UNKNOWN); + + const int v_index = s.indexOf("%v"); + + if ((v_index != -1) && isDigit(s[v_index + 2])) { + for (byte i = 0; i < CUSTOM_VARS_MAX; ++i) { + String key = "%v" + String(i + 1) + '%'; + if (s.indexOf(key) != -1) { + String value = String(customFloatVar[i]); + + if (useURLencode) { + value = URLEncode(value.c_str()); + } + s.replace(key, value); + } + } + } + + STOP_TIMER(PARSE_SYSVAR); +} + + +SystemVariables::Enum SystemVariables::nextReplacementEnum(const String& str, SystemVariables::Enum last_tested) +{ + if (str.indexOf('%') == -1) { + return Enum::UNKNOWN; + } + + SystemVariables::Enum nextTested = static_cast(0); + if (last_tested > nextTested) { + nextTested = static_cast(last_tested + 1); + } + if (nextTested >= Enum::UNKNOWN) { + return Enum::UNKNOWN; + } + + String str_prefix = SystemVariables::toString(nextTested).substring(0,2); + bool str_prefix_exists = str.indexOf(str_prefix) != -1; + for (int i = nextTested; i < Enum::UNKNOWN; ++i) { + SystemVariables::Enum enumval = static_cast(i); + String new_str_prefix = SystemVariables::toString(enumval).substring(0,2); + if (str_prefix == new_str_prefix && !str_prefix_exists) { + // Just continue + } else { + str_prefix = new_str_prefix; + str_prefix_exists = str.indexOf(str_prefix) != -1; + if (str_prefix_exists) { + if (str.indexOf(SystemVariables::toString(enumval)) != -1) { + return enumval; + } + } + } + } + + return Enum::UNKNOWN; +} + + + +String SystemVariables::toString(SystemVariables::Enum enumval) +{ + switch (enumval) { + case Enum::BSSID: return F("%bssid%"); + case Enum::CR: return F("%CR%"); + case Enum::IP4: return F("%ip4%"); + case Enum::IP: return F("%ip%"); + case Enum::ISMQTT: return F("%ismqtt%"); + case Enum::ISMQTTIMP: return F("%ismqttimp%"); + case Enum::ISNTP: return F("%isntp%"); + case Enum::ISWIFI: return F("%iswifi%"); + case Enum::LCLTIME: return F("%lcltime%"); + case Enum::LCLTIME_AM: return F("%lcltime_am%"); + case Enum::LF: return F("%LF%"); + case Enum::MAC: return F("%mac%"); + case Enum::MAC_INT: return F("%mac_int%"); + case Enum::RSSI: return F("%rssi%"); + case Enum::SPACE: return F("%SP%"); + case Enum::SSID: return F("%ssid%"); + case Enum::SUNRISE: return F("%sunrise"); + case Enum::SUNSET: return F("%sunset"); + case Enum::SYSBUILD_DATE: return F("%sysbuild_date%"); + case Enum::SYSBUILD_TIME: return F("%sysbuild_time%"); + case Enum::SYSDAY: return F("%sysday%"); + case Enum::SYSDAY_0: return F("%sysday_0%"); + case Enum::SYSHEAP: return F("%sysheap%"); + case Enum::SYSHOUR: return F("%syshour%"); + case Enum::SYSHOUR_0: return F("%syshour_0%"); + case Enum::SYSLOAD: return F("%sysload%"); + case Enum::SYSMIN: return F("%sysmin%"); + case Enum::SYSMIN_0: return F("%sysmin_0%"); + case Enum::SYSMONTH: return F("%sysmonth%"); + case Enum::SYSNAME: return F("%sysname%"); + case Enum::SYSSEC: return F("%syssec%"); + case Enum::SYSSEC_0: return F("%syssec_0%"); + case Enum::SYSSEC_D: return F("%syssec_d%"); + case Enum::SYSSTACK: return F("%sysstack%"); + case Enum::SYSTIME: return F("%systime%"); + case Enum::SYSTIME_AM: return F("%systime_am%"); + case Enum::SYSTM_HM: return F("%systm_hm%"); + case Enum::SYSTM_HM_AM: return F("%systm_hm_am%"); + case Enum::SYSWEEKDAY: return F("%sysweekday%"); + case Enum::SYSWEEKDAY_S: return F("%sysweekday_s%"); + case Enum::SYSYEAR: return F("%sysyear%"); + case Enum::SYSYEARS: return F("%sysyears%"); + case Enum::SYSYEAR_0: return F("%sysyear_0%"); + case Enum::SYS_MONTH_0: return F("%sysmonth_0%"); + case Enum::S_CR: return F("%R%"); + case Enum::S_LF: return F("%N%"); + case Enum::UNIT_sysvar: return F("%unit%"); + case Enum::UNIXDAY: return F("%unixday%"); + case Enum::UNIXDAY_SEC: return F("%unixday_sec%"); + case Enum::UNIXTIME: return F("%unixtime%"); + case Enum::UPTIME: return F("%uptime%"); + case Enum::VCC: return F("%vcc%"); + case Enum::WI_CH: return F("%wi_ch%"); + case Enum::UNKNOWN: break; + } + return F("Unknown"); +} \ No newline at end of file diff --git a/src/src/Helpers/SystemVariables.h b/src/src/Helpers/SystemVariables.h new file mode 100644 index 000000000..c82ceb918 --- /dev/null +++ b/src/src/Helpers/SystemVariables.h @@ -0,0 +1,85 @@ +#ifndef HELPERS_SYSTEMVARIABLES_H +#define HELPERS_SYSTEMVARIABLES_H + +#include + +class SystemVariables { + +public: + + enum Enum { + // For optmization, keep enums sorted alfabetically + BSSID, + CR, + IP, + IP4, // 4th IP octet + ISMQTT, + ISMQTTIMP, + ISNTP, + ISWIFI, + LCLTIME, + LCLTIME_AM, + LF, + MAC, + MAC_INT, + RSSI, + SPACE, + SSID, + SUNRISE, + SUNSET, + SYSBUILD_DATE, + SYSBUILD_TIME, + SYSDAY, + SYSDAY_0, + SYSHEAP, + SYSHOUR, + SYSHOUR_0, + SYSLOAD, + SYSMIN, + SYSMIN_0, + SYSMONTH, + SYSNAME, + SYSSEC, + SYSSEC_0, + SYSSEC_D, + SYSSTACK, + SYSTIME, + SYSTIME_AM, + SYSTM_HM, + SYSTM_HM_AM, + SYSWEEKDAY, + SYSWEEKDAY_S, + SYSYEAR, + SYSYEARS, + SYSYEAR_0, + SYS_MONTH_0, + S_CR, + S_LF, + UNIT_sysvar, // We already use UNIT as define. + UNIXDAY, + UNIXDAY_SEC, + UNIXTIME, + UPTIME, + VCC, + WI_CH, + + // Keep UNKNOWN as last + UNKNOWN + }; + + // Find the next thing to replace. + // Return UNKNOWN when nothing needs to be replaced. + static Enum nextReplacementEnum(const String& str, Enum last_tested); + + static String toString(Enum enumval); + + static void parseSystemVariables(String& s, boolean useURLencode); + + + +}; + + + + +#endif // HELPERS_SYSTEMVARIABLES_H diff --git a/src/src/Helpers/msecTimerHandlerStruct.cpp b/src/src/Helpers/msecTimerHandlerStruct.cpp new file mode 100644 index 000000000..8c9dd9207 --- /dev/null +++ b/src/src/Helpers/msecTimerHandlerStruct.cpp @@ -0,0 +1,142 @@ +#include "msecTimerHandlerStruct.h" + +#include + +#include "ESPEasy_time_calc.h" + + +#define MAX_SCHEDULER_WAIT_TIME 5 // Max delay used in the scheduler for passing idle time. + + msecTimerHandlerStruct::msecTimerHandlerStruct() : get_called(0), get_called_ret_id(0), max_queue_length(0), + last_exec_time_usec(0), total_idle_time_usec(0), idle_time_pct(0.0), is_idle(false), eco_mode(true) + { + last_log_start_time = millis(); + } + + void msecTimerHandlerStruct::setEcoMode(bool enabled) { + eco_mode = enabled; + } + + void msecTimerHandlerStruct::registerAt(unsigned long id, unsigned long timer) { + timer_id_couple item(id, timer); + + insert(item); + } + + // Check if timeout has been reached and also return its set timer. + // Return 0 if no item has reached timeout moment. + unsigned long msecTimerHandlerStruct::getNextId(unsigned long& timer) { + ++get_called; + + if (_timer_ids.empty()) { + recordIdle(); + + if (eco_mode) { + delay(MAX_SCHEDULER_WAIT_TIME); // Nothing to do, try save some power. + } + return 0; + } + timer_id_couple item = _timer_ids.front(); + const long passed = timePassedSince(item._timer); + + if (passed < 0) { + // No timeOutReached + recordIdle(); + + if (eco_mode) { + long waitTime = (-1 * passed) - 1; // will be non negative + + if (waitTime > MAX_SCHEDULER_WAIT_TIME) { + waitTime = MAX_SCHEDULER_WAIT_TIME; + } else if (waitTime < 0) { + // Should not happen, but just to be sure we will not wait forever. + waitTime = 0; + } + delay(waitTime); + } + return 0; + } + recordRunning(); + unsigned long size = _timer_ids.size(); + + if (size > max_queue_length) { max_queue_length = size; } + _timer_ids.pop_front(); + timer = item._timer; + ++get_called_ret_id; + return item._id; + } + + + + + + + + String msecTimerHandlerStruct::getQueueStats() { + String result; + + result += get_called; + result += '/'; + result += get_called_ret_id; + result += '/'; + result += max_queue_length; + result += '/'; + result += idle_time_pct; + get_called = 0; + get_called_ret_id = 0; + + // max_queue_length = 0; + return result; + } + + void msecTimerHandlerStruct::updateIdleTimeStats() { + const long duration = timePassedSince(last_log_start_time); + + last_log_start_time = millis(); + idle_time_pct = total_idle_time_usec / duration / 10.0; + total_idle_time_usec = 0; + } + + float msecTimerHandlerStruct::getIdleTimePct() { + return idle_time_pct; + } + + struct match_id { + match_id(unsigned long id) : _id(id) {} + + bool operator()(const timer_id_couple& item) { + return _id == item._id; + } + + unsigned long _id; + }; + + void msecTimerHandlerStruct::insert(const timer_id_couple& item) { + if (item._id == 0) { return; } + + // Make sure only one is present with the same id. + _timer_ids.remove_if(match_id(item._id)); + const bool mustSort = !_timer_ids.empty(); + _timer_ids.push_front(item); + + if (mustSort) { + _timer_ids.sort(); // TD-er: Must check if this is an expensive operation. + } + + // It should be a relative light operation, to insert into a sorted list. + // Perhaps it is better to use std::set ???? + // Keep in mind: order is based on timer, uniqueness is based on id. + } + + void msecTimerHandlerStruct::recordIdle() { + if (is_idle) { return; } + last_exec_time_usec = micros(); + is_idle = true; + delay(0); // Nothing to do, so leave time for backgroundtasks + } + + void msecTimerHandlerStruct::recordRunning() { + if (!is_idle) { return; } + is_idle = false; + total_idle_time_usec += usecPassedSince(last_exec_time_usec); + } diff --git a/src/src/Helpers/msecTimerHandlerStruct.h b/src/src/Helpers/msecTimerHandlerStruct.h new file mode 100644 index 000000000..77ba02a08 --- /dev/null +++ b/src/src/Helpers/msecTimerHandlerStruct.h @@ -0,0 +1,53 @@ +#ifndef HELPERS_MSECTIMERHANDLERSTRUCT_H +#define HELPERS_MSECTIMERHANDLERSTRUCT_H + + +#include + +#include "../DataStructs/timer_id_couple.h" + +class String; + +struct msecTimerHandlerStruct { + msecTimerHandlerStruct(); + + void setEcoMode(bool enabled); + + void registerAt(unsigned long id, unsigned long timer); + + // Check if timeout has been reached and also return its set timer. + // Return 0 if no item has reached timeout moment. + unsigned long getNextId(unsigned long& timer); + + String getQueueStats(); + + void updateIdleTimeStats(); + + float getIdleTimePct(); + +private: + + void insert(const timer_id_couple& item); + + void recordIdle(); + + void recordRunning(); + + // Statistics + unsigned long get_called; + unsigned long get_called_ret_id; + unsigned long max_queue_length; + + // Compute idle system time + unsigned long last_exec_time_usec; + unsigned long total_idle_time_usec; + unsigned long last_log_start_time; + float idle_time_pct; + bool is_idle; + bool eco_mode; + + // The list of set timers + std::list_timer_ids; +}; + +#endif // HELPERS_MSECTIMERHANDLERSTRUCT_H \ No newline at end of file