diff --git a/src/app.cpp b/src/app.cpp index 9ec3d86c..5a31a1d0 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -7,7 +7,7 @@ #include "app.h" #include "utils/sun.h" -#include "plugins/history.h" + //----------------------------------------------------------------------------- app::app() : ah::Scheduler {} {} @@ -91,10 +91,7 @@ void app::setup() { #endif #endif - mTotalPowerHistory = new TotalPowerHistory(); - mTotalPowerHistory->setup(this, &mSys, mConfig); - mYieldDayHistory = new YieldDayHistory(); - mYieldDayHistory->setup(this, &mSys, mConfig); + mHistory.setup(this, &mSys, mConfig, &mTimestamp); mPubSerial.setup(mConfig, &mSys, &mTimestamp); @@ -154,8 +151,7 @@ void app::regularTickers(void) { //everySec([this]() { mImprov.tickSerial(); }, "impro"); #endif - everySec(std::bind(&TotalPowerHistory::tickerSecond, mTotalPowerHistory), "totalPowerHistory"); - everySec(std::bind(&YieldDayHistory::tickerSecond, mYieldDayHistory), "yieldDayHistory"); + everySec(std::bind(&HistoryType::tickerSecond, mHistory), "hist"); } #if defined(ETHERNET) diff --git a/src/app.h b/src/app.h index 029f0f1e..4708ee5a 100644 --- a/src/app.h +++ b/src/app.h @@ -24,6 +24,7 @@ #include "utils/scheduler.h" #include "utils/syslog.h" #include "web/RestApi.h" +#include "plugins/history.h" #include "web/web.h" #include "hm/Communication.h" #if defined(ETHERNET) @@ -35,6 +36,7 @@ #include // position is relevant since version 1.4.7 of this library + // convert degrees and radians for sun calculation #define SIN(x) (sin(radians(x))) #define COS(x) (cos(radians(x))) @@ -42,12 +44,11 @@ #define ACOS(x) (degrees(acos(x))) typedef HmSystem HmSystemType; -#ifdef ESP32 -#endif typedef Web WebType; typedef RestApi RestApiType; typedef PubMqtt PubMqttType; typedef PubSerial PubSerialType; +typedef HistoryData HistoryType; // PLUGINS #if defined(PLUGIN_DISPLAY) @@ -55,7 +56,6 @@ typedef PubSerial PubSerialType; #include "plugins/Display/Display_data.h" typedef Display DisplayType; #endif -#include "plugins/history.h" class app : public IApp, public ah::Scheduler { public: @@ -244,8 +244,13 @@ class app : public IApp, public ah::Scheduler { Scheduler::setTimestamp(newTime); } - TotalPowerHistory *getTotalPowerHistoryPtr() { return mTotalPowerHistory; }; - YieldDayHistory *getYieldDayHistoryPtr() { return mYieldDayHistory; }; + uint16_t getHistoryValue(HistoryType type, uint16_t i) { + return mHistory.valueAt(type, i); + } + + uint16_t getHistoryMaxDay() { + return mHistory.getMaximumDay(); + } private: #define CHECK_AVAIL true @@ -354,8 +359,7 @@ class app : public IApp, public ah::Scheduler { DisplayType mDisplay; DisplayData mDispData; #endif - TotalPowerHistory *mTotalPowerHistory; - YieldDayHistory *mYieldDayHistory; + HistoryType mHistory; }; #endif /*__APP_H__*/ diff --git a/src/appInterface.h b/src/appInterface.h index 89d52d9e..a9c67147 100644 --- a/src/appInterface.h +++ b/src/appInterface.h @@ -8,19 +8,13 @@ #include "defines.h" #include "hm/hmSystem.h" +#include "plugins/history.h" #if defined(ETHERNET) #include "AsyncWebServer_ESP32_W5500.h" #else #include "ESPAsyncWebServer.h" #endif -class TotalPowerHistory; -class YieldDayHistory; -//#include "hms/hmsRadio.h" -#if defined(ESP32) -//typedef CmtRadio> CmtRadioType; -#endif - // abstract interface to App. Make members of App accessible from child class // like web or API without forward declaration class IApp { @@ -65,10 +59,10 @@ class IApp { virtual bool getProtection(AsyncWebServerRequest *request) = 0; - virtual TotalPowerHistory *getTotalPowerHistoryPtr() = 0; - virtual YieldDayHistory *getYieldDayHistoryPtr() = 0; - virtual void* getRadioObj(bool nrf) = 0; + virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0; + virtual uint16_t getHistoryMaxDay() = 0; + virtual void* getRadioObj(bool nrf) = 0; }; #endif /*__IAPP_H__*/ diff --git a/src/config/config.h b/src/config/config.h index 393c78e3..2cb9bcd3 100644 --- a/src/config/config.h +++ b/src/config/config.h @@ -36,9 +36,6 @@ // CONFIGURATION - COMPILE TIME //------------------------------------- -// Draw power chart in MONO-Display -#define DISPLAY_CHART 1 - // ethernet #if defined(ETHERNET) diff --git a/src/config/settings.h b/src/config/settings.h index ec61d733..f4a083f8 100644 --- a/src/config/settings.h +++ b/src/config/settings.h @@ -53,6 +53,7 @@ #define DEF_PROT_MQTT 0x0000 #define DEF_PROT_HISTORY 0x0000 + typedef struct { uint8_t ip[4]; // ip address uint8_t mask[4]; // sub mask diff --git a/src/plugins/Display/Display.h b/src/plugins/Display/Display.h index 2f0a94bf..0cfbd710 100644 --- a/src/plugins/Display/Display.h +++ b/src/plugins/Display/Display.h @@ -99,8 +99,7 @@ class Display { uint8_t nrprod = 0; uint8_t nrsleep = 0; - uint8_t nrAvailable = 0; - int8_t minQAllInv = 4; + int8_t minQAllInv = 4; Inverter<> *iv; record_t<> *rec; @@ -115,8 +114,6 @@ class Display { nrprod++; else nrsleep++; - if (iv->isAvailable()) - nrAvailable++; rec = iv->getRecordStruct(RealTimeRunData_Debug); @@ -144,7 +141,6 @@ class Display { // prepare display data mDisplayData.nrProducing = nrprod; mDisplayData.nrSleeping = nrsleep; - mDisplayData.nrAvailable = nrAvailable; mDisplayData.totalPower = totalPower; mDisplayData.totalYieldDay = totalYieldDay; mDisplayData.totalYieldTotal = totalYieldTotal; @@ -169,17 +165,7 @@ class Display { else mDisplayData.utcTs = 0; - const uint32_t sunriseTime = mApp->getSunrise(); - if (mDisplayData.utcTs == 0) - mDisplayData.sunIsShining = true; // Start with sunshine :-) - else { - mDisplayData.sunIsShining = false; - // new sunrise is calculated after sunset + user-offset - if (utc > sunriseTime) - mDisplayData.sunIsShining = true; - } - - if (mMono) { + if (mMono ) { mMono->disp(); } #if defined(ESP32) && !defined(ETHERNET) diff --git a/src/plugins/Display/Display_Mono.h b/src/plugins/Display/Display_Mono.h index f8c81914..3e998b6d 100644 --- a/src/plugins/Display/Display_Mono.h +++ b/src/plugins/Display/Display_Mono.h @@ -4,7 +4,6 @@ //----------------------------------------------------------------------------- #pragma once -#include "config/config.h" #include #define DISP_DEFAULT_TIMEOUT 60 // in seconds #define DISP_FMT_TEXT_LEN 32 @@ -102,13 +101,6 @@ class DisplayMono { int8_t mod = (millis() / 10000) % ((range >> 1) << 2); mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0; } - -#ifdef DISPLAY_CHART -#define DISP_WATT_ARR_LENGTH 128 // Number of WATT history values - float m_wattArr[DISP_WATT_ARR_LENGTH + 1]; // ring buffer for watt history - uint16_t m_wattListIdx; // index for next Element to write into WattArr - void drawPowerChart(); -#endif }; /* adapted 5x8 Font for low-res displays with symbols diff --git a/src/plugins/Display/Display_Mono_128X64.h b/src/plugins/Display/Display_Mono_128X64.h index 88c72979..afb581dd 100644 --- a/src/plugins/Display/Display_Mono_128X64.h +++ b/src/plugins/Display/Display_Mono_128X64.h @@ -9,12 +9,6 @@ class DisplayMono128X64 : public DisplayMono { public: DisplayMono128X64() : DisplayMono() { -#ifdef DISPLAY_CHART - for (uint16_t i = 0; i < DISP_WATT_ARR_LENGTH; i++) - m_wattArr[i] = 0.0; - m_wattListIdx = 0; - mDrawChart = false; -#endif mExtra = 0; } @@ -47,6 +41,7 @@ class DisplayMono128X64 : public DisplayMono { } void disp(void) { + uint8_t pos, sun_pos, moon_pos; mDisplay->clearBuffer(); @@ -66,149 +61,109 @@ class DisplayMono128X64 : public DisplayMono { // calculate current pixelshift for pixelshift screensaver calcPixelShift(pixelShiftRange); -#ifdef DISPLAY_CHART - static uint32_t dataUpdateTime = mDisplayData->utcTs + 60; // update chart every minute - if (mDisplayData->utcTs >= dataUpdateTime) - { - dataUpdateTime = mDisplayData->utcTs + 60; // next minute - m_wattArr[m_wattListIdx] = mDisplayData->totalPower; - m_wattListIdx = (m_wattListIdx + 1) % (DISP_WATT_ARR_LENGTH); + // print total power + if (mDisplayData->nrProducing > 0) { + if (mDisplayData->totalPower > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); + + printText(mFmtText, l_TotalPower, 0xff); + } else { + printText("offline", l_TotalPower, 0xff); } - if (mDrawChart && mDisplayData->sunIsShining && (mDisplayData->nrAvailable > 0)) - { - // print total power - if (mDisplayData->nrProducing > 0) { - if (mDisplayData->totalPower > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - printText(mFmtText, l_Time, 10); - } else { - printText("offline", l_Time, 0xff); - } - - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "today: %4.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, l_Status, 10); - - drawPowerChart(); + // print Date and time + if (0 != mDisplayData->utcTs) + printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); + // dynamic status bar, alternatively: + // print ip address + if (!(mExtra % 5) && (mDisplayData->ipAddress)) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); + printText(mFmtText, l_Status, 0xff); } - else -#endif - { - // print total power - if (mDisplayData->nrProducing > 0) { - if (mDisplayData->totalPower > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kW", (mDisplayData->totalPower / 1000.0)); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f W", mDisplayData->totalPower); - - printText(mFmtText, l_TotalPower, 0xff); - } else { - printText("offline", l_TotalPower, 0xff); - } - - // print Date and time - if (0 != mDisplayData->utcTs) - printText(ah::getDateTimeStrShort(gTimezone.toLocal(mDisplayData->utcTs)).c_str(), l_Time, 0xff); - - // dynamic status bar, alternatively: - // print ip address - if (!(mExtra % 5) && (mDisplayData->ipAddress)) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%s", (mDisplayData->ipAddress).toString().c_str()); - printText(mFmtText, l_Status, 0xff); - } - // print status of inverters - else { - uint8_t pos, sun_pos, moon_pos; - sun_pos = -1; - moon_pos = -1; - setLineFont(l_Status); - if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); - else if (0 == mDisplayData->nrSleeping) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - sun_pos = 0; + // print status of inverters + else { + sun_pos = -1; + moon_pos = -1; + setLineFont(l_Status); + if (0 == mDisplayData->nrSleeping + mDisplayData->nrProducing) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "no inverter"); + else if (0 == mDisplayData->nrSleeping) { + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + sun_pos = 0; } else if (0 == mDisplayData->nrProducing) { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); - moon_pos = 0; + snprintf(mFmtText, DISP_FMT_TEXT_LEN, " "); + moon_pos = 0; } else { - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); - sun_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText + 2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); - moon_pos = mDisplay->getStrWidth(mFmtText) + 1; - snprintf(mFmtText + 7, DISP_FMT_TEXT_LEN, " "); - } - printText(mFmtText, l_Status, 0xff); - - pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; - mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); - if (sun_pos != -1) - mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol - if (moon_pos != -1) - mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%2d", mDisplayData->nrProducing); + sun_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+2, DISP_FMT_TEXT_LEN, " %2d", mDisplayData->nrSleeping); + moon_pos = mDisplay->getStrWidth(mFmtText) + 1; + snprintf(mFmtText+7, DISP_FMT_TEXT_LEN, " "); } + printText(mFmtText, l_Status, 0xff); - // print yields - mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol - mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol - - if (mDisplayData->totalYieldDay > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); - printText(mFmtText, l_YieldDay, 0xff); - - if (mDisplayData->totalYieldTotal > 9999.0) - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); - else - snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); - printText(mFmtText, l_YieldTotal, 0xff); - - // draw dynamic RSSI bars - int xoffs; - if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver - xoffs = pixelShiftRange / 2; - else - xoffs = 0; - int rssi_bar_height = 9; - for (int i = 0; i < 4; i++) { - int radio_rssi_threshold = -60 - i * 10; - int wifi_rssi_threshold = -60 - i * 10; - if (mDisplayData->RadioRSSI > radio_rssi_threshold) - mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); - if (mDisplayData->WifiRSSI > wifi_rssi_threshold) - mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); - } - // draw dynamic antenna and WiFi symbols - mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); - char sym[] = " "; - sym[0] = mDisplayData->RadioSymbol ? 'A' : 'E'; // NRF - mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); - - if (mDisplayData->MQTTSymbol) - sym[0] = 'J'; // MQTT - else - sym[0] = mDisplayData->WifiSymbol ? 'B' : 'F'; // Wifi - mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); - mDisplay->sendBuffer(); + pos = (mDispWidth - mDisplay->getStrWidth(mFmtText)) / 2; + mDisplay->setFont(u8g2_font_ncenB08_symbols8_ahoy); + if (sun_pos!=-1) + mDisplay->drawStr(pos + sun_pos + mPixelshift, mLineYOffsets[l_Status], "G"); // sun symbol + if (moon_pos!=-1) + mDisplay->drawStr(pos + moon_pos + mPixelshift, mLineYOffsets[l_Status], "H"); // moon symbol } + // print yields + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldDay], "I"); // day symbol + mDisplay->drawStr(16 + mPixelshift, mLineYOffsets[l_YieldTotal], "D"); // total symbol + + if (mDisplayData->totalYieldDay > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f kWh", mDisplayData->totalYieldDay / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f Wh", mDisplayData->totalYieldDay); + printText(mFmtText, l_YieldDay, 0xff); + + if (mDisplayData->totalYieldTotal > 9999.0) + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.2f MWh", mDisplayData->totalYieldTotal / 1000.0); + else + snprintf(mFmtText, DISP_FMT_TEXT_LEN, "%.0f kWh", mDisplayData->totalYieldTotal); + printText(mFmtText, l_YieldTotal, 0xff); + + // draw dynamic RSSI bars + int xoffs; + if (mScreenSaver == 1) // shrink screenwidth for pixelshift screensaver + xoffs = pixelShiftRange/2; + else + xoffs = 0; + int rssi_bar_height = 9; + for (int i = 0; i < 4; i++) { + int radio_rssi_threshold = -60 - i * 10; + int wifi_rssi_threshold = -60 - i * 10; + if (mDisplayData->RadioRSSI > radio_rssi_threshold) + mDisplay->drawBox(xoffs + mPixelshift, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + if (mDisplayData->WifiRSSI > wifi_rssi_threshold) + mDisplay->drawBox(mDispWidth - 4 - xoffs + mPixelshift + i, 8 + (rssi_bar_height + 1) * i, 4 - i, rssi_bar_height); + } + // draw dynamic antenna and WiFi symbols + mDisplay->setFont(u8g2_font_ncenB10_symbols10_ahoy); + char sym[]=" "; + sym[0] = mDisplayData->RadioSymbol?'A':'E'; // NRF + mDisplay->drawStr(xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + + if (mDisplayData->MQTTSymbol) + sym[0] = 'J'; // MQTT + else + sym[0] = mDisplayData->WifiSymbol?'B':'F'; // Wifi + mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym); + mDisplay->sendBuffer(); + + mDisplay->sendBuffer(); - - mExtra++; -#ifdef DISPLAY_CHART - static uint32_t switchDisplayTime = mDisplayData->utcTs + 20; - if (mDisplayData->utcTs >= switchDisplayTime) { - switchDisplayTime = mDisplayData->utcTs + 20; - mDrawChart = !mDrawChart; - } -#endif + mExtra++; } private: @@ -271,46 +226,4 @@ class DisplayMono128X64 : public DisplayMono { dispX += mPixelshift; mDisplay->drawStr(dispX, mLineYOffsets[line], text); } - -#ifdef DISPLAY_CHART - bool mDrawChart ; - - void drawPowerChart() { - const int hight = 40; // chart hight - - // Clear area - // mDisplay->draw_rectangle(0, 63 - hight, DISP_WATT_ARR_LENGTH, 63, OLED::SOLID, OLED::BLACK); - mDisplay->setDrawColor(0); - mDisplay->drawBox(0, 63 - hight, DISP_WATT_ARR_LENGTH, hight); - mDisplay->setDrawColor(1); - - // Get max value for scaling - float maxValue = 0.0; - for (int i = 0; i < DISP_WATT_ARR_LENGTH; i++) { - float fValue = m_wattArr[i]; - if (fValue > maxValue) - maxValue = fValue; - } - // calc divider to fit into chart hight - int divider = round(maxValue / (float)hight); - if (divider < 1) - divider = 1; - - // draw chart bars - // Start display of data right behind last written data - uint16_t idx = m_wattListIdx; - for (uint16_t i = 0; i < DISP_WATT_ARR_LENGTH; i++) { - float fValue = m_wattArr[idx]; - int iValue = roundf(fValue); - iValue /= divider; - if (iValue > hight) - iValue = hight; - // mDisplay->draw_line(i, 63 - iValue, i, 63); - // mDisplay->drawVLine(i, 63 - iValue, iValue); - if (iValue>0) - mDisplay->drawLine(i, 63 - iValue, i, 63); - idx = (idx + 1) % (DISP_WATT_ARR_LENGTH); - } - } -#endif }; diff --git a/src/plugins/Display/Display_data.h b/src/plugins/Display/Display_data.h index 1ccc5f7d..a400377d 100644 --- a/src/plugins/Display/Display_data.h +++ b/src/plugins/Display/Display_data.h @@ -11,14 +11,12 @@ struct DisplayData { uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized uint8_t nrProducing=0; // indicate number of producing inverters uint8_t nrSleeping=0; // indicate number of sleeping inverters - uint8_t nrAvailable=0; // number of available (comunicating) inverters bool WifiSymbol = false; // indicate if WiFi is connected bool RadioSymbol = false; // indicate if radio module is connecting and working bool MQTTSymbol = false; // indicate if MQTT is connected int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio IPAddress ipAddress; // indicate ip adress of ahoy - bool sunIsShining; // indicate if time is between sunrise and sunset }; #endif /*__DISPLAY_DATA__*/ diff --git a/src/plugins/history.cpp b/src/plugins/history.cpp deleted file mode 100644 index 451d3e1e..00000000 --- a/src/plugins/history.cpp +++ /dev/null @@ -1,94 +0,0 @@ - -#include "plugins/history.h" - -#include "appInterface.h" -#include "config/config.h" -#include "utils/dbg.h" - -void TotalPowerHistory::setup(IApp *app, HmSystemType *sys, settings_t *config) { - mApp = app; - mSys = sys; - mConfig = config; - mRefreshCycle = mConfig->inst.sendInterval; - mMaximumDay = 0; - - // Debug - //for (uint16_t i = 0; i < HISTORY_DATA_ARR_LENGTH *1.5; i++) { - // addValue(i); - //} -} - -void TotalPowerHistory::tickerSecond() { - ++mLoopCnt; - if ((mLoopCnt % mRefreshCycle) == 0) { - //DPRINTLN(DBG_DEBUG,F("TotalPowerHistory::tickerSecond > refreshCycle" + String(mRefreshCycle) + "|" + String(mLoopCnt) + "|" + String(mRefreshCycle % mLoopCnt)); - mLoopCnt = 0; - float totalPower = 0; - float totalPowerDay = 0; - Inverter<> *iv; - record_t<> *rec; - for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { - iv = mSys->getInverterByPos(i); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (iv == NULL) - continue; - totalPower += iv->getChannelFieldValue(CH0, FLD_PAC, rec); - totalPowerDay += iv->getChannelFieldValue(CH0, FLD_MP, rec); - } - if (totalPower > 0) { - uint16_t iTotalPower = roundf(totalPower); - DPRINTLN(DBG_DEBUG, F("[TotalPowerHistory]: addValue(iTotalPower)=") + String(iTotalPower)); - addValue(iTotalPower); - } - if (totalPowerDay > 0) { - mMaximumDay = roundf(totalPowerDay); - } - } -} - -void YieldDayHistory::setup(IApp *app, HmSystemType *sys, settings_t *config) { - mApp = app; - mSys = sys; - mConfig = config; - mRefreshCycle = 60; // every minute - mDayStored = false; -}; - -void YieldDayHistory::tickerSecond() { - ++mLoopCnt; - if ((mLoopCnt % mRefreshCycle) == 0) { - mLoopCnt = 0; - // check for sunset. if so store yield of day once - uint32_t sunsetTime = mApp->getSunset(); - uint32_t sunriseTime = mApp->getSunrise(); - uint32_t currentTime = mApp->getTimestamp(); - DPRINTLN(DBG_DEBUG,F("[YieldDayHistory] current | rise | set -> ") + String(currentTime) + " | " + String(sunriseTime) + " | " + String(sunsetTime)); - - if (currentTime > sunsetTime) { - if (!mDayStored) { - DPRINTLN(DBG_DEBUG,F("currentTime > sunsetTime ") + String(currentTime) + " > " + String(sunsetTime)); - float totalYieldDay = -0.1; - Inverter<> *iv; - record_t<> *rec; - for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { - iv = mSys->getInverterByPos(i); - rec = iv->getRecordStruct(RealTimeRunData_Debug); - if (iv == NULL) - continue; - totalYieldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); - } - if (totalYieldDay > 0) { - uint16_t iTotalYieldDay = roundf(totalYieldDay); - DPRINTLN(DBG_DEBUG,F("addValue(iTotalYieldDay)=") + String(iTotalYieldDay)); - addValue(iTotalYieldDay); - mDayStored = true; - } - } - } else { - if (currentTime > sunriseTime) { - DPRINTLN(DBG_DEBUG,F("currentTime > sunriseTime ") + String(currentTime) + " > " + String(sunriseTime)); - mDayStored = false; - } - } - } -} \ No newline at end of file diff --git a/src/plugins/history.h b/src/plugins/history.h index f6485b17..f068a127 100644 --- a/src/plugins/history.h +++ b/src/plugins/history.h @@ -1,86 +1,123 @@ +//----------------------------------------------------------------------------- +// 2024 Ahoy, https://ahoydtu.de +// Creative Commons - https://creativecommons.org/licenses/by-nc-sa/4.0/deed +//----------------------------------------------------------------------------- + #ifndef __HISTORY_DATA_H__ #define __HISTORY_DATA_H__ -#include "utils/helper.h" -#include "defines.h" -#include "hm/hmSystem.h" - -typedef HmSystem HmSystemType; -class IApp; +#include +#include "../appInterface.h" +#include "../hm/hmSystem.h" +#include "../utils/helper.h" #define HISTORY_DATA_ARR_LENGTH 256 +enum class HistoryStorageType : uint8_t { + POWER, + YIELD +}; + +template class HistoryData { - public: - HistoryData() { - for (int i = 0; i < HISTORY_DATA_ARR_LENGTH; i++) - m_dataArr[i] = 0; - m_listIdx = 0; - m_dispIdx = 0; - m_wrapped = false; - }; - void addValue(uint16_t value) - { - if (m_wrapped) // after 1st time array wrap we have to increas the display index - m_dispIdx = (m_listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); - m_dataArr[m_listIdx] = value; - m_listIdx = (m_listIdx + 1) % (HISTORY_DATA_ARR_LENGTH); - if (m_listIdx == 0) - m_wrapped = true; - }; + private: + struct storage_t { + uint16_t refreshCycle; + uint16_t loopCnt; + uint16_t listIdx; // index for next Element to write into WattArr + uint16_t dispIdx; // index for 1st Element to display from WattArr + bool wrapped; + // ring buffer for watt history + std::array data; - uint16_t valueAt(int i){ - uint16_t idx = m_dispIdx + i; - idx = idx % HISTORY_DATA_ARR_LENGTH; - uint16_t value = m_dataArr[idx]; - return value; - }; + void reset() { + loopCnt = 0; + listIdx = 0; + dispIdx = 0; + wrapped = false; + for(uint16_t i = 0; i < (HISTORY_DATA_ARR_LENGTH + 1); i++) { + data[i] = 0; + } + } + }; - private: - uint16_t m_dataArr[HISTORY_DATA_ARR_LENGTH + 1]; // ring buffer for watt history - uint16_t m_listIdx; // index for next Element to write into WattArr - uint16_t m_dispIdx; // index for 1st Element to display from WattArr - bool m_wrapped; + public: + void setup(IApp *app, HMSYSTEM *sys, settings_t *config, uint32_t *ts) { + mApp = app; + mSys = sys; + mConfig = config; + mTs = ts; + + mCurPwr.reset(); + mCurPwr.refreshCycle = mConfig->inst.sendInterval; + mYieldDay.reset(); + mYieldDay.refreshCycle = 60; + } + + void tickerSecond() { + Inverter<> *iv; + record_t<> *rec; + float curPwr = 0; + float maxPwr = 0; + float yldDay = -0.1; + for (uint8_t i = 0; i < mSys->getNumInverters(); i++) { + iv = mSys->getInverterByPos(i); + rec = iv->getRecordStruct(RealTimeRunData_Debug); + if (iv == NULL) + continue; + curPwr += iv->getChannelFieldValue(CH0, FLD_PAC, rec); + maxPwr += iv->getChannelFieldValue(CH0, FLD_MP, rec); + yldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec); + } + + if ((++mCurPwr.loopCnt % mCurPwr.refreshCycle) == 0) { + mCurPwr.loopCnt = 0; + if (curPwr > 0) + addValue(&mCurPwr, roundf(curPwr)); + if (maxPwr > 0) + mMaximumDay = roundf(maxPwr); + } + + if (*mTs > mApp->getSunset()) { + if ((!mDayStored) && (yldDay > 0)) { + addValue(&mYieldDay, roundf(yldDay)); + mDayStored = true; + } + } else if (*mTs > mApp->getSunrise()) + mDayStored = false; + } + + uint16_t valueAt(HistoryStorageType type, uint16_t i) { + settings_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay; + uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH; + return s->data[idx]; + } + + uint16_t getMaximumDay() { + return mMaximumDay; + } + + 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; + } + + private: + IApp *mApp; + HMSYSTEM *mSys; + settings *mSettings; + settings_t *mConfig; + uint32_t *mTs; + + storage_t mCurPwr; + storage_t mYieldDay; + bool mDayStored = false; + uint16_t mMaximumDay = 0; }; -class TotalPowerHistory : public HistoryData { - public: - TotalPowerHistory() : HistoryData() { - mLoopCnt = 0; - }; - - void setup(IApp *app, HmSystemType *sys, settings_t *config); - void tickerSecond(); - uint16_t getMaximumDay() { return mMaximumDay; } - - private: - IApp *mApp; - HmSystemType *mSys; - settings *mSettings; - settings_t *mConfig; - uint16_t mRefreshCycle; - uint16_t mLoopCnt; - - uint16_t mMaximumDay; -}; - -class YieldDayHistory : public HistoryData { - public: - YieldDayHistory() : HistoryData(){ - mLoopCnt = 0; - }; - - void setup(IApp *app, HmSystemType *sys, settings_t *config); - void tickerSecond(); - - private: - IApp *mApp; - HmSystemType *mSys; - settings *mSettings; - settings_t *mConfig; - uint16_t mRefreshCycle; - uint16_t mLoopCnt; - bool mDayStored; -}; - -#endif \ No newline at end of file +#endif diff --git a/src/web/RestApi.h b/src/web/RestApi.h index 45c2670d..254b0ac4 100644 --- a/src/web/RestApi.h +++ b/src/web/RestApi.h @@ -52,10 +52,8 @@ class RestApi { mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false); #endif mConfig = config; - mSrv->on("/api/insertYieldDayHistory", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1), - std::bind(&RestApi::onApiPostYieldDHistory, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, std::placeholders::_6)); - 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_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)); mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1)); @@ -142,83 +140,6 @@ class RestApi { #endif } - void onApiPostYieldDHistory(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 - HistoryData *p; - if (obj["maximumDay"]>0) // this is power history data - p = mApp->getTotalPowerHistoryPtr(); - else - p = mApp->getYieldDayHistoryPtr(); - - 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]; - p->addValue(val); - // DPRINT(DBG_VERBOSE, "value " + String(i) + ": " + String(val) + ", "); - } - } - 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; - } - void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { DPRINTLN(DBG_VERBOSE, "onApiPostBody"); @@ -858,31 +779,29 @@ class RestApi { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = mConfig->inst.sendInterval; obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; - uint16_t maximum = 0; - TotalPowerHistory *p = mApp->getTotalPowerHistoryPtr(); + uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { - uint16_t value = p->valueAt(fld); + uint16_t value = mApp->getHistoryValue(HistoryStorageType::POWER, fld); obj[F("value")][fld] = value; - if (value > maximum) - maximum = value; + if (value > max) + max = value; } - obj[F("maximum")] = maximum; - obj[F("maximumDay")] = p->getMaximumDay(); + obj[F("max")] = max; + obj[F("maxDay")] = mApp->getHistoryMaxDay(); } void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { getGeneric(request, obj.createNestedObject(F("generic"))); obj[F("refresh")] = 86400; // 1 day obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH; - uint16_t maximum = 0; - YieldDayHistory *p = mApp->getYieldDayHistoryPtr(); + uint16_t max = 0; for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) { - uint16_t value = p->valueAt(fld); + uint16_t value = mApp->getHistoryValue(HistoryStorageType::YIELD, fld); obj[F("value")][fld] = value; - if (value > maximum) - maximum = value; - } - obj[F("maximum")] = maximum; + if (value > max) + max = value; + } + obj[F("max")] = max; }