diff --git a/tools/esp8266/ahoywifi.cpp b/tools/esp8266/ahoywifi.cpp index b3feb43c..436fd660 100644 --- a/tools/esp8266/ahoywifi.cpp +++ b/tools/esp8266/ahoywifi.cpp @@ -12,7 +12,6 @@ // NTP CONFIG #define NTP_PACKET_SIZE 48 -#define TIMEZONE 1 // Central European time +1 //----------------------------------------------------------------------------- @@ -185,7 +184,6 @@ time_t ahoywifi::getNtpTime(void) { WiFi.hostByName(mConfig->ntpAddr, timeServer); mUdp->begin(mConfig->ntpPort); - sendNTPpacket(timeServer); while(retry++ < 5) { @@ -200,7 +198,6 @@ time_t ahoywifi::getNtpTime(void) { secsSince1900 |= (buf[43] ); date = secsSince1900 - 2208988800UL; // UTC time - date += (TIMEZONE + offsetDayLightSaving(date)) * 3600; break; } else @@ -231,22 +228,3 @@ void ahoywifi::sendNTPpacket(IPAddress& address) { mUdp->write(buf, NTP_PACKET_SIZE); mUdp->endPacket(); } - - -//----------------------------------------------------------------------------- -// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC -// from: https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536 -time_t ahoywifi::offsetDayLightSaving (uint32_t local_t) { - //DPRINTLN(DBG_VERBOSE, F("wifi::offsetDayLightSaving")); - int m = month (local_t); - if(m < 3 || m > 10) return 0; // no DSL in Jan, Feb, Nov, Dez - if(m > 3 && m < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep - int y = year (local_t); - int h = hour (local_t); - int hToday = (h + 24 * day(local_t)); - if((m == 3 && hToday >= (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 4) % 7))) - || (m == 10 && hToday < (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 1) % 7))) ) - return 1; - else - return 0; -} diff --git a/tools/esp8266/ahoywifi.h b/tools/esp8266/ahoywifi.h index fdd3bff0..ba02c577 100644 --- a/tools/esp8266/ahoywifi.h +++ b/tools/esp8266/ahoywifi.h @@ -30,11 +30,9 @@ class ahoywifi { bool setupStation(uint32_t timeout); bool getApActive(void); time_t getNtpTime(void); - + private: void sendNTPpacket(IPAddress& address); - time_t offsetDayLightSaving (uint32_t local_t); - config_t *mConfig; sysConfig_t *mSysCfg; diff --git a/tools/esp8266/app.cpp b/tools/esp8266/app.cpp index f2aa473a..17798bfc 100644 --- a/tools/esp8266/app.cpp +++ b/tools/esp8266/app.cpp @@ -44,6 +44,8 @@ void app::setup(uint32_t timeout) { mWebInst = new web(this, &mSysConfig, &mConfig, &mStat, mVersion); mWebInst->setup(); + + mSun.setLocalization(52.6479, 13.6922); // ToDo: add lan/lon to setup, can also in app::loadEEpconfig or somewhere else in app.cpp } //----------------------------------------------------------------------------- @@ -56,6 +58,8 @@ void app::loop(void) { if(millis() - mPrevMillis >= 1000) { mPrevMillis += 1000; mUptimeSecs++; + if(0 != mUtcTimestamp) + mUtcTimestamp++; if(0 != mTimestamp) mTimestamp++; } @@ -67,7 +71,8 @@ void app::loop(void) { if(mUpdateNtp) { mUpdateNtp = false; - mTimestamp = mWifi->getNtpTime(); + mUtcTimestamp = mWifi->getNtpTime(); + mTimestamp = mUtcTimestamp + ((TIMEZONE + offsetDayLightSaving(mUtcTimestamp)) * 3600); DPRINTLN(DBG_INFO, "[NTP]: " + getDateTimeStr(mTimestamp)); } @@ -149,6 +154,12 @@ void app::loop(void) { mMqtt.loop(); if(checkTicker(&mTicker, 1000)) { + if(mTimestamp > 946684800 && mSun.LocalizationIsSet() && mTimestamp / 86400 > mLatestSunTimestamp / 86400) // update on reboot or new day + { + CalculateSunriseSunset(mTimestamp, &mSunrise, &mSunset); + mLatestSunTimestamp = mTimestamp; + } + if((++mMqttTicker >= mMqttInterval) && (mMqttInterval != 0xffff) && mMqttActive) { mMqttTicker = 0; mMqtt.isConnected(true); // really needed? See comment from HorstG-57 #176 @@ -195,7 +206,8 @@ void app::loop(void) { if(++mSendTicker >= mConfig.sendInterval) { mSendTicker = 0; - if(0 != mTimestamp) { + bool DisableNightCommunication = false; // ToDo: Add option in setup to disable inverter communication at night + if(mUtcTimestamp > 946684800 && (!DisableNightCommunication || !mLatestSunTimestamp || (mTimestamp >= mSunrise && mTimestamp <= mSunset))) { // Timestamp is set and (inverter communication only during the day if the option is activated and sunrise/sunset is set) if(mConfig.serialDebug) DPRINTLN(DBG_DEBUG, F("Free heap: 0x") + String(ESP.getFreeHeap(), HEX)); @@ -257,7 +269,7 @@ void app::loop(void) { } } else if(mConfig.serialDebug) - DPRINTLN(DBG_WARN, F("time not set, can't request inverter!")); + DPRINTLN(DBG_WARN, F("Time not set or it is night time, therefore no communication to the inverter!")); yield(); } } @@ -379,7 +391,7 @@ void app::processPayload(bool retransmit) { if(NULL == rec) DPRINTLN(DBG_ERROR, F("record is NULL!")); else { - rec->ts = mPayload[iv->id].ts; + rec->ts = mPayload[iv->id].ts + ((TIMEZONE + offsetDayLightSaving(mUtcTimestamp)) * 3600); for(uint8_t i = 0; i < rec->length; i++) { iv->addValue(i, payload, rec); yield(); @@ -679,8 +691,10 @@ void app::resetSystem(void) { mNtpRefreshInterval = NTP_REFRESH_INTERVAL; // [ms] #ifdef AP_ONLY + mUtcTimestamp = 1; mTimestamp = 1; #else + mUtcTimestamp = 0; mTimestamp = 0; #endif @@ -857,5 +871,35 @@ void app::resetPayload(Inverter<>* iv) { mPayload[iv->id].maxPackId = 0; mPayload[iv->id].complete = false; mPayload[iv->id].requested = false; - mPayload[iv->id].ts = mTimestamp; + mPayload[iv->id].ts = mUtcTimestamp; +} + +//----------------------------------------------------------------------------- +// calculates the daylight saving time for middle Europe. Input: Unixtime in UTC +// from: https://forum.arduino.cc/index.php?topic=172044.msg1278536#msg1278536 +uint8_t app::offsetDayLightSaving (uint32_t local_t) { + //DPRINTLN(DBG_VERBOSE, F("wifi::offsetDayLightSaving")); + int m = month (local_t); + if(m < 3 || m > 10) return 0; // no DSL in Jan, Feb, Nov, Dez + if(m > 3 && m < 10) return 1; // DSL in Apr, May, Jun, Jul, Aug, Sep + int y = year (local_t); + int h = hour (local_t); + int hToday = (h + 24 * day(local_t)); + if((m == 3 && hToday >= (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 4) % 7))) + || (m == 10 && hToday < (1 + TIMEZONE + 24 * (31 - (5 * y /4 + 1) % 7))) ) + return 1; + else + return 0; +} + +bool app::CalculateSunriseSunset(uint32_t localTimestamp, uint32_t *localSunrise, uint32_t *localSunset) { // true = day; false = night + uint32_t UTC_Timestamp_Sunrise, UTC_Timestamp_Sunset; + mSun.getRiseSet(localTimestamp, UTC_Timestamp_Sunrise, UTC_Timestamp_Sunset); // local timestamp is only used to calculate today's calendar day + *localSunrise = UTC_Timestamp_Sunrise + ((TIMEZONE + offsetDayLightSaving(UTC_Timestamp_Sunrise)) * 3600); // sunrise in local time, OPTIONAL: Add an offset of +-seconds to the end of the line + *localSunset = UTC_Timestamp_Sunset + ((TIMEZONE + offsetDayLightSaving(UTC_Timestamp_Sunset)) * 3600); // sunset in local time, OPTIONAL: Add an offset of +-seconds to the end of the line + + if(localTimestamp >= *localSunrise && localTimestamp <= *localSunset) + return true; // it is day + else + return false; // it is night } diff --git a/tools/esp8266/app.h b/tools/esp8266/app.h index fe92bb5e..5431909a 100644 --- a/tools/esp8266/app.h +++ b/tools/esp8266/app.h @@ -23,6 +23,9 @@ #include "mqtt.h" #include "ahoywifi.h" #include "web.h" +#include "sun.h" + +#define TIMEZONE 1 // Central European time +1 // hier läst sich das Verhalten der app in Bezug auf MQTT // durch PER-Conpiler defines anpassen @@ -117,7 +120,20 @@ class app { if(0 == newTime) mUpdateNtp = true; else - mTimestamp = newTime; + { + mUtcTimestamp = newTime; + mTimestamp = mUtcTimestamp + ((TIMEZONE + offsetDayLightSaving(mUtcTimestamp)) * 3600); + } + } + + inline uint32_t getSunrise(void) { + return mSunrise; + } + inline uint32_t getSunset(void) { + return mSunset; + } + inline uint32_t getLatestSunTimestamp(void) { + return mLatestSunTimestamp; } void eraseSettings(bool all = false) { @@ -234,6 +250,8 @@ class app { DPRINTLN(DBG_VERBOSE, F(" - frag: ") + String(frag)); } + uint8_t offsetDayLightSaving(uint32_t local_t); + bool CalculateSunriseSunset(uint32_t localTimestamp, uint32_t *localSunrise, uint32_t *localSunset); uint32_t mUptimeSecs; uint32_t mPrevMillis; @@ -246,6 +264,7 @@ class app { bool mSettingsValid; eep *mEep; + uint32_t mUtcTimestamp; uint32_t mTimestamp; bool mUpdateNtp; @@ -277,6 +296,12 @@ class app { // serial uint16_t mSerialTicker; + + // sun + sun mSun; + uint32_t mSunrise; + uint32_t mSunset; + uint32_t mLatestSunTimestamp; }; #endif /*__APP_H__*/ diff --git a/tools/esp8266/html/index.html b/tools/esp8266/html/index.html index 48517201..759e4309 100644 --- a/tools/esp8266/html/index.html +++ b/tools/esp8266/html/index.html @@ -17,6 +17,8 @@

Uptime:

ESP-Time:

+

Sunrise:

+

Sunset:

RSSI: dBm

Statistics: @@ -57,11 +59,15 @@ var hrs = parseInt(up / 3600) % 24; var min = parseInt(up / 60) % 60; var sec = up % 60; + var sunrise = new Date(obj["ts_sunrise"] * 1000); + var sunset = new Date(obj["ts_sunset"] * 1000); document.getElementById("uptime").innerHTML = days + " Days, " + ("0"+hrs).substr(-2) + ":" + ("0"+min).substr(-2) + ":" + ("0"+sec).substr(-2); document.getElementById("date").innerHTML = date.toLocaleString('de-DE', {timeZone: 'UTC'}); + document.getElementById("sunrise").innerHTML = sunrise.toLocaleString('de-DE', {timeZone: 'UTC'}); + document.getElementById("sunset").innerHTML = sunset.toLocaleString('de-DE', {timeZone: 'UTC'}); } function parseStat(obj) { diff --git a/tools/esp8266/sun.cpp b/tools/esp8266/sun.cpp new file mode 100644 index 00000000..3dad7ab7 --- /dev/null +++ b/tools/esp8266/sun.cpp @@ -0,0 +1,99 @@ +/* + sun.cpp - Library for calculating sunrise and sunset times based on current local time and position on Earth. + Calculations are based on Sunrise equation: https://en.wikipedia.org/wiki/Sunrise_equation + Input and output times are in Unix Timestamp format (seconds since Jan 1, 1970, 00:00:00). + + Copyright (C) 2017 Nejc Planinsek + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#include "sun.h" + +const float pi = 3.14159265; + +/* + * Constructor. Latitude and longitude must be provided. + */ +void sun::setLocalization(float lat, float lon) { + _lat = lat; + _lon = lon; +} + +bool sun::LocalizationIsSet() { + if (_lat && _lon) + return true; + else + return false; +} + +/* + * Calculates sunrise and sunset times based on current time. All times are in Unix Timestamp format. + */ +void sun::getRiseSet(uint32_t unixTime, uint32_t &sunrise, uint32_t &sunset) { + // Convert Julian day to Unix Timestamp + uint32_t Jdate = unixTime / 86400 + 2440588; + // Number of days since Jan 1st, 2000 12:00 + float n = Jdate - 2451545.0 + 0.0008; + // Mean solar noon + float Jstar = -_lon / 360 + n; + // Solar mean anomaly + float M = fmod((357.5291 + 0.98560028 * Jstar), 360); + // Equation of the center + float C = 1.9148 * sin(M / 360 * 2 * pi) + 0.02 * sin(2 * M / 360 * 2 * pi) + 0.0003 * sin(3 * M * 360 * 2 * pi); + // Ecliptic longitude + float lambda = fmod((M + C + 180 + 102.9372), 360); + // Solar transit + float Jtransit = Jstar + (0.0053 * sin(M / 360.0 * 2.0 * pi) - 0.0069 * sin(2.0 * (lambda / 360.0 * 2.0 * pi))); + // Declination of the sun + float delta = asin(sin(lambda / 360 * 2 * pi) * sin(23.44 / 360 * 2 * pi)) / (2 * pi) * 360; + // Hour angle + float omega0 = 360 / (2 * pi) * acos((sin(-0.83 / 360 * 2 * pi) - sin(_lat / 360 * 2 * pi) * sin(delta / 360 * 2 * pi)) / (cos(_lat / 360 * 2 * pi) * cos(delta / 360 * 2 * pi))); + // Julian day sunrise, sunset + float Jset = Jtransit + omega0 / 360; + float Jrise = Jtransit - omega0 / 360; + // Convert to Unix Timestamp + uint32_t unixRise = Jrise * 86400.0 + 946728000; // Convert days (incl. fraction) to seconds + offset 1/1/2000 12:00 + uint32_t unixSet = Jset * 86400.0 + 946728000; // Convert days (incl. fraction) to seconds + offset 1/1/2000 12:00 + sunrise = unixRise; + sunset = unixSet; +} + +uint32_t sun::getRise(uint32_t unixTime) { + uint32_t rise; + uint32_t set; + getRiseSet(unixTime, rise, set); + return rise; +} + +uint32_t sun::getSet(uint32_t unixTime) { + uint32_t rise; + uint32_t set; + getRiseSet(unixTime, rise, set); + return set; +} + +uint32_t sun::getDayLength(uint32_t unixTime) { + uint32_t rise; + uint32_t set; + getRiseSet(unixTime, rise, set); + return set - rise; +} + +uint32_t sun::getNightLength(uint32_t unixTime) { + uint32_t rise; + uint32_t set; + getRiseSet(unixTime, rise, set); + return 86400 - (set - rise); +} diff --git a/tools/esp8266/sun.h b/tools/esp8266/sun.h new file mode 100644 index 00000000..c614d18d --- /dev/null +++ b/tools/esp8266/sun.h @@ -0,0 +1,42 @@ +/* + sun.h - Library for calculating sunrise and sunset times based on current local time and position on Earth. + Calculations are based on Sunrise equation: https://en.wikipedia.org/wiki/Sunrise_equation + Input and output times are in Unix Timestamp format (seconds since Jan 1, 1970, 00:00:00). + + Copyright (C) 2017 Nejc Planinsek + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef __SUN_H__ +#define __SUN_H__ + +#include "Arduino.h" + +class sun +{ + public: + void setLocalization(float lat, float lon); + bool LocalizationIsSet(); + void getRiseSet(uint32_t unixTime, uint32_t &sunrise, uint32_t &sunset); + uint32_t getRise(uint32_t unixTime); + uint32_t getSet(uint32_t unixTime); + uint32_t getDayLength(uint32_t unixTime); + uint32_t getNightLength(uint32_t unixTime); + private: + float _lat = 0.0; + float _lon = 0.0; +}; + +#endif /*__SUN_H__*/ diff --git a/tools/esp8266/webApi.cpp b/tools/esp8266/webApi.cpp index 7bf372ab..34ba1cbb 100644 --- a/tools/esp8266/webApi.cpp +++ b/tools/esp8266/webApi.cpp @@ -125,6 +125,9 @@ void webApi::getSystem(JsonObject obj) { obj[F("build")] = String(AUTO_GIT_HASH); obj[F("ts_uptime")] = mApp->getUptime(); obj[F("ts_now")] = mApp->getTimestamp(); + obj[F("ts_sunrise")] = mApp->getSunrise(); + obj[F("ts_sunset")] = mApp->getSunset(); + obj[F("ts_sun_upd")] = mApp->getLatestSunTimestamp(); obj[F("wifi_rssi")] = WiFi.RSSI(); }