timezone support

This commit is contained in:
Paul
2019-11-01 15:04:17 +01:00
parent 905ae885f7
commit 6275245b03
14 changed files with 642 additions and 136 deletions

View File

@@ -97,8 +97,9 @@ MyESP::MyESP() {
// ntp // ntp
_ntp_server = strdup(MYESP_NTP_SERVER); _ntp_server = strdup(MYESP_NTP_SERVER);
_ntp_interval = 60; _ntp_interval = NTP_INTERVAL_DEFAULT;
_ntp_enabled = false; _ntp_enabled = false;
_ntp_timezone = NTP_TIMEZONE_DEFAULT;
// get the build time // get the build time
_buildTime = _getBuildTime(); _buildTime = _getBuildTime();
@@ -251,8 +252,8 @@ void MyESP::_wifiCallback(justwifi_messages_t code, char * parameter) {
// NTP now that we have a WiFi connection // NTP now that we have a WiFi connection
if (_ntp_enabled) { if (_ntp_enabled) {
NTP.Ntp(_ntp_server, _ntp_interval); // set up NTP server NTP.Ntp(_ntp_server, _ntp_interval, _ntp_timezone); // set up NTP server
myDebug_P(PSTR("[NTP] NTP internet time enabled via server %s"), _ntp_server); myDebug_P(PSTR("[NTP] NTP internet time enabled via server %s with timezone %d"), _ntp_server, _ntp_timezone);
} }
// call any final custom stuff // call any final custom stuff
@@ -632,6 +633,27 @@ void MyESP::_telnet_setup() {
memset(_command, 0, TELNET_MAX_COMMAND_LENGTH); memset(_command, 0, TELNET_MAX_COMMAND_LENGTH);
} }
// restart some services like web, mqtt, ntp etc...
void MyESP::_kick() {
myDebug_P(PSTR("Kicking services..."));
// NTP - fetch new time
if (_ntp_enabled) {
myDebug_P(PSTR(" - Requesting NTP time"));
NTP.getNtpTime();
}
// kick web
myDebug_P(PSTR(" - Restart web server and web sockets"));
_ws->enable(false);
//_webServer->reset();
_ws->enable(true);
// kick mqtt
myDebug_P(PSTR(" - Restart MQTT"));
mqttClient.disconnect();
}
// Show help of commands // Show help of commands
void MyESP::_consoleShowHelp() { void MyESP::_consoleShowHelp() {
myDebug_P(PSTR("")); myDebug_P(PSTR(""));
@@ -652,7 +674,7 @@ void MyESP::_consoleShowHelp() {
myDebug_P(PSTR("*")); myDebug_P(PSTR("*"));
myDebug_P(PSTR("* Commands:")); myDebug_P(PSTR("* Commands:"));
myDebug_P(PSTR("* ?/help=show commands, CTRL-D/quit=close telnet session")); myDebug_P(PSTR("* ?/help=show commands, CTRL-D/quit=close telnet session"));
myDebug_P(PSTR("* set, system, restart, mqttlog")); myDebug_P(PSTR("* set, system, restart, mqttlog, kick"));
#ifdef CRASH #ifdef CRASH
myDebug_P(PSTR("* crash <dump | clear | test [n]>")); myDebug_P(PSTR("* crash <dump | clear | test [n]>"));
@@ -696,6 +718,8 @@ void MyESP::_printSetCommands() {
myDebug_P(PSTR(" set mqtt_keepalive [seconds]")); myDebug_P(PSTR(" set mqtt_keepalive [seconds]"));
myDebug_P(PSTR(" set mqtt_retain [on | off]")); myDebug_P(PSTR(" set mqtt_retain [on | off]"));
myDebug_P(PSTR(" set ntp_enabled <on | off>")); myDebug_P(PSTR(" set ntp_enabled <on | off>"));
myDebug_P(PSTR(" set ntp_interval [n]"));
myDebug_P(PSTR(" set ntp_timezone [n]"));
myDebug_P(PSTR(" set serial <on | off>")); myDebug_P(PSTR(" set serial <on | off>"));
myDebug_P(PSTR(" set log_events <on | off>")); myDebug_P(PSTR(" set log_events <on | off>"));
@@ -760,6 +784,9 @@ void MyESP::_printSetCommands() {
#endif #endif
myDebug_P(PSTR(" ntp_enabled=%s"), (_ntp_enabled) ? "on" : "off"); myDebug_P(PSTR(" ntp_enabled=%s"), (_ntp_enabled) ? "on" : "off");
myDebug_P(PSTR(" ntp_interval=%d"), _ntp_interval);
myDebug_P(PSTR(" ntp_timezone=%d"), _ntp_timezone);
myDebug_P(PSTR(" log_events=%s"), (_general_log_events) ? "on" : "off"); myDebug_P(PSTR(" log_events=%s"), (_general_log_events) ? "on" : "off");
// print any custom settings // print any custom settings
@@ -849,6 +876,10 @@ bool MyESP::_changeSetting(uint8_t wc, const char * setting, const char * value)
save_config = fs_setSettingValue(&_mqtt_heartbeat, value, false); save_config = fs_setSettingValue(&_mqtt_heartbeat, value, false);
} else if (strcmp(setting, "ntp_enabled") == 0) { } else if (strcmp(setting, "ntp_enabled") == 0) {
save_config = fs_setSettingValue(&_ntp_enabled, value, false); save_config = fs_setSettingValue(&_ntp_enabled, value, false);
} else if (strcmp(setting, "ntp_interval") == 0) {
save_config = fs_setSettingValue(&_ntp_interval, value, NTP_INTERVAL_DEFAULT);
} else if (strcmp(setting, "ntp_timezone") == 0) {
save_config = fs_setSettingValue(&_ntp_timezone, value, NTP_TIMEZONE_DEFAULT);
} else if (strcmp(setting, "log_events") == 0) { } else if (strcmp(setting, "log_events") == 0) {
save_config = fs_setSettingValue(&_general_log_events, value, false); save_config = fs_setSettingValue(&_general_log_events, value, false);
} else { } else {
@@ -946,6 +977,12 @@ void MyESP::_telnetCommand(char * commandLine) {
return; return;
} }
// kick command
if ((strcmp(ptrToCommandName, "kick") == 0) && (wc == 1)) {
_kick();
return;
}
// restart command // restart command
if (((strcmp(ptrToCommandName, "restart") == 0) || (strcmp(ptrToCommandName, "reboot") == 0)) && (wc == 1)) { if (((strcmp(ptrToCommandName, "restart") == 0) || (strcmp(ptrToCommandName, "reboot") == 0)) && (wc == 1)) {
resetESP(); resetESP();
@@ -1282,8 +1319,9 @@ void MyESP::showSystemStats() {
myDebug_P(PSTR(" [MQTT] is disconnected")); myDebug_P(PSTR(" [MQTT] is disconnected"));
} }
if (_ntp_enabled) { if (_have_ntp_time) {
myDebug_P(PSTR(" [NTP] Time in UTC is %02d:%02d:%02d"), to_hour(now()), to_minute(now()), to_second(now())); uint32_t real_time = getSystemTime();
myDebug_P(PSTR(" [NTP] Local Time is %02d:%02d:%02d %s (%d)"), to_hour(real_time), to_minute(real_time), to_second(real_time), NTP.tcr->abbrev, real_time);
} }
#ifdef CRASH #ifdef CRASH
@@ -1540,18 +1578,18 @@ char * MyESP::_mqttTopic(const char * topic) {
// validates a file in SPIFFS, loads it into the json buffer and returns true if ok // validates a file in SPIFFS, loads it into the json buffer and returns true if ok
size_t MyESP::_fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc) { size_t MyESP::_fs_validateConfigFile(const char * filename, size_t maxsize, JsonDocument & doc) {
// myDebug_P(PSTR("[FS] Checking file %s"), filename); // remove for debugging
// see if we can open it // see if we can open it
File file = SPIFFS.open(filename, "r"); File file = SPIFFS.open(filename, "r");
if (!file) { if (!file) {
myDebug_P(PSTR("[FS] File %s not found"), filename); myDebug_P(PSTR("[FS] Cannot open config file %s for reading"), filename);
return 0; return 0;
} }
// check size // check size
size_t size = file.size(); size_t size = file.size();
// myDebug_P(PSTR("[FS] Checking file %s (%d bytes)"), filename, size); // remove for debugging
if (size > maxsize) { if (size > maxsize) {
file.close(); file.close();
myDebug_P(PSTR("[FS] Error. File %s size %d is too large (max %d)"), filename, size, maxsize); myDebug_P(PSTR("[FS] Error. File %s size %d is too large (max %d)"), filename, size, maxsize);
@@ -1752,8 +1790,9 @@ bool MyESP::_fs_loadConfig() {
_ntp_server = strdup(ntp["server"] | ""); _ntp_server = strdup(ntp["server"] | "");
_ntp_interval = ntp["interval"] | 60; _ntp_interval = ntp["interval"] | 60;
if (_ntp_interval < 2) if (_ntp_interval < 2)
_ntp_interval = 60; _ntp_interval = NTP_INTERVAL_DEFAULT;
_ntp_enabled = ntp["enabled"]; _ntp_enabled = ntp["enabled"];
_ntp_timezone = ntp["timezone"] | NTP_TIMEZONE_DEFAULT;
myDebug_P(PSTR("[FS] System config loaded (%d bytes)"), size); myDebug_P(PSTR("[FS] System config loaded (%d bytes)"), size);
@@ -1800,6 +1839,18 @@ bool MyESP::fs_setSettingValue(uint8_t * setting, const char * value, uint8_t va
return true; return true;
} }
// saves a signed 8-bit integer into a config setting, using default value if non set
// returns true if successful
bool MyESP::fs_setSettingValue(int8_t * setting, const char * value, int8_t value_default) {
if (_hasValue(value)) {
*setting = (int8_t)atoi(value);
} else {
*setting = value_default; // use the default value
}
return true;
}
// saves a bool into a config setting, using default value if non set // saves a bool into a config setting, using default value if non set
// returns true if successful // returns true if successful
bool MyESP::fs_setSettingValue(bool * setting, const char * value, bool value_default) { bool MyESP::fs_setSettingValue(bool * setting, const char * value, bool value_default) {
@@ -1830,6 +1881,7 @@ bool MyESP::_fs_loadCustomConfig() {
if (_fs_loadsave_callback_f) { if (_fs_loadsave_callback_f) {
const JsonObject & json = doc["settings"]; const JsonObject & json = doc["settings"];
if (!(_fs_loadsave_callback_f)(MYESP_FSACTION_LOAD, json)) { if (!(_fs_loadsave_callback_f)(MYESP_FSACTION_LOAD, json)) {
myDebug_P(PSTR("[FS] Error reading custom config")); myDebug_P(PSTR("[FS] Error reading custom config"));
return false; return false;
@@ -1852,6 +1904,7 @@ bool MyESP::fs_saveCustomConfig(JsonObject root) {
// open for writing // open for writing
File configFile = SPIFFS.open(MYESP_CUSTOMCONFIG_FILE, "w"); File configFile = SPIFFS.open(MYESP_CUSTOMCONFIG_FILE, "w");
if (!configFile) { if (!configFile) {
myDebug_P(PSTR("[FS] Failed to open custom config for writing")); myDebug_P(PSTR("[FS] Failed to open custom config for writing"));
ok = false; ok = false;
@@ -1888,15 +1941,16 @@ bool MyESP::fs_saveCustomConfig(JsonObject root) {
// save system config to spiffs // save system config to spiffs
bool MyESP::fs_saveConfig(JsonObject root) { bool MyESP::fs_saveConfig(JsonObject root) {
bool ok = false;
// call any custom functions before handling SPIFFS // call any custom functions before handling SPIFFS
if (_ota_pre_callback_f) { if (_ota_pre_callback_f) {
(_ota_pre_callback_f)(); (_ota_pre_callback_f)();
} }
bool ok = false;
// open for writing // open for writing
File configFile = SPIFFS.open(MYESP_CONFIG_FILE, "w"); File configFile = SPIFFS.open(MYESP_CONFIG_FILE, "w");
if (!configFile) { if (!configFile) {
myDebug_P(PSTR("[FS] Failed to open system config for writing")); myDebug_P(PSTR("[FS] Failed to open system config for writing"));
ok = false; ok = false;
@@ -1940,6 +1994,7 @@ bool MyESP::_fs_writeConfig() {
general["serial"] = _general_serial; general["serial"] = _general_serial;
general["hostname"] = _general_hostname; general["hostname"] = _general_hostname;
general["log_events"] = _general_log_events; general["log_events"] = _general_log_events;
general["version"] = _app_version;
JsonObject mqtt = doc.createNestedObject("mqtt"); JsonObject mqtt = doc.createNestedObject("mqtt");
mqtt["enabled"] = _mqtt_enabled; mqtt["enabled"] = _mqtt_enabled;
@@ -1950,7 +2005,6 @@ bool MyESP::_fs_writeConfig() {
mqtt["qos"] = _mqtt_qos; mqtt["qos"] = _mqtt_qos;
mqtt["keepalive"] = _mqtt_keepalive; mqtt["keepalive"] = _mqtt_keepalive;
mqtt["retain"] = _mqtt_retain; mqtt["retain"] = _mqtt_retain;
mqtt["password"] = _mqtt_password; mqtt["password"] = _mqtt_password;
mqtt["base"] = _mqtt_base; mqtt["base"] = _mqtt_base;
@@ -1958,6 +2012,7 @@ bool MyESP::_fs_writeConfig() {
ntp["server"] = _ntp_server; ntp["server"] = _ntp_server;
ntp["interval"] = _ntp_interval; ntp["interval"] = _ntp_interval;
ntp["enabled"] = _ntp_enabled; ntp["enabled"] = _ntp_enabled;
ntp["timezone"] = _ntp_timezone;
bool ok = fs_saveConfig(root); // save it bool ok = fs_saveConfig(root); // save it
@@ -1993,28 +2048,15 @@ void MyESP::_fs_setup() {
(_ota_pre_callback_f)(); // call custom function (_ota_pre_callback_f)(); // call custom function
} }
// check SPIFFS is OK
if (!SPIFFS.begin()) { if (!SPIFFS.begin()) {
myDebug_P(PSTR("[FS] Formatting filesystem..."));
if (SPIFFS.format()) { if (SPIFFS.format()) {
if (_general_log_events) { myDebug_P(PSTR("[FS] File system formatted"));
_writeEvent("WARN", "system", "File system formatted", "");
}
} else { } else {
myDebug_P(PSTR("[FS] Failed to format file system")); myDebug_P(PSTR("[FS] Failed to format file system"));
} }
} }
// load the main system config file if we can. Otherwise create it and expect user to configure in web interface
if (!_fs_loadConfig()) {
myDebug_P(PSTR("[FS] Creating a new system config"));
_fs_writeConfig(); // create the initial config file
}
// load system and custom config
if (!_fs_loadCustomConfig()) {
_fs_createCustomConfig(); // create the initial config file
}
/* /*
// fill event log with tests // fill event log with tests
SPIFFS.remove(MYESP_EVENTLOG_FILE); SPIFFS.remove(MYESP_EVENTLOG_FILE);
@@ -2039,6 +2081,17 @@ void MyESP::_fs_setup() {
} }
} }
// load the main system config file if we can. Otherwise create it and expect user to configure in web interface
if (!_fs_loadConfig()) {
myDebug_P(PSTR("[FS] Creating a new system config"));
_fs_writeConfig(); // create the initial config file
}
// load system and custom config
if (!_fs_loadCustomConfig()) {
_fs_createCustomConfig(); // create the initial config file
}
if (_ota_post_callback_f) { if (_ota_post_callback_f) {
(_ota_post_callback_f)(); // call custom function (_ota_post_callback_f)(); // call custom function
} }
@@ -2279,12 +2332,9 @@ void MyESP::_writeEvent(const char * type, const char * src, const char * desc,
root["time"] = now(); // is relative if we're not using NTP root["time"] = now(); // is relative if we're not using NTP
// Serialize JSON to file // Serialize JSON to file
size_t n = serializeJson(root, eventlog); (void)serializeJson(root, eventlog);
eventlog.print("\n"); // this indicates end of the entry
if (!n) { eventlog.print("\n"); // this indicates end of the entry
//Serial.println("[SYSTEM] Error writing to event log"); // for debugging
}
eventlog.close(); eventlog.close();
} }
@@ -2453,17 +2503,7 @@ void MyESP::_procMsg(AsyncWebSocketClient * client, size_t sz) {
uint8_t page = doc["page"]; uint8_t page = doc["page"];
_sendEventLog(page); _sendEventLog(page);
} else if (strcmp(command, "clearevent") == 0) { } else if (strcmp(command, "clearevent") == 0) {
if (_ota_pre_callback_f) { _emptyEventLog();
(_ota_pre_callback_f)(); // call custom function
}
if (SPIFFS.remove(MYESP_EVENTLOG_FILE)) {
_writeEvent("WARN", "system", "Event log cleared", "");
} else {
myDebug_P(PSTR("[WEB] Could not clear event log"));
}
if (_ota_post_callback_f) {
(_ota_post_callback_f)(); // call custom function
}
} else if (strcmp(command, "scan") == 0) { } else if (strcmp(command, "scan") == 0) {
WiFi.scanNetworksAsync(std::bind(&MyESP::_printScanResult, this, std::placeholders::_1), true); WiFi.scanNetworksAsync(std::bind(&MyESP::_printScanResult, this, std::placeholders::_1), true);
} else if (strcmp(command, "gettime") == 0) { } else if (strcmp(command, "gettime") == 0) {
@@ -2480,6 +2520,22 @@ void MyESP::_procMsg(AsyncWebSocketClient * client, size_t sz) {
client->_tempObject = NULL; client->_tempObject = NULL;
} }
// delete the event log
void MyESP::_emptyEventLog() {
if (_ota_pre_callback_f) {
(_ota_pre_callback_f)(); // call custom function
}
if (SPIFFS.remove(MYESP_EVENTLOG_FILE)) {
_writeEvent("WARN", "system", "Event log cleared", "");
myDebug_P(PSTR("[WEB] Event log cleared"));
} else {
myDebug_P(PSTR("[WEB] Could not clear event log"));
}
if (_ota_post_callback_f) {
(_ota_post_callback_f)(); // call custom function
}
}
// read both system config and the custom config and send as json to web socket // read both system config and the custom config and send as json to web socket
bool MyESP::_fs_sendConfig() { bool MyESP::_fs_sendConfig() {
File configFile; File configFile;
@@ -2866,6 +2922,16 @@ void MyESP::_sendTime() {
_ws->textAll(buffer, len); _ws->textAll(buffer, len);
} }
// returns real time from the internet/NTP if availble
// otherwise elapsed system time
unsigned long MyESP::getSystemTime() {
if (_have_ntp_time) {
return now();
}
return millis(); // elapsed time
}
// bootup sequence // bootup sequence
// quickly flash LED until we get a Wifi connection, or AP established // quickly flash LED until we get a Wifi connection, or AP established
void MyESP::_bootupSequence() { void MyESP::_bootupSequence() {
@@ -2965,6 +3031,7 @@ void MyESP::loop() {
_bootupSequence(); // see if a reset was pressed during bootup _bootupSequence(); // see if a reset was pressed during bootup
jw.loop(); // WiFi jw.loop(); // WiFi
ArduinoOTA.handle(); // OTA ArduinoOTA.handle(); // OTA
ESP.wdtFeed(); // feed the watchdog... ESP.wdtFeed(); // feed the watchdog...

View File

@@ -9,7 +9,7 @@
#ifndef MyESP_h #ifndef MyESP_h
#define MyESP_h #define MyESP_h
#define MYESP_VERSION "1.2.13" #define MYESP_VERSION "1.2.14"
#include <ArduinoJson.h> #include <ArduinoJson.h>
#include <ArduinoOTA.h> #include <ArduinoOTA.h>
@@ -295,6 +295,7 @@ class MyESP {
bool fs_setSettingValue(char ** setting, const char * value, const char * value_default); bool fs_setSettingValue(char ** setting, const char * value, const char * value_default);
bool fs_setSettingValue(uint16_t * setting, const char * value, uint16_t value_default); bool fs_setSettingValue(uint16_t * setting, const char * value, uint16_t value_default);
bool fs_setSettingValue(uint8_t * setting, const char * value, uint8_t value_default); bool fs_setSettingValue(uint8_t * setting, const char * value, uint8_t value_default);
bool fs_setSettingValue(int8_t * setting, const char * value, int8_t value_default);
bool fs_setSettingValue(bool * setting, const char * value, bool value_default); bool fs_setSettingValue(bool * setting, const char * value, bool value_default);
// Web // Web
@@ -317,6 +318,7 @@ class MyESP {
uint32_t getSystemResetReason(); uint32_t getSystemResetReason();
uint8_t getSystemBootStatus(); uint8_t getSystemBootStatus();
bool _have_ntp_time; bool _have_ntp_time;
unsigned long getSystemTime();
private: private:
// mqtt // mqtt
@@ -418,6 +420,7 @@ class MyESP {
char * _getBuildTime(); char * _getBuildTime();
bool _hasValue(const char * s); bool _hasValue(const char * s);
void _printHeap(const char * s); void _printHeap(const char * s);
void _kick();
// reset reason and rtcmem // reset reason and rtcmem
bool _rtcmem_status; bool _rtcmem_status;
@@ -464,6 +467,7 @@ class MyESP {
// log // log
void _sendEventLog(uint8_t page); void _sendEventLog(uint8_t page);
void _emptyEventLog();
// web // web
void _onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len); void _onWsEvent(AsyncWebSocket * server, AsyncWebSocketClient * client, AwsEventType type, void * arg, uint8_t * data, size_t len);
@@ -481,6 +485,7 @@ class MyESP {
char * _ntp_server; char * _ntp_server;
uint8_t _ntp_interval; uint8_t _ntp_interval;
bool _ntp_enabled; bool _ntp_enabled;
uint8_t _ntp_timezone;
}; };
extern MyESP myESP; extern MyESP myESP;

View File

@@ -3,16 +3,75 @@
*/ */
#include "Ntp.h" #include "Ntp.h"
#include "MyESP.h"
char * NtpClient::TimeServerName; char * NtpClient::TimeServerName;
Timezone * NtpClient::tz;
TimeChangeRule * NtpClient::tcr;
time_t NtpClient::syncInterval; time_t NtpClient::syncInterval;
IPAddress NtpClient::timeServer; IPAddress NtpClient::timeServer;
AsyncUDP NtpClient::udpListener; AsyncUDP NtpClient::udpListener;
byte NtpClient::NTPpacket[NTP_PACKET_SIZE]; byte NtpClient::NTPpacket[NTP_PACKET_SIZE];
void ICACHE_FLASH_ATTR NtpClient::Ntp(const char * server, time_t syncMins) { // Australia Eastern Time Zone (Sydney, Melbourne)
TimeChangeRule aEDT = {"AEDT", First, Sun, Oct, 2, 660}; // UTC + 11 hours
TimeChangeRule aEST = {"AEST", First, Sun, Apr, 3, 600}; // UTC + 10 hours
Timezone ausET(aEDT, aEST);
// Moscow Standard Time (MSK, does not observe DST)
TimeChangeRule msk = {"MSK", Last, Sun, Mar, 1, 180};
Timezone tzMSK(msk);
// Central European Time (Frankfurt, Paris)
TimeChangeRule CEST = {"CEST", Last, Sun, Mar, 2, 120}; // Central European Summer Time
TimeChangeRule CET = {"CET ", Last, Sun, Oct, 3, 60}; // Central European Standard Time
Timezone CE(CEST, CET);
// United Kingdom (London, Belfast)
TimeChangeRule BST = {"BST", Last, Sun, Mar, 1, 60}; // British Summer Time
TimeChangeRule GMT = {"GMT", Last, Sun, Oct, 2, 0}; // Standard Time
Timezone UK(BST, GMT);
// UTC
TimeChangeRule utcRule = {"UTC", Last, Sun, Mar, 1, 0}; // UTC
Timezone UTC(utcRule);
// US Eastern Time Zone (New York, Detroit)
TimeChangeRule usEDT = {"EDT", Second, Sun, Mar, 2, -240}; // Eastern Daylight Time = UTC - 4 hours
TimeChangeRule usEST = {"EST", First, Sun, Nov, 2, -300}; // Eastern Standard Time = UTC - 5 hours
Timezone usET(usEDT, usEST);
// US Central Time Zone (Chicago, Houston)
TimeChangeRule usCDT = {"CDT", Second, Sun, Mar, 2, -300};
TimeChangeRule usCST = {"CST", First, Sun, Nov, 2, -360};
Timezone usCT(usCDT, usCST);
// US Mountain Time Zone (Denver, Salt Lake City)
TimeChangeRule usMDT = {"MDT", Second, Sun, Mar, 2, -360};
TimeChangeRule usMST = {"MST", First, Sun, Nov, 2, -420};
Timezone usMT(usMDT, usMST);
// Arizona is US Mountain Time Zone but does not use DST
Timezone usAZ(usMST);
// US Pacific Time Zone (Las Vegas, Los Angeles)
TimeChangeRule usPDT = {"PDT", Second, Sun, Mar, 2, -420};
TimeChangeRule usPST = {"PST", First, Sun, Nov, 2, -480};
Timezone usPT(usPDT, usPST);
// build index of all timezones
Timezone * timezones[] = {&ausET, &tzMSK, &CE, &UK, &UTC, &usET, &usCT, &usMT, &usAZ, &usPT};
void ICACHE_FLASH_ATTR NtpClient::Ntp(const char * server, time_t syncMins, uint8_t tz_index) {
TimeServerName = strdup(server); TimeServerName = strdup(server);
syncInterval = syncMins * 60; // convert to seconds syncInterval = syncMins * 60; // convert to seconds
// check for out of bounds
if (tz_index > NTP_TIMEZONE_MAX) {
tz_index = NTP_TIMEZONE_DEFAULT;
}
tz = timezones[tz_index]; // set timezone
WiFi.hostByName(TimeServerName, timeServer); WiFi.hostByName(TimeServerName, timeServer);
setSyncProvider(getNtpTime); setSyncProvider(getNtpTime);
setSyncInterval(syncInterval); setSyncInterval(syncInterval);
@@ -38,7 +97,20 @@ time_t ICACHE_FLASH_ATTR NtpClient::getNtpTime() {
unsigned long highWord = word(packet.data()[40], packet.data()[41]); unsigned long highWord = word(packet.data()[40], packet.data()[41]);
unsigned long lowWord = word(packet.data()[42], packet.data()[43]); unsigned long lowWord = word(packet.data()[42], packet.data()[43]);
time_t UnixUTCtime = (highWord << 16 | lowWord) - 2208988800UL; time_t UnixUTCtime = (highWord << 16 | lowWord) - 2208988800UL;
setTime(UnixUTCtime); time_t adjustedtime = (*tz).toLocal(UnixUTCtime, &tcr);
myESP.myDebug("[NTP] UTC internet time is %d/%d %02d:%02d:%02d. System time set to local timezone which is %02d:%02d:%02d %s\n",
to_day(UnixUTCtime),
to_month(UnixUTCtime),
to_hour(UnixUTCtime),
to_minute(UnixUTCtime),
to_second(UnixUTCtime),
to_hour(adjustedtime),
to_minute(adjustedtime),
to_second(adjustedtime),
tcr->abbrev);
setTime(adjustedtime);
}); });
} }
udpListener.write(NTPpacket, sizeof(NTPpacket)); udpListener.write(NTPpacket, sizeof(NTPpacket));

View File

@@ -10,18 +10,24 @@
#include <ESP8266WiFi.h> #include <ESP8266WiFi.h>
#include <ESPAsyncUDP.h> #include <ESPAsyncUDP.h>
#include "TimeLib.h" // customized version of the time library #include "TimeLib.h" // customized version of the Time library
#include "Timezone.h"
#define NTP_PACKET_SIZE 48 // NTP time is in the first 48 bytes of message #define NTP_PACKET_SIZE 48 // NTP time is in the first 48 bytes of message
#define NTP_INTERVAL_DEFAULT 60 // every hour
#define NTP_TIMEZONE_DEFAULT 2 // CE
#define NTP_TIMEZONE_MAX 9
class NtpClient { class NtpClient {
public: public:
void ICACHE_FLASH_ATTR Ntp(const char * server, time_t syncMins); void ICACHE_FLASH_ATTR Ntp(const char * server, time_t syncMins, uint8_t tz_index);
ICACHE_FLASH_ATTR virtual ~NtpClient(); ICACHE_FLASH_ATTR virtual ~NtpClient();
static char * TimeServerName; static char * TimeServerName;
static IPAddress timeServer; static IPAddress timeServer;
static time_t syncInterval; static time_t syncInterval;
static Timezone * tz;
static TimeChangeRule * tcr;
static AsyncUDP udpListener; static AsyncUDP udpListener;

View File

@@ -1,3 +1,8 @@
/*
* customized version of Time library, originally Copyright (c) Michael Margolis 2009-2014
* modified by Paul S https://github.com/PaulStoffregen/Time
*/
#include "TimeLib.h" #include "TimeLib.h"
static tmElements_t tm; // a cache of time elements static tmElements_t tm; // a cache of time elements
@@ -96,6 +101,34 @@ void breakTime(time_t timeInput, tmElements_t & tm) {
tm.Day = time + 1; // day of month tm.Day = time + 1; // day of month
} }
// assemble time elements into time_t
time_t makeTime(const tmElements_t & tm) {
int i;
uint32_t seconds;
// seconds from 1970 till 1 jan 00:00:00 of the given year
seconds = tm.Year * (SECS_PER_DAY * 365);
for (i = 0; i < 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.Month; i++) {
if ((i == 2) && LEAP_YEAR(tm.Year)) {
seconds += SECS_PER_DAY * 29;
} else {
seconds += SECS_PER_DAY * monthDays[i - 1]; // monthDay array starts from 0
}
}
seconds += (tm.Day - 1) * SECS_PER_DAY;
seconds += tm.Hour * SECS_PER_HOUR;
seconds += tm.Minute * SECS_PER_MIN;
seconds += tm.Second;
return (time_t)seconds;
}
void refreshCache(time_t t) { void refreshCache(time_t t) {
if (t != cacheTime) { if (t != cacheTime) {
breakTime(t, tm); breakTime(t, tm);
@@ -118,6 +151,26 @@ uint8_t to_hour(time_t t) { // the hour for the given time
return tm.Hour; return tm.Hour;
} }
uint8_t to_day(time_t t) { // the day for the given time (0-6)
refreshCache(t);
return tm.Day;
}
uint8_t to_month(time_t t) { // the month for the given time
refreshCache(t);
return tm.Month;
}
uint8_t to_weekday(time_t t) {
refreshCache(t);
return tm.Wday;
}
uint16_t to_year(time_t t) { // the year for the given time
refreshCache(t);
return tm.Year + 1970;
}
void setTime(time_t t) { void setTime(time_t t) {
sysTime = (uint32_t)t; sysTime = (uint32_t)t;
nextSyncTime = (uint32_t)t + syncInterval; nextSyncTime = (uint32_t)t + syncInterval;

View File

@@ -1,3 +1,8 @@
/*
* customized version of Time library, originally Copyright (c) Michael Margolis 2009-2014
* modified by Paul S https://github.com/PaulStoffregen/Time
*/
#ifndef _TimeLib_h #ifndef _TimeLib_h
#define _TimeLib_h #define _TimeLib_h
@@ -41,5 +46,10 @@ time_t makeTime(const tmElements_t & tm); // convert time e
uint8_t to_hour(time_t t); // the hour for the given time uint8_t to_hour(time_t t); // the hour for the given time
uint8_t to_minute(time_t t); // the minute for the given time uint8_t to_minute(time_t t); // the minute for the given time
uint8_t to_second(time_t t); // the second for the given time uint8_t to_second(time_t t); // the second for the given time
uint8_t to_day(time_t t); // the day for the given time (0-6)
uint8_t to_month(time_t t); // the month for the given time
uint8_t to_weekday(time_t t); // weekday, sunday is day 1
uint16_t to_year(time_t t); // the year for the given time
} }
#endif #endif

200
src/Timezone.cpp Normal file
View File

@@ -0,0 +1,200 @@
/*----------------------------------------------------------------------*
* Arduino Timezone Library *
* Jack Christensen Mar 2012 *
* *
* Stripped down for myESP by Paul Derbyshire *
* *
* Arduino Timezone Library Copyright (C) 2018 by Jack Christensen and *
* licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html *
*----------------------------------------------------------------------*/
#include "Timezone.h"
/*----------------------------------------------------------------------*
* Create a Timezone object from the given time change rules. *
*----------------------------------------------------------------------*/
Timezone::Timezone(TimeChangeRule dstStart, TimeChangeRule stdStart)
: m_dst(dstStart)
, m_std(stdStart) {
initTimeChanges();
}
/*----------------------------------------------------------------------*
* Create a Timezone object for a zone that does not observe *
* daylight time. *
*----------------------------------------------------------------------*/
Timezone::Timezone(TimeChangeRule stdTime)
: m_dst(stdTime)
, m_std(stdTime) {
initTimeChanges();
}
/*----------------------------------------------------------------------*
* Convert the given UTC time to local time, standard or *
* daylight time, as appropriate. *
*----------------------------------------------------------------------*/
time_t Timezone::toLocal(time_t utc) {
// recalculate the time change points if needed
if (to_year(utc) != to_year(m_dstUTC))
calcTimeChanges(to_year(utc));
if (utcIsDST(utc))
return utc + m_dst.offset * SECS_PER_MIN;
else
return utc + m_std.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Convert the given UTC time to local time, standard or *
* daylight time, as appropriate, and return a pointer to the time *
* change rule used to do the conversion. The caller must take care *
* not to alter this rule. *
*----------------------------------------------------------------------*/
time_t Timezone::toLocal(time_t utc, TimeChangeRule ** tcr) {
// recalculate the time change points if needed
if (to_year(utc) != to_year(m_dstUTC))
calcTimeChanges(to_year(utc));
if (utcIsDST(utc)) {
*tcr = &m_dst;
return utc + m_dst.offset * SECS_PER_MIN;
} else {
*tcr = &m_std;
return utc + m_std.offset * SECS_PER_MIN;
}
}
/*----------------------------------------------------------------------*
* Convert the given local time to UTC time. *
* *
* WARNING: *
* This function is provided for completeness, but should seldom be *
* needed and should be used sparingly and carefully. *
* *
* Ambiguous situations occur after the Standard-to-DST and the *
* DST-to-Standard time transitions. When changing to DST, there is *
* one hour of local time that does not exist, since the clock moves *
* forward one hour. Similarly, when changing to standard time, there *
* is one hour of local times that occur twice since the clock moves *
* back one hour. *
* *
* This function does not test whether it is passed an erroneous time *
* value during the Local -> DST transition that does not exist. *
* If passed such a time, an incorrect UTC time value will be returned. *
* *
* If passed a local time value during the DST -> Local transition *
* that occurs twice, it will be treated as the earlier time, i.e. *
* the time that occurs before the transistion. *
* *
* Calling this function with local times during a transition interval *
* should be avoided! *
*----------------------------------------------------------------------*/
time_t Timezone::toUTC(time_t local) {
// recalculate the time change points if needed
if (to_year(local) != to_year(m_dstLoc))
calcTimeChanges(to_year(local));
if (locIsDST(local))
return local - m_dst.offset * SECS_PER_MIN;
else
return local - m_std.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Determine whether the given UTC time_t is within the DST interval *
* or the Standard time interval. *
*----------------------------------------------------------------------*/
bool Timezone::utcIsDST(time_t utc) {
// recalculate the time change points if needed
if (to_year(utc) != to_year(m_dstUTC))
calcTimeChanges(to_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 time_t is within the DST interval *
* or the Standard time interval. *
*----------------------------------------------------------------------*/
bool Timezone::locIsDST(time_t local) {
// recalculate the time change points if needed
if (to_year(local) != to_year(m_dstLoc))
calcTimeChanges(to_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);
}
/*----------------------------------------------------------------------*
* Calculate the DST and standard time change points for the given *
* given year as local and UTC time_t values. *
*----------------------------------------------------------------------*/
void Timezone::calcTimeChanges(int yr) {
m_dstLoc = toTime_t(m_dst, yr);
m_stdLoc = toTime_t(m_std, yr);
m_dstUTC = m_dstLoc - m_std.offset * SECS_PER_MIN;
m_stdUTC = m_stdLoc - m_dst.offset * SECS_PER_MIN;
}
/*----------------------------------------------------------------------*
* Initialize the DST and standard time change points. *
*----------------------------------------------------------------------*/
void Timezone::initTimeChanges() {
m_dstLoc = 0;
m_stdLoc = 0;
m_dstUTC = 0;
m_stdUTC = 0;
}
/*----------------------------------------------------------------------*
* Convert the given time change rule to a time_t value *
* for the given year. *
*----------------------------------------------------------------------*/
time_t Timezone::toTime_t(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
tmElements_t tm;
tm.Hour = r.hour;
tm.Minute = 0;
tm.Second = 0;
tm.Day = 1;
tm.Month = m;
tm.Year = yr - 1970;
time_t t = makeTime(tm);
// add offset from the first of the month to r.dow, and offset for the given week
t += ((r.dow - to_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;
}
/*----------------------------------------------------------------------*
* Read or update the daylight and standard time rules from RAM. *
*----------------------------------------------------------------------*/
void Timezone::setRules(TimeChangeRule dstStart, TimeChangeRule stdStart) {
m_dst = dstStart;
m_std = stdStart;
initTimeChanges(); // force calcTimeChanges() at next conversion call
}

53
src/Timezone.h Normal file
View File

@@ -0,0 +1,53 @@
/*----------------------------------------------------------------------*
* Arduino Timezone Library *
* Jack Christensen Mar 2012 *
* *
* Arduino Timezone Library Copyright (C) 2018 by Jack Christensen and *
* licensed under GNU GPL v3.0, https://www.gnu.org/licenses/gpl.html *
*----------------------------------------------------------------------*/
#ifndef TIMEZONE_H_INCLUDED
#define TIMEZONE_H_INCLUDED
#include <Arduino.h>
#include <TimeLib.h>
// convenient constants for TimeChangeRules
enum week_t { Last, 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 };
// structure to describe rules for when daylight/summer time begins,
// or when standard time begins.
struct TimeChangeRule {
char abbrev[6]; // five chars max
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
int offset; // offset from UTC in minutes
};
class Timezone {
public:
Timezone(TimeChangeRule dstStart, TimeChangeRule stdStart);
Timezone(TimeChangeRule stdTime);
time_t toLocal(time_t utc);
time_t toLocal(time_t utc, TimeChangeRule ** tcr);
time_t toUTC(time_t local);
bool utcIsDST(time_t utc);
bool locIsDST(time_t local);
void setRules(TimeChangeRule dstStart, TimeChangeRule stdStart);
private:
void calcTimeChanges(int yr);
void initTimeChanges();
time_t toTime_t(TimeChangeRule r, int yr);
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
time_t m_dstUTC; // dst start for given/current year, given in UTC
time_t m_stdUTC; // std time start for given/current year, given in UTC
time_t m_dstLoc; // dst start for given/current year, given in local time
time_t m_stdLoc; // std time start for given/current year, given in local time
};
#endif

View File

@@ -70,7 +70,6 @@ Ticker showerColdShotStopTimer;
#define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water #define SHOWER_MAX_DURATION 420000 // in ms. 7 minutes, before trigger a shot of cold water
// set this if using an external temperature sensor like a DS18B20 // set this if using an external temperature sensor like a DS18B20
// D5 is the default on a bbqkees board
#define EMSESP_DALLAS_GPIO D5 #define EMSESP_DALLAS_GPIO D5
#define EMSESP_DALLAS_PARASITE false #define EMSESP_DALLAS_PARASITE false
@@ -80,7 +79,6 @@ Ticker showerColdShotStopTimer;
#define EMSESP_LED_GPIO LED_BUILTIN #define EMSESP_LED_GPIO LED_BUILTIN
typedef struct { typedef struct {
uint32_t timestamp; // for internal timings, via millis()
uint8_t dallas_sensors; // count of dallas sensors uint8_t dallas_sensors; // count of dallas sensors
// custom params // custom params
@@ -930,7 +928,7 @@ void do_systemCheck() {
// only if we have a EMS connection // only if we have a EMS connection
void do_regularUpdates() { void do_regularUpdates() {
if (ems_getBusConnected() && !ems_getTxDisabled()) { if (ems_getBusConnected() && !ems_getTxDisabled()) {
myDebugLog("Starting scheduled query from EMS devices"); myDebugLog("Fetching data from EMS devices");
ems_getThermostatValues(); ems_getThermostatValues();
ems_getBoilerValues(); ems_getBoilerValues();
ems_getSolarModuleValues(); ems_getSolarModuleValues();
@@ -1917,7 +1915,6 @@ void initEMSESP() {
EMSESP_Settings.listen_mode = false; EMSESP_Settings.listen_mode = false;
EMSESP_Settings.publish_time = DEFAULT_PUBLISHTIME; EMSESP_Settings.publish_time = DEFAULT_PUBLISHTIME;
EMSESP_Settings.publish_always = false; EMSESP_Settings.publish_always = false;
EMSESP_Settings.timestamp = millis();
EMSESP_Settings.dallas_sensors = 0; EMSESP_Settings.dallas_sensors = 0;
EMSESP_Settings.led_gpio = EMSESP_LED_GPIO; EMSESP_Settings.led_gpio = EMSESP_LED_GPIO;
EMSESP_Settings.dallas_gpio = EMSESP_DALLAS_GPIO; EMSESP_Settings.dallas_gpio = EMSESP_DALLAS_GPIO;
@@ -1937,6 +1934,7 @@ void initEMSESP() {
* Shower Logic * Shower Logic
*/ */
void showerCheck() { void showerCheck() {
uint32_t time_now = millis();
// if already in cold mode, ignore all this logic until we're out of the cold blast // if already in cold mode, ignore all this logic until we're out of the cold blast
if (!EMSESP_Shower.doingColdShot) { if (!EMSESP_Shower.doingColdShot) {
// is the hot water running? // is the hot water running?
@@ -1944,7 +1942,7 @@ void showerCheck() {
// if heater was previously off, start the timer // if heater was previously off, start the timer
if (EMSESP_Shower.timerStart == 0) { if (EMSESP_Shower.timerStart == 0) {
// hot water just started... // hot water just started...
EMSESP_Shower.timerStart = EMSESP_Settings.timestamp; EMSESP_Shower.timerStart = time_now;
EMSESP_Shower.timerPause = 0; // remove any last pauses EMSESP_Shower.timerPause = 0; // remove any last pauses
EMSESP_Shower.doingColdShot = false; EMSESP_Shower.doingColdShot = false;
EMSESP_Shower.duration = 0; EMSESP_Shower.duration = 0;
@@ -1952,13 +1950,12 @@ void showerCheck() {
} else { } else {
// hot water has been on for a while // hot water has been on for a while
// first check to see if hot water has been on long enough to be recognized as a Shower/Bath // first check to see if hot water has been on long enough to be recognized as a Shower/Bath
if (!EMSESP_Shower.showerOn && (EMSESP_Settings.timestamp - EMSESP_Shower.timerStart) > SHOWER_MIN_DURATION) { if (!EMSESP_Shower.showerOn && (time_now - EMSESP_Shower.timerStart) > SHOWER_MIN_DURATION) {
EMSESP_Shower.showerOn = true; EMSESP_Shower.showerOn = true;
myDebugLog("[Shower] hot water still running, starting shower timer"); myDebugLog("[Shower] hot water still running, starting shower timer");
} }
// check if the shower has been on too long // check if the shower has been on too long
else if ((((EMSESP_Settings.timestamp - EMSESP_Shower.timerStart) > SHOWER_MAX_DURATION) && !EMSESP_Shower.doingColdShot) else if ((((time_now - EMSESP_Shower.timerStart) > SHOWER_MAX_DURATION) && !EMSESP_Shower.doingColdShot) && EMSESP_Settings.shower_alert) {
&& EMSESP_Settings.shower_alert) {
myDebugLog("[Shower] exceeded max shower time"); myDebugLog("[Shower] exceeded max shower time");
_showerColdShotStart(); _showerColdShotStart();
} }
@@ -1966,11 +1963,11 @@ void showerCheck() {
} else { // hot water is off } else { // hot water is off
// if it just turned off, record the time as it could be a short pause // if it just turned off, record the time as it could be a short pause
if ((EMSESP_Shower.timerStart) && (EMSESP_Shower.timerPause == 0)) { if ((EMSESP_Shower.timerStart) && (EMSESP_Shower.timerPause == 0)) {
EMSESP_Shower.timerPause = EMSESP_Settings.timestamp; EMSESP_Shower.timerPause = time_now;
} }
// if shower has been off for longer than the wait time // if shower has been off for longer than the wait time
if ((EMSESP_Shower.timerPause) && ((EMSESP_Settings.timestamp - EMSESP_Shower.timerPause) > SHOWER_PAUSE_TIME)) { if ((EMSESP_Shower.timerPause) && ((time_now - EMSESP_Shower.timerPause) > SHOWER_PAUSE_TIME)) {
// it is over the wait period, so assume that the shower has finished and calculate the total time and publish // it is over the wait period, so assume that the shower has finished and calculate the total time and publish
// because its unsigned long, can't have negative so check if length is less than OFFSET_TIME // because its unsigned long, can't have negative so check if length is less than OFFSET_TIME
if ((EMSESP_Shower.timerPause - EMSESP_Shower.timerStart) > SHOWER_OFFSET_TIME) { if ((EMSESP_Shower.timerPause - EMSESP_Shower.timerStart) > SHOWER_OFFSET_TIME) {
@@ -2062,8 +2059,6 @@ void setup() {
// Main loop // Main loop
// //
void loop() { void loop() {
EMSESP_Settings.timestamp = millis();
// the main loop // the main loop
myESP.loop(); myESP.loop();

View File

@@ -519,7 +519,6 @@ void ems_setTxMode(uint8_t mode) {
EMS_Sys_Status.emsTxMode = mode; EMS_Sys_Status.emsTxMode = mode;
} }
/** /**
* debug print a telegram to telnet/serial including the CRC * debug print a telegram to telnet/serial including the CRC
*/ */
@@ -530,18 +529,42 @@ void _debugPrintTelegram(const char * prefix, _EMS_RxTelegram * EMS_RxTelegram,
uint8_t data_len = EMS_RxTelegram->data_length; // length of data block uint8_t data_len = EMS_RxTelegram->data_length; // length of data block
uint8_t length = EMS_RxTelegram->length; // includes CRC uint8_t length = EMS_RxTelegram->length; // includes CRC
// get elapsed system time or internet time if available
uint8_t t_sec, t_min, t_hour;
uint16_t t_msec;
unsigned long timestamp = EMS_RxTelegram->timestamp;
bool haveNTPtime = (timestamp > 1572307205); // after Jan 1st 1970
if (haveNTPtime) {
t_sec = timestamp % 60;
timestamp /= 60; // now it is minutes
t_min = timestamp % 60;
timestamp /= 60; // now it is hours
t_hour = timestamp % 24;
} else {
t_hour = timestamp / 3600000;
t_min = (timestamp / 60000) % 60;
t_sec = (timestamp / 1000) % 60;
t_msec = timestamp % 1000;
}
strlcpy(output_str, "(", sizeof(output_str)); strlcpy(output_str, "(", sizeof(output_str));
strlcat(output_str, COLOR_CYAN, sizeof(output_str)); strlcat(output_str, COLOR_CYAN, sizeof(output_str));
strlcat(output_str, _smallitoa((uint8_t)((EMS_RxTelegram->timestamp / 3600000) % 24), buffer), sizeof(output_str));
strlcat(output_str, _smallitoa(t_hour, buffer), sizeof(output_str));
strlcat(output_str, ":", sizeof(output_str)); strlcat(output_str, ":", sizeof(output_str));
strlcat(output_str, _smallitoa((uint8_t)((EMS_RxTelegram->timestamp / 60000) % 60), buffer), sizeof(output_str)); strlcat(output_str, _smallitoa(t_min, buffer), sizeof(output_str));
strlcat(output_str, ":", sizeof(output_str)); strlcat(output_str, ":", sizeof(output_str));
strlcat(output_str, _smallitoa((uint8_t)((EMS_RxTelegram->timestamp / 1000) % 60), buffer), sizeof(output_str)); strlcat(output_str, _smallitoa(t_sec, buffer), sizeof(output_str));
// internet time doesn't have millisecond precision, so ignore it
if (!haveNTPtime) {
strlcat(output_str, ".", sizeof(output_str)); strlcat(output_str, ".", sizeof(output_str));
strlcat(output_str, _smallitoa3(EMS_RxTelegram->timestamp % 1000, buffer), sizeof(output_str)); strlcat(output_str, _smallitoa3(t_msec, buffer), sizeof(output_str));
}
strlcat(output_str, COLOR_RESET, sizeof(output_str)); strlcat(output_str, COLOR_RESET, sizeof(output_str));
strlcat(output_str, ") ", sizeof(output_str)); strlcat(output_str, ") ", sizeof(output_str));
strlcat(output_str, color, sizeof(output_str)); strlcat(output_str, color, sizeof(output_str));
strlcat(output_str, prefix, sizeof(output_str)); strlcat(output_str, prefix, sizeof(output_str));
@@ -606,7 +629,7 @@ void _ems_sendTelegram() {
EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram EMS_RxTelegram.length = EMS_TxTelegram.length; // full length of telegram
EMS_RxTelegram.telegram = EMS_TxTelegram.data; EMS_RxTelegram.telegram = EMS_TxTelegram.data;
EMS_RxTelegram.data_length = 0; // ignore #data= EMS_RxTelegram.data_length = 0; // ignore #data=
EMS_RxTelegram.timestamp = millis(); // now EMS_RxTelegram.timestamp = myESP.getSystemTime(); // now
_debugPrintTelegram("Sending raw: ", &EMS_RxTelegram, COLOR_CYAN, true); _debugPrintTelegram("Sending raw: ", &EMS_RxTelegram, COLOR_CYAN, true);
} }
@@ -677,7 +700,7 @@ void _ems_sendTelegram() {
EMS_RxTelegram.length = EMS_TxTelegram.length; // complete length of telegram incl CRC EMS_RxTelegram.length = EMS_TxTelegram.length; // complete length of telegram incl CRC
EMS_RxTelegram.data_length = 0; // ignore the data length for read and writes. only used for incoming. EMS_RxTelegram.data_length = 0; // ignore the data length for read and writes. only used for incoming.
EMS_RxTelegram.telegram = EMS_TxTelegram.data; EMS_RxTelegram.telegram = EMS_TxTelegram.data;
EMS_RxTelegram.timestamp = millis(); // now EMS_RxTelegram.timestamp = myESP.getSystemTime(); // now
_debugPrintTelegram(s, &EMS_RxTelegram, COLOR_CYAN); _debugPrintTelegram(s, &EMS_RxTelegram, COLOR_CYAN);
} }
@@ -880,7 +903,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
static _EMS_RxTelegram EMS_RxTelegram; // create the Rx package static _EMS_RxTelegram EMS_RxTelegram; // create the Rx package
EMS_RxTelegram.telegram = telegram; EMS_RxTelegram.telegram = telegram;
EMS_RxTelegram.timestamp = millis(); EMS_RxTelegram.timestamp = myESP.getSystemTime();
EMS_RxTelegram.length = length; EMS_RxTelegram.length = length;
EMS_RxTelegram.src = telegram[0] & 0x7F; // removing 8th bit as we deal with both reads and writes here EMS_RxTelegram.src = telegram[0] & 0x7F; // removing 8th bit as we deal with both reads and writes here
@@ -939,7 +962,7 @@ void ems_parseTelegram(uint8_t * telegram, uint8_t length) {
// here we know its a valid incoming telegram of at least 6 bytes // here we know its a valid incoming telegram of at least 6 bytes
// we use this to see if we always have a connection to the boiler, in case of drop outs // we use this to see if we always have a connection to the boiler, in case of drop outs
EMS_Sys_Status.emsRxTimestamp = EMS_RxTelegram.timestamp; // timestamp of last read EMS_Sys_Status.emsRxTimestamp = millis(); // timestamp of last read
EMS_Sys_Status.emsBusConnected = true; EMS_Sys_Status.emsBusConnected = true;
// now lets process it and see what to do next // now lets process it and see what to do next
@@ -1526,7 +1549,7 @@ void _process_RCPLUSStatusMessage(_EMS_RxTelegram * EMS_RxTelegram) {
// manual : 10 00 FF 0A 01 A5 02 // manual : 10 00 FF 0A 01 A5 02
// auto : Thermostat -> all, type 0x01A5 telegram: 10 00 FF 0A 01 A5 03 // auto : Thermostat -> all, type 0x01A5 telegram: 10 00 FF 0A 01 A5 03
// TODO this may be bit 2 instead of 1 on an RC300 - still to validate // Note this may be bit 2 instead of 1 on an RC300 - still to validate
EMS_Thermostat.hc[hc].mode = _bitRead(0, 0); // bit 1, mode (auto=1 or manual=0) EMS_Thermostat.hc[hc].mode = _bitRead(0, 0); // bit 1, mode (auto=1 or manual=0)
EMS_Thermostat.hc[hc].day_mode = _bitRead(0, 1); // get day mode flag EMS_Thermostat.hc[hc].day_mode = _bitRead(0, 1); // get day mode flag
@@ -2791,6 +2814,7 @@ void ems_sendRawTelegram(char * telegram) {
*/ */
void ems_setThermostatTemp(float temperature, uint8_t hc_num, uint8_t temptype) { void ems_setThermostatTemp(float temperature, uint8_t hc_num, uint8_t temptype) {
if (!ems_getThermostatEnabled()) { if (!ems_getThermostatEnabled()) {
myDebug_P(PSTR("Thermostat not online."));
return; return;
} }
@@ -2905,6 +2929,7 @@ void ems_setThermostatTemp(float temperature, uint8_t hc_num, uint8_t temptype)
*/ */
void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) { void ems_setThermostatMode(uint8_t mode, uint8_t hc_num) {
if (!ems_getThermostatEnabled()) { if (!ems_getThermostatEnabled()) {
myDebug_P(PSTR("Thermostat not online."));
return; return;
} }

View File

@@ -147,13 +147,13 @@ typedef struct {
uint8_t comparisonOffset; // offset of where the byte is we want to compare too during validation uint8_t comparisonOffset; // offset of where the byte is we want to compare too during validation
uint16_t comparisonPostRead; // after a successful write, do a read from this type ID uint16_t comparisonPostRead; // after a successful write, do a read from this type ID
bool forceRefresh; // should we send to MQTT after a successful Tx? bool forceRefresh; // should we send to MQTT after a successful Tx?
uint32_t timestamp; // when created unsigned long timestamp; // when created
uint8_t data[EMS_MAX_TELEGRAM_LENGTH]; uint8_t data[EMS_MAX_TELEGRAM_LENGTH];
} _EMS_TxTelegram; } _EMS_TxTelegram;
// The Rx receive package // The Rx receive package
typedef struct { typedef struct {
uint32_t timestamp; // timestamp from millis() unsigned long timestamp; // timestamp from millis()
uint8_t * telegram; // the full data package uint8_t * telegram; // the full data package
uint8_t data_length; // length in bytes of the data uint8_t data_length; // length in bytes of the data
uint8_t length; // full length of the complete telegram uint8_t length; // full length of the complete telegram

View File

@@ -391,10 +391,29 @@
aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right" aria-hidden="true" data-toggle="popover" data-trigger="hover" data-placement="right"
data-content="Time in minutes between scheduled NTP refreshes"></i></label> data-content="Time in minutes between scheduled NTP refreshes"></i></label>
<span class="col-xs-9 col-md-5"> <span class="col-xs-9 col-md-5">
<input class="form-control input-sm" placeholder="in Minutes" value="30" id="intervals" type="text"> <input class="form-control input-sm" placeholder="in Minutes" value="30" id="interval" type="text">
</span> </span>
<br> <br>
</div> </div>
<div class="row form-group">
<label class="col-xs-3">Time Zone</label>
<span class="col-xs-9 col-md-5">
<select class="form-control input-sm" name="timzeone" id="timezone">
<option value="0">Australia Eastern (Sydney, Melbourne)</option>
<option value="1">Moscow (MSK, does not observe DST)</option>
<option selected="selected" value="2">Central European Time (Frankfurt, Paris, Amsterdam)</option>
<option value="3">United Kingdom (London, Belfast)</option>
<option value="4">UTC</option>
<option value="5">US Eastern (New York, Detroit)</option>
<option value="6">US Central (Chicago, Houston)</option>
<option value="7">US Mountain (Denver, Salt Lake City)</option>
<option value="8">Arizona (no DST)</option>
<option value="9">US Pacific (Las Vegas, Los Angeles)</option>
</select>
</span>
</div>
<br> <br>
<div class="col-xs-9 col-md-8"> <div class="col-xs-9 col-md-8">
<button onclick="saventp()" class="btn btn-primary btn-sm pull-right">Save</button> <button onclick="saventp()" class="btn btn-primary btn-sm pull-right">Save</button>

View File

@@ -2,7 +2,7 @@ var version = "";
var websock = null; var websock = null;
var wsUri = "ws://" + window.location.host + "/ws"; var wsUri = "ws://" + window.location.host + "/ws";
var utcSeconds; var ntpSeconds;
var data = []; var data = [];
var ajaxobj; var ajaxobj;
@@ -37,7 +37,8 @@ var config = {
"hostname": "", "hostname": "",
"serial": false, "serial": false,
"password": "admin", "password": "admin",
"log_events": true "log_events": true,
"version": "1.0.0"
}, },
"mqtt": { "mqtt": {
"enabled": false, "enabled": false,
@@ -53,13 +54,14 @@ var config = {
}, },
"ntp": { "ntp": {
"server": "pool.ntp.org", "server": "pool.ntp.org",
"interval": 30, "interval": 60,
"timezone": 2,
"enabled": true "enabled": true
} }
}; };
function browserTime() { function browserTime() {
var d = new Date(0); var d = new Date(0); // The 0 there is the key, which sets the date to the epoch
var c = new Date(); var c = new Date();
var timestamp = Math.floor((c.getTime() / 1000) + ((c.getTimezoneOffset() * 60) * -1)); var timestamp = Math.floor((c.getTime() / 1000) + ((c.getTimezoneOffset() * 60) * -1));
d.setUTCSeconds(timestamp); d.setUTCSeconds(timestamp);
@@ -67,10 +69,8 @@ function browserTime() {
} }
function deviceTime() { function deviceTime() {
var c = new Date();
var t = new Date(0); // The 0 there is the key, which sets the date to the epoch var t = new Date(0); // The 0 there is the key, which sets the date to the epoch
var devTime = Math.floor(utcSeconds + ((c.getTimezoneOffset() * 60) * -1)); t.setUTCSeconds(ntpSeconds);
t.setUTCSeconds(devTime);
document.getElementById("utc").innerHTML = t.toUTCString().slice(0, -3); document.getElementById("utc").innerHTML = t.toUTCString().slice(0, -3);
} }
@@ -96,7 +96,8 @@ function listntp() {
websock.send("{\"command\":\"gettime\"}"); websock.send("{\"command\":\"gettime\"}");
document.getElementById("ntpserver").value = config.ntp.server; document.getElementById("ntpserver").value = config.ntp.server;
document.getElementById("intervals").value = config.ntp.interval; document.getElementById("interval").value = config.ntp.interval;
document.getElementById("timezone").value = config.ntp.timezone;
if (config.ntp.enabled) { if (config.ntp.enabled) {
$("input[name=\"ntpenabled\"][value=\"1\"]").prop("checked", true); $("input[name=\"ntpenabled\"][value=\"1\"]").prop("checked", true);
@@ -137,7 +138,8 @@ function custom_saveconfig() {
function saventp() { function saventp() {
config.ntp.server = document.getElementById("ntpserver").value; config.ntp.server = document.getElementById("ntpserver").value;
config.ntp.interval = parseInt(document.getElementById("intervals").value); config.ntp.interval = parseInt(document.getElementById("interval").value);
config.ntp.timezone = parseInt(document.getElementById("timezone").value);
config.ntp.enabled = false; config.ntp.enabled = false;
if (parseInt($("input[name=\"ntpenabled\"]:checked").val()) === 1) { if (parseInt($("input[name=\"ntpenabled\"]:checked").val()) === 1) {
@@ -674,8 +676,6 @@ function initEventTable() {
if (value < 1563300000) { if (value < 1563300000) {
return "(" + value + ")"; return "(" + value + ")";
} else { } else {
var comp = new Date();
value = Math.floor(value + ((comp.getTimezoneOffset() * 60) * -1));
var vuepoch = new Date(value * 1000); var vuepoch = new Date(value * 1000);
var formatted = vuepoch.getUTCFullYear() + var formatted = vuepoch.getUTCFullYear() +
"-" + twoDigits(vuepoch.getUTCMonth() + 1) + "-" + twoDigits(vuepoch.getUTCMonth() + 1) +
@@ -769,7 +769,7 @@ function socketMessageListener(evt) {
builddata(obj); builddata(obj);
break; break;
case "gettime": case "gettime":
utcSeconds = obj.epoch; ntpSeconds = obj.epoch;
deviceTime(); deviceTime();
break; break;
case "ssidlist": case "ssidlist":

View File

@@ -54,7 +54,7 @@ var eventlog = {
"list": [ "list": [
"{\"type\":\"WARN\",\"src\":\"system\",\"desc\":\"test data\",\"data\":\"Record #1\",\"time\": 1563371160}", "{\"type\":\"WARN\",\"src\":\"system\",\"desc\":\"test data\",\"data\":\"Record #1\",\"time\": 1563371160}",
"{\"type\":\"WARN\",\"src\":\"system\",\"desc\":\"test data\",\"data\":\"Record #2\",\"time\":0}", "{\"type\":\"WARN\",\"src\":\"system\",\"desc\":\"test data\",\"data\":\"Record #2\",\"time\":0}",
"{\"type\":\"INFO\",\"src\":\"system\",\"desc\":\"System booted\",\"data\":\"\",\"time\":1568660479}", "{\"type\":\"INFO\",\"src\":\"system\",\"desc\":\"System booted Local Time is 13:02:54 CET\",\"data\":\"\",\"time\":1572613374}",
"{\"type\":\"WARN\",\"src\":\"system\",\"desc\":\"test data\",\"data\":\"Record #3\",\"time\":0}" "{\"type\":\"WARN\",\"src\":\"system\",\"desc\":\"test data\",\"data\":\"Record #3\",\"time\":0}"
] ]
} }
@@ -70,6 +70,7 @@ var configfile = {
"hostname": "myesp", "hostname": "myesp",
"password": "admin", "password": "admin",
"serial": true, "serial": true,
"version": "1.9.1",
"log_events": true "log_events": true
}, },
"mqtt": { "mqtt": {
@@ -86,7 +87,8 @@ var configfile = {
}, },
"ntp": { "ntp": {
"server": "pool.ntp.org", "server": "pool.ntp.org",
"interval": "30", "interval": 60,
"timezone": 2,
"enabled": false "enabled": false
} }
}; };
@@ -128,7 +130,7 @@ function sendStatus() {
"availsize": 2469, "availsize": 2469,
"ip": "10.10.10.198", "ip": "10.10.10.198",
"ssid": "my_ssid", "ssid": "my_ssid",
"mac": "DC:4F:11:22:93:06", "mac": "DC:4F:12:22:13:06",
"signalstr": 62, "signalstr": 62,
"systemload": 0, "systemload": 0,
"mqttconnected": true, "mqttconnected": true,
@@ -243,8 +245,7 @@ wss.on('connection', function connection(ws) {
console.log("[INFO] Sending time"); console.log("[INFO] Sending time");
var res = {}; var res = {};
res.command = "gettime"; res.command = "gettime";
res.epoch = Math.floor((new Date).getTime() / 1000); res.epoch = 1572613374; // this is 13:02:54 CET
//res.epoch = 1567107755;
wss.broadcast(res); wss.broadcast(res);
break; break;
case "settime": case "settime":