mirror of
https://github.com/lumapu/ahoy.git
synced 2025-06-14 16:41:38 +02:00
* Added history charts to web and to Display_Mono_128x64
This commit is contained in:
parent
6fb0535939
commit
2b4de00a89
17 changed files with 694 additions and 100 deletions
10
src/app.cpp
10
src/app.cpp
|
@ -7,7 +7,7 @@
|
||||||
#include "app.h"
|
#include "app.h"
|
||||||
#include "utils/sun.h"
|
#include "utils/sun.h"
|
||||||
|
|
||||||
|
#include "plugins/history.h"
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
app::app() : ah::Scheduler {} {}
|
app::app() : ah::Scheduler {} {}
|
||||||
|
|
||||||
|
@ -91,6 +91,11 @@ void app::setup() {
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
mTotalPowerHistory = new TotalPowerHistory();
|
||||||
|
mTotalPowerHistory->setup(this, &mSys, mConfig);
|
||||||
|
mYieldDayHistory = new YieldDayHistory();
|
||||||
|
mYieldDayHistory->setup(this, &mSys, mConfig);
|
||||||
|
|
||||||
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
|
mPubSerial.setup(mConfig, &mSys, &mTimestamp);
|
||||||
|
|
||||||
#if !defined(ETHERNET)
|
#if !defined(ETHERNET)
|
||||||
|
@ -148,6 +153,9 @@ void app::regularTickers(void) {
|
||||||
#if !defined(ETHERNET)
|
#if !defined(ETHERNET)
|
||||||
//everySec([this]() { mImprov.tickSerial(); }, "impro");
|
//everySec([this]() { mImprov.tickSerial(); }, "impro");
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
everySec(std::bind(&TotalPowerHistory::tickerSecond, mTotalPowerHistory), "totalPowerHistory");
|
||||||
|
everySec(std::bind(&YieldDayHistory::tickerSecond, mYieldDayHistory), "yieldDayHistory");
|
||||||
}
|
}
|
||||||
|
|
||||||
#if defined(ETHERNET)
|
#if defined(ETHERNET)
|
||||||
|
|
|
@ -55,6 +55,7 @@ typedef PubSerial<HmSystemType> PubSerialType;
|
||||||
#include "plugins/Display/Display_data.h"
|
#include "plugins/Display/Display_data.h"
|
||||||
typedef Display<HmSystemType, Radio> DisplayType;
|
typedef Display<HmSystemType, Radio> DisplayType;
|
||||||
#endif
|
#endif
|
||||||
|
#include "plugins/history.h"
|
||||||
|
|
||||||
class app : public IApp, public ah::Scheduler {
|
class app : public IApp, public ah::Scheduler {
|
||||||
public:
|
public:
|
||||||
|
@ -243,6 +244,9 @@ class app : public IApp, public ah::Scheduler {
|
||||||
Scheduler::setTimestamp(newTime);
|
Scheduler::setTimestamp(newTime);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TotalPowerHistory *getTotalPowerHistoryPtr() { return mTotalPowerHistory; };
|
||||||
|
YieldDayHistory *getYieldDayHistoryPtr() { return mYieldDayHistory; };
|
||||||
|
|
||||||
private:
|
private:
|
||||||
#define CHECK_AVAIL true
|
#define CHECK_AVAIL true
|
||||||
#define SKIP_YIELD_DAY true
|
#define SKIP_YIELD_DAY true
|
||||||
|
@ -350,6 +354,8 @@ class app : public IApp, public ah::Scheduler {
|
||||||
DisplayType mDisplay;
|
DisplayType mDisplay;
|
||||||
DisplayData mDispData;
|
DisplayData mDispData;
|
||||||
#endif
|
#endif
|
||||||
|
TotalPowerHistory *mTotalPowerHistory;
|
||||||
|
YieldDayHistory *mYieldDayHistory;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__APP_H__*/
|
#endif /*__APP_H__*/
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
class TotalPowerHistory;
|
||||||
|
class YieldDayHistory;
|
||||||
//#include "hms/hmsRadio.h"
|
//#include "hms/hmsRadio.h"
|
||||||
#if defined(ESP32)
|
#if defined(ESP32)
|
||||||
//typedef CmtRadio<esp32_3wSpi<>> CmtRadioType;
|
//typedef CmtRadio<esp32_3wSpi<>> CmtRadioType;
|
||||||
|
@ -63,6 +65,8 @@ class IApp {
|
||||||
|
|
||||||
virtual bool getProtection(AsyncWebServerRequest *request) = 0;
|
virtual bool getProtection(AsyncWebServerRequest *request) = 0;
|
||||||
|
|
||||||
|
virtual TotalPowerHistory *getTotalPowerHistoryPtr() = 0;
|
||||||
|
virtual YieldDayHistory *getYieldDayHistoryPtr() = 0;
|
||||||
virtual void* getRadioObj(bool nrf) = 0;
|
virtual void* getRadioObj(bool nrf) = 0;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -36,6 +36,9 @@
|
||||||
// CONFIGURATION - COMPILE TIME
|
// CONFIGURATION - COMPILE TIME
|
||||||
//-------------------------------------
|
//-------------------------------------
|
||||||
|
|
||||||
|
// Draw power chart in MONO-Display
|
||||||
|
#define DISPLAY_CHART 1
|
||||||
|
|
||||||
// ethernet
|
// ethernet
|
||||||
|
|
||||||
#if defined(ETHERNET)
|
#if defined(ETHERNET)
|
||||||
|
|
|
@ -41,6 +41,7 @@
|
||||||
#define PROT_MASK_SYSTEM 0x0020
|
#define PROT_MASK_SYSTEM 0x0020
|
||||||
#define PROT_MASK_API 0x0040
|
#define PROT_MASK_API 0x0040
|
||||||
#define PROT_MASK_MQTT 0x0080
|
#define PROT_MASK_MQTT 0x0080
|
||||||
|
#define PROT_MASK_HISTORY 0x0100
|
||||||
|
|
||||||
#define DEF_PROT_INDEX 0x0001
|
#define DEF_PROT_INDEX 0x0001
|
||||||
#define DEF_PROT_LIVE 0x0000
|
#define DEF_PROT_LIVE 0x0000
|
||||||
|
@ -50,7 +51,7 @@
|
||||||
#define DEF_PROT_SYSTEM 0x0020
|
#define DEF_PROT_SYSTEM 0x0020
|
||||||
#define DEF_PROT_API 0x0000
|
#define DEF_PROT_API 0x0000
|
||||||
#define DEF_PROT_MQTT 0x0000
|
#define DEF_PROT_MQTT 0x0000
|
||||||
|
#define DEF_PROT_HISTORY 0x0000
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
uint8_t ip[4]; // ip address
|
uint8_t ip[4]; // ip address
|
||||||
|
@ -373,7 +374,7 @@ class settings {
|
||||||
// erase all settings and reset to default
|
// erase all settings and reset to default
|
||||||
memset(&mCfg, 0, sizeof(settings_t));
|
memset(&mCfg, 0, sizeof(settings_t));
|
||||||
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
||||||
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
|
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY;
|
||||||
mCfg.sys.darkMode = false;
|
mCfg.sys.darkMode = false;
|
||||||
mCfg.sys.schedReboot = false;
|
mCfg.sys.schedReboot = false;
|
||||||
// restore temp settings
|
// restore temp settings
|
||||||
|
@ -546,7 +547,7 @@ class settings {
|
||||||
|
|
||||||
if(mCfg.sys.protectionMask == 0)
|
if(mCfg.sys.protectionMask == 0)
|
||||||
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
mCfg.sys.protectionMask = DEF_PROT_INDEX | DEF_PROT_LIVE | DEF_PROT_SERIAL | DEF_PROT_SETUP
|
||||||
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT;
|
| DEF_PROT_UPDATE | DEF_PROT_SYSTEM | DEF_PROT_API | DEF_PROT_MQTT | DEF_PROT_HISTORY;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,7 @@ class Display {
|
||||||
|
|
||||||
uint8_t nrprod = 0;
|
uint8_t nrprod = 0;
|
||||||
uint8_t nrsleep = 0;
|
uint8_t nrsleep = 0;
|
||||||
|
uint8_t nrAvailable = 0;
|
||||||
int8_t minQAllInv = 4;
|
int8_t minQAllInv = 4;
|
||||||
|
|
||||||
Inverter<> *iv;
|
Inverter<> *iv;
|
||||||
|
@ -114,6 +115,8 @@ class Display {
|
||||||
nrprod++;
|
nrprod++;
|
||||||
else
|
else
|
||||||
nrsleep++;
|
nrsleep++;
|
||||||
|
if (iv->isAvailable())
|
||||||
|
nrAvailable++;
|
||||||
|
|
||||||
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
|
|
||||||
|
@ -141,6 +144,7 @@ class Display {
|
||||||
// prepare display data
|
// prepare display data
|
||||||
mDisplayData.nrProducing = nrprod;
|
mDisplayData.nrProducing = nrprod;
|
||||||
mDisplayData.nrSleeping = nrsleep;
|
mDisplayData.nrSleeping = nrsleep;
|
||||||
|
mDisplayData.nrAvailable = nrAvailable;
|
||||||
mDisplayData.totalPower = totalPower;
|
mDisplayData.totalPower = totalPower;
|
||||||
mDisplayData.totalYieldDay = totalYieldDay;
|
mDisplayData.totalYieldDay = totalYieldDay;
|
||||||
mDisplayData.totalYieldTotal = totalYieldTotal;
|
mDisplayData.totalYieldTotal = totalYieldTotal;
|
||||||
|
@ -165,6 +169,16 @@ class Display {
|
||||||
else
|
else
|
||||||
mDisplayData.utcTs = 0;
|
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();
|
mMono->disp();
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
//-----------------------------------------------------------------------------
|
//-----------------------------------------------------------------------------
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
#include "config/config.h"
|
||||||
#include <U8g2lib.h>
|
#include <U8g2lib.h>
|
||||||
#define DISP_DEFAULT_TIMEOUT 60 // in seconds
|
#define DISP_DEFAULT_TIMEOUT 60 // in seconds
|
||||||
#define DISP_FMT_TEXT_LEN 32
|
#define DISP_FMT_TEXT_LEN 32
|
||||||
|
@ -101,6 +102,13 @@ class DisplayMono {
|
||||||
int8_t mod = (millis() / 10000) % ((range >> 1) << 2);
|
int8_t mod = (millis() / 10000) % ((range >> 1) << 2);
|
||||||
mPixelshift = mScreenSaver == 1 ? ((mod < range) ? mod - (range >> 1) : -(mod - range - (range >> 1) + 1)) : 0;
|
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
|
/* adapted 5x8 Font for low-res displays with symbols
|
||||||
|
|
|
@ -9,6 +9,12 @@
|
||||||
class DisplayMono128X64 : public DisplayMono {
|
class DisplayMono128X64 : public DisplayMono {
|
||||||
public:
|
public:
|
||||||
DisplayMono128X64() : DisplayMono() {
|
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;
|
mExtra = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +47,6 @@ class DisplayMono128X64 : public DisplayMono {
|
||||||
}
|
}
|
||||||
|
|
||||||
void disp(void) {
|
void disp(void) {
|
||||||
uint8_t pos, sun_pos, moon_pos;
|
|
||||||
|
|
||||||
mDisplay->clearBuffer();
|
mDisplay->clearBuffer();
|
||||||
|
|
||||||
|
@ -61,6 +66,37 @@ class DisplayMono128X64 : public DisplayMono {
|
||||||
// calculate current pixelshift for pixelshift screensaver
|
// calculate current pixelshift for pixelshift screensaver
|
||||||
calcPixelShift(pixelShiftRange);
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
}
|
||||||
|
else
|
||||||
|
#endif
|
||||||
|
{
|
||||||
// print total power
|
// print total power
|
||||||
if (mDisplayData->nrProducing > 0) {
|
if (mDisplayData->nrProducing > 0) {
|
||||||
if (mDisplayData->totalPower > 9999.0)
|
if (mDisplayData->totalPower > 9999.0)
|
||||||
|
@ -85,6 +121,7 @@ class DisplayMono128X64 : public DisplayMono {
|
||||||
}
|
}
|
||||||
// print status of inverters
|
// print status of inverters
|
||||||
else {
|
else {
|
||||||
|
uint8_t pos, sun_pos, moon_pos;
|
||||||
sun_pos = -1;
|
sun_pos = -1;
|
||||||
moon_pos = -1;
|
moon_pos = -1;
|
||||||
setLineFont(l_Status);
|
setLineFont(l_Status);
|
||||||
|
@ -159,11 +196,19 @@ class DisplayMono128X64 : public DisplayMono {
|
||||||
sym[0] = mDisplayData->WifiSymbol ? 'B' : 'F'; // Wifi
|
sym[0] = mDisplayData->WifiSymbol ? 'B' : 'F'; // Wifi
|
||||||
mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym);
|
mDisplay->drawStr(mDispWidth - mDisplay->getStrWidth(sym) - xoffs + mPixelshift, mLineYOffsets[l_RSSI], sym);
|
||||||
mDisplay->sendBuffer();
|
mDisplay->sendBuffer();
|
||||||
|
}
|
||||||
|
|
||||||
mDisplay->sendBuffer();
|
mDisplay->sendBuffer();
|
||||||
|
|
||||||
mExtra++;
|
mExtra++;
|
||||||
|
|
||||||
|
#ifdef DISPLAY_CHART
|
||||||
|
static uint32_t switchDisplayTime = mDisplayData->utcTs + 20;
|
||||||
|
if (mDisplayData->utcTs >= switchDisplayTime) {
|
||||||
|
switchDisplayTime = mDisplayData->utcTs + 20;
|
||||||
|
mDrawChart = !mDrawChart;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -226,4 +271,46 @@ class DisplayMono128X64 : public DisplayMono {
|
||||||
dispX += mPixelshift;
|
dispX += mPixelshift;
|
||||||
mDisplay->drawStr(dispX, mLineYOffsets[line], text);
|
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
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,12 +11,14 @@ struct DisplayData {
|
||||||
uint32_t utcTs=0; // indicate absolute timestamp (utc unix time). 0 = time is not synchonized
|
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 nrProducing=0; // indicate number of producing inverters
|
||||||
uint8_t nrSleeping=0; // indicate number of sleeping 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 WifiSymbol = false; // indicate if WiFi is connected
|
||||||
bool RadioSymbol = false; // indicate if radio module is connecting and working
|
bool RadioSymbol = false; // indicate if radio module is connecting and working
|
||||||
bool MQTTSymbol = false; // indicate if MQTT is connected
|
bool MQTTSymbol = false; // indicate if MQTT is connected
|
||||||
int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi
|
int8_t WifiRSSI=SCHAR_MIN; // indicate RSSI value for WiFi
|
||||||
int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio
|
int8_t RadioRSSI=SCHAR_MIN; // indicate RSSI value for radio
|
||||||
IPAddress ipAddress; // indicate ip adress of ahoy
|
IPAddress ipAddress; // indicate ip adress of ahoy
|
||||||
|
bool sunIsShining; // indicate if time is between sunrise and sunset
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif /*__DISPLAY_DATA__*/
|
#endif /*__DISPLAY_DATA__*/
|
||||||
|
|
94
src/plugins/history.cpp
Normal file
94
src/plugins/history.cpp
Normal file
|
@ -0,0 +1,94 @@
|
||||||
|
|
||||||
|
#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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
86
src/plugins/history.h
Normal file
86
src/plugins/history.h
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
#ifndef __HISTORY_DATA_H__
|
||||||
|
#define __HISTORY_DATA_H__
|
||||||
|
|
||||||
|
#include "utils/helper.h"
|
||||||
|
#include "defines.h"
|
||||||
|
#include "hm/hmSystem.h"
|
||||||
|
|
||||||
|
typedef HmSystem<MAX_NUM_INVERTERS> HmSystemType;
|
||||||
|
class IApp;
|
||||||
|
|
||||||
|
#define HISTORY_DATA_ARR_LENGTH 256
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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
|
|
@ -22,6 +22,8 @@
|
||||||
#include "ESPAsyncWebServer.h"
|
#include "ESPAsyncWebServer.h"
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "plugins/history.h"
|
||||||
|
|
||||||
#if defined(F) && defined(ESP32)
|
#if defined(F) && defined(ESP32)
|
||||||
#undef F
|
#undef F
|
||||||
#define F(sl) (sl)
|
#define F(sl) (sl)
|
||||||
|
@ -50,8 +52,10 @@ class RestApi {
|
||||||
mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false);
|
mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false);
|
||||||
#endif
|
#endif
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
mSrv->on("/api", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1)).onBody(
|
mSrv->on("/api/insertYieldDayHistory", HTTP_POST, std::bind(&RestApi::onApiPost, this, std::placeholders::_1),
|
||||||
std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
|
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_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1));
|
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));
|
mSrv->on("/get_setup", HTTP_GET, std::bind(&RestApi::onDwnldSetup, this, std::placeholders::_1));
|
||||||
|
@ -102,6 +106,8 @@ class RestApi {
|
||||||
else if(path == "setup/networks") getNetworks(root);
|
else if(path == "setup/networks") getNetworks(root);
|
||||||
#endif /* !defined(ETHERNET) */
|
#endif /* !defined(ETHERNET) */
|
||||||
else if(path == "live") getLive(request,root);
|
else if(path == "live") getLive(request,root);
|
||||||
|
else if (path == "powerHistory") getPowerHistory(request, root);
|
||||||
|
else if (path == "yieldDayHistory") getYieldDayHistory(request, root);
|
||||||
else {
|
else {
|
||||||
if(path.substring(0, 12) == "inverter/id/")
|
if(path.substring(0, 12) == "inverter/id/")
|
||||||
getInverter(root, request->url().substring(17).toInt());
|
getInverter(root, request->url().substring(17).toInt());
|
||||||
|
@ -136,6 +142,83 @@ class RestApi {
|
||||||
#endif
|
#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<JsonObject>();
|
||||||
|
|
||||||
|
|
||||||
|
// 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) {
|
void onApiPostBody(AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) {
|
||||||
DPRINTLN(DBG_VERBOSE, "onApiPostBody");
|
DPRINTLN(DBG_VERBOSE, "onApiPostBody");
|
||||||
|
|
||||||
|
@ -196,6 +279,8 @@ class RestApi {
|
||||||
ep[F("setup")] = url + F("setup");
|
ep[F("setup")] = url + F("setup");
|
||||||
ep[F("system")] = url + F("system");
|
ep[F("system")] = url + F("system");
|
||||||
ep[F("live")] = url + F("live");
|
ep[F("live")] = url + F("live");
|
||||||
|
ep[F("powerHistory")] = url + F("powerHistory");
|
||||||
|
ep[F("yieldDayHistory")] = url + F("yieldDayHistory");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -769,6 +854,38 @@ class RestApi {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
|
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();
|
||||||
|
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
||||||
|
uint16_t value = p->valueAt(fld);
|
||||||
|
obj[F("value")][fld] = value;
|
||||||
|
if (value > maximum)
|
||||||
|
maximum = value;
|
||||||
|
}
|
||||||
|
obj[F("maximum")] = maximum;
|
||||||
|
obj[F("maximumDay")] = p->getMaximumDay();
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
||||||
|
uint16_t value = p->valueAt(fld);
|
||||||
|
obj[F("value")][fld] = value;
|
||||||
|
if (value > maximum)
|
||||||
|
maximum = value;
|
||||||
|
}
|
||||||
|
obj[F("maximum")] = maximum;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) {
|
bool setCtrl(JsonObject jsonIn, JsonObject jsonOut) {
|
||||||
Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]);
|
Inverter<> *iv = mSys->getInverterByPos(jsonIn[F("id")]);
|
||||||
bool accepted = true;
|
bool accepted = true;
|
||||||
|
|
|
@ -84,7 +84,7 @@ function topnav() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseNav(obj) {
|
function parseNav(obj) {
|
||||||
for(i = 0; i < 11; i++) {
|
for(i = 0; i < 12; i++) {
|
||||||
if(i == 2)
|
if(i == 2)
|
||||||
continue;
|
continue;
|
||||||
var l = document.getElementById("nav"+i);
|
var l = document.getElementById("nav"+i);
|
||||||
|
|
135
src/web/html/history.html
Normal file
135
src/web/html/history.html
Normal file
|
@ -0,0 +1,135 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html>
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<title>History</title>
|
||||||
|
{#HTML_HEADER}
|
||||||
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
{#HTML_NAV}
|
||||||
|
<div id="wrapper">
|
||||||
|
<div id="content">
|
||||||
|
<h3>Total Power history</h3>
|
||||||
|
<div class="chartDivContainer">
|
||||||
|
<div class="chartDiv" id="phHistoryChart"> </div>
|
||||||
|
<p class="center" style="margin:0px;border:0px;">
|
||||||
|
Maximum day: <span id="phMaximumDay"></span> W. Last value: <span id="phActual"></span> W.<br />
|
||||||
|
Maximum graphics: <span id="phMaximum"></span> W. Updated every <span id="phRefresh"></span> seconds </p>
|
||||||
|
</div>
|
||||||
|
<h3>Yield per day history</h3>
|
||||||
|
<div class="chartDivContainer">
|
||||||
|
<div class="chartDiv" id="ydHistoryChart"> </div>
|
||||||
|
<p class="center" style="margin:0px;border:0px;">
|
||||||
|
Maximum value: <span id="ydMaximum"></span> Wh<br />
|
||||||
|
Updated every <span id="ydRefresh"></span> seconds </p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h4 style="margin-bottom:0px;">Insert data into Yield per day history</h4>
|
||||||
|
<fieldset style="padding: 1px;">
|
||||||
|
<legend class="des" style="margin-top: 0px;">Insert data (*.json) i.e. from a saved "/api/yieldDayHistory" call </legend>
|
||||||
|
<form id="form" method="POST" action="/api/insertYieldDayHistory" enctype="multipart/form-data" accept-charset="utf-8">
|
||||||
|
<input type="button" class="btn my-4" style="padding: 3px;margin: 3px;" value="Insert" onclick="submit()">
|
||||||
|
<input type="file" name="insert" style="width: 80%;">
|
||||||
|
</form>
|
||||||
|
</fieldset>
|
||||||
|
<p></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{#HTML_FOOTER}
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
const svgns = "http://www.w3.org/2000/svg";
|
||||||
|
var phExeOnce = true;
|
||||||
|
var ydExeOnce = true;
|
||||||
|
// make a simple rectangle
|
||||||
|
var mRefresh = 60;
|
||||||
|
var phDatapoints = 512;
|
||||||
|
var mMaximum = 0;
|
||||||
|
var mLastValue = 0;
|
||||||
|
var mDataValues = [];
|
||||||
|
const mChartHight = 250;
|
||||||
|
|
||||||
|
function parseHistory(obj, namePrefix, execOnce) {
|
||||||
|
mRefresh = obj["refresh"];
|
||||||
|
phDatapoints = obj["datapoints"];
|
||||||
|
mDataValues = Object.assign({}, obj["value"]);
|
||||||
|
mMaximum = obj["maximum"];
|
||||||
|
// generate svg
|
||||||
|
if (true == execOnce) {
|
||||||
|
let svg = document.createElementNS(svgns, "svg");
|
||||||
|
svg.setAttribute("class", "chart");
|
||||||
|
svg.setAttribute("width", String((phDatapoints+2) * 2));
|
||||||
|
svg.setAttribute("height", String(mChartHight) + "");
|
||||||
|
svg.setAttribute("aria-labelledby", "title desc");
|
||||||
|
svg.setAttribute("role", "img");
|
||||||
|
t = ml("title");
|
||||||
|
t.innerHTML = "History of day";
|
||||||
|
svg.appendChild(t);
|
||||||
|
let g = document.createElementNS(svgns, "g");
|
||||||
|
svg.appendChild(g);
|
||||||
|
for (var i = 0; i < phDatapoints; i++) {
|
||||||
|
val = mDataValues[i];
|
||||||
|
let rect = document.createElementNS(svgns, "rect");
|
||||||
|
rect.setAttribute("id", namePrefix+"Rect" + i);
|
||||||
|
rect.setAttribute("x", String(i * 2) + "");
|
||||||
|
rect.setAttribute("width", String(2) + "");
|
||||||
|
g.appendChild(rect);
|
||||||
|
}
|
||||||
|
document.getElementById(namePrefix+"HistoryChart").appendChild(svg);
|
||||||
|
}
|
||||||
|
// normalize data to chart
|
||||||
|
let divider = mMaximum / mChartHight;
|
||||||
|
if (divider == 0)
|
||||||
|
divider = 1;
|
||||||
|
for (var i = 0; i < phDatapoints; i++) {
|
||||||
|
val = mDataValues[i];
|
||||||
|
if (val>0)
|
||||||
|
mLastValue = val
|
||||||
|
val = val / divider
|
||||||
|
rect = document.getElementById(namePrefix+"Rect" + i);
|
||||||
|
rect.setAttribute("height", val);
|
||||||
|
rect.setAttribute("y", mChartHight - val);
|
||||||
|
}
|
||||||
|
document.getElementById(namePrefix + "Maximum").innerHTML = mMaximum;
|
||||||
|
if (mRefresh < 5)
|
||||||
|
mRefresh = 5;
|
||||||
|
document.getElementById(namePrefix + "Refresh").innerHTML = mRefresh;
|
||||||
|
}
|
||||||
|
function parsePowerHistory(obj){
|
||||||
|
if (null != obj) {
|
||||||
|
parseNav(obj["generic"]);
|
||||||
|
parseHistory(obj,"ph", phExeOnce)
|
||||||
|
let maximumDay = obj["maximumDay"];
|
||||||
|
document.getElementById("phActual").innerHTML = mLastValue;
|
||||||
|
document.getElementById("phMaximumDay").innerHTML = maximumDay;
|
||||||
|
}
|
||||||
|
if (true == phExeOnce) {
|
||||||
|
phExeOnce = false;
|
||||||
|
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000);
|
||||||
|
// one after the other
|
||||||
|
setTimeout(() => {
|
||||||
|
getAjax("/api/yieldDayHistory", parseYieldDayHistory);
|
||||||
|
} , 20);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function parseYieldDayHistory(obj) {
|
||||||
|
if (null != obj) {
|
||||||
|
parseNav(obj["generic"]);
|
||||||
|
parseHistory(obj, "yd", ydExeOnce)
|
||||||
|
}
|
||||||
|
if (true == ydExeOnce) {
|
||||||
|
ydExeOnce = false;
|
||||||
|
window.setInterval("getAjax('/api/yieldDayHistory', parseYieldDayHistory)", mRefresh * 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAjax("/api/powerHistory", parsePowerHistory);
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -7,6 +7,7 @@
|
||||||
</a>
|
</a>
|
||||||
<div id="topnav" class="mobile">
|
<div id="topnav" class="mobile">
|
||||||
<a id="nav3" class="hide" href="/live?v={#VERSION}">Live</a>
|
<a id="nav3" class="hide" href="/live?v={#VERSION}">Live</a>
|
||||||
|
<a id="nav11" class="acitve" href="/history?v={#VERSION}">History</a>
|
||||||
<a id="nav4" class="hide" href="/serial?v={#VERSION}">Webserial</a>
|
<a id="nav4" class="hide" href="/serial?v={#VERSION}">Webserial</a>
|
||||||
<a id="nav5" class="hide" href="/setup?v={#VERSION}">Settings</a>
|
<a id="nav5" class="hide" href="/setup?v={#VERSION}">Settings</a>
|
||||||
<span class="seperator"></span>
|
<span class="seperator"></span>
|
||||||
|
|
|
@ -33,6 +33,26 @@ textarea {
|
||||||
color: var(--fg2);
|
color: var(--fg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
svg rect {fill: #0000AA;}
|
||||||
|
svg.chart {
|
||||||
|
background: #f2f2f2;
|
||||||
|
border: 2px solid gray;
|
||||||
|
padding: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.chartDivContainer {
|
||||||
|
padding: 1px;
|
||||||
|
margin: 1px;
|
||||||
|
}
|
||||||
|
div.chartdivContainer span {
|
||||||
|
color: var(--fg2);
|
||||||
|
}
|
||||||
|
div.chartDiv {
|
||||||
|
padding: 0px;
|
||||||
|
margin: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.topnav {
|
.topnav {
|
||||||
background-color: var(--nav-bg);
|
background-color: var(--nav-bg);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
|
|
@ -36,6 +36,7 @@
|
||||||
#include "html/h/update_html.h"
|
#include "html/h/update_html.h"
|
||||||
#include "html/h/visualization_html.h"
|
#include "html/h/visualization_html.h"
|
||||||
#include "html/h/about_html.h"
|
#include "html/h/about_html.h"
|
||||||
|
#include "html/h/history_html.h"
|
||||||
|
|
||||||
#define WEB_SERIAL_BUF_SIZE 2048
|
#define WEB_SERIAL_BUF_SIZE 2048
|
||||||
|
|
||||||
|
@ -80,6 +81,7 @@ class Web {
|
||||||
mWeb.on("/save", HTTP_POST, std::bind(&Web::showSave, this, std::placeholders::_1));
|
mWeb.on("/save", HTTP_POST, std::bind(&Web::showSave, this, std::placeholders::_1));
|
||||||
|
|
||||||
mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1));
|
mWeb.on("/live", HTTP_ANY, std::bind(&Web::onLive, this, std::placeholders::_1));
|
||||||
|
mWeb.on("/history", HTTP_ANY, std::bind(&Web::onHistory, this, std::placeholders::_1));
|
||||||
|
|
||||||
#ifdef ENABLE_PROMETHEUS_EP
|
#ifdef ENABLE_PROMETHEUS_EP
|
||||||
mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1));
|
mWeb.on("/metrics", HTTP_ANY, std::bind(&Web::showMetrics, this, std::placeholders::_1));
|
||||||
|
@ -249,6 +251,8 @@ class Web {
|
||||||
request->redirect(F("/index"));
|
request->redirect(F("/index"));
|
||||||
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
|
else if ((mConfig->sys.protectionMask & PROT_MASK_LIVE) != PROT_MASK_LIVE)
|
||||||
request->redirect(F("/live"));
|
request->redirect(F("/live"));
|
||||||
|
else if ((mConfig->sys.protectionMask & PROT_MASK_HISTORY) != PROT_MASK_HISTORY)
|
||||||
|
request->redirect(F("/history"));
|
||||||
else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL)
|
else if ((mConfig->sys.protectionMask & PROT_MASK_SERIAL) != PROT_MASK_SERIAL)
|
||||||
request->redirect(F("/serial"));
|
request->redirect(F("/serial"));
|
||||||
else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM)
|
else if ((mConfig->sys.protectionMask & PROT_MASK_SYSTEM) != PROT_MASK_SYSTEM)
|
||||||
|
@ -264,7 +268,7 @@ class Web {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void getPage(AsyncWebServerRequest *request, uint8_t mask, const uint8_t *zippedHtml, uint32_t len) {
|
void getPage(AsyncWebServerRequest *request, uint16_t mask, const uint8_t *zippedHtml, uint32_t len) {
|
||||||
if (CHECK_MASK(mConfig->sys.protectionMask, mask))
|
if (CHECK_MASK(mConfig->sys.protectionMask, mask))
|
||||||
checkProtection(request);
|
checkProtection(request);
|
||||||
|
|
||||||
|
@ -594,6 +598,10 @@ class Web {
|
||||||
getPage(request, PROT_MASK_LIVE, visualization_html, visualization_html_len);
|
getPage(request, PROT_MASK_LIVE, visualization_html, visualization_html_len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onHistory(AsyncWebServerRequest *request) {
|
||||||
|
getPage(request, PROT_MASK_HISTORY, history_html, history_html_len);
|
||||||
|
}
|
||||||
|
|
||||||
void onAbout(AsyncWebServerRequest *request) {
|
void onAbout(AsyncWebServerRequest *request) {
|
||||||
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len);
|
AsyncWebServerResponse *response = request->beginResponse_P(200, F("text/html; charset=UTF-8"), about_html, about_html_len);
|
||||||
response->addHeader(F("Content-Encoding"), "gzip");
|
response->addHeader(F("Content-Encoding"), "gzip");
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue