From 524979aa5f6786217357be1d28d3898865960bee Mon Sep 17 00:00:00 2001 From: Theo Arends Date: Thu, 5 Apr 2018 12:49:43 +0200 Subject: [PATCH] v5.12.0j - Add Sunrise/set timer option 5.12.0j * Add optional Sunrise and Sunset timers with commands Latitide and Longitude to be enabled with define USE_SUNRISE in user_config.h (#2317) --- README.md | 2 +- sonoff/_releasenotes.ino | 5 +- sonoff/i18n.h | 11 +- sonoff/language/cs-CZ.h | 2 + sonoff/language/de-DE.h | 2 + sonoff/language/en-GB.h | 2 + sonoff/language/es-AR.h | 2 + sonoff/language/fr-FR.h | 2 + sonoff/language/hu-HU.h | 2 + sonoff/language/it-IT.h | 2 + sonoff/language/nl-NL.h | 2 + sonoff/language/pl-PL.h | 2 + sonoff/language/pt-PT.h | 2 + sonoff/language/ru-RU.h | 2 + sonoff/language/zh-CN.h | 2 + sonoff/language/zh-TW.h | 2 + sonoff/settings.h | 7 +- sonoff/settings.ino | 14 ++ sonoff/sonoff.ino | 13 +- sonoff/sonoff_post.h | 3 + sonoff/support.ino | 43 +++++- sonoff/user_config.h | 3 + sonoff/xdrv_09_timers.ino | 309 ++++++++++++++++++++++++++++++++++---- 23 files changed, 388 insertions(+), 48 deletions(-) diff --git a/README.md b/README.md index 7734cacfd54e..60700342db21 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ## Sonoff-Tasmota Provide ESP8266 based Sonoff by [iTead Studio](https://www.itead.cc/) and ElectroDragon IoT Relay with Serial, Web and MQTT control allowing 'Over the Air' or OTA firmware updates using Arduino IDE. -Current version is **5.12.0i** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information. +Current version is **5.12.0j** - See [sonoff/_releasenotes.ino](https://github.com/arendst/Sonoff-Tasmota/blob/development/sonoff/_releasenotes.ino) for change information. ### Quick install diff --git a/sonoff/_releasenotes.ino b/sonoff/_releasenotes.ino index 6f2e91046716..a61c583fb531 100644 --- a/sonoff/_releasenotes.ino +++ b/sonoff/_releasenotes.ino @@ -1,4 +1,7 @@ -/* 5.12.0i +/* 5.12.0j + * Add optional Sunrise and Sunset timers with commands Latitide and Longitude to be enabled with define USE_SUNRISE in user_config.h (#2317) + * + * 5.12.0i * Add 16 timers using commands Timer and Timers (#1091) * Add commands Timer 0 to clear timer and Timer 1..16 to copy timer * Add optional Timer configuration webpage to be enabled in user_config.h with define USE_TIMERS_WEB diff --git a/sonoff/i18n.h b/sonoff/i18n.h index 95b17fea641c..5dea3bbe9d9f 100644 --- a/sonoff/i18n.h +++ b/sonoff/i18n.h @@ -105,6 +105,8 @@ #define D_JSON_STARTUPUTC "StartupUTC" #define D_JSON_SUBNETMASK "Subnetmask" #define D_JSON_SUCCESSFUL "Successful" +#define D_JSON_SUNRISE "Sunrise" +#define D_JSON_SUNSET "Sunset" #define D_JSON_SWITCH "Switch" #define D_JSON_SYNC "Sync" #define D_JSON_TEMPERATURE "Temperature" @@ -270,9 +272,9 @@ #define D_CMND_IRSEND "IRSend" #define D_JSON_INVALID_JSON "Invalid JSON" #define D_JSON_PROTOCOL_NOT_SUPPORTED "Protocol not supported" - #define D_JSON_IR_PROTOCOL "PROTOCOL" - #define D_JSON_IR_BITS "BITS" - #define D_JSON_IR_DATA "DATA" + #define D_JSON_IR_PROTOCOL "Protocol" + #define D_JSON_IR_BITS "Bits" + #define D_JSON_IR_DATA "Data" #define D_CMND_IRHVAC "IRHVAC" #define D_JSON_IRHVAC_VENDOR "VENDOR" #define D_JSON_IRHVAC_POWER "POWER" @@ -354,6 +356,7 @@ // Commands xdrv_09_timers.ino #define D_CMND_TIMER "Timer" #define D_JSON_TIMER_ARM "Arm" + #define D_JSON_TIMER_MODE "Mode" #define D_JSON_TIMER_TIME "Time" #define D_JSON_TIMER_DAYS "Days" #define D_JSON_TIMER_REPEAT "Repeat" @@ -361,6 +364,8 @@ #define D_JSON_TIMER_POWER "Power" #define D_JSON_TIMER_NO_DEVICE "No GPIO as output configured" #define D_CMND_TIMERS "Timers" +#define D_CMND_LATITUDE "Latitude" +#define D_CMND_LONGITUDE "Longitude" /********************************************************************************************/ diff --git a/sonoff/language/cs-CZ.h b/sonoff/language/cs-CZ.h index 21336443ca9d..0920fc2e618d 100644 --- a/sonoff/language/cs-CZ.h +++ b/sonoff/language/cs-CZ.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Maska podsítě" #define D_SUBSCRIBE_TO "Přihlaš se do" #define D_SUCCESSFUL "úspěšné." +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Teplota" #define D_TO "do" #define D_TOGGLE "Přepni" diff --git a/sonoff/language/de-DE.h b/sonoff/language/de-DE.h index caa74fd41fb2..2b9295e34954 100644 --- a/sonoff/language/de-DE.h +++ b/sonoff/language/de-DE.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Subnetzmaske" #define D_SUBSCRIBE_TO "subscribe to" #define D_SUCCESSFUL "erfolgreich" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Temperatur" #define D_TO "zu" #define D_TOGGLE "An/Aus" diff --git a/sonoff/language/en-GB.h b/sonoff/language/en-GB.h index 484946200c4a..26a2e9fe01b8 100644 --- a/sonoff/language/en-GB.h +++ b/sonoff/language/en-GB.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Subnet Mask" #define D_SUBSCRIBE_TO "Subscribe to" #define D_SUCCESSFUL "Successful" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Temperature" #define D_TO "to" #define D_TOGGLE "Toggle" diff --git a/sonoff/language/es-AR.h b/sonoff/language/es-AR.h index 143ee4d3caf4..5dfc111dab2d 100644 --- a/sonoff/language/es-AR.h +++ b/sonoff/language/es-AR.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Máscara Subred" #define D_SUBSCRIBE_TO "Suscribir a" #define D_SUCCESSFUL "Exitosa" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Temperatura" #define D_TO "a" #define D_TOGGLE "Conmutar" diff --git a/sonoff/language/fr-FR.h b/sonoff/language/fr-FR.h index 5d09cfcb6ced..d0aff6c774cc 100644 --- a/sonoff/language/fr-FR.h +++ b/sonoff/language/fr-FR.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Masque sous réseau" #define D_SUBSCRIBE_TO "Souscrire à" #define D_SUCCESSFUL "Réussi" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Temperature" #define D_TO "à" #define D_TOGGLE "Bascule" diff --git a/sonoff/language/hu-HU.h b/sonoff/language/hu-HU.h index e043b1b96b7a..455e0ba1f429 100644 --- a/sonoff/language/hu-HU.h +++ b/sonoff/language/hu-HU.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Subnet Mask" #define D_SUBSCRIBE_TO "Feliratkozás a" #define D_SUCCESSFUL "Sikeres" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Hőmérséklet" #define D_TO "-nak" #define D_TOGGLE "Toggle" diff --git a/sonoff/language/it-IT.h b/sonoff/language/it-IT.h index 59851e67715e..cab2e7892bb0 100644 --- a/sonoff/language/it-IT.h +++ b/sonoff/language/it-IT.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Maschera sottorete" #define D_SUBSCRIBE_TO "Sottoscrivi a" #define D_SUCCESSFUL "Riuscito" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Temperatura" #define D_TO "a" #define D_TOGGLE "Toggle" diff --git a/sonoff/language/nl-NL.h b/sonoff/language/nl-NL.h index b21bea2aa1c7..802255b3a668 100644 --- a/sonoff/language/nl-NL.h +++ b/sonoff/language/nl-NL.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Subnet Masker" #define D_SUBSCRIBE_TO "Abonneer op" #define D_SUCCESSFUL "Gelukt" +#define D_SUNRISE "Zonsopgang" +#define D_SUNSET "Zonsondergang" #define D_TEMPERATURE "Temperatuur" #define D_TO "naar" #define D_TOGGLE "Toggle" // Wissel, Tuimel diff --git a/sonoff/language/pl-PL.h b/sonoff/language/pl-PL.h index 9d0352315cce..3ce638de6871 100644 --- a/sonoff/language/pl-PL.h +++ b/sonoff/language/pl-PL.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Maska podsieci" #define D_SUBSCRIBE_TO "Subskrybuj do" #define D_SUCCESSFUL "Powodzenie" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Temperatura" #define D_TO "do" #define D_TOGGLE "Przełącz" diff --git a/sonoff/language/pt-PT.h b/sonoff/language/pt-PT.h index 5c52cd528175..cae2832db64a 100644 --- a/sonoff/language/pt-PT.h +++ b/sonoff/language/pt-PT.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Mascara sub rede" #define D_SUBSCRIBE_TO "Subescrever para" #define D_SUCCESSFUL "Successo" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Temperatura" #define D_TO "para" #define D_TOGGLE "Pressionar" diff --git a/sonoff/language/ru-RU.h b/sonoff/language/ru-RU.h index d11bfcda20f6..0fa85d5a94f0 100644 --- a/sonoff/language/ru-RU.h +++ b/sonoff/language/ru-RU.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "Маска Подсети" #define D_SUBSCRIBE_TO "Подписаться на" #define D_SUCCESSFUL "Успешно" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "Температура" #define D_TO "до" #define D_TOGGLE "Переключить" diff --git a/sonoff/language/zh-CN.h b/sonoff/language/zh-CN.h index f9d0d5cc2b6a..0f1a973470b1 100644 --- a/sonoff/language/zh-CN.h +++ b/sonoff/language/zh-CN.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "子网掩码" #define D_SUBSCRIBE_TO "订阅" #define D_SUCCESSFUL "成功" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "温度" #define D_TO "to" #define D_TOGGLE "切换" diff --git a/sonoff/language/zh-TW.h b/sonoff/language/zh-TW.h index a95b772ca0a4..f0ae1d59ea4d 100644 --- a/sonoff/language/zh-TW.h +++ b/sonoff/language/zh-TW.h @@ -137,6 +137,8 @@ #define D_SUBNET_MASK "子網遮罩" #define D_SUBSCRIBE_TO "訂閱" #define D_SUCCESSFUL "成功" +#define D_SUNRISE "Sunrise" +#define D_SUNSET "Sunset" #define D_TEMPERATURE "溫度" #define D_TO "to" #define D_TOGGLE "切換" diff --git a/sonoff/settings.h b/sonoff/settings.h index 5c18c9ca26df..a83b24e1e9f6 100644 --- a/sonoff/settings.h +++ b/sonoff/settings.h @@ -93,7 +93,7 @@ typedef union { uint32_t data; struct { uint32_t time : 11; // bits 0 - 10 = minutes in a day - uint32_t mday : 5; // bits 11 - 15 = 32 days in a month + uint32_t mode : 5; // bits 11 - 15 = timer modes - Scheduler, Sunrise, Sunset uint32_t days : 7; // bits 16 - 22 = week day mask uint32_t device : 4; // bits 23 - 26 = 16 devices uint32_t power : 2; // bits 27 - 28 = 4 power states - Off, On, Toggle, Blink @@ -253,8 +253,9 @@ struct SYSCFG { byte free_66d[3]; // 66D Timer timer[MAX_TIMERS]; // 670 - - // 6B0 - FFF free locations + int latitude; // 6B0 + int longitude; // 6B4 + // 6B8 - FFF free locations } Settings; struct RTCMEM { diff --git a/sonoff/settings.ino b/sonoff/settings.ino index db75406b4596..7a8105c3e94a 100644 --- a/sonoff/settings.ino +++ b/sonoff/settings.ino @@ -54,6 +54,13 @@ #define HOME_ASSISTANT_DISCOVERY_ENABLE 0 #endif +#ifndef LATITUDE +#define LATITUDE 48.858360 // [Latitude] Your location to be used with sunrise and sunset +#endif +#ifndef LONGITUDE +#define LONGITUDE 2.294442 // [Longitude] Your location to be used with sunrise and sunset +#endif + /*********************************************************************************************\ * RTC memory \*********************************************************************************************/ @@ -597,6 +604,9 @@ void SettingsDefaultSet2() // 5.10.1 SettingsDefaultSet_5_10_1(); + + Settings.latitude = (int)((double)LATITUDE * 1000000); + Settings.longitude = (int)((double)LONGITUDE * 1000000); } /********************************************************************************************/ @@ -913,6 +923,10 @@ void SettingsDelta() if (Settings.version < 0x050C0009) { memset(&Settings.timer, 0x00, sizeof(Timer) * MAX_TIMERS); } + if (Settings.version < 0x050C000A) { + Settings.latitude = (int)((double)LATITUDE * 1000000); + Settings.longitude = (int)((double)LONGITUDE * 1000000); + } Settings.version = VERSION; SettingsSave(1); diff --git a/sonoff/sonoff.ino b/sonoff/sonoff.ino index ba91347ed8c2..c354aabc8314 100644 --- a/sonoff/sonoff.ino +++ b/sonoff/sonoff.ino @@ -25,20 +25,18 @@ - Select IDE Tools - Flash Size: "1M (no SPIFFS)" ====================================================*/ -#define VERSION 0x050C0009 // 5.12.0i +#define VERSION 0x050C000A // 5.12.0j // Location specific includes #include // Arduino_Esp8266 version information (ARDUINO_ESP8266_RELEASE and ARDUINO_ESP8266_RELEASE_2_3_0) #include "sonoff.h" // Enumeration used in user_config.h #include "user_config.h" // Fixed user configurable options - #ifdef USE_CONFIG_OVERRIDE #include "user_config_override.h" // Configuration overrides for user_config.h #endif - +#include "sonoff_post.h" // Configuration overrides for all previous includes #include "i18n.h" // Language support configured by user_config.h #include "sonoff_template.h" // Hardware configuration -#include "sonoff_post.h" // Configuration overrides for all previous includes #ifdef ARDUINO_ESP8266_RELEASE_2_4_0 #include "lwip/init.h" @@ -1036,7 +1034,7 @@ void MqttDataHandler(char* topic, byte* data, unsigned int data_len) } } else if (CMND_TIMEZONE == command_code) { - if ((data_len > 0) && (((payload >= -13) && (payload <= 13)) || (99 == payload))) Settings.timezone = payload; + if ((data_len > 0) && (((payload >= -13) && (payload <= 14)) || (99 == payload))) Settings.timezone = payload; snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_NVALUE, command, Settings.timezone); } else if (CMND_ALTITUDE == command_code) { @@ -1307,8 +1305,13 @@ void PublishStatus(uint8_t payload) } if ((0 == payload) || (7 == payload)) { +#ifdef USE_SUNRISE + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\"" D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%d,\"" D_JSON_SUNRISE "\":\"%s\",\"" D_JSON_SUNSET "\":\"%s\"}}"), + GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(), GetTime(3).c_str(), Settings.timezone, GetSun(0).c_str(), GetSun(1).c_str()); +#else snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("{\"" D_CMND_STATUS D_STATUS7_TIME "\":{\"" D_JSON_UTC_TIME "\":\"%s\",\"" D_JSON_LOCAL_TIME "\":\"%s\",\"" D_JSON_STARTDST "\":\"%s\",\"" D_JSON_ENDDST "\":\"%s\",\"" D_CMND_TIMEZONE "\":%d}}"), GetTime(0).c_str(), GetTime(1).c_str(), GetTime(2).c_str(), GetTime(3).c_str(), Settings.timezone); +#endif // USE_SUNRISE MqttPublishPrefixTopic_P(option, PSTR(D_CMND_STATUS "7")); } diff --git a/sonoff/sonoff_post.h b/sonoff/sonoff_post.h index f1f980edfc11..4455519d063d 100644 --- a/sonoff/sonoff_post.h +++ b/sonoff/sonoff_post.h @@ -105,6 +105,9 @@ void WifiWpsStatusCallback(wps_cb_status status); #ifdef USE_TIMERS #undef USE_TIMERS // Disable support for up to 16 timers #endif +#ifdef USE_SUNRISE +#undef USE_SUNRISE // Disable support for Sunrise and sunset tools +#endif #ifdef USE_PZEM004T #undef USE_PZEM004T // Disable PZEM004T energy sensor #endif diff --git a/sonoff/support.ino b/sonoff/support.ino index d0250690a7e3..b845af7082fa 100644 --- a/sonoff/support.ino +++ b/sonoff/support.ino @@ -192,6 +192,35 @@ size_t strchrspn(const char *str1, int character) return ret; } +double AtoD(char *str) +{ + // simple ascii to double, because atof or strtod are too large + char strbuf[24]; + + strcpy(strbuf, str); + char *pt; + double left = atoi(strbuf); + double right = 0; + short len = 0; + pt = strtok (strbuf, "."); + if (pt) { + pt = strtok (NULL, "."); + if (pt) { + right = atoi(pt); + len = strlen(pt); + double fac = 1; + while (len) { + fac /= 10.0; + len--; + } + // pow is also very large + //double fac=pow(10,-len); + right *= fac; + } + } + return left + right; +} + char* dtostrfd(double number, unsigned char prec, char *s) { return dtostrf(number, 1, prec, s); @@ -1077,6 +1106,7 @@ uint32_t standard_time = 0; uint32_t ntp_time = 0; uint32_t midnight = 1451602800; uint32_t restart_time = 0; +int16_t time_timezone = 0; // Timezone * 10 uint8_t midnight_now = 0; uint8_t ntp_sync_minute = 0; @@ -1325,8 +1355,8 @@ boolean MidnightNow() void RtcSecond() { - uint32_t stdoffset; - uint32_t dstoffset; + int32_t stdoffset; + int32_t dstoffset; TIME_T tmpTime; if ((ntp_sync_minute > 59) && (RtcTime.minute > 2)) ntp_sync_minute = 1; // If sync prepare for a new cycle @@ -1353,6 +1383,7 @@ void RtcSecond() utc_time++; local_time = utc_time; if (local_time > 1451602800) { // 2016-01-01 + int32_t time_offset = Settings.timezone * SECS_PER_HOUR; if (99 == Settings.timezone) { if (DaylightSavingTime.hemis) { dstoffset = StandardTime.offset * SECS_PER_MIN; // Southern hemisphere @@ -1362,13 +1393,13 @@ void RtcSecond() stdoffset = StandardTime.offset * SECS_PER_MIN; } if ((utc_time >= (daylight_saving_time - stdoffset)) && (utc_time < (standard_time - dstoffset))) { - local_time += dstoffset; // Daylight Saving Time + time_offset = dstoffset; // Daylight Saving Time } else { - local_time += stdoffset; // Standard Time + time_offset = stdoffset; // Standard Time } - } else { - local_time += Settings.timezone * SECS_PER_HOUR; } + local_time += time_offset; + time_timezone = time_offset / (SECS_PER_HOUR / 10); } BreakTime(local_time, RtcTime); if (!RtcTime.hour && !RtcTime.minute && !RtcTime.second && RtcTime.valid) { diff --git a/sonoff/user_config.h b/sonoff/user_config.h index db5e9a50da23..6c5a134e0be4 100644 --- a/sonoff/user_config.h +++ b/sonoff/user_config.h @@ -167,6 +167,9 @@ #define USE_TIMERS // Add support for up to 16 timers (+2k2 code) #define USE_TIMERS_WEB // Add timer webpage support (+4k5 code) + #define USE_SUNRISE // Add support for Sunrise and sunset tools (+16k) + #define LATITUDE 48.858360 // [Latitude] Your location to be used with sunrise and sunset + #define LONGITUDE 2.294442 // [Longitude] Your location to be used with sunrise and sunset // -- Time - Start Daylight Saving Time and timezone offset from UTC in minutes #define TIME_DST North, Last, Sun, Mar, 2, +120 // Northern Hemisphere, Last sunday in march at 02:00 +120 minutes diff --git a/sonoff/xdrv_09_timers.ino b/sonoff/xdrv_09_timers.ino index b82eb7e2c859..0975c8c64189 100644 --- a/sonoff/xdrv_09_timers.ino +++ b/sonoff/xdrv_09_timers.ino @@ -22,9 +22,10 @@ * Timers * * Arm a timer using one or all of the following JSON values: - * {"Arm":1,"Time":"09:23","Days":"--TW--S","Repeat":1,"Device":1,"Power":1} + * {"Arm":1,"Mode":0,"Time":"09:23","Days":"--TW--S","Repeat":1,"Device":1,"Power":1} * * Arm 0 = Off, 1 = On + * Mode 0 = Schedule, 1 = Sunrise, 2 = Sunset * Time hours:minutes * Days 7 day character mask starting with Sunday (SMTWTFS). 0 or - = Off, any other value = On * Repeat 0 = Execute once, 1 = Execute again @@ -33,28 +34,201 @@ * \*********************************************************************************************/ -enum TimerCommands { CMND_TIMER, CMND_TIMERS }; -const char kTimerCommands[] PROGMEM = D_CMND_TIMER "|" D_CMND_TIMERS ; +enum TimerCommands { CMND_TIMER, CMND_TIMERS +#ifdef USE_SUNRISE +, CMND_LATITUDE, CMND_LONGITUDE +#endif + }; +const char kTimerCommands[] PROGMEM = D_CMND_TIMER "|" D_CMND_TIMERS +#ifdef USE_SUNRISE +"|" D_CMND_LATITUDE "|" D_CMND_LONGITUDE +#endif +; -uint16_t fired = 0; +uint16_t timer_fired = 0; +uint16_t timer_last_minute = 60; + +#ifdef USE_SUNRISE +/*********************************************************************************************\ + * Sunrise and sunset (+13k code) + * + * https://forum.arduino.cc/index.php?topic=218280.0 + * Source: C-Programm von http://lexikon.astronomie.info/zeitgleichung/neu.html + * Rewrite for Arduino by 'jurs' for German Arduino forum +\*********************************************************************************************/ + +const double pi2 = TWO_PI; +const double pi = PI; +const double RAD = DEG_TO_RAD; + +double JulianischesDatum() +{ + // Gregorianischer Kalender + int Gregor; + int Jahr = RtcTime.year; + int Monat = RtcTime.month; + int Tag = RtcTime.day_of_month; + + if (Monat <= 2) { + Monat += 12; + Jahr -= 1; + } + Gregor = (Jahr / 400) - (Jahr / 100) + (Jahr / 4); // Gregorianischer Kalender + return 2400000.5 + 365.0*Jahr - 679004.0 + Gregor + (int)(30.6001 * (Monat +1)) + Tag + 0.5; +} + +double InPi(double x) +{ + int n = (int)(x / pi2); + x = x - n*pi2; + if (x < 0) x += pi2; + return x; +} + +double eps(double T) +{ + // Neigung der Erdachse + return RAD * (23.43929111 + (-46.8150*T - 0.00059*T*T + 0.001813*T*T*T)/3600.0); +} + +double BerechneZeitgleichung(double *DK,double T) +{ + double RA_Mittel = 18.71506921 + 2400.0513369*T +(2.5862e-5 - 1.72e-9*T)*T*T; + double M = InPi(pi2 * (0.993133 + 99.997361*T)); + double L = InPi(pi2 * (0.7859453 + M/pi2 + (6893.0*sin(M)+72.0*sin(2.0*M)+6191.2*T) / 1296.0e3)); + double e = eps(T); + double RA = atan(tan(L)*cos(e)); + if (RA < 0.0) RA += pi; + if (L > pi) RA += pi; + RA = 24.0*RA/pi2; + *DK = asin(sin(e)*sin(L)); + // Damit 0<=RA_Mittel<24 + RA_Mittel = 24.0 * InPi(pi2*RA_Mittel/24.0)/pi2; + double dRA = RA_Mittel - RA; + if (dRA < -12.0) dRA += 24.0; + if (dRA > 12.0) dRA -= 24.0; + dRA = dRA * 1.0027379; + return dRA; +} + +void DuskTillDawn(uint8_t *hour_up,uint8_t *minute_up, uint8_t *hour_down, uint8_t *minute_down) +{ + double JD2000 = 2451545.0; + double JD = JulianischesDatum(); + double T = (JD - JD2000) / 36525.0; + double DK; + /* + h (D) = -0.8333 normaler SA & SU-Gang + h (D) = -6.0 civile Dämmerung + h (D) = -12.0 nautische Dämmerung + h (D) = -18.0 astronomische Dämmerung + */ + double h = -50/60.0*RAD; + double B = ((double)Settings.latitude/1000000) * RAD; // geographische Breite + double GeographischeLaenge = (double)Settings.longitude/1000000; + double Zeitzone = (double)time_timezone / 10; + double Zeitgleichung = BerechneZeitgleichung(&DK, T); + double Minuten = Zeitgleichung * 60.0; + double Zeitdifferenz = 12.0*acos((sin(h) - sin(B)*sin(DK)) / (cos(B)*cos(DK)))/pi; + double AufgangOrtszeit = 12.0 - Zeitdifferenz - Zeitgleichung; + double UntergangOrtszeit = 12.0 + Zeitdifferenz - Zeitgleichung; + double AufgangWeltzeit = AufgangOrtszeit - GeographischeLaenge / 15.0; + double UntergangWeltzeit = UntergangOrtszeit - GeographischeLaenge / 15.0; + double Aufgang = AufgangWeltzeit + Zeitzone; // In Stunden + if (Aufgang < 0.0) { + Aufgang += 24.0; + } else { + if (Aufgang >= 24.0) Aufgang -= 24.0; + } + double Untergang = UntergangWeltzeit + Zeitzone; + if (Untergang < 0.0) { + Untergang += 24.0; + } else { + if (Untergang >= 24.0) Untergang -= 24.0; + } + int AufgangMinuten = (int)(60.0*(Aufgang - (int)Aufgang)+0.5); + int AufgangStunden = (int)Aufgang; + if (AufgangMinuten >= 60.0) { + AufgangMinuten -= 60.0; + AufgangStunden++; + } else { + if (AufgangMinuten < 0.0) { + AufgangMinuten += 60.0; + AufgangStunden--; + if (AufgangStunden < 0.0) AufgangStunden += 24.0; + } + } + int UntergangMinuten = (int)(60.0*(Untergang - (int)Untergang)+0.5); + int UntergangStunden = (int)Untergang; + if (UntergangMinuten >= 60.0) { + UntergangMinuten -= 60.0; + UntergangStunden++; + } else { + if (UntergangMinuten<0) { + UntergangMinuten += 60.0; + UntergangStunden--; + if (UntergangStunden < 0.0) UntergangStunden += 24.0; + } + } + *hour_up = AufgangStunden; + *minute_up = AufgangMinuten; + *hour_down = UntergangStunden; + *minute_down = UntergangMinuten; +} + +String GetSun(byte dawn) +{ + char stime[6]; + + uint8_t hour[2]; + uint8_t minute[2]; + + DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]); + dawn &= 1; + snprintf_P(stime, sizeof(stime), PSTR("%02d:%02d"), hour[dawn], minute[dawn]); + return String(stime); +} + +uint16_t GetSunMinutes(byte dawn) +{ + uint8_t hour[2]; + uint8_t minute[2]; + + DuskTillDawn(&hour[0], &minute[0], &hour[1], &minute[1]); + dawn &= 1; + return (hour[dawn] *60) + minute[dawn]; +} + +#endif // USE_SUNRISE + +/*******************************************************************************************/ void TimerEverySecond() { if (RtcTime.valid) { - uint16_t time = (RtcTime.hour * 60) + RtcTime.minute; - uint8_t days = 1 << (RtcTime.day_of_week -1); + if (RtcTime.minute != timer_last_minute) { // Execute every minute + timer_last_minute = RtcTime.minute; + uint16_t time = (RtcTime.hour *60) + RtcTime.minute; + uint8_t days = 1 << (RtcTime.day_of_week -1); - for (byte i = 0; i < MAX_TIMERS; i++) { - if (Settings.timer[i].device >= devices_present) Settings.timer[i].data = 0; // Reset timer due to change in devices present - if (Settings.timer[i].arm) { - if (time == Settings.timer[i].time) { - if (!bitRead(fired, i) && (Settings.timer[i].days & days)) { - bitSet(fired, i); - Settings.timer[i].arm = Settings.timer[i].repeat; - ExecuteCommandPower(Settings.timer[i].device +1, Settings.timer[i].power); + for (byte i = 0; i < MAX_TIMERS; i++) { + if (Settings.timer[i].device >= devices_present) Settings.timer[i].data = 0; // Reset timer due to change in devices present + uint16_t set_time = Settings.timer[i].time; +#ifdef USE_SUNRISE + if ((1 == Settings.timer[i].mode) || (2 == Settings.timer[i].mode)) { // Sunrise or Sunset + set_time = GetSunMinutes(Settings.timer[i].mode -1); + } +#endif + if (Settings.timer[i].arm) { + if (time == set_time) { + if (!bitRead(timer_fired, i) && (Settings.timer[i].days & days)) { + bitSet(timer_fired, i); + Settings.timer[i].arm = Settings.timer[i].repeat; + ExecuteCommandPower(Settings.timer[i].device +1, Settings.timer[i].power); + } + } else { + bitClear(timer_fired, i); } - } else { - bitClear(fired, i); } } } @@ -70,8 +244,13 @@ void PrepShowTimer(uint8_t index) uint8_t mask = 1 << i; snprintf(days, sizeof(days), "%s%d", days, ((Settings.timer[index].days & mask) > 0)); } +#ifdef USE_SUNRISE + snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_MODE "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_POWER "\":%d}"), + mqtt_data, index +1, Settings.timer[index].arm, Settings.timer[index].mode, Settings.timer[index].time / 60, Settings.timer[index].time % 60, days, Settings.timer[index].repeat, Settings.timer[index].device +1, Settings.timer[index].power); +#else snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s\"" D_CMND_TIMER "%d\":{\"" D_JSON_TIMER_ARM "\":%d,\"" D_JSON_TIMER_TIME "\":\"%02d:%02d\",\"" D_JSON_TIMER_DAYS "\":\"%s\",\"" D_JSON_TIMER_REPEAT "\":%d,\"" D_JSON_TIMER_OUTPUT "\":%d,\"" D_JSON_TIMER_POWER "\":%d}"), mqtt_data, index +1, Settings.timer[index].arm, Settings.timer[index].time / 60, Settings.timer[index].time % 60, days, Settings.timer[index].repeat, Settings.timer[index].device +1, Settings.timer[index].power); +#endif // USE_SUNRISE } /*********************************************************************************************\ @@ -110,6 +289,11 @@ boolean TimerCommand() if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_ARM))].success()) { Settings.timer[index].arm = (root[parm_uc] != 0); } +#ifdef USE_SUNRISE + if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_MODE))].success()) { + Settings.timer[index].mode = (uint8_t)root[parm_uc] & 0x03; + } +#endif if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_TIME))].success()) { uint16_t itime = 0; uint8_t value = 0; @@ -154,7 +338,7 @@ boolean TimerCommand() if (root[UpperCase_P(parm_uc, PSTR(D_JSON_TIMER_POWER))].success()) { Settings.timer[index].power = (uint8_t)root[parm_uc] & 0x03; } - if (Settings.timer[index].arm) bitClear(fired, index); + if (Settings.timer[index].arm) bitClear(timer_fired, index); index++; } @@ -181,7 +365,7 @@ boolean TimerCommand() } jsflg = 1; PrepShowTimer(i +1); - if ((strlen(mqtt_data) > (LOGSZ - TOPSZ)) || (i == MAX_TIMERS -1)) { + if ((strlen(mqtt_data) > (LOGSZ - TOPSZ - 20)) || (i == MAX_TIMERS -1)) { snprintf_P(mqtt_data, sizeof(mqtt_data), PSTR("%s}}"), mqtt_data); MqttPublishPrefixTopic_P(RESULT_OR_STAT, PSTR(D_CMND_TIMERS)); jsflg = 0; @@ -189,6 +373,24 @@ boolean TimerCommand() } mqtt_data[0] = '\0'; } +#ifdef USE_SUNRISE + else if (CMND_LONGITUDE == command_code) { + if (XdrvMailbox.data_len) { + Settings.longitude = (int)(AtoD(XdrvMailbox.data) *1000000); + } + char lbuff[32]; + dtostrfd(((double)Settings.longitude) /1000000, 6, lbuff); + snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, lbuff); + } + else if (CMND_LATITUDE == command_code) { + if (XdrvMailbox.data_len) { + Settings.latitude = (int)(AtoD(XdrvMailbox.data) *1000000); + } + char lbuff[32]; + dtostrfd(((double)Settings.latitude) /1000000, 6, lbuff); + snprintf_P(mqtt_data, sizeof(mqtt_data), S_JSON_COMMAND_SVALUE, command, lbuff); + } +#endif else serviced = false; return serviced; @@ -210,12 +412,26 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM = "o.textContent=i;" "q.appendChild(o);" "}" +#ifdef USE_SUNRISE + "function gt(){" // Set hours and minutas according to mode + "var m,p,q;" + "m=qs('input[name=\"rd\"]:checked').value;" // Get mode + "if(m==0){p=pt[ct]&0x7FF;}" // Schedule time + "if(m==1){p=pt[" STR(MAX_TIMERS) "];}" // Sunrise + "if(m==2){p=pt[" STR(MAX_TIMERS +1) "];}" // Sunset + "q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" // Set hours + "q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes + "}" +#endif "function st(){" // Save parameters to hidden area "var i,n,p,s;" "s=0;" "n=1<<30;if(eb('a0').checked){s|=n;}" // Get arm "n=1<<29;if(eb('r0').checked){s|=n;}" // Get repeat "for(i=0;i<7;i++){n=1<<(16+i);if(eb('w'+i).checked){s|=n;}}" // Get weekdays +#ifdef USE_SUNRISE + "s|=(qs('input[name=\"rd\"]:checked').value<<11);" // Get mode +#endif "s|=(eb('p1').value<<27);" // Get power "s|=(qs('#d1').selectedIndex<<23);" // Get device "s|=((qs('#ho').selectedIndex*60)+qs('#mi').selectedIndex)&0x7FF;" // Get time @@ -224,22 +440,25 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM = "}" "function ot(t,e){" // Select tab and update elements "var i,n,o,p,q,s;" - "if(ct<99){" - "st();" // Save changes - "}" + "if(ct<99){st();}" // Save changes + "ct=t;" "o=document.getElementsByClassName('tl');" // Restore style to all tabs/buttons "for(i=0;i>11)&3;eb('b'+p).checked=1;" // Set mode + "gt();" // Set hours and minutes according to mode +#else "p=s&0x7FF;" // Get time "q=Math.floor(p/60);if(q<10){q='0'+q;}qs('#ho').value=q;" // Set hours "q=p%60;if(q<10){q='0'+q;}qs('#mi').value=q;" // Set minutes +#endif "for(i=0;i<7;i++){p=(s>>(16+i))&1;eb('w'+i).checked=p;}" // Set weekdays "p=(s>>23)&0xF;qs('#d1').value=p+1;" // Set device "p=(s>>27)&3;eb('p1').value=p;" // Set power "p=(s>>29)&1;eb('r0').checked=p;" // Set repeat "p=(s>>30)&1;eb('a0').checked=p;" // Set arm - "ct=t;" "}" "function it(){" // Initialize elements and select first tab "var b,i,o,s;" @@ -256,6 +475,9 @@ const char HTTP_TIMER_SCRIPT[] PROGMEM = "}"; const char HTTP_TIMER_STYLE[] PROGMEM = ".tl{float:left;border-radius:0;border:1px solid #fff;padding:1px;width:6.25%;}" +#ifdef USE_SUNRISE + "input[type='radio']{width:13px;height:34px;margin-top:-1px;margin-right:8px;vertical-align:middle;}" +#endif ""; const char HTTP_FORM_TIMER[] PROGMEM = "
 " D_TIMER_PARAMETERS " 
" @@ -263,8 +485,8 @@ const char HTTP_FORM_TIMER[] PROGMEM = const char HTTP_FORM_TIMER1[] PROGMEM = "' hidden>



" "
" - "" D_TIMER_OUTPUT "  " - "" D_TIMER_POWER "  " + "" D_TIMER_POWER " " "

" "
" -// "Time  " - "" D_TIMER_TIME "  " D_HOUR_MINUTE_SEPARATOR "  " "" D_TIMER_ARM " " "" D_TIMER_REPEAT "" + "

" + "
" +// "Time " +#ifdef USE_SUNRISE + "
" + "" D_TIMER_TIME " " + "" + " " D_HOUR_MINUTE_SEPARATOR " " + "
" + "" D_SUNRISE "
" + "" D_SUNSET "
" + "
" +#else + "" D_TIMER_TIME " " + "" + " " D_HOUR_MINUTE_SEPARATOR " " + "" +#endif // USE_SUNRISE "

" "
"; const char HTTP_FORM_TIMER2[] PROGMEM = @@ -301,8 +539,15 @@ void HandleTimerConfiguration() if (i > 0) page += F(","); page += String(Settings.timer[i].data); } +#ifdef USE_SUNRISE + page += F(","); page += String(GetSunMinutes(0)); // Add Sunrise + page += F(","); page += String(GetSunMinutes(1)); // Add Sunset +#endif // USE_SUNRISE page += FPSTR(HTTP_FORM_TIMER1); page.replace(F("}1"), String(devices_present)); +#ifdef USE_SUNRISE + page.replace(F("299"), String(180 + (strlen(D_TIMER_TIME) *10))); // Fix string length to keep radios centered +#endif // USE_SUNRISE page += FPSTR(HTTP_FORM_END); page.replace(F("type='submit'"), FPSTR(HTTP_FORM_TIMER2)); page += F(""); // Init elements and select first tab/button @@ -313,14 +558,20 @@ void HandleTimerConfiguration() void TimerSaveSettings() { char tmp[MAX_TIMERS *12]; // Need space for MAX_TIMERS x 10 digit numbers separated by a comma + Timer timer; WebGetArg("t0", tmp, sizeof(tmp)); char *p = tmp; snprintf_P(log_data, sizeof(log_data), PSTR(D_LOG_MQTT D_CMND_TIMERS " ")); for (byte i = 0; i < MAX_TIMERS; i++) { - uint32_t data = strtol(p, &p, 10); + timer.data = strtol(p, &p, 10); p++; // Skip comma - if ((data & 0x7FF) < 1440) Settings.timer[i].data = data; + if (timer.time < 1440) { +#ifdef USE_SUNRISE + if ((1 == timer.mode) || (2 == timer.mode)) timer.time = Settings.timer[i].time; // Do not save time on Sunrise or Sunset +#endif + Settings.timer[i].data = timer.data; + } snprintf_P(log_data, sizeof(log_data), PSTR("%s%s0x%08X"), log_data, (i > 0)?",":"", Settings.timer[i].data); } AddLog(LOG_LEVEL_DEBUG);