[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.
This commit is contained in:
Gijs Noorlander
2020-03-03 00:01:45 +01:00
parent 6bc909f026
commit b386b73bd2
43 changed files with 2351 additions and 1650 deletions
+4
View File
@@ -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"
+28 -5
View File
@@ -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;
+1
View File
@@ -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();
+2 -223
View File
@@ -5,14 +5,9 @@
#include <list>
#include <time.h>
#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_id_couple>_timer_ids;
};
#endif /* ESPEASY_TIMETYPES_H_ */
+2
View File
@@ -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;
+2 -2
View File
@@ -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;
+8
View File
@@ -28,6 +28,8 @@
#include <FS.h>
#include <WiFiUdp.h>
// 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
+3 -3
View File
@@ -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 += ',';
+21
View File
@@ -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<unsigned long>(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());
}
+17 -161
View File
@@ -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)
+1 -1
View File
@@ -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());
-964
View File
@@ -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 <time.h>
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<double>(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<uint32_t>(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<double>(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<unsigned long>(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<double>(txTm);
// Add fractional part.
unixTime_d += (static_cast<double>(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<double>(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<long>(diff);
} else {
// prev has overflow, return a negative difference value
signed_diff = static_cast<long>((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<long>(diff);
signed_diff = -1 * signed_diff;
} else {
// next has overflow, return a positive difference value
signed_diff = static_cast<long>((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<unsigned long>(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;
}
-268
View File
@@ -1,268 +0,0 @@
#include <time.h>
/********************************************************************************************\
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);
}
}
+5 -3
View File
@@ -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);
+2 -2
View File
@@ -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");
+1 -1
View File
@@ -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));
}
+2 -2
View File
@@ -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);
}
+2 -2
View File
@@ -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));
+1 -1
View File
@@ -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));
/*
+6 -6
View File
@@ -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 *)&current);
}
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("-");
}
+8 -5
View File
@@ -14,6 +14,9 @@
#include <TinyGPS++.h>
#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<double>(age) / 1000.0;
setExternalTimeSource(time, GPS_time_source);
initTime();
node_time.setExternalTimeSource(time, GPS_time_source);
node_time.initTime();
}
P082_pps_time = 0;
}
+31
View File
@@ -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
}
+38
View File
@@ -0,0 +1,38 @@
#ifndef DATASTRUCTS_TIMECHANGERULE_H
#define DATASTRUCTS_TIMECHANGERULE_H
#include <Arduino.h>
// 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
@@ -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"
+16
View File
@@ -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);
}
+23
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
#include "../Globals/ESPEasy_time.h"
ESPEasy_time node_time;
+10
View File
@@ -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
+1 -1
View File
@@ -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
+3
View File
@@ -0,0 +1,3 @@
#include "TimeZone.h"
ESPEasy_time_zone time_zone;
+9
View File
@@ -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
+633
View File
@@ -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 <time.h>
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<double>(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<uint32_t>(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<double>(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<unsigned long>(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<double>(txTm);
// Add fractional part.
unixTime_d += (static_cast<double>(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<double>(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);
}
+192
View File
@@ -0,0 +1,192 @@
#ifndef HELPERS_ESPEASY_TIME_H
#define HELPERS_ESPEASY_TIME_H
#include <Arduino.h>
#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 +/-<nn>[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
+291
View File
@@ -0,0 +1,291 @@
#include "ESPEasy_time_calc.h"
#include <Arduino.h>
#include <limits.h>
#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<long>(diff);
} else {
// prev has overflow, return a negative difference value
signed_diff = static_cast<long>((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<long>(diff);
signed_diff = -1 * signed_diff;
} else {
// next has overflow, return a positive difference value
signed_diff = static_cast<long>((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;
}
+57
View File
@@ -0,0 +1,57 @@
#ifndef HELPERS_ESPEASY_TIME_CALC_H
#define HELPERS_ESPEASY_TIME_CALC_H
#include <Arduino.h>
/********************************************************************************************\
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
+198
View File
@@ -0,0 +1,198 @@
#include "ESPEasy_time_zone.h"
#include <time.h>
#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);
}
}
+118
View File
@@ -0,0 +1,118 @@
#ifndef HELPERS_ESPEASY_TIME_ZONE_H
#define HELPERS_ESPEASY_TIME_ZONE_H
#include <Arduino.h>
#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
+42
View File
@@ -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);
}
}
+12
View File
@@ -0,0 +1,12 @@
#ifndef HELPERS_STRINGCONVERTER_H
#define HELPERS_STRINGCONVERTER_H
#include <Arduino.h>
String URLEncode(const char *msg);
void repl(const String& key, const String& val, String& s, boolean useURLencode);
#endif // HELPERS_STRINGCONVERTER_H
+277
View File
@@ -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<SystemVariables::Enum>(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<SystemVariables::Enum>(0);
if (last_tested > nextTested) {
nextTested = static_cast<SystemVariables::Enum>(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<SystemVariables::Enum>(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");
}
+85
View File
@@ -0,0 +1,85 @@
#ifndef HELPERS_SYSTEMVARIABLES_H
#define HELPERS_SYSTEMVARIABLES_H
#include <Arduino.h>
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
+142
View File
@@ -0,0 +1,142 @@
#include "msecTimerHandlerStruct.h"
#include <Arduino.h>
#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);
}
+53
View File
@@ -0,0 +1,53 @@
#ifndef HELPERS_MSECTIMERHANDLERSTRUCT_H
#define HELPERS_MSECTIMERHANDLERSTRUCT_H
#include <list>
#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_id_couple>_timer_ids;
};
#endif // HELPERS_MSECTIMERHANDLERSTRUCT_H