mirror of
https://github.com/letscontrolit/ESPEasy.git
synced 2026-09-12 01:24:04 +00:00
[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:
@@ -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
@@ -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;
|
||||
|
||||
@@ -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
@@ -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_ */
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
@@ -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 += ',';
|
||||
|
||||
@@ -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
@@ -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)
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
@@ -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
@@ -137,16 +137,16 @@ void P081_setCronExecTimes(struct EventStruct *event, time_t lastExecTime, time_
|
||||
|
||||
time_t P081_getCurrentTime()
|
||||
{
|
||||
now();
|
||||
node_time.now();
|
||||
|
||||
// FIXME TD-er: Why work on a deepcopy of tm?
|
||||
struct tm current = tm;
|
||||
struct tm current = node_time.tm;
|
||||
return mktime((struct tm *)¤t);
|
||||
}
|
||||
|
||||
void P081_check_or_init(struct EventStruct *event)
|
||||
{
|
||||
if (systemTimePresent()) {
|
||||
if (node_time.systemTimePresent()) {
|
||||
const time_t current_time = P081_getCurrentTime();
|
||||
time_t last_exec_time = P081_getCronExecTime(LASTEXECUTION);
|
||||
time_t next_exec_time = P081_getCronExecTime(NEXTEXECUTION);
|
||||
@@ -299,7 +299,7 @@ boolean Plugin_081(byte function, struct EventStruct *event, String& string)
|
||||
case PLUGIN_ONCE_A_SECOND:
|
||||
{
|
||||
// code to be executed once a second. Tasks which do not require fast response can be added here
|
||||
if (systemTimePresent()) {
|
||||
if (node_time.systemTimePresent()) {
|
||||
P081_check_or_init(event);
|
||||
time_t next_exec_time = P081_getCronExecTime(NEXTEXECUTION);
|
||||
|
||||
@@ -314,7 +314,7 @@ boolean Plugin_081(byte function, struct EventStruct *event, String& string)
|
||||
next_exec_time = P081_computeNextCronTime(event->TaskIndex, current_time);
|
||||
P081_setCronExecTimes(event, last_exec_time, next_exec_time);
|
||||
|
||||
addLog(LOG_LEVEL_DEBUG, String(F("Next execution:")) + getDateTimeString(*gmtime(&next_exec_time)));
|
||||
addLog(LOG_LEVEL_DEBUG, String(F("Next execution:")) + ESPEasy_time::getDateTimeString(*gmtime(&next_exec_time)));
|
||||
|
||||
if (function != PLUGIN_TIME_CHANGE) {
|
||||
LoadTaskSettings(event->TaskIndex);
|
||||
@@ -398,7 +398,7 @@ String P081_formatExecTime(float execTime_f) {
|
||||
time_t exec_time = P081_getCronExecTime(execTime_f);
|
||||
|
||||
if (exec_time != CRON_INVALID_INSTANT) {
|
||||
return getDateTimeString(*gmtime(&exec_time));
|
||||
return ESPEasy_time::getDateTimeString(*gmtime(&exec_time));
|
||||
}
|
||||
return F("-");
|
||||
}
|
||||
|
||||
+8
-5
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "../Globals/ESPEasy_time.h"
|
||||
|
||||
ESPEasy_time node_time;
|
||||
@@ -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,8 +1,8 @@
|
||||
#ifndef GLOBALS_RTC_H
|
||||
#define GLOBALS_RTC_H
|
||||
|
||||
#include "../DataStructs/RTCStruct.h"
|
||||
|
||||
struct RTCStruct;
|
||||
extern RTCStruct RTC;
|
||||
|
||||
#endif // GLOBALS_RTC_H
|
||||
@@ -0,0 +1,3 @@
|
||||
#include "TimeZone.h"
|
||||
|
||||
ESPEasy_time_zone time_zone;
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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");
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user