diff --git a/cmd/handlers.c b/cmd/handlers.c index 8605f68..5b3f2a7 100644 --- a/cmd/handlers.c +++ b/cmd/handlers.c @@ -19,8 +19,8 @@ #endif #include #include "esp-link/cgi.h" - #include "config.h" +#include #ifdef CMD_DBG #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) @@ -199,6 +199,8 @@ cmdWifiStatus(CmdPacket *cmd) { // Command handler for time static void ICACHE_FLASH_ATTR cmdGetTime(CmdPacket *cmd) { + cgiServicesCheckDST(); // This can cause DST to change, so the sntp call to return 0. + cmdResponseStart(CMD_RESP_V, sntp_get_current_timestamp(), 0); cmdResponseEnd(); return; diff --git a/esp-link/cgiservices.c b/esp-link/cgiservices.c index dddb616..2ae2e4f 100644 --- a/esp-link/cgiservices.c +++ b/esp-link/cgiservices.c @@ -8,6 +8,8 @@ #ifdef SYSLOG #include "syslog.h" #endif +#include "time.h" +#include "cgiservices.h" #ifdef CGISERVICES_DBG #define DBG(format, ...) do { os_printf(format, ## __VA_ARGS__); } while(0) @@ -26,6 +28,10 @@ char* flash_maps[7] = { static ETSTimer reassTimer; +// Daylight Savings Time support +static ETSTimer dstTimer; +static bool old_dst; + // Cgi to update system info (name/description) int ICACHE_FLASH_ATTR cgiSystemSet(HttpdConnData *connData) { if (connData->conn == NULL) return HTTPD_CGI_DONE; // Connection aborted. Clean up. @@ -96,6 +102,144 @@ int ICACHE_FLASH_ATTR cgiSystemInfo(HttpdConnData *connData) { return HTTPD_CGI_DONE; } +static bool ICACHE_FLASH_ATTR isDSTEurope(struct tm *tp) { + int mon = tp->tm_mon + 1; + + if (mon < 3 || tp->tm_mon > 11) return false; + if (mon > 3 && tp->tm_mon < 11) return true; + + int previousSunday = tp->tm_mday - tp->tm_wday; + + if (mon == 10) { + if (previousSunday < 25) + return true; + if (tp->tm_wday > 0) + return false; + if (tp->tm_hour < 2) + return true; + return false; + } + if (mon == 3) { + if (previousSunday < 25) + return false; + if (tp->tm_wday > 0) + return true; + if (tp->tm_hour < 2) + return false; + return true; + } + + return true; +} + +static bool ICACHE_FLASH_ATTR isDSTUSA(struct tm *tp) { + int month = tp->tm_mon + 1; + + // January, february, and december are out. + if (month < 3 || month > 11) + return false; + + // April to October are in + if (month > 3 && month < 11) + return true; + + int previousSunday = tp->tm_mday - tp->tm_wday; + + // In march, we are DST if our previous sunday was on or after the 8th. + if (month == 3) + return previousSunday >= 8; + + // In november we must be before the first sunday to be dst. + // That means the previous sunday must be before the 1st. + return previousSunday <= 0; +} + +/* + * When adding more regions, this must be extended. + * If someone needs a half hour timezone, then it might be better to change + * the return type to int instead of bool. + * It could then mean the number of minutes to add. + */ +static bool ICACHE_FLASH_ATTR isDST(struct tm *tp) { + switch (flashConfig.dst_mode) { + case DST_EUROPE: + return isDSTEurope(tp); + case DST_USA: + return isDSTUSA(tp); + case DST_NONE: + default: + return false; + } +} + +static ICACHE_FLASH_ATTR char *dstMode2text(int dm) { + switch(dm) { + case DST_EUROPE: + return "Europe"; + case DST_USA: + return "USA"; + case DST_NONE: + default: + return "None"; + } +} + + +#if 1 +/* + * For debugging only + * This function is called yet another timeout after changing the timezone offset. + * This is the only way to report the time correctly if we're in DST. + */ +static void ICACHE_FLASH_ATTR dstDelayed2() { + time_t ts = sntp_get_current_timestamp(); + if (ts < 10000) + return; + struct tm *tp = gmtime(&ts); + + os_printf("dstDelayed (dst) : date %04d-%02d-%02d time %02d:%02d:%02d\n", + tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec); +} +#endif + +/* + * This gets called a while after initializing SNTP. + * The assumption is we will now know the date/time so we can determine whether we're in + * a Daylight Savings Time period. + * And then set the time offset accordingly. + */ +static void ICACHE_FLASH_ATTR dstDelayed() { + time_t ts = sntp_get_current_timestamp(); + if (ts < 10000) + return; // FIX ME failed to obtain time. Now what ? + + struct tm *tp = gmtime(&ts); + + bool dst = isDST(tp); + + os_printf("dstDelayed : date %04d-%02d-%02d time %02d:%02d:%02d, wday = %d, DST = %s (%s)\n", + tp->tm_year + 1900, tp->tm_mon + 1, tp->tm_mday, tp->tm_hour, tp->tm_min, tp->tm_sec, + tp->tm_wday, dst ? "true" : "false", dstMode2text(flashConfig.dst_mode)); + + old_dst = dst; + + // Only change the timezone offset if we're in DST + if (dst) { + // Changing timezone offset requires a SNTP stop/start. + sntp_stop(); + if (! sntp_set_timezone(flashConfig.timezone_offset + 1)) + os_printf("sntp_set_timezone(%d) failed\n", flashConfig.timezone_offset + 1); + sntp_init(); + +#if 1 + // FIX ME for debugging only + os_timer_disarm(&dstTimer); + os_timer_setfn(&dstTimer, dstDelayed2, NULL); + os_timer_arm(&dstTimer, 5000, 0); // wait 5 seconds +#endif + } +} + void ICACHE_FLASH_ATTR cgiServicesSNTPInit() { if (flashConfig.sntp_server[0] != '\0') { sntp_stop(); @@ -103,7 +247,58 @@ void ICACHE_FLASH_ATTR cgiServicesSNTPInit() { sntp_setservername(0, flashConfig.sntp_server); sntp_init(); } - DBG("SNTP timesource set to %s with offset %d\n", flashConfig.sntp_server, flashConfig.timezone_offset); + old_dst = false; + + // If we support daylight savings time, delay setting the timezone until we know the date/time + if (flashConfig.dst_mode != 0) { + os_timer_disarm(&dstTimer); + os_timer_setfn(&dstTimer, dstDelayed, NULL); + os_timer_arm(&dstTimer, 5000, 0); // wait 5 seconds + + DBG("SNTP timesource set to %s with offset %d, awaiting DST info\n", + flashConfig.sntp_server, flashConfig.timezone_offset); + } else { + DBG("SNTP timesource set to %s with offset %d\n", + flashConfig.sntp_server, flashConfig.timezone_offset); + } + } +} + +/* + * Check whether to change DST, at every CMD_GET_TIME call. + * + * If we need to change, then the next call to sntp_get_current_timestamp() will return 0 + * so a retry will be required. The apps already had to cope with such situations, I believe. + */ +void ICACHE_FLASH_ATTR cgiServicesCheckDST() { + if (flashConfig.dst_mode == 0) + return; + + time_t ts = sntp_get_current_timestamp(); + if (ts < 10000) + return; // FIX ME failed to obtain time. Now what ? + + struct tm *tp = gmtime(&ts); + bool dst = isDST(tp); + + if (dst != old_dst) { + // Just calling this would be easy but too slow, we already know the new DST setting. + // cgiServicesSNTPInit(); + + old_dst = dst; + + int add = dst ? 1 : 0; + sntp_stop(); + if (! sntp_set_timezone(flashConfig.timezone_offset + add)) + os_printf("sntp_set_timezone(%d) failed\n", flashConfig.timezone_offset + add); + sntp_init(); + +#if 1 + // FIX ME for debugging only + os_timer_disarm(&dstTimer); + os_timer_setfn(&dstTimer, dstDelayed2, NULL); + os_timer_arm(&dstTimer, 5000, 0); // wait 5 seconds +#endif } } @@ -124,7 +319,8 @@ int ICACHE_FLASH_ATTR cgiServicesInfo(HttpdConnData *connData) { "\"timezone_offset\": %d, " "\"sntp_server\": \"%s\", " "\"mdns_enable\": \"%s\", " - "\"mdns_servername\": \"%s\"" + "\"mdns_servername\": \"%s\", " + "\"dst_mode\": \"%d\" " " }", #ifdef SYSLOG flashConfig.syslog_host, @@ -136,7 +332,13 @@ int ICACHE_FLASH_ATTR cgiServicesInfo(HttpdConnData *connData) { flashConfig.timezone_offset, flashConfig.sntp_server, flashConfig.mdns_enable ? "enabled" : "disabled", - flashConfig.mdns_servername + flashConfig.mdns_servername, +#if 0 + flashConfig.dst_mode ? + (flashConfig.dst_mode == DST_EUROPE ? "Europe" : "USA") + : "None" +#endif + flashConfig.dst_mode ); jsonHeader(connData, 200); @@ -172,6 +374,10 @@ int ICACHE_FLASH_ATTR cgiServicesSet(HttpdConnData *connData) { sntp |= getStringArg(connData, "sntp_server", flashConfig.sntp_server, sizeof(flashConfig.sntp_server)); if (sntp < 0) return HTTPD_CGI_DONE; + sntp |= getUInt8Arg(connData, "dst_mode", &flashConfig.dst_mode); + os_printf("DSTMODE %d\n", flashConfig.dst_mode); + if (sntp < 0) return HTTPD_CGI_DONE; + if (sntp > 0) { cgiServicesSNTPInit(); } diff --git a/esp-link/cgiservices.h b/esp-link/cgiservices.h index 9c44242..b679cdf 100644 --- a/esp-link/cgiservices.h +++ b/esp-link/cgiservices.h @@ -3,10 +3,17 @@ #include "httpd.h" +// Need to be in sync with html/services.html +#define DST_NONE 0 +#define DST_EUROPE 1 +#define DST_USA 2 + int cgiSystemSet(HttpdConnData *connData); int cgiSystemInfo(HttpdConnData *connData); void cgiServicesSNTPInit(); +void cgiServicesCheckDST(); + int cgiServicesInfo(HttpdConnData *connData); int cgiServicesSet(HttpdConnData *connData); diff --git a/esp-link/cgiwifi.h b/esp-link/cgiwifi.h index ccae68f..0d4630e 100644 --- a/esp-link/cgiwifi.h +++ b/esp-link/cgiwifi.h @@ -2,6 +2,7 @@ #define CGIWIFI_H #include "httpd.h" +#include "cmd.h" enum { wifiIsDisconnected, wifiIsConnected, wifiGotIP }; typedef void(*WifiStateChangeCb)(uint8_t wifiStatus); diff --git a/esp-link/config.c b/esp-link/config.c index 224f047..84a58b1 100644 --- a/esp-link/config.c +++ b/esp-link/config.c @@ -34,6 +34,7 @@ FlashConfig flashDefault = { .data_bits = EIGHT_BITS, .parity = NONE_BITS, .stop_bits = ONE_STOP_BIT, + .dst_mode = 0, }; typedef union { diff --git a/esp-link/config.h b/esp-link/config.h index 65195d2..f2f7654 100644 --- a/esp-link/config.h +++ b/esp-link/config.h @@ -41,6 +41,7 @@ typedef struct { int8_t data_bits; int8_t parity; int8_t stop_bits; + uint8_t dst_mode; } FlashConfig; extern FlashConfig flashConfig;