diff --git a/.github/workflows/compile_development.yml b/.github/workflows/compile_development.yml index 48a3f42a..1d536b5e 100644 --- a/.github/workflows/compile_development.yml +++ b/.github/workflows/compile_development.yml @@ -23,7 +23,10 @@ jobs: strategy: matrix: variant: + - opendtufusion + - opendtufusion-ethernet - esp8266 + - esp8266-all - esp8266-minimal - esp8266-prometheus - esp8285 @@ -33,8 +36,6 @@ jobs: - esp32-wroom32-ethernet - esp32-s2-mini - esp32-c3-mini - - opendtufusion - - opendtufusion-ethernet steps: - uses: actions/checkout@v4 - uses: benjlevesque/short-sha@v3.0 @@ -86,7 +87,10 @@ jobs: strategy: matrix: variant: + - opendtufusion-de + - opendtufusion-ethernet-de - esp8266-de + - esp8266-all-de - esp8266-prometheus-de - esp8285-de - esp32-wroom32-de @@ -94,8 +98,6 @@ jobs: - esp32-wroom32-ethernet-de - esp32-s2-mini-de - esp32-c3-mini-de - - opendtufusion-de - - opendtufusion-ethernet-de steps: - uses: actions/checkout@v4 - uses: benjlevesque/short-sha@v3.0 diff --git a/patches/AsyncWeb_Prometheus.patch b/patches/AsyncWeb_Prometheus.patch index 21fe22cd..3c7deac4 100644 --- a/patches/AsyncWeb_Prometheus.patch +++ b/patches/AsyncWeb_Prometheus.patch @@ -1,26 +1,26 @@ diff --git a/src/AsyncWebSocket.cpp b/src/AsyncWebSocket.cpp -index 12be5f8..cffeed7 100644 +index 6e88da9..09359c3 100644 --- a/src/AsyncWebSocket.cpp +++ b/src/AsyncWebSocket.cpp -@@ -737,7 +737,7 @@ void AsyncWebSocketClient::binary(const __FlashStringHelper *data, size_t len) - IPAddress AsyncWebSocketClient::remoteIP() const - { - if (!_client) -- return IPAddress(0U); -+ return IPAddress(); +@@ -827,7 +827,7 @@ void AsyncWebSocketClient::binary(AsyncWebSocketMessageBuffer * buffer) + IPAddress AsyncWebSocketClient::remoteIP() { + if(!_client) { +- return IPAddress((uint32_t)0); ++ return IPAddress(); + } return _client->remoteIP(); } diff --git a/src/WebResponses.cpp b/src/WebResponses.cpp -index 22a549f..e0b36b3 100644 +index a22e991..babef18 100644 --- a/src/WebResponses.cpp +++ b/src/WebResponses.cpp -@@ -318,7 +318,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u +@@ -317,7 +317,7 @@ size_t AsyncAbstractResponse::_ack(AsyncWebServerRequest *request, size_t len, u free(buf); return 0; } -- outLen = sprintf_P((char*)buf+headLen, PSTR("%x"), readLen) + headLen; -+ outLen = sprintf_P((char*)buf+headLen, PSTR("%04x"), readLen) + headLen; +- outLen = sprintf((char*)buf+headLen, "%x", readLen) + headLen; ++ outLen = sprintf((char*)buf+headLen, "%04x", readLen) + headLen; while(outLen < headLen + 4) buf[outLen++] = ' '; buf[outLen++] = '\r'; buf[outLen++] = '\n'; diff --git a/scripts/applyPatches.py b/scripts/applyPatches.py index 147fb0f3..57f1fa23 100644 --- a/scripts/applyPatches.py +++ b/scripts/applyPatches.py @@ -26,9 +26,10 @@ def applyPatch(libName, patchFile): # list of patches to apply (relative to /src) -if env['PIOENV'][:22] != "opendtufusion-ethernet": - applyPatch("ESP Async WebServer", "../patches/AsyncWeb_Prometheus.patch") +applyPatch("ESPAsyncWebServer-esphome", "../patches/AsyncWeb_Prometheus.patch") if env['PIOENV'][:13] == "opendtufusion": applyPatch("GxEPD2", "../patches/GxEPD2_SW_SPI.patch") + +if (env['PIOENV'][:13] == "opendtufusion"): # or (env['PIOENV'][:13] == "esp32-wroom32"): applyPatch("RF24", "../patches/RF24_Hal.patch") diff --git a/scripts/htmlPreprocessorDefines.py b/scripts/htmlPreprocessorDefines.py index f8230a90..f5d7cc31 100644 --- a/scripts/htmlPreprocessorDefines.py +++ b/scripts/htmlPreprocessorDefines.py @@ -35,6 +35,6 @@ def check(inp, lst, pattern): return out def conv(inp, lst): - print(lst) + #print(lst) out = check(inp, lst, r'\/\*(?:IF_|ELS|ENDIF_)([A-Z0-9\-_]+)?\*\/') return check(out, lst, r'\<\!\-\-(?:IF_|ELS|ENDIF_)([A-Z0-9\-_]+)?\-\-\>') diff --git a/src/CHANGES.md b/src/CHANGES.md index 876e7f93..99cf8ac8 100644 --- a/src/CHANGES.md +++ b/src/CHANGES.md @@ -1,5 +1,24 @@ # Development Changes +## 0.8.95 - 2024-03-17 +* fix NTP issues #1440 #1497 #1499 + +## 0.8.94 - 2024-03-16 +* switched AsyncWebServer library +* Ethernet version now uses same AsyncWebServer library as Wifi version +* fix translation of `/history` +* fix RSSI on `/history` #1463 + +## 0.8.93 - 2024-03-14 +* improved history graph in WebUI #1491 +* merge PR: 1491 + +## 0.8.92 - 2024-03-10 +* fix read back of limit value, now with one decimal place +* added grid profile for Mexico #1493 +* added language to display on compile time #1484, #1255, #1479 +* added new environment `esp8266-all` which replace the original `esp8266`. The original now only have `MqTT` support but `Display` and `History` plugins are not included any more #1451 + ## 0.8.91 - 2024-03-05 * fix javascript issues #1480 diff --git a/src/app.cpp b/src/app.cpp index ae400c94..f6e4bf2b 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -63,7 +63,7 @@ void app::setup() { #endif // ETHERNET #if !defined(ETHERNET) - mWifi.setup(mConfig, &mTimestamp, std::bind(&app::onNetwork, this, std::placeholders::_1)); + mWifi.setup(mConfig, &mTimestamp, [this](bool gotIp) { this->onNetwork(gotIp); }, [this](bool gotTime) { this->onNtpUpdate(gotTime); }); #if !defined(AP_ONLY) everySec(std::bind(&ahoywifi::tickWifiLoop, &mWifi), "wifiL"); #endif @@ -152,7 +152,6 @@ void app::setup() { }); #endif /*ENABLE_SIMULATOR*/ - esp_task_wdt_reset(); regularTickers(); } @@ -190,7 +189,7 @@ void app::loop(void) { //----------------------------------------------------------------------------- void app::onNetwork(bool gotIp) { - DPRINTLN(DBG_DEBUG, F("onNetwork")); + DPRINTLN(DBG_INFO, F("onNetwork")); mNetworkConnected = gotIp; ah::Scheduler::resetTicker(); regularTickers(); //reinstall regular tickers @@ -238,11 +237,9 @@ void app::regularTickers(void) { #endif /*ENABLE_SIMULATOR*/ } -#if defined(ETHERNET) void app::onNtpUpdate(bool gotTime) { mNtpReceived = true; } -#endif /* defined(ETHERNET) */ //----------------------------------------------------------------------------- void app::updateNtp(void) { @@ -283,30 +280,30 @@ void app::updateNtp(void) { //----------------------------------------------------------------------------- void app::tickNtpUpdate(void) { uint32_t nxtTrig = 5; // default: check again in 5 sec - bool isOK = false; #if defined(ETHERNET) - if (!mNtpReceived) - mEth.updateNtpTime(); - else { - mNtpReceived = false; - isOK = true; - } - #else - isOK = mWifi.getNtpTime(); + if (!mNtpReceived) + mEth.updateNtpTime(); + else + mNtpReceived = false; + #else + if (!mNtpReceived) + mWifi.updateNtpTime(); + else + mNtpReceived = false; #endif - if (isOK) { - this->updateNtp(); - nxtTrig = mConfig->ntp.interval * 60; // check again in 12h - - // immediately start communicating - if (mSendFirst) { - mSendFirst = false; - once(std::bind(&app::tickSend, this), 1, "senOn"); - } - mMqttReconnect = false; + updateNtp(); + nxtTrig = mConfig->ntp.interval * 60; // check again in 12h + + // immediately start communicating + if (mSendFirst) { + mSendFirst = false; + once(std::bind(&app::tickSend, this), 1, "senOn"); } + + mMqttReconnect = false; + once(std::bind(&app::tickNtpUpdate, this), nxtTrig, "ntp"); } @@ -608,10 +605,7 @@ void app::resetSystem(void) { mSaveReboot = false; mNetworkConnected = false; - -#if defined(ETHERNET) mNtpReceived = false; -#endif } //----------------------------------------------------------------------------- diff --git a/src/app.h b/src/app.h index 73477943..383bdc5d 100644 --- a/src/app.h +++ b/src/app.h @@ -306,7 +306,7 @@ class app : public IApp, public ah::Scheduler { #if defined(ETHERNET) mEth.updateNtpTime(); #else /* defined(ETHERNET) */ - mWifi.getNtpTime(); + mWifi.updateNtpTime(); #endif /* defined(ETHERNET) */ } else @@ -321,6 +321,14 @@ class app : public IApp, public ah::Scheduler { #endif } + uint32_t getHistoryPeriod(uint8_t type) override { + #if defined(ENABLE_HISTORY) + return mHistory.getPeriod((HistoryStorageType)type); + #else + return 0; + #endif + } + uint16_t getHistoryMaxDay() override { #if defined(ENABLE_HISTORY) return mHistory.getMaximumDay(); @@ -329,6 +337,21 @@ class app : public IApp, public ah::Scheduler { #endif } + uint32_t getHistoryLastValueTs(uint8_t type) override { + #if defined(ENABLE_HISTORY) + return mHistory.getLastValueTs((HistoryStorageType)type); + #else + return 0; + #endif + } + #if defined(ENABLE_HISTORY_LOAD_DATA) + void addValueToHistory(uint8_t historyType, uint8_t valueType, uint32_t value) override { + #if defined(ENABLE_HISTORY) + return mHistory.addValue((HistoryStorageType)historyType, valueType, value); + #endif + } + #endif + private: #define CHECK_AVAIL true #define SKIP_YIELD_DAY true @@ -379,10 +402,8 @@ class app : public IApp, public ah::Scheduler { } void tickNtpUpdate(void); - #if defined(ETHERNET) void onNtpUpdate(bool gotTime); bool mNtpReceived = false; - #endif /* defined(ETHERNET) */ void updateNtp(void); void triggerTickSend() override { diff --git a/src/appInterface.h b/src/appInterface.h index 536455e0..b465edf9 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -7,11 +7,7 @@ #define __IAPP_H__ #include "defines.h" -#if defined(ETHERNET) -#include "AsyncWebServer_ESP32_W5500.h" -#else #include "ESPAsyncWebServer.h" -#endif // abstract interface to App. Make members of App accessible from child class // like web or API without forward declaration @@ -67,8 +63,12 @@ class IApp { virtual bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const = 0; virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0; + virtual uint32_t getHistoryPeriod(uint8_t type) = 0; virtual uint16_t getHistoryMaxDay() = 0; - + virtual uint32_t getHistoryLastValueTs(uint8_t type) = 0; + #if defined(ENABLE_HISTORY_LOAD_DATA) + virtual void addValueToHistory(uint8_t historyType, uint8_t valueType, uint32_t value) = 0; + #endif virtual void* getRadioObj(bool nrf) = 0; }; diff --git a/src/config/config.h b/src/config/config.h index df8cec63..b7f3cc2f 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -77,6 +77,9 @@ #ifndef DEF_ETH_CS_PIN #define DEF_ETH_CS_PIN 15 #endif + #ifndef DEF_ETH_RST_PIN + #define DEF_ETH_RST_PIN 2 + #endif #else /* defined(ETHERNET) */ // time in seconds how long the station info (ssid + pwd) will be tried #define WIFI_TRY_CONNECT_TIME 30 diff --git a/src/defines.h b/src/defines.h index ad4fda0c..29a08533 100644 --- a/src/defines.h +++ b/src/defines.h @@ -13,7 +13,7 @@ //------------------------------------- #define VERSION_MAJOR 0 #define VERSION_MINOR 8 -#define VERSION_PATCH 910012 +#define VERSION_PATCH 950001 //------------------------------------- typedef struct { diff --git a/src/eth/ahoyeth.cpp b/src/eth/ahoyeth.cpp index 2226fce6..83f0aef0 100644 --- a/src/eth/ahoyeth.cpp +++ b/src/eth/ahoyeth.cpp @@ -25,16 +25,13 @@ void ahoyeth::setup(settings_t *config, uint32_t *utcTimestamp, OnNetworkCB onNe mUtcTimestamp = utcTimestamp; mOnNetworkCB = onNetworkCB; mOnTimeCB = onTimeCB; + mEthConnected = false; Serial.flush(); WiFi.onEvent([this](WiFiEvent_t event, arduino_event_info_t info) -> void { this->onEthernetEvent(event, info); }); Serial.flush(); - #if defined(CONFIG_IDF_TARGET_ESP32S3) mEthSpi.begin(DEF_ETH_MISO_PIN, DEF_ETH_MOSI_PIN, DEF_ETH_SCK_PIN, DEF_ETH_CS_PIN, DEF_ETH_IRQ_PIN, DEF_ETH_RST_PIN); - #else - ETH.begin(DEF_ETH_MISO_PIN, DEF_ETH_MOSI_PIN, DEF_ETH_SCK_PIN, DEF_ETH_CS_PIN, DEF_ETH_IRQ_PIN, ETH_SPI_CLOCK_MHZ, ETH_SPI_HOST); - #endif if(mConfig->sys.ip.ip[0] != 0) { IPAddress ip(mConfig->sys.ip.ip); @@ -50,11 +47,6 @@ void ahoyeth::setup(settings_t *config, uint32_t *utcTimestamp, OnNetworkCB onNe //----------------------------------------------------------------------------- bool ahoyeth::updateNtpTime(void) { - DPRINTLN(DBG_DEBUG, F(__FUNCTION__)); Serial.flush(); - Serial.printf("ETH.linkUp()=%s\n", ETH.linkUp() ? "up" : "down"); - Serial.print("ETH.localIP()="); - Serial.println(ETH.localIP()); - Serial.printf("Go on? %s\n", (!ETH.localIP()) ? "No..." : "Yes..."); if (!ETH.localIP()) return false; @@ -130,131 +122,57 @@ void ahoyeth::welcome(String ip, String mode) { } void ahoyeth::onEthernetEvent(WiFiEvent_t event, arduino_event_info_t info) { - AWS_LOG(F("[ETH]: Got event...")); + DPRINTLN(DBG_VERBOSE, F("[ETH]: Got event...")); switch (event) { -#if ( ( defined(ESP_ARDUINO_VERSION_MAJOR) && (ESP_ARDUINO_VERSION_MAJOR >= 2) ) && ( ARDUINO_ESP32_GIT_VER != 0x46d5afb1 ) ) - // For breaking core v2.0.0 - // Why so strange to define a breaking enum arduino_event_id_t in WiFiGeneric.h - // compared to the old system_event_id_t, now in tools/sdk/esp32/include/esp_event/include/esp_event_legacy.h - // You can preserve the old enum order and just adding new items to do no harm - case ARDUINO_EVENT_ETH_START: - AWS_LOG(F("\nETH Started")); - //set eth hostname here - if(String(mConfig->sys.deviceName) != "") - ETH.setHostname(mConfig->sys.deviceName); - else - ETH.setHostname("ESP32_W5500"); - break; - - case ARDUINO_EVENT_ETH_CONNECTED: - AWS_LOG(F("ETH Connected")); - break; - - case ARDUINO_EVENT_ETH_GOT_IP: - if (!ESP32_W5500_eth_connected) { - #if defined (CONFIG_IDF_TARGET_ESP32S3) - AWS_LOG3(F("ETH MAC: "), mEthSpi.macAddress(), F(", IPv4: "), ETH.localIP()); - #else - AWS_LOG3(F("ETH MAC: "), ETH.macAddress(), F(", IPv4: "), ETH.localIP()); - #endif - - if (ETH.fullDuplex()) { - AWS_LOG0(F("FULL_DUPLEX, ")); - } else { - AWS_LOG0(F("HALF_DUPLEX, ")); + case ARDUINO_EVENT_ETH_START: + DPRINTLN(DBG_VERBOSE, F("ETH Started")); + + if(String(mConfig->sys.deviceName) != "") + ETH.setHostname(mConfig->sys.deviceName); + else + ETH.setHostname(F("ESP32_W5500")); + break; + + case ARDUINO_EVENT_ETH_CONNECTED: + DPRINTLN(DBG_VERBOSE, F("ETH Connected")); + break; + + case ARDUINO_EVENT_ETH_GOT_IP: + if (!mEthConnected) { + /*DPRINT(DBG_INFO, F("ETH MAC: ")); + DBGPRINT(mEthSpi.macAddress());*/ + welcome(ETH.localIP().toString(), F(" (Station)")); + + mEthConnected = true; + mOnNetworkCB(true); } - AWS_LOG1(ETH.linkSpeed(), F("Mbps")); - - ESP32_W5500_eth_connected = true; - mOnNetworkCB(true); - } - if (!MDNS.begin(mConfig->sys.deviceName)) { - DPRINTLN(DBG_ERROR, F("Error setting up MDNS responder!")); - } else { - DBGPRINT(F("[WiFi] mDNS established: ")); - DBGPRINT(mConfig->sys.deviceName); - DBGPRINTLN(F(".local")); - } - break; - - case ARDUINO_EVENT_ETH_DISCONNECTED: - AWS_LOG("ETH Disconnected"); - ESP32_W5500_eth_connected = false; - mUdp.close(); - mOnNetworkCB(false); - break; - - case ARDUINO_EVENT_ETH_STOP: - AWS_LOG("\nETH Stopped"); - ESP32_W5500_eth_connected = false; - mUdp.close(); - mOnNetworkCB(false); - break; - -#else - - // For old core v1.0.6- - // Core v2.0.0 defines a stupid enum arduino_event_id_t, breaking any code for ESP32_W5500 written for previous core - // Why so strange to define a breaking enum arduino_event_id_t in WiFiGeneric.h - // compared to the old system_event_id_t, now in tools/sdk/esp32/include/esp_event/include/esp_event_legacy.h - // You can preserve the old enum order and just adding new items to do no harm - case SYSTEM_EVENT_ETH_START: - AWS_LOG(F("\nETH Started")); - //set eth hostname here - if(String(mConfig->sys.deviceName) != "") - ETH.setHostname(mConfig->sys.deviceName); - else - ETH.setHostname("ESP32_W5500"); - break; - - case SYSTEM_EVENT_ETH_CONNECTED: - AWS_LOG(F("ETH Connected")); - break; - - case SYSTEM_EVENT_ETH_GOT_IP: - if (!ESP32_W5500_eth_connected) { - AWS_LOG3(F("ETH MAC: "), ETH.macAddress(), F(", IPv4: "), ETH.localIP()); - - if (ETH.fullDuplex()) { - AWS_LOG0(F("FULL_DUPLEX, ")); + if (!MDNS.begin(mConfig->sys.deviceName)) { + DPRINTLN(DBG_ERROR, F("Error setting up MDNS responder!")); } else { - AWS_LOG0(F("HALF_DUPLEX, ")); + DBGPRINT(F("mDNS established: ")); + DBGPRINT(mConfig->sys.deviceName); + DBGPRINTLN(F(".local")); } - - AWS_LOG1(ETH.linkSpeed(), F("Mbps")); - - ESP32_W5500_eth_connected = true; - mOnNetworkCB(true); - } - if (!MDNS.begin(mConfig->sys.deviceName)) { - DPRINTLN(DBG_ERROR, F("Error setting up MDNS responder!")); - } else { - DBGPRINT(F("[WiFi] mDNS established: ")); - DBGPRINT(mConfig->sys.deviceName); - DBGPRINTLN(F(".local")); - } - break; - - case SYSTEM_EVENT_ETH_DISCONNECTED: - AWS_LOG("ETH Disconnected"); - ESP32_W5500_eth_connected = false; - mUdp.close(); - mOnNetworkCB(false); - break; - - case SYSTEM_EVENT_ETH_STOP: - AWS_LOG("\nETH Stopped"); - ESP32_W5500_eth_connected = false; - mUdp.close(); - mOnNetworkCB(false); - break; -#endif - - default: - - break; - } + break; + + case ARDUINO_EVENT_ETH_DISCONNECTED: + DPRINTLN(DBG_INFO, F("ETH Disconnected")); + mEthConnected = false; + mUdp.close(); + mOnNetworkCB(false); + break; + + case ARDUINO_EVENT_ETH_STOP: + DPRINTLN(DBG_INFO, F("ETH Stopped")); + mEthConnected = false; + mUdp.close(); + mOnNetworkCB(false); + break; + + default: + break; + } } diff --git a/src/eth/ahoyeth.h b/src/eth/ahoyeth.h index ebd91c67..557db5ec 100644 --- a/src/eth/ahoyeth.h +++ b/src/eth/ahoyeth.h @@ -9,17 +9,17 @@ #include +#include "../utils/dbg.h" #include #include #include #include "ethSpi.h" +#include #include "../utils/dbg.h" #include "../config/config.h" #include "../config/settings.h" -#include "AsyncWebServer_ESP32_W5500.h" - class app; @@ -46,9 +46,9 @@ class ahoyeth { void onEthernetEvent(WiFiEvent_t event, arduino_event_info_t info); private: - #if defined(CONFIG_IDF_TARGET_ESP32S3) + //#if defined(CONFIG_IDF_TARGET_ESP32S3) EthSpi mEthSpi; - #endif + //#endif settings_t *mConfig = nullptr; uint32_t *mUtcTimestamp; @@ -57,6 +57,7 @@ class ahoyeth { OnNetworkCB mOnNetworkCB; OnTimeCB mOnTimeCB; + bool mEthConnected; }; diff --git a/src/eth/ethSpi.h b/src/eth/ethSpi.h index d0ef9487..1339c8ec 100644 --- a/src/eth/ethSpi.h +++ b/src/eth/ethSpi.h @@ -1,10 +1,8 @@ //----------------------------------------------------------------------------- -// 2023 Ahoy, https://www.mikrocontroller.net/topic/525778 +// 2024 Ahoy, https://www.mikrocontroller.net/topic/525778 // Creative Commons - http://creativecommons.org/licenses/by-nc-sa/3.0/de/ //----------------------------------------------------------------------------- - -#if defined(CONFIG_IDF_TARGET_ESP32S3) #if defined(ETHERNET) #ifndef __ETH_SPI_H__ #define __ETH_SPI_H__ @@ -138,4 +136,3 @@ class EthSpi { #endif /*__ETH_SPI_H__*/ #endif /*ETHERNET*/ -#endif /*CONFIG_IDF_TARGET_ESP32S3*/ diff --git a/src/hm/hmInverter.h b/src/hm/hmInverter.h index 4252800d..1d7e6620 100644 --- a/src/hm/hmInverter.h +++ b/src/hm/hmInverter.h @@ -335,7 +335,7 @@ class Inverter { // eg. hw version ... } else if (rec->assign == SystemConfigParaAssignment) { DPRINTLN(DBG_DEBUG, "add config"); - if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos){ + if (getPosByChFld(0, FLD_ACT_ACTIVE_PWR_LIMIT, rec) == pos) { actPowerLimit = rec->record[pos]; DPRINT(DBG_DEBUG, F("Inverter actual power limit: ")); DPRINTLN(DBG_DEBUG, String(actPowerLimit, 1)); diff --git a/src/platformio.ini b/src/platformio.ini index 0f54f168..34a40c21 100644 --- a/src/platformio.ini +++ b/src/platformio.ini @@ -25,7 +25,7 @@ extra_scripts = pre:../scripts/reduceGxEPD2.py lib_deps = - https://github.com/yubox-node-org/ESPAsyncWebServer + https://github.com/esphome/ESPAsyncWebServer @ ^3.1.0 https://github.com/nRF24/RF24 @ 1.4.8 paulstoffregen/Time @ ^1.6.1 https://github.com/bertmelis/espMqttClient#v1.6.0 @@ -44,6 +44,29 @@ build_unflags = platform = espressif8266 board = esp12e board_build.f_cpu = 80000000L +build_flags = ${env.build_flags} + -DEMC_MIN_FREE_MEMORY=4096 + -DENABLE_MQTT + ;-Wl,-Map,output.map +monitor_filters = + esp8266_exception_decoder + +[env:esp8266-de] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 80000000L +build_flags = ${env.build_flags} + -DEMC_MIN_FREE_MEMORY=4096 + -DLANG_DE + -DENABLE_MQTT + ;-Wl,-Map,output.map +monitor_filters = + esp8266_exception_decoder + +[env:esp8266-all] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 80000000L build_flags = ${env.build_flags} -DEMC_MIN_FREE_MEMORY=4096 -DENABLE_MQTT @@ -53,7 +76,7 @@ build_flags = ${env.build_flags} monitor_filters = esp8266_exception_decoder -[env:esp8266-de] +[env:esp8266-all-de] platform = espressif8266 board = esp12e board_build.f_cpu = 80000000L @@ -192,47 +215,51 @@ monitor_filters = [env:esp32-wroom32-ethernet] platform = espressif32 board = lolin_d32 -lib_deps = - khoih-prog/AsyncWebServer_ESP32_W5500 - khoih-prog/AsyncUDP_ESP32_W5500 - https://github.com/nRF24/RF24 @ ^1.4.8 - paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.6.0 - bblanchon/ArduinoJson @ ^6.21.3 - https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = ${env.build_flags} - -D ETHERNET + -DETHERNET -DRELEASE -DUSE_HSPI_FOR_EPD -DENABLE_MQTT -DPLUGIN_DISPLAY -DENABLE_HISTORY + -DDEF_ETH_CS_PIN=15 + -DDEF_ETH_SCK_PIN=14 + -DDEF_ETH_MISO_PIN=12 + -DDEF_ETH_MOSI_PIN=13 + -DDEF_ETH_IRQ_PIN=4 + -DDEF_ETH_RST_PIN=2 + -DDEF_NRF_CS_PIN=5 + -DDEF_NRF_CE_PIN=17 + -DDEF_NRF_IRQ_PIN=16 + -DDEF_NRF_MISO_PIN=19 + -DDEF_NRF_MOSI_PIN=23 + -DDEF_NRF_SCLK_PIN=18 monitor_filters = esp32_exception_decoder [env:esp32-wroom32-ethernet-de] platform = espressif32 board = lolin_d32 -lib_deps = - khoih-prog/AsyncWebServer_ESP32_W5500 - khoih-prog/AsyncUDP_ESP32_W5500 - https://github.com/nRF24/RF24 @ ^1.4.8 - paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.6.0 - bblanchon/ArduinoJson @ ^6.21.3 - https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 build_flags = ${env.build_flags} - -D ETHERNET + -DETHERNET -DRELEASE -DUSE_HSPI_FOR_EPD -DLANG_DE -DENABLE_MQTT -DPLUGIN_DISPLAY -DENABLE_HISTORY + -DDEF_ETH_CS_PIN=15 + -DDEF_ETH_SCK_PIN=14 + -DDEF_ETH_MISO_PIN=12 + -DDEF_ETH_MOSI_PIN=13 + -DDEF_ETH_IRQ_PIN=4 + -DDEF_ETH_RST_PIN=2 + -DDEF_NRF_CS_PIN=5 + -DDEF_NRF_CE_PIN=17 + -DDEF_NRF_IRQ_PIN=16 + -DDEF_NRF_MISO_PIN=19 + -DDEF_NRF_MOSI_PIN=23 + -DDEF_NRF_SCLK_PIN=18 monitor_filters = esp32_exception_decoder @@ -414,16 +441,16 @@ monitor_filters = [env:opendtufusion-ethernet] platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 -lib_deps = - khoih-prog/AsyncWebServer_ESP32_W5500 - khoih-prog/AsyncUDP_ESP32_W5500 - https://github.com/nrf24/RF24 @ ^1.4.8 - paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.6.0 - bblanchon/ArduinoJson @ ^6.21.3 - https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 +#lib_deps = +# khoih-prog/AsyncWebServer_ESP32_W5500 +# khoih-prog/AsyncUDP_ESP32_W5500 +# https://github.com/nrf24/RF24 @ ^1.4.8 +# paulstoffregen/Time @ ^1.6.1 +# https://github.com/bertmelis/espMqttClient#v1.6.0 +# bblanchon/ArduinoJson @ ^6.21.3 +# https://github.com/JChristensen/Timezone @ ^1.2.4 +# olikraus/U8g2 @ ^2.35.9 +# https://github.com/zinggjm/GxEPD2#1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET @@ -460,16 +487,16 @@ monitor_filters = [env:opendtufusion-ethernet-de] platform = espressif32@6.5.0 board = esp32-s3-devkitc-1 -lib_deps = - khoih-prog/AsyncWebServer_ESP32_W5500 - khoih-prog/AsyncUDP_ESP32_W5500 - https://github.com/nrf24/RF24 @ ^1.4.8 - paulstoffregen/Time @ ^1.6.1 - https://github.com/bertmelis/espMqttClient#v1.6.0 - bblanchon/ArduinoJson @ ^6.21.3 - https://github.com/JChristensen/Timezone @ ^1.2.4 - olikraus/U8g2 @ ^2.35.9 - https://github.com/zinggjm/GxEPD2#1.5.3 +#lib_deps = +# khoih-prog/AsyncWebServer_ESP32_W5500 +# khoih-prog/AsyncUDP_ESP32_W5500 +# https://github.com/nrf24/RF24 @ ^1.4.8 +# paulstoffregen/Time @ ^1.6.1 +# https://github.com/bertmelis/espMqttClient#v1.6.0 +# bblanchon/ArduinoJson @ ^6.21.3 +# https://github.com/JChristensen/Timezone @ ^1.2.4 +# olikraus/U8g2 @ ^2.35.9 +# https://github.com/zinggjm/GxEPD2#1.5.3 upload_protocol = esp-builtin build_flags = ${env.build_flags} -DETHERNET diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index b2c88b05..e263d667 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -9,6 +9,7 @@ #include "../../hm/hmSystem.h" #include "../../hm/hmRadio.h" #include "../../utils/helper.h" +#include "../plugin_lang.h" #include "Display_Mono.h" #include "Display_Mono_128X32.h" #include "Display_Mono_128X64.h" diff --git a/src/plugins/Display/Display_Mono_128X32.h b/src/plugins/Display/Display_Mono_128X32.h index 6262e3f6..def87507 100644 --- a/src/plugins/Display/Display_Mono_128X32.h +++ b/src/plugins/Display/Display_Mono_128X32.h @@ -40,20 +40,20 @@ class DisplayMono128X32 : public DisplayMono { printText(mFmtText, 0); } else { - printText("offline", 0); + printText(STR_OFFLINE, 0); } - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s: %4.0f Wh", STR_TODAY, mDisplayData->totalYieldDay); printText(mFmtText, 1); - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "total: %.1f kWh", mDisplayData->totalYieldTotal); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s: %.1f kWh", STR_TOTAL, mDisplayData->totalYieldTotal); printText(mFmtText, 2); IPAddress ip = WiFi.localIP(); if (!(mExtra % 10) && (ip)) printText(ip.toString().c_str(), 3); else if (!(mExtra % 5)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%d Inverter on", mDisplayData->nrProducing); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s: %d", STR_ACTIVE_INVERTERS, mDisplayData->nrProducing); printText(mFmtText, 3); } else if (0 != mDisplayData->utcTs) printText(ah::getTimeStr(mDisplayData->utcTs).c_str(), 3); diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index c63f0b22..c93c5c1a 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -93,7 +93,7 @@ class DisplayMono128X64 : public DisplayMono { // print Date and time if (0 != mDisplayData->utcTs) - printText(ah::getDateTimeStrShort(mDisplayData->utcTs).c_str(), l_Time, 0xff); + printText(ah::getDateTimeStrShort_i18n(mDisplayData->utcTs).c_str(), l_Time, 0xff); if (showLine(l_Status)) { // alternatively: @@ -108,7 +108,7 @@ class DisplayMono128X64 : public DisplayMono { int8_t moon_pos = -1; setLineFont(l_Status); if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, STR_NO_INVERTER); else if (0 == mDisplayData->nrSleeping) { snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); sun_pos = 0; @@ -145,7 +145,7 @@ class DisplayMono128X64 : public DisplayMono { printText(mFmtText, l_TotalPower, 0xff); } else { - printText("offline", l_TotalPower, 0xff); + printText(STR_OFFLINE, l_TotalPower, 0xff); } } diff --git a/src/plugins/Display/Display_Mono_64X48.h b/src/plugins/Display/Display_Mono_64X48.h index 7f98cae5..799d787e 100644 --- a/src/plugins/Display/Display_Mono_64X48.h +++ b/src/plugins/Display/Display_Mono_64X48.h @@ -42,7 +42,7 @@ class DisplayMono64X48 : public DisplayMono { printText(mFmtText, 0); } else { - printText("offline", 0); + printText(STR_OFFLINE, 0); } snprintf(mFmtText, DISP_FMT_TEXT_LEN, "D: %4.0f Wh", mDisplayData->totalYieldDay); @@ -55,7 +55,7 @@ class DisplayMono64X48 : public DisplayMono { if (!(mExtra % 10) && (ip)) printText(ip.toString().c_str(), 3); else if (!(mExtra % 5)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "active Inv: %d", mDisplayData->nrProducing); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s: %d", STR_ACTIVE_INVERTERS, mDisplayData->nrProducing); printText(mFmtText, 3); } else if (0 != mDisplayData->utcTs) printText(ah::getTimeStr(mDisplayData->utcTs).c_str(), 3); diff --git a/src/plugins/Display/Display_Mono_84X48.h b/src/plugins/Display/Display_Mono_84X48.h index b5daacd5..ccbc8083 100644 --- a/src/plugins/Display/Display_Mono_84X48.h +++ b/src/plugins/Display/Display_Mono_84X48.h @@ -78,7 +78,7 @@ class DisplayMono84X48 : public DisplayMono { // print Date and time if (0 != mDisplayData->utcTs) - printText(ah::getDateTimeStrShort(mDisplayData->utcTs).c_str(), l_Time, 0xff); + printText(ah::getDateTimeStrShort_i18n(mDisplayData->utcTs).c_str(), l_Time, 0xff); if (showLine(l_Status)) { // alternatively: @@ -90,7 +90,7 @@ class DisplayMono84X48 : public DisplayMono { // print status of inverters else { if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + snprintf(mFmtText, DISP_FMT_TEXT_LEN, STR_NO_INVERTER); else if (0 == mDisplayData->nrSleeping) snprintf(mFmtText, DISP_FMT_TEXT_LEN, "\x86"); // sun symbol else if (0 == mDisplayData->nrProducing) @@ -110,9 +110,8 @@ class DisplayMono84X48 : public DisplayMono { snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); printText(mFmtText, l_TotalPower, 0xff); - } else { - printText("offline", l_TotalPower, 0xff); - } + } else + printText(STR_OFFLINE, l_TotalPower, 0xff); } if (showLine(l_YieldDay)) { diff --git a/src/plugins/Display/Display_ePaper.cpp b/src/plugins/Display/Display_ePaper.cpp index 087d784b..6d9d929e 100644 --- a/src/plugins/Display/Display_ePaper.cpp +++ b/src/plugins/Display/Display_ePaper.cpp @@ -8,6 +8,7 @@ #include "../../utils/helper.h" #include "imagedata.h" #include "defines.h" +#include "../plugin_lang.h" #if defined(ESP32) @@ -120,7 +121,7 @@ void DisplayEPaper::headlineIP() { if ((WiFi.isConnected() == true) && (WiFi.localIP() > 0)) { snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "%s", WiFi.localIP().toString().c_str()); } else { - snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "WiFi not connected"); + snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, STR_NO_WIFI); } _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = ((_display->width() - tbw) / 2) - tbx; @@ -162,7 +163,7 @@ void DisplayEPaper::versionFooter() { _display->setPartialWindow(0, _display->height() - mHeadFootPadding, _display->width(), mHeadFootPadding); _display->fillScreen(GxEPD_BLACK); do { - snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "Version: %s", _version); + snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "%s: %s", STR_VERSION, _version); _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = ((_display->width() - tbw) / 2) - tbx; @@ -183,7 +184,7 @@ void DisplayEPaper::offlineFooter() { _display->fillScreen(GxEPD_BLACK); do { if (NULL != mUtcTs) { - snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "offline"); + snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, STR_OFFLINE); _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); uint16_t x = ((_display->width() - tbw) / 2) - tbx; @@ -213,7 +214,7 @@ void DisplayEPaper::actualPowerPaged(float totalPower, float totalYieldDay, floa snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "%.0f W", totalPower); _changed = true; } else - snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, "offline"); + snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, STR_OFFLINE); if ((totalPower == 0) && (mEnPowerSave)) { _display->fillRect(0, mHeadFootPadding, 200, 200, GxEPD_BLACK); @@ -268,7 +269,7 @@ void DisplayEPaper::actualPowerPaged(float totalPower, float totalYieldDay, floa // Inverter online _display->setFont(&FreeSans12pt7b); y = _display->height() - (mHeadFootPadding + 10); - snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, " %d online", isprod); + snprintf(_fmtText, EPAPER_MAX_TEXT_LEN, " %d %s", isprod, STR_ONLINE); _display->getTextBounds(_fmtText, 0, 0, &tbx, &tby, &tbw, &tbh); _display->drawInvertedBitmap(10, y - tbh, myWR, 20, 20, GxEPD_BLACK); x = ((_display->width() - tbw - 20) / 2) - tbx; diff --git a/src/plugins/history.h b/src/plugins/history.h index 5076e295..7d7be57c 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -17,6 +17,7 @@ enum class HistoryStorageType : uint8_t { POWER, + POWER_DAY, YIELD }; @@ -27,12 +28,14 @@ class HistoryData { uint16_t refreshCycle = 0; uint16_t loopCnt = 0; uint16_t listIdx = 0; // index for next Element to write into WattArr - uint16_t dispIdx = 0; // index for 1st Element to display from WattArr - bool wrapped = false; // ring buffer for watt history std::array data; - storage_t() { data.fill(0); } + void reset() { + loopCnt = 0; + listIdx = 0; + data.fill(0); + } }; public: @@ -43,33 +46,56 @@ class HistoryData { mTs = ts; mCurPwr.refreshCycle = mConfig->inst.sendInterval; - //mYieldDay.refreshCycle = 60; + mCurPwrDay.refreshCycle = mConfig->inst.sendInterval; + #if defined(ENABLE_HISTORY_YIELD_PER_DAY) + mYieldDay.refreshCycle = 60; + #endif + mLastValueTs = 0; + mPgPeriod=0; + mMaximumDay = 0; } void tickerSecond() { - ; float curPwr = 0; - float maxPwr = 0; + //float maxPwr = 0; float yldDay = -0.1; + uint32_t ts = 0; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { Inverter<> *iv = mSys->getInverterByPos(i); record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug); if (iv == NULL) continue; curPwr += iv->getChannelFieldValue(CH0, FLD_PAC, rec); - maxPwr += iv->getChannelFieldValue(CH0, FLD_MP, rec); + //maxPwr += iv->getChannelFieldValue(CH0, FLD_MP, rec); yldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); + if (rec->ts > ts) + ts = rec->ts; } if ((++mCurPwr.loopCnt % mCurPwr.refreshCycle) == 0) { mCurPwr.loopCnt = 0; - if (curPwr > 0) + if (curPwr > 0) { + mLastValueTs = ts; addValue(&mCurPwr, roundf(curPwr)); - if (maxPwr > 0) - mMaximumDay = roundf(maxPwr); + if (curPwr > mMaximumDay) + mMaximumDay = roundf(curPwr); + } + //if (maxPwr > 0) + // mMaximumDay = roundf(maxPwr); + } + + if ((++mCurPwrDay.loopCnt % mCurPwrDay.refreshCycle) == 0) { + mCurPwrDay.loopCnt = 0; + if (curPwr > 0) { + mLastValueTs = ts; + addValueDay(&mCurPwrDay, roundf(curPwr)); + } } - /*if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) { + #if defined(ENABLE_HISTORY_YIELD_PER_DAY) + if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) { + mYieldDay.loopCnt = 0; if (*mTs > mApp->getSunset()) { if ((!mDayStored) && (yldDay > 0)) { addValue(&mYieldDay, roundf(yldDay)); @@ -77,28 +103,172 @@ class HistoryData { } } else if (*mTs > mApp->getSunrise()) mDayStored = false; - }*/ + } + #endif } uint16_t valueAt(HistoryStorageType type, uint16_t i) { - //storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay; - storage_t *s = &mCurPwr; - uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH; - return s->data[idx]; + storage_t *s = nullptr; + uint16_t idx=i; + DPRINTLN(DBG_VERBOSE, F("valueAt ") + String((uint8_t)type) + " i=" + String(i)); + + switch (type) { + default: + [[fallthrough]]; + case HistoryStorageType::POWER: + s = &mCurPwr; + idx = (s->listIdx + i) % HISTORY_DATA_ARR_LENGTH; + break; + case HistoryStorageType::POWER_DAY: + s = &mCurPwrDay; + break; + #if defined(ENABLE_HISTORY_YIELD_PER_DAY) + case HistoryStorageType::YIELD: + s = &mYieldDay; + idx = (s->listIdx + i) % HISTORY_DATA_ARR_LENGTH; + break; + #endif + } + + return (nullptr == s) ? 0 : s->data[idx]; } uint16_t getMaximumDay() { return mMaximumDay; } + uint32_t getLastValueTs(HistoryStorageType type) { + DPRINTLN(DBG_VERBOSE, F("getLastValueTs ") + String((uint8_t)type)); + if (type == HistoryStorageType::POWER_DAY) + return mPgEndTime; + return mLastValueTs; + } + + uint32_t getPeriod(HistoryStorageType type) { + DPRINTLN(DBG_VERBOSE, F("getPeriode ") + String((uint8_t)type)); + switch (type) { + case HistoryStorageType::POWER: + return mCurPwr.refreshCycle; + break; + case HistoryStorageType::POWER_DAY: + return mPgPeriod / HISTORY_DATA_ARR_LENGTH; + break; + case HistoryStorageType::YIELD: + return (60 * 60 * 24); // 1 day + break; + } + return 0; + } + + bool isDataValid(void) { + return ((0 != mPgStartTime) && (0 != mPgEndTime)); + } + + #if defined(ENABLE_HISTORY_LOAD_DATA) + void addValue(HistoryStorageType historyType, uint8_t valueType, uint32_t value) { + if (valueType < 2) { + storage_t *s = NULL; + switch (historyType) { + default: + [[fallthrough]]; + case HistoryStorageType::POWER: + s = &mCurPwr; + break; + case HistoryStorageType::POWER_DAY: + s = &mCurPwrDay; + break; + #if defined(ENABLE_HISTORY_YIELD_PER_DAY) + case HistoryStorageType::YIELD: + s = &mYieldDay; + break; + #endif + } + if (s) { + if (0 == valueType) + addValue(s, value); + else { + if (historyType == HistoryStorageType::POWER) + s->refreshCycle = value; + if (historyType == HistoryStorageType::POWER_DAY) + mPgPeriod = value * HISTORY_DATA_ARR_LENGTH; + } + } + return; + } + if (2 == valueType) { + if (historyType == HistoryStorageType::POWER) + mLastValueTs = value; + if (historyType == HistoryStorageType::POWER_DAY) + mPgEndTime = value; + } + } + #endif + private: void addValue(storage_t *s, uint16_t value) { - if (s->wrapped) // after 1st time array wrap we have to increase the display index - s->dispIdx = (s->listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); s->data[s->listIdx] = value; s->listIdx = (s->listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); - if (s->listIdx == 0) - s->wrapped = true; + } + + void addValueDay(storage_t *s, uint16_t value) { + DPRINTLN(DBG_VERBOSE, F("addValueDay ") + String(value)); + bool storeStartEndTimes = false; + bool store_entry = false; + uint32_t pGraphStartTime = mApp->getSunrise(); + uint32_t pGraphEndTime = mApp->getSunset(); + uint32_t utcTs = mApp->getTimestamp(); + switch (mPgState) { + case PowerGraphState::NO_TIME_SYNC: + if ((pGraphStartTime > 0) + && (pGraphEndTime > 0) // wait until period data is available ... + && (utcTs >= pGraphStartTime) + && (utcTs < pGraphEndTime)) // and current time is in period + { + storeStartEndTimes = true; // period was received -> store + store_entry = true; + mPgState = PowerGraphState::IN_PERIOD; + } + break; + case PowerGraphState::IN_PERIOD: + if (utcTs > mPgEndTime) // check if end of day is reached ... + mPgState = PowerGraphState::WAIT_4_NEW_PERIOD; // then wait for new period setting + else + store_entry = true; + break; + case PowerGraphState::WAIT_4_NEW_PERIOD: + if ((mPgStartTime != pGraphStartTime) || (mPgEndTime != pGraphEndTime)) { // wait until new time period was received ... + storeStartEndTimes = true; // and store it for next period + mPgState = PowerGraphState::WAIT_4_RESTART; + } + break; + case PowerGraphState::WAIT_4_RESTART: + if ((utcTs >= mPgStartTime) && (utcTs < mPgEndTime)) { // wait until current time is in period again ... + mCurPwrDay.reset(); // then reset power graph data + store_entry = true; + mPgState = PowerGraphState::IN_PERIOD; + mCurPwr.reset(); // also reset "last values" graph + mMaximumDay = 0; // and the maximum of the (last) day + } + break; + } + + // store start and end times of current time period and calculate period length + if (storeStartEndTimes) { + mPgStartTime = pGraphStartTime; + mPgEndTime = pGraphEndTime; + mPgPeriod = pGraphEndTime - pGraphStartTime; // time period of power graph in sec for scaling of x-axis + } + + if (store_entry) { + DPRINTLN(DBG_VERBOSE, F("addValueDay store_entry") + String(value)); + if (mPgPeriod) { + uint16_t pgPos = (utcTs - mPgStartTime) * (HISTORY_DATA_ARR_LENGTH - 1) / mPgPeriod; + s->listIdx = std::min(pgPos, (uint16_t)(HISTORY_DATA_ARR_LENGTH - 1)); + } else + s->listIdx = 0; + DPRINTLN(DBG_VERBOSE, F("addValueDay store_entry idx=") + String(s->listIdx)); + s->data[s->listIdx] = std::max(s->data[s->listIdx], value); // update current datapoint to maximum of all seen values + } } private: @@ -109,8 +279,23 @@ class HistoryData { uint32_t *mTs = nullptr; storage_t mCurPwr; + storage_t mCurPwrDay; + #if defined(ENABLE_HISTORY_YIELD_PER_DAY) + storage_t mYieldDay; + #endif bool mDayStored = false; uint16_t mMaximumDay = 0; + uint32_t mLastValueTs = 0; + enum class PowerGraphState { + NO_TIME_SYNC, + IN_PERIOD, + WAIT_4_NEW_PERIOD, + WAIT_4_RESTART + }; + PowerGraphState mPgState = PowerGraphState::NO_TIME_SYNC; + uint32_t mPgStartTime = 0; + uint32_t mPgEndTime = 0; + uint32_t mPgPeriod = 0; // seconds }; #endif /*ENABLE_HISTORY*/ diff --git a/src/plugins/plugin_lang.h b/src/plugins/plugin_lang.h new file mode 100644 index 00000000..8d7a987f --- /dev/null +++ b/src/plugins/plugin_lang.h @@ -0,0 +1,44 @@ +//----------------------------------------------------------------------------- +// 2024 Ahoy, https://ahoydtu.de +// Creative Commons - http://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + +#ifndef __PLUGIN_LANG_H__ +#define __PLUGIN_LANG_H__ + +#ifdef LANG_DE + #define STR_MONTHNAME_3_CHAR_LIST "ErrJanFebMrzAprMaiJunJulAugSepOktNovDez" + #define STR_DAYNAME_3_CHAR_LIST "ErrSonMonDieMitDonFreSam" + #define STR_OFFLINE "aus" + #define STR_ONLINE "aktiv" + #define STR_NO_INVERTER "kein inverter" + #define STR_NO_WIFI "WLAN nicht verbunden" + #define STR_VERSION "Version" + #define STR_ACTIVE_INVERTERS "aktive WR" + #define STR_TODAY "heute" + #define STR_TOTAL "Gesamt" +#elif LANG_FR + #define STR_MONTHNAME_3_CHAR_LIST "ErrJanFevMarAvrMaiJunJulAouSepOctNovDec" + #define STR_DAYNAME_3_CHAR_LIST "ErrDimLunMarMerJeuVenSam" + #define STR_OFFLINE "eteint" + #define STR_ONLINE "online" + #define STR_NO_INVERTER "pas d'onduleur" + #define STR_NO_WIFI "WiFi not connected" + #define STR_VERSION "Version" + #define STR_ACTIVE_INVERTERS "active Inv" + #define STR_TODAY "today" + #define STR_TOTAL "total" +#else + #define STR_MONTHNAME_3_CHAR_LIST "ErrJanFebMarAprMayJunJulAugSepOctNovDec" + #define STR_DAYNAME_3_CHAR_LIST "ErrSunMonTueWedThuFriSat" + #define STR_OFFLINE "offline" + #define STR_ONLINE "online" + #define STR_NO_INVERTER "no inverter" + #define STR_NO_WIFI "WiFi not connected" + #define STR_VERSION "Version" + #define STR_ACTIVE_INVERTERS "active Inv" + #define STR_TODAY "today" + #define STR_TOTAL "total" +#endif + +#endif /*__PLUGIN_LANG_H__*/ diff --git a/src/publisher/pubMqtt.h b/src/publisher/pubMqtt.h index 4b09b649..2dbf67ec 100644 --- a/src/publisher/pubMqtt.h +++ b/src/publisher/pubMqtt.h @@ -16,6 +16,10 @@ #endif #include +#if defined(ETHERNET) +#include "../eth/ahoyeth.h" +#endif + #include "../utils/dbg.h" #include "../config/config.h" #include diff --git a/src/utils/helper.cpp b/src/utils/helper.cpp index 24a4d9ee..edb9b9b9 100644 --- a/src/utils/helper.cpp +++ b/src/utils/helper.cpp @@ -5,6 +5,12 @@ #include "helper.h" #include "dbg.h" +#include "../plugins/plugin_lang.h" + +#define dt_SHORT_STR_LEN_i18n 3 // the length of short strings +static char buffer_i18n[dt_SHORT_STR_LEN_i18n + 1]; // must be big enough for longest string and the terminating null +const char monthShortNames_P[] PROGMEM = STR_MONTHNAME_3_CHAR_LIST; +const char dayShortNames_P[] PROGMEM = STR_DAYNAME_3_CHAR_LIST; namespace ah { void ip2Arr(uint8_t ip[], const char *ipStr) { @@ -28,6 +34,10 @@ namespace ah { snprintf(str, 16, "%d.%d.%d.%d", ip[0], ip[1], ip[2], ip[3]); } + double round1(double value) { + return (int)(value * 10 + 0.5) / 10.0; + } + double round3(double value) { return (int)(value * 1000 + 0.5) / 1000.0; } @@ -82,6 +92,31 @@ namespace ah { return String(str); } + static char* monthShortStr_i18n(uint8_t month) { + for (int i=0; i < dt_SHORT_STR_LEN_i18n; i++) + buffer_i18n[i] = pgm_read_byte(&(monthShortNames_P[i + month * dt_SHORT_STR_LEN_i18n])); + buffer_i18n[dt_SHORT_STR_LEN_i18n] = 0; + return buffer_i18n; + } + + static char* dayShortStr_i18n(uint8_t day) { + for (int i=0; i < dt_SHORT_STR_LEN_i18n; i++) + buffer_i18n[i] = pgm_read_byte(&(dayShortNames_P[i + day * dt_SHORT_STR_LEN_i18n])); + buffer_i18n[dt_SHORT_STR_LEN_i18n] = 0; + return buffer_i18n; + } + + String getDateTimeStrShort_i18n(time_t t) { + char str[20]; + if(0 == t) + sprintf(str, "n/a"); + else { + sprintf(str, "%3s ", dayShortStr_i18n(dayOfWeek(t))); + sprintf(str+4, "%2d.%3s %02d:%02d", day(t), monthShortStr_i18n(month(t)), hour(t), minute(t)); + } + return String(str); + } + uint64_t Serial2u64(const char *val) { char tmp[3]; uint64_t ret = 0ULL; diff --git a/src/utils/helper.h b/src/utils/helper.h index 1dbba3d9..ff1a9aed 100644 --- a/src/utils/helper.h +++ b/src/utils/helper.h @@ -39,9 +39,11 @@ static Timezone gTimezone(CEST, CET); namespace ah { void ip2Arr(uint8_t ip[], const char *ipStr); void ip2Char(uint8_t ip[], char *str); + double round1(double value); double round3(double value); String getDateTimeStr(time_t t); String getDateTimeStrShort(time_t t); + String getDateTimeStrShort_i18n(time_t t); String getDateTimeStrFile(time_t t); String getTimeStr(time_t t); String getTimeStrMs(uint64_t t); diff --git a/src/web/RestApi.h b/src/web/RestApi.h index c6ffb75a..5d841cec 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -17,11 +17,7 @@ #include "../utils/helper.h" #include "lang.h" #include "AsyncJson.h" -#if defined(ETHERNET) -#include "AsyncWebServer_ESP32_W5500.h" -#else #include "ESPAsyncWebServer.h" -#endif #include "plugins/history.h" @@ -49,6 +45,11 @@ class RestApi { mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false); #endif mConfig = config; + #if defined(ENABLE_HISTORY_LOAD_DATA) + mSrv->on("/api/addYDHist", + HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1), + std::bind(&RestApi::onApiPostYDHist,this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); + #endif mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody( std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)); mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1)); @@ -103,6 +104,8 @@ class RestApi { #endif /* !defined(ETHERNET) */ else if(path == "live") getLive(request,root); else if (path == "powerHistory") getPowerHistory(request, root); + else if (path == "powerHistoryDay") getPowerHistoryDay(request, root); + else if (path == "yieldDayHistory") getYieldDayHistory(request, root); else { if(path.substring(0, 12) == "inverter/id/") getInverter(root, request->url().substring(17).toInt()); @@ -137,7 +140,94 @@ class RestApi { #endif } - void onApiPostBody(AsyncWebServerRequest *request, const uint8_t *data, size_t len, size_t index, size_t total) { + #if defined(ENABLE_HISTORY_LOAD_DATA) + // VArt67: For debugging history graph. Loading data into graph + void onApiPostYDHist(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, size_t final) { + uint32_t total = request->contentLength(); + DPRINTLN(DBG_DEBUG, "[onApiPostYieldDHistory ] " + filename + " index:" + index + " len:" + len + " total:" + total + " final:" + final); + + if (0 == index) { + if (NULL != mTmpBuf) + delete[] mTmpBuf; + mTmpBuf = new uint8_t[total + 1]; + mTmpSize = total; + } + if (mTmpSize >= (len + index)) + memcpy(&mTmpBuf[index], data, len); + + if (!final) + return; // not last frame - nothing to do + + mTmpSize = len + index; // correct the total size + mTmpBuf[mTmpSize] = 0; + + #ifndef ESP32 + DynamicJsonDocument json(ESP.getMaxFreeBlockSize() - 512); // need some memory on heap + #else + DynamicJsonDocument json(12000); // does this work? I have no ESP32 :-( + #endif + DeserializationError err = deserializeJson(json, (const char *)mTmpBuf, mTmpSize); + json.shrinkToFit(); + JsonObject obj = json.as(); + + // Debugging + // mTmpBuf[mTmpSize] = 0; + // DPRINTLN(DBG_DEBUG, (const char *)mTmpBuf); + + if (!err && obj) { + // insert data into yieldDayHistory object + HistoryStorageType dataType; + if (obj["maxDay"] > 0) // this is power history data + { + dataType = HistoryStorageType::POWER; + if (obj["refresh"] > 60) + dataType = HistoryStorageType::POWER_DAY; + + } + else + dataType = HistoryStorageType::YIELD; + + size_t cnt = obj[F("value")].size(); + DPRINTLN(DBG_DEBUG, "ArraySize: " + String(cnt)); + + for (uint16_t i = 0; i < cnt; i++) { + uint16_t val = obj[F("value")][i]; + mApp->addValueToHistory((uint8_t)dataType, 0, val); + // DPRINT(DBG_VERBOSE, "value " + String(i) + ": " + String(val) + ", "); + } + uint32_t refresh = obj[F("refresh")]; + mApp->addValueToHistory((uint8_t)dataType, 1, refresh); + if (dataType != HistoryStorageType::YIELD) { + uint32_t ts = obj[F("lastValueTs")]; + mApp->addValueToHistory((uint8_t)dataType, 2, ts); + } + + } else { + switch (err.code()) { + case DeserializationError::Ok: + break; + case DeserializationError::IncompleteInput: + DPRINTLN(DBG_DEBUG, F("Incomplete input")); + break; + case DeserializationError::InvalidInput: + DPRINTLN(DBG_DEBUG, F("Invalid input")); + break; + case DeserializationError::NoMemory: + DPRINTLN(DBG_DEBUG, F("Not enough memory ") + String(json.capacity()) + " bytes"); + break; + default: + DPRINTLN(DBG_DEBUG, F("Deserialization failed")); + break; + } + } + + request->send(204); // Success with no page load + delete[] mTmpBuf; + mTmpBuf = NULL; + } + #endif + + void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, "onApiPostBody"); if(0 == index) { @@ -207,6 +297,8 @@ class RestApi { ep[F("live")] = url + F("live"); #if defined(ENABLE_HISTORY) ep[F("powerHistory")] = url + F("powerHistory"); + ep[F("powerHistoryDay")] = url + F("powerHistoryDay"); + ep[F("yieldDayHistory")] = url + F("yieldDayHistory"); #endif } @@ -258,8 +350,9 @@ class RestApi { void getGeneric(AsyncWebServerRequest *request, JsonObject obj) { mApp->resetLockTimeout(); - + #if !defined(ETHERNET) obj[F("wifi_rssi")] = (WiFi.status() != WL_CONNECTED) ? 0 : WiFi.RSSI(); + #endif obj[F("ts_uptime")] = mApp->getUptime(); obj[F("ts_now")] = mApp->getTimestamp(); obj[F("version")] = String(mApp->getVersion()); @@ -286,12 +379,13 @@ class RestApi { obj[F("ssid")] = mConfig->sys.stationSsid; obj[F("ap_pwd")] = mConfig->sys.apPwd; obj[F("hidd")] = mConfig->sys.isHidden; + obj[F("mac")] = WiFi.macAddress(); + obj[F("wifi_channel")] = WiFi.channel(); #endif /* !defined(ETHERNET) */ obj[F("device_name")] = mConfig->sys.deviceName; obj[F("dark_mode")] = (bool)mConfig->sys.darkMode; obj[F("sched_reboot")] = (bool)mConfig->sys.schedReboot; - obj[F("mac")] = WiFi.macAddress(); obj[F("hostname")] = mConfig->sys.deviceName; obj[F("pwd_set")] = (strlen(mConfig->sys.adminPwd) > 0); obj[F("prot_mask")] = mConfig->sys.protectionMask; @@ -333,9 +427,9 @@ class RestApi { //obj[F("littlefs_total")] = LittleFS.totalBytes(); //obj[F("littlefs_used")] = LittleFS.usedBytes(); - uint8_t max; + /*uint8_t max; mApp->getSchedulerInfo(&max); - obj[F("schMax")] = max; + obj[F("schMax")] = max;*/ } void getHtmlSystem(AsyncWebServerRequest *request, JsonObject obj) { @@ -499,7 +593,7 @@ class RestApi { obj[F("name")] = String(iv->config->name); obj[F("serial")] = String(iv->config->serial.u64, HEX); obj[F("version")] = String(iv->getFwVersion()); - obj[F("power_limit_read")] = iv->actPowerLimit; + obj[F("power_limit_read")] = ah::round1(iv->getChannelFieldValue(CH0, FLD_ACT_ACTIVE_PWR_LIMIT, iv->getRecordStruct(SystemConfigPara))); obj[F("power_limit_ack")] = iv->powerLimitAck; obj[F("max_pwr")] = iv->getMaxPower(); obj[F("ts_last_success")] = rec->ts; @@ -873,7 +967,7 @@ class RestApi { void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); #if defined(ENABLE_HISTORY) - obj[F("refresh")] = mConfig->inst.sendInterval; + obj[F("refresh")] = mApp->getHistoryPeriod((uint8_t)HistoryStorageType::POWER); uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER, fld); @@ -882,9 +976,39 @@ class RestApi { max = value; } obj[F("max")] = max; - obj[F("maxDay")] = mApp->getHistoryMaxDay(); - #else - obj[F("refresh")] = 86400; // 1 day; + obj[F("lastValueTs")] = mApp->getHistoryLastValueTs((uint8_t)HistoryStorageType::POWER); + #endif /*ENABLE_HISTORY*/ + } + + void getPowerHistoryDay(AsyncWebServerRequest *request, JsonObject obj){ + //getGeneric(request, obj.createNestedObject(F("generic"))); + #if defined(ENABLE_HISTORY) + obj[F("refresh")] = mApp->getHistoryPeriod((uint8_t)HistoryStorageType::POWER_DAY); + uint16_t max = 0; + for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { + uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER_DAY, fld); + obj[F("value")][fld] = value; + if (value > max) + max = value; + } + obj[F("max")] = max; + obj[F("lastValueTs")] = mApp->getHistoryLastValueTs((uint8_t)HistoryStorageType::POWER_DAY); + #endif /*ENABLE_HISTORY*/ + } + + + void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { + //getGeneric(request, obj.createNestedObject(F("generic"))); + #if defined(ENABLE_HISTORY) && defined(ENABLE_HISTORY_YIELD_PER_DAY) + obj[F("refresh")] = mApp->getHistoryPeriod((uint8_t)HistoryStorageType::YIELD); + uint16_t max = 0; + for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { + uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::YIELD, fld); + obj[F("value")][fld] = value; + if (value > max) + max = value; + } + obj[F("max")] = max; #endif /*ENABLE_HISTORY*/ } diff --git a/src/web/html/api.js b/src/web/html/api.js index 1b51699c..6c2ccec2 100644 --- a/src/web/html/api.js +++ b/src/web/html/api.js @@ -61,6 +61,23 @@ function ml(tagName, ...args) { return nester(el, args[1]) } +function mlNs(tagName, ...args) { + var el = document.createElementNS("http://www.w3.org/2000/svg", tagName); + if(args[0]) { + for(var name in args[0]) { + if(name.indexOf("on") === 0) { + el.addEventListener(name.substr(2).toLowerCase(), args[0][name], false) + } else { + el.setAttribute(name, args[0][name]); + } + } + } + if (!args[1]) { + return el; + } + return nester(el, args[1]) +} + function nester(el, n) { if (typeof n === "string") { el.innerHTML = n; diff --git a/src/web/html/colorBright.css b/src/web/html/colorBright.css index 2e676029..aedd05d4 100644 --- a/src/web/html/colorBright.css +++ b/src/web/html/colorBright.css @@ -30,4 +30,8 @@ --ch-head-bg: #006ec0; --ts-head: #333; --ts-bg: #555; + + --chart-cont: #fbfbfb; + --chart-bg: #f9f9f9; + --chart-text: #000000; } diff --git a/src/web/html/colorDark.css b/src/web/html/colorDark.css index 40bd4cf3..b5b1a72b 100644 --- a/src/web/html/colorDark.css +++ b/src/web/html/colorDark.css @@ -30,4 +30,8 @@ --ch-head-bg: #236; --ts-head: #333; --ts-bg: #555; + + --chart-cont: #0b0b0b; + --chart-bg: #090909; + --chart-text: #FFFFFF; } diff --git a/src/web/html/grid_info.json b/src/web/html/grid_info.json index 12760ccf..bff1ae80 100644 --- a/src/web/html/grid_info.json +++ b/src/web/html/grid_info.json @@ -14,6 +14,7 @@ {"0x0d04": "NF_EN_50549-1:2019"}, {"0x1000": "ES_RD1699"}, {"0x1200": "EU_EN50438"}, + {"0x1300": "MEX_NOM_220V"}, {"0x2600": "BE_C10_26"}, {"0x2900": "NL_NEN-EN50549-1_2019"}, {"0x2a00": "PL_PN-EN 50549-1:2019"}, @@ -35,6 +36,44 @@ {"0xb0": "Watt Power Factor"} ], "group": [ + { + "0x0000": [ + { + "name": "Nominal Voltage", + "div": 10, + "def": 220, + "unit": "V" + }, + { + "name": "Low Voltage 1", + "div": 10, + "min": 170, + "max": 195.5, + "def": 184, + "unit": "V" + }, + { + "name": "LV1 Maximum Trip Time", + "div": 10, + "def": 0.1, + "unit": "s" + }, + { + "name": "High Voltage 1", + "div": 10, + "min": 253, + "max": 275, + "def": 270, + "unit": "V" + }, + { + "name": "HV1 Maximum Trip Time", + "div": 10, + "def": 0.1, + "unit": "s" + } + ] + }, { "0x0003": [ { diff --git a/src/web/html/history.html b/src/web/html/history.html index 6372ab82..57bff2fc 100644 --- a/src/web/html/history.html +++ b/src/web/html/history.html @@ -13,83 +13,176 @@

{#TOTAL_POWER}

-
-
-

- {#MAX_DAY}: W. {#LAST_VALUE}: W.
- {#MAXIMUM}: W. {#UPDATED} {#SECONDS} -

-
+
+

{#TOTAL_POWER_DAY}

+
+ +

{#TOTAL_YIELD_PER_DAY}

+
+ + +

Insert data into Yield per day history

+
+ Insert data (*.json) i.e. from a saved "/api/yieldDayHistory" call + +
+ + +
+
+
{#HTML_FOOTER} diff --git a/src/web/html/style.css b/src/web/html/style.css index 18f9aeaa..dfe505a4 100644 --- a/src/web/html/style.css +++ b/src/web/html/style.css @@ -33,13 +33,17 @@ textarea { color: var(--fg2); } -svg rect {fill: #00A;} -svg.chart { - background: #f2f2f2; - border: 2px solid gray; - padding: 1px; +svg polyline { + fill-opacity: .5; + stroke-width: 1; +} + +svg text { + font-size: x-small; + fill: var(--chart-text); } + div.chartDivContainer { padding: 1px; margin: 1px; diff --git a/src/web/html/system.html b/src/web/html/system.html index a646e8b8..4b2ea1f7 100644 --- a/src/web/html/system.html +++ b/src/web/html/system.html @@ -21,8 +21,8 @@ } function parseSysInfo(obj) { - const data = ["sdk", "cpu_freq", "chip_revision", - "chip_model", "chip_cores", "esp_type", "mac", "wifi_rssi", "ts_uptime", + const data = ["sdk", "cpu_freq", "chip_revision", "device_name", + "chip_model", "chip_cores", "esp_type", "mac", "wifi_rssi", "wifi_channel", "ts_uptime", "flash_size", "sketch_used", "heap_total", "heap_free", "heap_frag", "max_free_blk", "version", "modules", "env", "core_version", "reboot_reason"]; @@ -49,7 +49,7 @@ case 0: return badge(false, "{#UNKNOWN}", "warning"); break; case 1: return badge(true, "{#TRUE}"); break; default: return badge(false, "{#FALSE}"); break; - } + } } function parseRadio(obj) { diff --git a/src/web/html/visualization.html b/src/web/html/visualization.html index f7d10ffa..c6fafe54 100644 --- a/src/web/html/visualization.html +++ b/src/web/html/visualization.html @@ -118,7 +118,7 @@ if(65535 != obj.power_limit_read) { pwrLimit = obj.power_limit_read + " %"; if(0 != obj.max_pwr) - pwrLimit += ", " + Math.round(obj.max_pwr * obj.power_limit_read / 100) + " W"; + pwrLimit += ", " + (obj.max_pwr * obj.power_limit_read / 100) + " W"; } var maxAcPwr = toIsoDateStr(new Date(obj.ts_max_ac_pwr * 1000)); diff --git a/src/web/lang.json b/src/web/lang.json index c70d8c6c..133f1fd8 100644 --- a/src/web/lang.json +++ b/src/web/lang.json @@ -1753,6 +1753,11 @@ "en": "Total Power", "de": "Gesamtleistung" }, + { + "token": "TOTAL_POWER_DAY", + "en": "Total Power Today", + "de": "Gesamtleistung heute" + }, { "token": "TOTAL_YIELD_PER_DAY", "en": "Total Yield per day", @@ -1760,28 +1765,13 @@ }, { "token": "MAX_DAY", - "en": "maximum day", + "en": "Maximum day", "de": "Tagesmaximum" }, { "token": "LAST_VALUE", - "en": "last value", - "de": "letzter Wert" - }, - { - "token": "MAXIMUM", - "en": "maximum value", - "de": "Maximalwert" - }, - { - "token": "UPDATED", - "en": "Updated every", - "de": "aktualisiert alle" - }, - { - "token": "SECONDS", - "en": "seconds", - "de": "Sekunden" + "en": "Last value", + "de": "Letzter Wert" } ] } diff --git a/src/web/web.h b/src/web/web.h index 9c8d4521..e098b468 100644 --- a/src/web/web.h +++ b/src/web/web.h @@ -16,11 +16,7 @@ #include "../appInterface.h" #include "../hm/hmSystem.h" #include "../utils/helper.h" -#if defined(ETHERNET) -#include "AsyncWebServer_ESP32_W5500.h" -#else /* defined(ETHERNET) */ #include "ESPAsyncWebServer.h" -#endif /* defined(ETHERNET) */ #include "html/h/api_js.h" #include "html/h/colorBright_css.h" #include "html/h/colorDark_css.h" diff --git a/src/wifi/ahoywifi.cpp b/src/wifi/ahoywifi.cpp index 9bcbdadc..95bf0b8a 100644 --- a/src/wifi/ahoywifi.cpp +++ b/src/wifi/ahoywifi.cpp @@ -30,10 +30,11 @@ ahoywifi::ahoywifi() : mApIp(192, 168, 4, 1) {} */ //----------------------------------------------------------------------------- -void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb) { +void ahoywifi::setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb, OnTimeCB onTimeCb) { mConfig = config; mUtcTimestamp = utcTimestamp; mAppWifiCb = cb; + mOnTimeCb = onTimeCb; mGotDisconnect = false; mStaConn = DISCONNECTED; @@ -198,7 +199,7 @@ void ahoywifi::tickWifiLoop() { if (!MDNS.begin(mConfig->sys.deviceName)) { DPRINTLN(DBG_ERROR, F("Error setting up MDNS responder!")); } else { - DBGPRINT(F("[WiFi] mDNS established: ")); + DBGPRINT(F("mDNS established: ")); DBGPRINT(mConfig->sys.deviceName); DBGPRINTLN(F(".local")); } @@ -275,7 +276,7 @@ void ahoywifi::setupStation(void) { //----------------------------------------------------------------------------- -bool ahoywifi::getNtpTime(void) { +bool ahoywifi::updateNtpTime(void) { if(IN_STA_MODE != mStaConn) return false; @@ -302,6 +303,7 @@ bool ahoywifi::getNtpTime(void) { *mUtcTimestamp = secsSince1900 - 2208988800UL; // UTC time DPRINTLN(DBG_INFO, "[NTP]: " + ah::getDateTimeStr(*mUtcTimestamp) + " UTC"); + mOnTimeCb(true); return true; } else delay(10); diff --git a/src/wifi/ahoywifi.h b/src/wifi/ahoywifi.h index d38701aa..78ec6ab1 100644 --- a/src/wifi/ahoywifi.h +++ b/src/wifi/ahoywifi.h @@ -9,6 +9,7 @@ #include "../utils/dbg.h" #include +#include #include #include #include "ESPAsyncWebServer.h" @@ -20,13 +21,14 @@ class app; class ahoywifi { public: typedef std::function appWifiCb; + typedef std::function OnTimeCB; ahoywifi(); - void setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb); + void setup(settings_t *config, uint32_t *utcTimestamp, appWifiCb cb, OnTimeCB onTimeCB); void tickWifiLoop(void); - bool getNtpTime(void); + bool updateNtpTime(void); void scanAvailNetworks(void); bool getAvailNetworks(JsonObject obj); void setStopApAllowedMode(bool allowed) { @@ -73,6 +75,7 @@ class ahoywifi { settings_t *mConfig = nullptr; appWifiCb mAppWifiCb; + OnTimeCB mOnTimeCb; DNSServer mDns; IPAddress mApIp;