mirror of
https://github.com/lumapu/ahoy.git
synced 2025-04-30 10:46:24 +02:00
Merge pull request #3 from VArt67/development03_merge
History page completed
This commit is contained in:
commit
33081fde91
10 changed files with 750 additions and 99 deletions
23
src/app.h
23
src/app.h
|
@ -314,6 +314,14 @@ class app : public IApp, public ah::Scheduler {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint32_t getHistoryPeriode(uint8_t type) override {
|
||||||
|
#if defined(ENABLE_HISTORY)
|
||||||
|
return mHistory.getPeriode((HistoryStorageType)type);
|
||||||
|
#else
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
uint16_t getHistoryMaxDay() override {
|
uint16_t getHistoryMaxDay() override {
|
||||||
#if defined(ENABLE_HISTORY)
|
#if defined(ENABLE_HISTORY)
|
||||||
return mHistory.getMaximumDay();
|
return mHistory.getMaximumDay();
|
||||||
|
@ -322,6 +330,21 @@ class app : public IApp, public ah::Scheduler {
|
||||||
#endif
|
#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:
|
private:
|
||||||
#define CHECK_AVAIL true
|
#define CHECK_AVAIL true
|
||||||
#define SKIP_YIELD_DAY true
|
#define SKIP_YIELD_DAY true
|
||||||
|
|
|
@ -67,8 +67,12 @@ class IApp {
|
||||||
virtual bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const = 0;
|
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 uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0;
|
||||||
|
virtual uint32_t getHistoryPeriode(uint8_t type) = 0;
|
||||||
virtual uint16_t getHistoryMaxDay() = 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;
|
virtual void* getRadioObj(bool nrf) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
|
|
||||||
enum class HistoryStorageType : uint8_t {
|
enum class HistoryStorageType : uint8_t {
|
||||||
POWER,
|
POWER,
|
||||||
|
POWER_DAY,
|
||||||
YIELD
|
YIELD
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,14 +26,16 @@ class HistoryData {
|
||||||
private:
|
private:
|
||||||
struct storage_t {
|
struct storage_t {
|
||||||
uint16_t refreshCycle = 0;
|
uint16_t refreshCycle = 0;
|
||||||
uint16_t loopCnt = 0;
|
uint16_t loopCnt;
|
||||||
uint16_t listIdx = 0; // index for next Element to write into WattArr
|
uint16_t listIdx; // 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
|
// ring buffer for watt history
|
||||||
std::array<uint16_t, (HISTORY_DATA_ARR_LENGTH + 1)> data;
|
std::array<uint16_t, (HISTORY_DATA_ARR_LENGTH + 1)> data;
|
||||||
|
|
||||||
storage_t() { data.fill(0); }
|
void reset() {
|
||||||
|
loopCnt = 0;
|
||||||
|
listIdx = 0;
|
||||||
|
data.fill(0);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
@ -42,63 +45,225 @@ class HistoryData {
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
mTs = ts;
|
mTs = ts;
|
||||||
|
|
||||||
|
mCurPwr.reset();
|
||||||
mCurPwr.refreshCycle = mConfig->inst.sendInterval;
|
mCurPwr.refreshCycle = mConfig->inst.sendInterval;
|
||||||
//mYieldDay.refreshCycle = 60;
|
mCurPwrDay.reset();
|
||||||
|
mCurPwrDay.refreshCycle = mConfig->inst.sendInterval;
|
||||||
|
mYieldDay.reset();
|
||||||
|
mYieldDay.refreshCycle = 60;
|
||||||
|
mLastValueTs = 0;
|
||||||
|
mPgPeriod=0;
|
||||||
|
mMaximumDay = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void tickerSecond() {
|
void tickerSecond() {
|
||||||
;
|
|
||||||
float curPwr = 0;
|
float curPwr = 0;
|
||||||
float maxPwr = 0;
|
//float maxPwr = 0;
|
||||||
float yldDay = -0.1;
|
float yldDay = -0.1;
|
||||||
|
uint32_t ts = 0;
|
||||||
|
|
||||||
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
|
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
|
||||||
Inverter<> *iv = mSys->getInverterByPos(i);
|
Inverter<> *iv = mSys->getInverterByPos(i);
|
||||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||||
if (iv == NULL)
|
if (iv == NULL)
|
||||||
continue;
|
continue;
|
||||||
curPwr += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
|
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);
|
yldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec);
|
||||||
|
if (rec->ts > ts)
|
||||||
|
ts = rec->ts;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((++mCurPwr.loopCnt % mCurPwr.refreshCycle) == 0) {
|
if ((++mCurPwr.loopCnt % mCurPwr.refreshCycle) == 0) {
|
||||||
mCurPwr.loopCnt = 0;
|
mCurPwr.loopCnt = 0;
|
||||||
if (curPwr > 0)
|
if (curPwr > 0) {
|
||||||
|
mLastValueTs = ts;
|
||||||
addValue(&mCurPwr, roundf(curPwr));
|
addValue(&mCurPwr, roundf(curPwr));
|
||||||
if (maxPwr > 0)
|
if (curPwr > mMaximumDay)
|
||||||
mMaximumDay = roundf(maxPwr);
|
mMaximumDay = roundf(curPwr);
|
||||||
|
}
|
||||||
|
//if (maxPwr > 0)
|
||||||
|
// mMaximumDay = roundf(maxPwr);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) {
|
if ((++mCurPwrDay.loopCnt % mCurPwrDay.refreshCycle) == 0) {
|
||||||
if (*mTs > mApp->getSunset()) {
|
mCurPwrDay.loopCnt = 0;
|
||||||
|
if (curPwr > 0) {
|
||||||
|
mLastValueTs = ts;
|
||||||
|
addValueDay(&mCurPwrDay, roundf(curPwr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) {
|
||||||
|
mYieldDay.loopCnt = 0;
|
||||||
|
if (*mTs > mApp->getSunset())
|
||||||
|
{
|
||||||
if ((!mDayStored) && (yldDay > 0)) {
|
if ((!mDayStored) && (yldDay > 0)) {
|
||||||
addValue(&mYieldDay, roundf(yldDay));
|
addValue(&mYieldDay, roundf(yldDay));
|
||||||
mDayStored = true;
|
mDayStored = true;
|
||||||
}
|
}
|
||||||
} else if (*mTs > mApp->getSunrise())
|
}
|
||||||
|
else if (*mTs > mApp->getSunrise())
|
||||||
mDayStored = false;
|
mDayStored = false;
|
||||||
}*/
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t valueAt(HistoryStorageType type, uint16_t i) {
|
uint16_t valueAt(HistoryStorageType type, uint16_t i) {
|
||||||
//storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay;
|
storage_t *s=NULL;
|
||||||
storage_t *s = &mCurPwr;
|
uint16_t idx=i;
|
||||||
uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH;
|
DPRINTLN(DBG_VERBOSE, F("valueAt ") + String((uint8_t)type) + " i=" + String(i));
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case HistoryStorageType::POWER:
|
||||||
|
s = &mCurPwr;
|
||||||
|
idx = (s->listIdx + i) % HISTORY_DATA_ARR_LENGTH;
|
||||||
|
break;
|
||||||
|
case HistoryStorageType::POWER_DAY:
|
||||||
|
s = &mCurPwrDay;
|
||||||
|
idx = i;
|
||||||
|
break;
|
||||||
|
case HistoryStorageType::YIELD:
|
||||||
|
s = &mYieldDay;
|
||||||
|
idx = (s->listIdx + i) % HISTORY_DATA_ARR_LENGTH;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (s)
|
||||||
return s->data[idx];
|
return s->data[idx];
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t getMaximumDay() {
|
uint16_t getMaximumDay() {
|
||||||
return mMaximumDay;
|
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 getPeriode(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;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(ENABLE_HISTORY_LOAD_DATA)
|
||||||
|
/* For filling data from outside */
|
||||||
|
void addValue(HistoryStorageType historyType, uint8_t valueType, uint32_t value) {
|
||||||
|
if (valueType<2) {
|
||||||
|
storage_t *s=NULL;
|
||||||
|
switch (historyType) {
|
||||||
|
case HistoryStorageType::POWER:
|
||||||
|
s = &mCurPwr;
|
||||||
|
break;
|
||||||
|
case HistoryStorageType::POWER_DAY:
|
||||||
|
s = &mCurPwrDay;
|
||||||
|
break;
|
||||||
|
case HistoryStorageType::YIELD:
|
||||||
|
s = &mYieldDay;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (s)
|
||||||
|
{
|
||||||
|
if (valueType==0)
|
||||||
|
addValue(s, value);
|
||||||
|
if (valueType==1)
|
||||||
|
{
|
||||||
|
if (historyType == HistoryStorageType::POWER)
|
||||||
|
s->refreshCycle = value;
|
||||||
|
if (historyType == HistoryStorageType::POWER_DAY)
|
||||||
|
mPgPeriod = value * HISTORY_DATA_ARR_LENGTH;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (valueType == 2)
|
||||||
|
{
|
||||||
|
if (historyType == HistoryStorageType::POWER)
|
||||||
|
mLastValueTs = value;
|
||||||
|
if (historyType == HistoryStorageType::POWER_DAY)
|
||||||
|
mPgEndTime = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void addValue(storage_t *s, uint16_t value) {
|
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->data[s->listIdx] = value;
|
||||||
s->listIdx = (s->listIdx + 1) % (HISTORY_DATA_ARR_LENGTH);
|
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:
|
private:
|
||||||
|
@ -109,8 +274,21 @@ class HistoryData {
|
||||||
uint32_t *mTs = nullptr;
|
uint32_t *mTs = nullptr;
|
||||||
|
|
||||||
storage_t mCurPwr;
|
storage_t mCurPwr;
|
||||||
|
storage_t mCurPwrDay;
|
||||||
|
storage_t mYieldDay;
|
||||||
bool mDayStored = false;
|
bool mDayStored = false;
|
||||||
uint16_t mMaximumDay = 0;
|
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*/
|
#endif /*ENABLE_HISTORY*/
|
||||||
|
|
|
@ -49,6 +49,12 @@ class RestApi {
|
||||||
mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false);
|
mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false);
|
||||||
#endif
|
#endif
|
||||||
mConfig = config;
|
mConfig = config;
|
||||||
|
#if defined(ENABLE_HISTORY_LOAD_DATA)
|
||||||
|
//Vart67: Debugging history graph (loading data into graph storage
|
||||||
|
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(
|
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));
|
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));
|
||||||
|
@ -103,6 +109,8 @@ class RestApi {
|
||||||
#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 == "powerHistory") getPowerHistory(request, root);
|
||||||
|
else if (path == "powerHistoryDay") getPowerHistoryDay(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());
|
||||||
|
@ -137,7 +145,94 @@ class RestApi {
|
||||||
#endif
|
#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<JsonObject>();
|
||||||
|
|
||||||
|
// 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");
|
DPRINTLN(DBG_VERBOSE, "onApiPostBody");
|
||||||
|
|
||||||
if(0 == index) {
|
if(0 == index) {
|
||||||
|
@ -207,6 +302,8 @@ class RestApi {
|
||||||
ep[F("live")] = url + F("live");
|
ep[F("live")] = url + F("live");
|
||||||
#if defined(ENABLE_HISTORY)
|
#if defined(ENABLE_HISTORY)
|
||||||
ep[F("powerHistory")] = url + F("powerHistory");
|
ep[F("powerHistory")] = url + F("powerHistory");
|
||||||
|
ep[F("powerHistoryDay")] = url + F("powerHistoryDay");
|
||||||
|
ep[F("yieldDayHistory")] = url + F("yieldDayHistory");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -301,6 +398,7 @@ class RestApi {
|
||||||
obj[F("heap_free")] = mHeapFree;
|
obj[F("heap_free")] = mHeapFree;
|
||||||
obj[F("sketch_total")] = ESP.getFreeSketchSpace();
|
obj[F("sketch_total")] = ESP.getFreeSketchSpace();
|
||||||
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
|
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
|
||||||
|
obj[F("wifi_channel")] = WiFi.channel();
|
||||||
getGeneric(request, obj);
|
getGeneric(request, obj);
|
||||||
|
|
||||||
getRadioNrf(obj.createNestedObject(F("radioNrf")));
|
getRadioNrf(obj.createNestedObject(F("radioNrf")));
|
||||||
|
@ -815,7 +913,7 @@ class RestApi {
|
||||||
void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) {
|
void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) {
|
||||||
getGeneric(request, obj.createNestedObject(F("generic")));
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
#if defined(ENABLE_HISTORY)
|
#if defined(ENABLE_HISTORY)
|
||||||
obj[F("refresh")] = mConfig->inst.sendInterval;
|
obj[F("refresh")] = mApp->getHistoryPeriode((uint8_t)HistoryStorageType::POWER);
|
||||||
uint16_t max = 0;
|
uint16_t max = 0;
|
||||||
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
||||||
uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER, fld);
|
uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER, fld);
|
||||||
|
@ -825,8 +923,40 @@ class RestApi {
|
||||||
}
|
}
|
||||||
obj[F("max")] = max;
|
obj[F("max")] = max;
|
||||||
obj[F("maxDay")] = mApp->getHistoryMaxDay();
|
obj[F("maxDay")] = mApp->getHistoryMaxDay();
|
||||||
#else
|
obj[F("lastValueTs")] = mApp->getHistoryLastValueTs((uint8_t)HistoryStorageType::POWER);
|
||||||
obj[F("refresh")] = 86400; // 1 day;
|
#endif /*ENABLE_HISTORY*/
|
||||||
|
}
|
||||||
|
|
||||||
|
void getPowerHistoryDay(AsyncWebServerRequest *request, JsonObject obj){
|
||||||
|
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||||
|
#if defined(ENABLE_HISTORY)
|
||||||
|
obj[F("refresh")] = mApp->getHistoryPeriode((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("maxDay")] = mApp->getHistoryMaxDay();
|
||||||
|
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)
|
||||||
|
obj[F("refresh")] = mApp->getHistoryPeriode((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*/
|
#endif /*ENABLE_HISTORY*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -30,4 +30,8 @@
|
||||||
--ch-head-bg: #006ec0;
|
--ch-head-bg: #006ec0;
|
||||||
--ts-head: #333;
|
--ts-head: #333;
|
||||||
--ts-bg: #555;
|
--ts-bg: #555;
|
||||||
|
|
||||||
|
--chart-cont: #fbfbfb;
|
||||||
|
--chart-bg: #f9f9f9;
|
||||||
|
--chart-text: #000000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,4 +30,8 @@
|
||||||
--ch-head-bg: #236;
|
--ch-head-bg: #236;
|
||||||
--ts-head: #333;
|
--ts-head: #333;
|
||||||
--ts-bg: #555;
|
--ts-bg: #555;
|
||||||
|
|
||||||
|
--chart-cont: #0b0b0b;
|
||||||
|
--chart-bg: #090909;
|
||||||
|
--chart-text: #FFFFFF;
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,81 +13,360 @@
|
||||||
<div id="wrapper">
|
<div id="wrapper">
|
||||||
<div id="content">
|
<div id="content">
|
||||||
<h3>{#TOTAL_POWER}</h3>
|
<h3>{#TOTAL_POWER}</h3>
|
||||||
<div>
|
{#LAST} <span id="pwrNumValues"></span> {#VALUES}
|
||||||
|
<div class="chartDivContainer">
|
||||||
<div class="chartDiv" id="pwrChart"> </div>
|
<div class="chartDiv" id="pwrChart"> </div>
|
||||||
<p>
|
<p>
|
||||||
{#MAX_DAY}: <span id="pwrMaxDay"></span> W. {#LAST_VALUE}: <span id="pwrLast"></span> W.<br />
|
{#LAST_VALUE}: <span id="pwrLast"></span> W.<br />
|
||||||
{#MAXIMUM}: <span id="pwrMax"></span> W. {#UPDATED} <span id="pwrRefresh"></span> {#SECONDS}
|
{#MAXIMUM}: <span id="pwrMax"></span> W.
|
||||||
|
{#UPDATED} <span id="pwrRefresh"></span> {#SECONDS}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
<h3>{#TOTAL_POWER_DAY}</h3>
|
||||||
|
<div class="chartDivContainer">
|
||||||
|
<div class="chartDiv" id="pwrDayChart"> </div>
|
||||||
|
<p>
|
||||||
|
{#MAX_DAY}: <span id="pwrDayMaxDay"></span> W. <br />
|
||||||
|
{#UPDATED} <span id="pwrDayRefresh"></span> {#SECONDS}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
<h3>{#TOTAL_YIELD_PER_DAY}</h3>
|
||||||
|
<div class="chartDivContainer">
|
||||||
|
<div class="chartDiv" id="ydChart"> </div>
|
||||||
|
<p>
|
||||||
|
{#MAXIMUM}: <span id="ydMax"></span> Wh<br />
|
||||||
|
</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/addYDHist" 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>
|
||||||
|
-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{#HTML_FOOTER}
|
{#HTML_FOOTER}
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
const svgns = "http://www.w3.org/2000/svg";
|
var powerHistObj = null;
|
||||||
var pwrExeOnce = true;
|
var powerHistDayObj = null;
|
||||||
var ydExeOnce = true;
|
var ydHistObj = null;
|
||||||
// make a simple rectangle
|
|
||||||
var mRefresh = 60;
|
|
||||||
var mLastValue = 0;
|
|
||||||
const mChartHeight = 250;
|
|
||||||
|
|
||||||
function parseHistory(obj, namePrefix, execOnce) {
|
|
||||||
mRefresh = obj.refresh
|
|
||||||
var data = Object.assign({}, obj.value)
|
|
||||||
numDataPts = Object.keys(data).length
|
|
||||||
|
|
||||||
if (true == execOnce) {
|
|
||||||
let s = document.createElementNS(svgns, "svg");
|
|
||||||
s.setAttribute("class", "chart");
|
|
||||||
s.setAttribute("width", (numDataPts + 2) * 2);
|
|
||||||
s.setAttribute("height", mChartHeight);
|
|
||||||
s.setAttribute("role", "img");
|
|
||||||
|
|
||||||
let g = document.createElementNS(svgns, "g");
|
Number.prototype.pad = function (size) {
|
||||||
s.appendChild(g);
|
var s = String(this);
|
||||||
for (var i = 0; i < numDataPts; i++) {
|
while (s.length < (size || 2)) { s = "0" + s; }
|
||||||
val = data[i];
|
return s;
|
||||||
let rect = document.createElementNS(svgns, "rect");
|
}
|
||||||
rect.setAttribute("id", namePrefix+"Rect" + i);
|
|
||||||
rect.setAttribute("x", i * 2);
|
class powChart {
|
||||||
rect.setAttribute("width", 2);
|
static objcnt = 0; // to give each object elemets a unique name prefix
|
||||||
g.appendChild(rect);
|
|
||||||
}
|
constructor(namePrefix) {
|
||||||
document.getElementById(namePrefix+"Chart").appendChild(s);
|
// configurable vars
|
||||||
}
|
this.mChartHight = 250;
|
||||||
|
this.datapoints = 256;
|
||||||
|
this.xGridDist = 50;
|
||||||
|
this.yGridDist = 100;
|
||||||
|
// info vars
|
||||||
|
this.maxValue = 0;
|
||||||
|
this.mLastValue = 0;
|
||||||
|
// intern vars
|
||||||
|
this.svg = null;
|
||||||
|
this.refreshIntervall = 30; // seconds
|
||||||
|
this.lastValueTs = 0; // Timestmp of last value
|
||||||
|
|
||||||
|
++this.objcnt;
|
||||||
|
if (namePrefix === undefined)
|
||||||
|
this.namePrefix = "powChart" + this.objcnt;
|
||||||
|
else
|
||||||
|
this.namePrefix = namePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
init(numDatapoints) {
|
||||||
|
this.datapoints = numDatapoints;
|
||||||
|
// generate svg
|
||||||
|
const svgns = "http://www.w3.org/2000/svg";
|
||||||
|
this.svg = document.createElementNS(svgns, "svg");
|
||||||
|
this.svg.setAttribute("class", "container");
|
||||||
|
this.svg.setAttribute("id", this.namePrefix + "_svg");
|
||||||
|
this.svg.setAttribute("width", String(this.datapoints * 2 + 50));
|
||||||
|
this.svg.setAttribute("height", String(this.mChartHight + 20));
|
||||||
|
// Gradient Line
|
||||||
|
let defLgLine = document.createElementNS(svgns, "defs");
|
||||||
|
{
|
||||||
|
let lg = document.createElementNS(svgns, "linearGradient")
|
||||||
|
lg.setAttribute("id", "verlVertLine");
|
||||||
|
lg.setAttribute("x1", "0%");
|
||||||
|
lg.setAttribute("y1", "0%");
|
||||||
|
lg.setAttribute("x2", "0%");
|
||||||
|
lg.setAttribute("y2", "100%");
|
||||||
|
let s1 = document.createElementNS(svgns, "stop")
|
||||||
|
s1.setAttribute("offset", "0%");
|
||||||
|
s1.setAttribute("stop-color", "blue");
|
||||||
|
let s2 = document.createElementNS(svgns, "stop")
|
||||||
|
s2.setAttribute("offset", "80%");
|
||||||
|
s2.setAttribute("stop-color", "#5050FF");
|
||||||
|
let s3 = document.createElementNS(svgns, "stop")
|
||||||
|
s3.setAttribute("offset", "100%");
|
||||||
|
s3.setAttribute("stop-color", "gray");
|
||||||
|
lg.appendChild(s1);
|
||||||
|
lg.appendChild(s2);
|
||||||
|
lg.appendChild(s3);
|
||||||
|
defLgLine.appendChild(lg);
|
||||||
|
}
|
||||||
|
this.svg.appendChild(defLgLine);
|
||||||
|
// Gradient Fill
|
||||||
|
let defLg = document.createElementNS(svgns, "defs");
|
||||||
|
{
|
||||||
|
let lg = document.createElementNS(svgns, "linearGradient")
|
||||||
|
lg.setAttribute("id", "verlVertFill");
|
||||||
|
lg.setAttribute("x1", "0%");
|
||||||
|
lg.setAttribute("y1", "0%");
|
||||||
|
lg.setAttribute("x2", "0%");
|
||||||
|
lg.setAttribute("y2", "100%");
|
||||||
|
let s1 = document.createElementNS(svgns, "stop")
|
||||||
|
s1.setAttribute("offset", "0%");
|
||||||
|
s1.setAttribute("stop-color", "#A0A0FF");
|
||||||
|
let s2 = document.createElementNS(svgns, "stop")
|
||||||
|
s2.setAttribute("offset", "50%");
|
||||||
|
s2.setAttribute("stop-color", "#C0C0FF");
|
||||||
|
let s3 = document.createElementNS(svgns, "stop")
|
||||||
|
s3.setAttribute("offset", "100%");
|
||||||
|
s3.setAttribute("stop-color", "#E0E0F0");
|
||||||
|
lg.appendChild(s1);
|
||||||
|
lg.appendChild(s2);
|
||||||
|
lg.appendChild(s3);
|
||||||
|
defLgLine.appendChild(lg);
|
||||||
|
}
|
||||||
|
this.svg.appendChild(defLg);
|
||||||
|
|
||||||
|
let chartFrame = document.createElementNS(svgns, "rect");
|
||||||
|
chartFrame.setAttribute("id", this.namePrefix + "_chartFrame");
|
||||||
|
chartFrame.setAttribute("class", "chartFrame");
|
||||||
|
chartFrame.setAttribute("x", "0");
|
||||||
|
chartFrame.setAttribute("y", "0");
|
||||||
|
chartFrame.setAttribute("width", String(this.datapoints * 2));
|
||||||
|
chartFrame.setAttribute("height", String(this.mChartHight));
|
||||||
|
this.svg.appendChild(chartFrame);
|
||||||
|
|
||||||
|
// Group chart content
|
||||||
|
let chartContent = document.createElementNS(svgns, "g");
|
||||||
|
chartContent.setAttribute("id", this.namePrefix + "_svgChartContent");
|
||||||
|
chartFrame.setAttribute("transform", "translate(29, 5)");
|
||||||
|
chartContent.setAttribute("transform", "translate(30, 5)");
|
||||||
|
|
||||||
|
// Graph values in a polyline
|
||||||
|
let poly = document.createElementNS(svgns, "polyline");
|
||||||
|
poly.setAttribute("id", this.namePrefix + "Poly");
|
||||||
|
poly.setAttribute("stroke", "url(#verlVertLine)");
|
||||||
|
poly.setAttribute("fill", "none");
|
||||||
|
chartContent.appendChild(poly);
|
||||||
|
// hidden polyline for fill
|
||||||
|
let polyFill = document.createElementNS(svgns, "polyline");
|
||||||
|
polyFill.setAttribute("id", this.namePrefix + "PolyFill");
|
||||||
|
polyFill.setAttribute("stroke", "none");
|
||||||
|
polyFill.setAttribute("fill", "url(#verlVertFill)");
|
||||||
|
chartContent.appendChild(polyFill);
|
||||||
|
|
||||||
|
// X-grid lines
|
||||||
|
let numXGridLines = (this.mChartHight / this.xGridDist);
|
||||||
|
for (let i = 0; i < numXGridLines; i++) {
|
||||||
|
let line = document.createElementNS(svgns, "line");
|
||||||
|
line.setAttribute("id", this.namePrefix + "XGrid" + i);
|
||||||
|
line.setAttribute("x1", String(0));
|
||||||
|
line.setAttribute("x2", String(this.datapoints * 2));
|
||||||
|
line.setAttribute("y1", String(this.mChartHight - (i + 1) * this.xGridDist));
|
||||||
|
line.setAttribute("y2", String(this.mChartHight - (i + 1) * this.xGridDist));
|
||||||
|
line.setAttribute("stroke-width", "1");
|
||||||
|
line.setAttribute("stroke-dasharray", "1,1");
|
||||||
|
line.setAttribute("stroke", "#A0A0A0");
|
||||||
|
chartContent.appendChild(line);
|
||||||
|
let text = document.createElementNS(svgns, "text");
|
||||||
|
text.setAttribute("id", this.namePrefix + "XGridText" + i);
|
||||||
|
text.setAttribute("x", "0");
|
||||||
|
text.setAttribute("y", String(this.mChartHight + 10 - (i + 1) * this.xGridDist));
|
||||||
|
text.innerHTML = (i + 1) * this.xGridDist;
|
||||||
|
this.svg.appendChild(text);
|
||||||
|
}
|
||||||
|
// Y-grid lines
|
||||||
|
let numYGridLines = (this.datapoints / this.yGridDist) * 2;
|
||||||
|
for (let i = numYGridLines; i > 0; i--) {
|
||||||
|
let line = document.createElementNS(svgns, "line");
|
||||||
|
line.setAttribute("id", this.namePrefix + "YGrid" + i);
|
||||||
|
line.setAttribute("x1", String((i) * this.yGridDist) - 1);
|
||||||
|
line.setAttribute("x2", String((i) * this.yGridDist) - 1);
|
||||||
|
line.setAttribute("y1", String(0));
|
||||||
|
line.setAttribute("y2", String(this.mChartHight));
|
||||||
|
line.setAttribute("stroke-width", "1");
|
||||||
|
line.setAttribute("stroke-dasharray", "1,3");
|
||||||
|
line.setAttribute("stroke", "#A0A0A0");
|
||||||
|
chartContent.appendChild(line);
|
||||||
|
let text = document.createElementNS(svgns, "text");
|
||||||
|
text.setAttribute("id", this.namePrefix + "YGridText" + i);
|
||||||
|
text.setAttribute("x", String((i) * this.yGridDist + 15));
|
||||||
|
text.setAttribute("y", String(this.mChartHight + 17));
|
||||||
|
text.innerHTML = "";
|
||||||
|
this.svg.appendChild(text);
|
||||||
|
}
|
||||||
|
//
|
||||||
|
this.svg.appendChild(chartContent);
|
||||||
|
};
|
||||||
|
|
||||||
|
getContainer() { return this.svg; };
|
||||||
|
|
||||||
|
setXScale(refreshIntervall, lastValueTs) {
|
||||||
|
this.refreshIntervall = refreshIntervall;
|
||||||
|
this.lastValueTs = lastValueTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
update(values, maxVal) {
|
||||||
|
if (maxVal === undefined) {
|
||||||
|
this.maxValue = 0;
|
||||||
|
for (let val in values)
|
||||||
|
if (val > this.maxValue) this.maxValue = val;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
this.maxValue = maxVal;
|
||||||
|
|
||||||
|
// normalize data to chart
|
||||||
|
let divider = this.maxValue / this.mChartHight;
|
||||||
|
if (divider == 0)
|
||||||
|
divider = 1;
|
||||||
|
|
||||||
|
let firstValPos = -1; // position of first value >0 W
|
||||||
|
let lastValPos = -1; // position of last value >0 W
|
||||||
|
let points = "";
|
||||||
|
for (let i = 0; i < this.datapoints; i++) {
|
||||||
|
let val = values[i];
|
||||||
|
if (val > 0) {
|
||||||
|
this.mLastValue = val;
|
||||||
|
lastValPos = i;
|
||||||
|
if (firstValPos < 0)
|
||||||
|
firstValPos = i;
|
||||||
|
val = val / divider;
|
||||||
|
points += ' ' + String(i * 2) + ',' + String(this.mChartHight - val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let poly = document.getElementById(this.namePrefix + "Poly");
|
||||||
|
poly.setAttribute("points", points);
|
||||||
|
// "close" polyFill-line down to the x-axis
|
||||||
|
points += ' ' + +String(lastValPos * 2) + ',' + String(this.mChartHight);
|
||||||
|
points += ' ' + +String(firstValPos * 2) + ',' + String(this.mChartHight);
|
||||||
|
let polyFill = document.getElementById(this.namePrefix + "PolyFill");
|
||||||
|
polyFill.setAttribute("points", points);
|
||||||
|
|
||||||
|
// X-Grid lines
|
||||||
|
let numXGridLines = (this.mChartHight / this.xGridDist);
|
||||||
|
let dist = (this.maxValue / numXGridLines);
|
||||||
|
for (let i = 0; i < numXGridLines; i++) {
|
||||||
|
let tex = document.getElementById(this.namePrefix + "XGridText" + i);
|
||||||
|
tex.innerHTML = ((i + 1) * dist).toFixed(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Y-Grid lines
|
||||||
|
if (isNaN(this.lastValueTs) || this.lastValueTs == 0)
|
||||||
|
this.lastValueTs = Date.now();
|
||||||
|
let date = new Date(this.lastValueTs);
|
||||||
|
let numYGridLines = (this.datapoints / this.yGridDist) * 2;
|
||||||
|
for (let i = numYGridLines; i > 0; i--) {
|
||||||
|
let tex = document.getElementById(this.namePrefix + "YGridText" + i);
|
||||||
|
if (this.refreshIntervall > 8600) // Display date
|
||||||
|
tex.innerHTML = date.getDate() + "." + (date.getMonth() + 1).pad(2);
|
||||||
|
else // Display time
|
||||||
|
tex.innerHTML = date.getHours() + ":" + date.getMinutes().pad(2);
|
||||||
|
date = new Date(date.getTime() - (this.refreshIntervall * (this.yGridDist / 2) * 1000));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}// class powChart
|
||||||
|
|
||||||
|
|
||||||
// normalize data to chart
|
|
||||||
let divider = obj.max / mChartHeight;
|
|
||||||
if (divider == 0)
|
|
||||||
divider = 1;
|
|
||||||
for (var i = 0; i < numDataPts; i++) {
|
|
||||||
val = data[i];
|
|
||||||
if (val > 0)
|
|
||||||
mLastValue = val
|
|
||||||
val = val / divider
|
|
||||||
rect = document.getElementById(namePrefix+"Rect" + i);
|
|
||||||
rect.setAttribute("height", val);
|
|
||||||
rect.setAttribute("y", mChartHeight - val);
|
|
||||||
}
|
|
||||||
document.getElementById(namePrefix + "Max").innerHTML = obj.max;
|
|
||||||
if (mRefresh < 5)
|
|
||||||
mRefresh = 5;
|
|
||||||
document.getElementById(namePrefix + "Refresh").innerHTML = mRefresh;
|
|
||||||
}
|
|
||||||
|
|
||||||
function parsePowerHistory(obj){
|
function parsePowerHistory(obj){
|
||||||
if (null != obj) {
|
if (null != obj) {
|
||||||
parseNav(obj.generic);
|
let refresh = obj.refresh
|
||||||
parseESP(obj.generic);
|
let maximum = obj.max;
|
||||||
parseHistory(obj,"pwr", pwrExeOnce)
|
let addNextChart=false;
|
||||||
document.getElementById("pwrLast").innerHTML = mLastValue
|
if (powerHistObj == null) {
|
||||||
document.getElementById("pwrMaxDay").innerHTML = obj.maxDay
|
powerHistObj = new powChart("ph");
|
||||||
|
powerHistObj.init(obj.value.length);
|
||||||
|
document.getElementById("pwrChart").appendChild(powerHistObj.getContainer());
|
||||||
|
// Regular update:
|
||||||
|
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", refresh * 1000);
|
||||||
|
// one after the other
|
||||||
|
addNextChart=true;
|
||||||
|
}
|
||||||
|
powerHistObj.setXScale(refresh, obj.lastValueTs * 1000);
|
||||||
|
powerHistObj.update(obj.value, maximum);
|
||||||
|
|
||||||
|
document.getElementById("pwrLast").innerHTML = powerHistObj.mLastValue;
|
||||||
|
//document.getElementById("pwrMaxDay").innerHTML = obj.maxDay;
|
||||||
|
document.getElementById("pwrMax").innerHTML = maximum;
|
||||||
|
document.getElementById("pwrRefresh").innerHTML = refresh;
|
||||||
|
document.getElementById("pwrNumValues").innerHTML = obj.value.length;
|
||||||
|
if (addNextChart)
|
||||||
|
setTimeout(() => { getAjax("/api/powerHistoryDay", parsePowerHistoryDay); }, 50);
|
||||||
}
|
}
|
||||||
if (pwrExeOnce) {
|
}
|
||||||
pwrExeOnce = false;
|
|
||||||
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000);
|
function parsePowerHistoryDay(obj) {
|
||||||
|
if (null != obj) {
|
||||||
|
let refresh = obj.refresh
|
||||||
|
if (refresh<30)
|
||||||
|
refresh = 30;
|
||||||
|
let maximum = obj.max;
|
||||||
|
let addNextChart = false;
|
||||||
|
if (powerHistDayObj == null) {
|
||||||
|
powerHistDayObj = new powChart("phDay");
|
||||||
|
powerHistDayObj.init(obj.value.length);
|
||||||
|
document.getElementById("pwrDayChart").appendChild(powerHistDayObj.getContainer());
|
||||||
|
// Regular update:
|
||||||
|
window.setInterval("getAjax('/api/powerHistoryDay', parsePowerHistoryDay)", refresh * 1000);
|
||||||
|
// one after the other
|
||||||
|
addNextChart = false; // if true: add YieldDayHistory
|
||||||
|
}
|
||||||
|
powerHistDayObj.setXScale(refresh, obj.lastValueTs * 1000);
|
||||||
|
powerHistDayObj.update(obj.value, maximum);
|
||||||
|
|
||||||
|
//document.getElementById("pwrDayLast").innerHTML = powerHistDayObj.mLastValue;
|
||||||
|
document.getElementById("pwrDayMaxDay").innerHTML = obj.maxDay;
|
||||||
|
//document.getElementById("pwrDayMax").innerHTML = maximum;
|
||||||
|
document.getElementById("pwrDayRefresh").innerHTML = refresh;
|
||||||
|
if (addNextChart)
|
||||||
|
setTimeout(() => { getAjax("/api/yieldDayHistory", parseYieldDayHistory); }, 50);
|
||||||
|
else
|
||||||
|
parseNav(obj.generic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function parseYieldDayHistory(obj) {
|
||||||
|
if (null != obj) {
|
||||||
|
parseNav(obj.generic);
|
||||||
|
let refresh = obj.refresh
|
||||||
|
let maximum = obj.max;
|
||||||
|
let addNextChart = false;
|
||||||
|
if (ydHistObj == null) {
|
||||||
|
ydHistObj = new powChart("yd");
|
||||||
|
ydHistObj.init(obj.value.length);
|
||||||
|
document.getElementById("ydChart").appendChild(ydHistObj.getContainer());
|
||||||
|
// Regular update:
|
||||||
|
window.setInterval("getAjax('/api/yieldDayHistory', parseYieldDayHistory)", refresh * 500);
|
||||||
|
addNextChart = true;
|
||||||
|
}
|
||||||
|
ydHistObj.setXScale(refresh, obj.lastValueTs * 1000);
|
||||||
|
ydHistObj.update(obj.value, maximum);
|
||||||
|
|
||||||
|
document.getElementById("ydMax").innerHTML = maximum;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,13 +33,27 @@ textarea {
|
||||||
color: var(--fg2);
|
color: var(--fg2);
|
||||||
}
|
}
|
||||||
|
|
||||||
svg rect {fill: #00A;}
|
svg.container {
|
||||||
svg.chart {
|
background:var(--chart-cont);
|
||||||
background: #f2f2f2;
|
|
||||||
border: 2px solid gray;
|
|
||||||
padding: 1px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rect.chartFrame {
|
||||||
|
fill: var(--chart-bg);
|
||||||
|
stroke: gray;
|
||||||
|
stroke-width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg polyline {
|
||||||
|
fill-opacity: .5;
|
||||||
|
stroke-width: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg text {
|
||||||
|
font-size: x-small;
|
||||||
|
fill: var(--chart-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
div.chartDivContainer {
|
div.chartDivContainer {
|
||||||
padding: 1px;
|
padding: 1px;
|
||||||
margin: 1px;
|
margin: 1px;
|
||||||
|
|
|
@ -21,8 +21,8 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseSysInfo(obj) {
|
function parseSysInfo(obj) {
|
||||||
const data = ["sdk", "cpu_freq", "chip_revision",
|
const data = ["sdk", "cpu_freq", "chip_revision", "device_name",
|
||||||
"chip_model", "chip_cores", "esp_type", "mac", "wifi_rssi", "ts_uptime",
|
"chip_model", "chip_cores", "esp_type", "mac", "wifi_rssi", "wifi_channel", "ts_uptime",
|
||||||
"flash_size", "sketch_used", "heap_total", "heap_free", "heap_frag",
|
"flash_size", "sketch_used", "heap_total", "heap_free", "heap_frag",
|
||||||
"max_free_blk", "version", "modules", "env", "core_version", "reboot_reason"];
|
"max_free_blk", "version", "modules", "env", "core_version", "reboot_reason"];
|
||||||
|
|
||||||
|
@ -49,7 +49,7 @@
|
||||||
case 0: return badge(false, "{#UNKNOWN}", "warning"); break;
|
case 0: return badge(false, "{#UNKNOWN}", "warning"); break;
|
||||||
case 1: return badge(true, "{#TRUE}"); break;
|
case 1: return badge(true, "{#TRUE}"); break;
|
||||||
default: return badge(false, "{#FALSE}"); break;
|
default: return badge(false, "{#FALSE}"); break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseRadio(obj) {
|
function parseRadio(obj) {
|
||||||
|
|
|
@ -1528,6 +1528,21 @@
|
||||||
"en": "Total Power",
|
"en": "Total Power",
|
||||||
"de": "Gesamtleistung"
|
"de": "Gesamtleistung"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"token": "LAST",
|
||||||
|
"en": "Last",
|
||||||
|
"de": "Die letzten"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "VALUES",
|
||||||
|
"en": "values",
|
||||||
|
"de": "Werte"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"token": "TOTAL_POWER_DAY",
|
||||||
|
"en": "Total Power Today",
|
||||||
|
"de": "Gesamtleistung heute"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"token": "TOTAL_YIELD_PER_DAY",
|
"token": "TOTAL_YIELD_PER_DAY",
|
||||||
"en": "Total Yield per day",
|
"en": "Total Yield per day",
|
||||||
|
@ -1535,23 +1550,23 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"token": "MAX_DAY",
|
"token": "MAX_DAY",
|
||||||
"en": "maximum day",
|
"en": "Maximum day",
|
||||||
"de": "Tagesmaximum"
|
"de": "Tagesmaximum"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"token": "LAST_VALUE",
|
"token": "LAST_VALUE",
|
||||||
"en": "last value",
|
"en": "Last value",
|
||||||
"de": "letzter Wert"
|
"de": "Letzter Wert"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"token": "MAXIMUM",
|
"token": "MAXIMUM",
|
||||||
"en": "maximum value",
|
"en": "Maximum value",
|
||||||
"de": "Maximalwert"
|
"de": "Maximalwert"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"token": "UPDATED",
|
"token": "UPDATED",
|
||||||
"en": "Updated every",
|
"en": "Updated every",
|
||||||
"de": "aktualisiert alle"
|
"de": "Aktualisiert alle"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"token": "SECONDS",
|
"token": "SECONDS",
|
||||||
|
|
Loading…
Add table
Reference in a new issue