mirror of
https://github.com/lumapu/ahoy.git
synced 2025-04-29 18:26:21 +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
|
||||
}
|
||||
|
||||
uint32_t getHistoryPeriode(uint8_t type) override {
|
||||
#if defined(ENABLE_HISTORY)
|
||||
return mHistory.getPeriode((HistoryStorageType)type);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
|
||||
uint16_t getHistoryMaxDay() override {
|
||||
#if defined(ENABLE_HISTORY)
|
||||
return mHistory.getMaximumDay();
|
||||
|
@ -322,6 +330,21 @@ class app : public IApp, public ah::Scheduler {
|
|||
#endif
|
||||
}
|
||||
|
||||
uint32_t getHistoryLastValueTs(uint8_t type) override {
|
||||
#if defined(ENABLE_HISTORY)
|
||||
return mHistory.getLastValueTs((HistoryStorageType)type);
|
||||
#else
|
||||
return 0;
|
||||
#endif
|
||||
}
|
||||
#if defined(ENABLE_HISTORY_LOAD_DATA)
|
||||
void addValueToHistory(uint8_t historyType, uint8_t valueType, uint32_t value) override {
|
||||
#if defined(ENABLE_HISTORY)
|
||||
return mHistory.addValue((HistoryStorageType)historyType, valueType, value);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
#define CHECK_AVAIL true
|
||||
#define SKIP_YIELD_DAY true
|
||||
|
|
|
@ -67,8 +67,12 @@ class IApp {
|
|||
virtual bool isProtected(const char *clientIp, const char *token, bool askedFromWeb) const = 0;
|
||||
|
||||
virtual uint16_t getHistoryValue(uint8_t type, uint16_t i) = 0;
|
||||
virtual uint32_t getHistoryPeriode(uint8_t type) = 0;
|
||||
virtual uint16_t getHistoryMaxDay() = 0;
|
||||
|
||||
virtual uint32_t getHistoryLastValueTs(uint8_t type) = 0;
|
||||
#if defined(ENABLE_HISTORY_LOAD_DATA)
|
||||
virtual void addValueToHistory(uint8_t historyType, uint8_t valueType, uint32_t value) = 0;
|
||||
#endif
|
||||
virtual void* getRadioObj(bool nrf) = 0;
|
||||
};
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
enum class HistoryStorageType : uint8_t {
|
||||
POWER,
|
||||
POWER_DAY,
|
||||
YIELD
|
||||
};
|
||||
|
||||
|
@ -25,14 +26,16 @@ class HistoryData {
|
|||
private:
|
||||
struct storage_t {
|
||||
uint16_t refreshCycle = 0;
|
||||
uint16_t loopCnt = 0;
|
||||
uint16_t listIdx = 0; // index for next Element to write into WattArr
|
||||
uint16_t dispIdx = 0; // index for 1st Element to display from WattArr
|
||||
bool wrapped = false;
|
||||
uint16_t loopCnt;
|
||||
uint16_t listIdx; // index for next Element to write into WattArr
|
||||
// ring buffer for watt history
|
||||
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:
|
||||
|
@ -42,63 +45,225 @@ class HistoryData {
|
|||
mConfig = config;
|
||||
mTs = ts;
|
||||
|
||||
mCurPwr.reset();
|
||||
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() {
|
||||
;
|
||||
float curPwr = 0;
|
||||
float maxPwr = 0;
|
||||
//float maxPwr = 0;
|
||||
float yldDay = -0.1;
|
||||
uint32_t ts = 0;
|
||||
|
||||
for (uint8_t i = 0; i < mSys->getNumInverters(); i++) {
|
||||
Inverter<> *iv = mSys->getInverterByPos(i);
|
||||
record_t<> *rec = iv->getRecordStruct(RealTimeRunData_Debug);
|
||||
if (iv == NULL)
|
||||
continue;
|
||||
curPwr += iv->getChannelFieldValue(CH0, FLD_PAC, rec);
|
||||
maxPwr += iv->getChannelFieldValue(CH0, FLD_MP, rec);
|
||||
//maxPwr += iv->getChannelFieldValue(CH0, FLD_MP, rec);
|
||||
yldDay += iv->getChannelFieldValue(CH0, FLD_YD, rec);
|
||||
if (rec->ts > ts)
|
||||
ts = rec->ts;
|
||||
}
|
||||
|
||||
if ((++mCurPwr.loopCnt % mCurPwr.refreshCycle) == 0) {
|
||||
mCurPwr.loopCnt = 0;
|
||||
if (curPwr > 0)
|
||||
if (curPwr > 0) {
|
||||
mLastValueTs = ts;
|
||||
addValue(&mCurPwr, roundf(curPwr));
|
||||
if (maxPwr > 0)
|
||||
mMaximumDay = roundf(maxPwr);
|
||||
if (curPwr > mMaximumDay)
|
||||
mMaximumDay = roundf(curPwr);
|
||||
}
|
||||
//if (maxPwr > 0)
|
||||
// mMaximumDay = roundf(maxPwr);
|
||||
}
|
||||
|
||||
/*if((++mYieldDay.loopCnt % mYieldDay.refreshCycle) == 0) {
|
||||
if (*mTs > mApp->getSunset()) {
|
||||
if ((++mCurPwrDay.loopCnt % mCurPwrDay.refreshCycle) == 0) {
|
||||
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)) {
|
||||
addValue(&mYieldDay, roundf(yldDay));
|
||||
mDayStored = true;
|
||||
}
|
||||
} else if (*mTs > mApp->getSunrise())
|
||||
}
|
||||
else if (*mTs > mApp->getSunrise())
|
||||
mDayStored = false;
|
||||
}*/
|
||||
}
|
||||
}
|
||||
|
||||
uint16_t valueAt(HistoryStorageType type, uint16_t i) {
|
||||
//storage_t *s = (HistoryStorageType::POWER == type) ? &mCurPwr : &mYieldDay;
|
||||
storage_t *s = &mCurPwr;
|
||||
uint16_t idx = (s->dispIdx + i) % HISTORY_DATA_ARR_LENGTH;
|
||||
storage_t *s=NULL;
|
||||
uint16_t idx=i;
|
||||
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 0;
|
||||
}
|
||||
|
||||
uint16_t getMaximumDay() {
|
||||
return mMaximumDay;
|
||||
}
|
||||
|
||||
uint32_t getLastValueTs(HistoryStorageType type) {
|
||||
DPRINTLN(DBG_VERBOSE, F("getLastValueTs ") + String((uint8_t)type));
|
||||
if (type == HistoryStorageType::POWER_DAY)
|
||||
return mPgEndTime;
|
||||
return mLastValueTs;
|
||||
}
|
||||
|
||||
uint32_t 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:
|
||||
void addValue(storage_t *s, uint16_t value) {
|
||||
if (s->wrapped) // after 1st time array wrap we have to increase the display index
|
||||
s->dispIdx = (s->listIdx + 1) % (HISTORY_DATA_ARR_LENGTH);
|
||||
s->data[s->listIdx] = value;
|
||||
s->listIdx = (s->listIdx + 1) % (HISTORY_DATA_ARR_LENGTH);
|
||||
if (s->listIdx == 0)
|
||||
s->wrapped = true;
|
||||
}
|
||||
|
||||
void addValueDay(storage_t *s, uint16_t value) {
|
||||
DPRINTLN(DBG_VERBOSE, F("addValueDay ") + String(value));
|
||||
bool storeStartEndTimes = false;
|
||||
bool store_entry = false;
|
||||
uint32_t pGraphStartTime = mApp->getSunrise();
|
||||
uint32_t pGraphEndTime = mApp->getSunset();
|
||||
uint32_t utcTs = mApp->getTimestamp();
|
||||
switch (mPgState) {
|
||||
case PowerGraphState::NO_TIME_SYNC:
|
||||
if ((pGraphStartTime > 0)
|
||||
&& (pGraphEndTime > 0) // wait until period data is available ...
|
||||
&& (utcTs >= pGraphStartTime)
|
||||
&& (utcTs < pGraphEndTime)) // and current time is in period
|
||||
{
|
||||
storeStartEndTimes = true; // period was received -> store
|
||||
store_entry = true;
|
||||
mPgState = PowerGraphState::IN_PERIOD;
|
||||
}
|
||||
break;
|
||||
case PowerGraphState::IN_PERIOD:
|
||||
if (utcTs > mPgEndTime) // check if end of day is reached ...
|
||||
mPgState = PowerGraphState::WAIT_4_NEW_PERIOD; // then wait for new period setting
|
||||
else
|
||||
store_entry = true;
|
||||
break;
|
||||
case PowerGraphState::WAIT_4_NEW_PERIOD:
|
||||
if ((mPgStartTime != pGraphStartTime) || (mPgEndTime != pGraphEndTime)) { // wait until new time period was received ...
|
||||
storeStartEndTimes = true; // and store it for next period
|
||||
mPgState = PowerGraphState::WAIT_4_RESTART;
|
||||
}
|
||||
break;
|
||||
case PowerGraphState::WAIT_4_RESTART:
|
||||
if ((utcTs >= mPgStartTime) && (utcTs < mPgEndTime)) { // wait until current time is in period again ...
|
||||
mCurPwrDay.reset(); // then reset power graph data
|
||||
store_entry = true;
|
||||
mPgState = PowerGraphState::IN_PERIOD;
|
||||
mCurPwr.reset(); // also reset "last values" graph
|
||||
mMaximumDay = 0; // and the maximum of the (last) day
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// store start and end times of current time period and calculate period length
|
||||
if (storeStartEndTimes) {
|
||||
mPgStartTime = pGraphStartTime;
|
||||
mPgEndTime = pGraphEndTime;
|
||||
mPgPeriod = pGraphEndTime - pGraphStartTime; // time period of power graph in sec for scaling of x-axis
|
||||
}
|
||||
|
||||
if (store_entry) {
|
||||
DPRINTLN(DBG_VERBOSE, F("addValueDay store_entry") + String(value));
|
||||
if (mPgPeriod) {
|
||||
uint16_t pgPos = (utcTs - mPgStartTime) * (HISTORY_DATA_ARR_LENGTH - 1) / mPgPeriod;
|
||||
s->listIdx = std::min(pgPos, (uint16_t)(HISTORY_DATA_ARR_LENGTH - 1));
|
||||
} else
|
||||
s->listIdx = 0;
|
||||
DPRINTLN(DBG_VERBOSE, F("addValueDay store_entry idx=") + String(s->listIdx));
|
||||
s->data[s->listIdx] = std::max(s->data[s->listIdx], value); // update current datapoint to maximum of all seen values
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -109,8 +274,21 @@ class HistoryData {
|
|||
uint32_t *mTs = nullptr;
|
||||
|
||||
storage_t mCurPwr;
|
||||
storage_t mCurPwrDay;
|
||||
storage_t mYieldDay;
|
||||
bool mDayStored = false;
|
||||
uint16_t mMaximumDay = 0;
|
||||
uint32_t mLastValueTs = 0;
|
||||
enum class PowerGraphState {
|
||||
NO_TIME_SYNC,
|
||||
IN_PERIOD,
|
||||
WAIT_4_NEW_PERIOD,
|
||||
WAIT_4_RESTART
|
||||
};
|
||||
PowerGraphState mPgState = PowerGraphState::NO_TIME_SYNC;
|
||||
uint32_t mPgStartTime = 0;
|
||||
uint32_t mPgEndTime = 0;
|
||||
uint32_t mPgPeriod = 0; // seconds
|
||||
};
|
||||
|
||||
#endif /*ENABLE_HISTORY*/
|
||||
|
|
|
@ -49,6 +49,12 @@ class RestApi {
|
|||
mRadioCmt = (CmtRadio<>*)mApp->getRadioObj(false);
|
||||
#endif
|
||||
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(
|
||||
std::bind(&RestApi::onApiPostBody, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5));
|
||||
mSrv->on("/api", HTTP_GET, std::bind(&RestApi::onApi, this, std::placeholders::_1));
|
||||
|
@ -103,6 +109,8 @@ class RestApi {
|
|||
#endif /* !defined(ETHERNET) */
|
||||
else if(path == "live") getLive(request,root);
|
||||
else if (path == "powerHistory") getPowerHistory(request, root);
|
||||
else if (path == "powerHistoryDay") getPowerHistoryDay(request, root);
|
||||
else if (path == "yieldDayHistory") getYieldDayHistory(request, root);
|
||||
else {
|
||||
if(path.substring(0, 12) == "inverter/id/")
|
||||
getInverter(root, request->url().substring(17).toInt());
|
||||
|
@ -137,7 +145,94 @@ class RestApi {
|
|||
#endif
|
||||
}
|
||||
|
||||
void onApiPostBody(AsyncWebServerRequest *request, const uint8_t *data, size_t len, size_t index, size_t total) {
|
||||
#if defined(ENABLE_HISTORY_LOAD_DATA)
|
||||
// VArt67: For debugging history graph. Loading data into graph
|
||||
void onApiPostYDHist(AsyncWebServerRequest *request, String filename, size_t index, uint8_t *data, size_t len, size_t final) {
|
||||
uint32_t total = request->contentLength();
|
||||
DPRINTLN(DBG_DEBUG, "[onApiPostYieldDHistory ] " + filename + " index:" + index + " len:" + len + " total:" + total + " final:" + final);
|
||||
|
||||
if (0 == index) {
|
||||
if (NULL != mTmpBuf)
|
||||
delete[] mTmpBuf;
|
||||
mTmpBuf = new uint8_t[total + 1];
|
||||
mTmpSize = total;
|
||||
}
|
||||
if (mTmpSize >= (len + index))
|
||||
memcpy(&mTmpBuf[index], data, len);
|
||||
|
||||
if (!final)
|
||||
return; // not last frame - nothing to do
|
||||
|
||||
mTmpSize = len + index; // correct the total size
|
||||
mTmpBuf[mTmpSize] = 0;
|
||||
|
||||
#ifndef ESP32
|
||||
DynamicJsonDocument json(ESP.getMaxFreeBlockSize() - 512); // need some memory on heap
|
||||
#else
|
||||
DynamicJsonDocument json(12000); // does this work? I have no ESP32 :-(
|
||||
#endif
|
||||
DeserializationError err = deserializeJson(json, (const char *)mTmpBuf, mTmpSize);
|
||||
json.shrinkToFit();
|
||||
JsonObject obj = json.as<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");
|
||||
|
||||
if(0 == index) {
|
||||
|
@ -207,6 +302,8 @@ class RestApi {
|
|||
ep[F("live")] = url + F("live");
|
||||
#if defined(ENABLE_HISTORY)
|
||||
ep[F("powerHistory")] = url + F("powerHistory");
|
||||
ep[F("powerHistoryDay")] = url + F("powerHistoryDay");
|
||||
ep[F("yieldDayHistory")] = url + F("yieldDayHistory");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -301,6 +398,7 @@ class RestApi {
|
|||
obj[F("heap_free")] = mHeapFree;
|
||||
obj[F("sketch_total")] = ESP.getFreeSketchSpace();
|
||||
obj[F("sketch_used")] = ESP.getSketchSize() / 1024; // in kb
|
||||
obj[F("wifi_channel")] = WiFi.channel();
|
||||
getGeneric(request, obj);
|
||||
|
||||
getRadioNrf(obj.createNestedObject(F("radioNrf")));
|
||||
|
@ -815,7 +913,7 @@ class RestApi {
|
|||
void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||
#if defined(ENABLE_HISTORY)
|
||||
obj[F("refresh")] = mConfig->inst.sendInterval;
|
||||
obj[F("refresh")] = mApp->getHistoryPeriode((uint8_t)HistoryStorageType::POWER);
|
||||
uint16_t max = 0;
|
||||
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
||||
uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER, fld);
|
||||
|
@ -825,8 +923,40 @@ class RestApi {
|
|||
}
|
||||
obj[F("max")] = max;
|
||||
obj[F("maxDay")] = mApp->getHistoryMaxDay();
|
||||
#else
|
||||
obj[F("refresh")] = 86400; // 1 day;
|
||||
obj[F("lastValueTs")] = mApp->getHistoryLastValueTs((uint8_t)HistoryStorageType::POWER);
|
||||
#endif /*ENABLE_HISTORY*/
|
||||
}
|
||||
|
||||
void getPowerHistoryDay(AsyncWebServerRequest *request, JsonObject obj){
|
||||
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||
#if defined(ENABLE_HISTORY)
|
||||
obj[F("refresh")] = mApp->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*/
|
||||
}
|
||||
|
||||
|
|
|
@ -30,4 +30,8 @@
|
|||
--ch-head-bg: #006ec0;
|
||||
--ts-head: #333;
|
||||
--ts-bg: #555;
|
||||
|
||||
--chart-cont: #fbfbfb;
|
||||
--chart-bg: #f9f9f9;
|
||||
--chart-text: #000000;
|
||||
}
|
||||
|
|
|
@ -30,4 +30,8 @@
|
|||
--ch-head-bg: #236;
|
||||
--ts-head: #333;
|
||||
--ts-bg: #555;
|
||||
|
||||
--chart-cont: #0b0b0b;
|
||||
--chart-bg: #090909;
|
||||
--chart-text: #FFFFFF;
|
||||
}
|
||||
|
|
|
@ -13,81 +13,360 @@
|
|||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<h3>{#TOTAL_POWER}</h3>
|
||||
<div>
|
||||
{#LAST} <span id="pwrNumValues"></span> {#VALUES}
|
||||
<div class="chartDivContainer">
|
||||
<div class="chartDiv" id="pwrChart"> </div>
|
||||
<p>
|
||||
{#MAX_DAY}: <span id="pwrMaxDay"></span> W. {#LAST_VALUE}: <span id="pwrLast"></span> W.<br />
|
||||
{#MAXIMUM}: <span id="pwrMax"></span> W. {#UPDATED} <span id="pwrRefresh"></span> {#SECONDS}
|
||||
{#LAST_VALUE}: <span id="pwrLast"></span> W.<br />
|
||||
{#MAXIMUM}: <span id="pwrMax"></span> W.
|
||||
{#UPDATED} <span id="pwrRefresh"></span> {#SECONDS}
|
||||
</p>
|
||||
</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>
|
||||
{#HTML_FOOTER}
|
||||
|
||||
<script type="text/javascript">
|
||||
var powerHistObj = null;
|
||||
var powerHistDayObj = null;
|
||||
var ydHistObj = null;
|
||||
|
||||
|
||||
|
||||
Number.prototype.pad = function (size) {
|
||||
var s = String(this);
|
||||
while (s.length < (size || 2)) { s = "0" + s; }
|
||||
return s;
|
||||
}
|
||||
|
||||
class powChart {
|
||||
static objcnt = 0; // to give each object elemets a unique name prefix
|
||||
|
||||
constructor(namePrefix) {
|
||||
// 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";
|
||||
var pwrExeOnce = true;
|
||||
var ydExeOnce = true;
|
||||
// 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");
|
||||
s.appendChild(g);
|
||||
for (var i = 0; i < numDataPts; i++) {
|
||||
val = data[i];
|
||||
let rect = document.createElementNS(svgns, "rect");
|
||||
rect.setAttribute("id", namePrefix+"Rect" + i);
|
||||
rect.setAttribute("x", i * 2);
|
||||
rect.setAttribute("width", 2);
|
||||
g.appendChild(rect);
|
||||
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);
|
||||
}
|
||||
document.getElementById(namePrefix+"Chart").appendChild(s);
|
||||
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 = obj.max / mChartHeight;
|
||||
let divider = this.maxValue / this.mChartHight;
|
||||
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);
|
||||
|
||||
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);
|
||||
}
|
||||
document.getElementById(namePrefix + "Max").innerHTML = obj.max;
|
||||
if (mRefresh < 5)
|
||||
mRefresh = 5;
|
||||
document.getElementById(namePrefix + "Refresh").innerHTML = mRefresh;
|
||||
}
|
||||
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
|
||||
|
||||
|
||||
|
||||
function parsePowerHistory(obj){
|
||||
if (null != obj) {
|
||||
parseNav(obj.generic);
|
||||
parseESP(obj.generic);
|
||||
parseHistory(obj,"pwr", pwrExeOnce)
|
||||
document.getElementById("pwrLast").innerHTML = mLastValue
|
||||
document.getElementById("pwrMaxDay").innerHTML = obj.maxDay
|
||||
let refresh = obj.refresh
|
||||
let maximum = obj.max;
|
||||
let addNextChart=false;
|
||||
if (powerHistObj == null) {
|
||||
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;
|
||||
}
|
||||
if (pwrExeOnce) {
|
||||
pwrExeOnce = false;
|
||||
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
svg rect {fill: #00A;}
|
||||
svg.chart {
|
||||
background: #f2f2f2;
|
||||
border: 2px solid gray;
|
||||
padding: 1px;
|
||||
svg.container {
|
||||
background:var(--chart-cont);
|
||||
}
|
||||
|
||||
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 {
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
|
|
|
@ -21,8 +21,8 @@
|
|||
}
|
||||
|
||||
function parseSysInfo(obj) {
|
||||
const data = ["sdk", "cpu_freq", "chip_revision",
|
||||
"chip_model", "chip_cores", "esp_type", "mac", "wifi_rssi", "ts_uptime",
|
||||
const data = ["sdk", "cpu_freq", "chip_revision", "device_name",
|
||||
"chip_model", "chip_cores", "esp_type", "mac", "wifi_rssi", "wifi_channel", "ts_uptime",
|
||||
"flash_size", "sketch_used", "heap_total", "heap_free", "heap_frag",
|
||||
"max_free_blk", "version", "modules", "env", "core_version", "reboot_reason"];
|
||||
|
||||
|
|
|
@ -1528,6 +1528,21 @@
|
|||
"en": "Total Power",
|
||||
"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",
|
||||
"en": "Total Yield per day",
|
||||
|
@ -1535,23 +1550,23 @@
|
|||
},
|
||||
{
|
||||
"token": "MAX_DAY",
|
||||
"en": "maximum day",
|
||||
"en": "Maximum day",
|
||||
"de": "Tagesmaximum"
|
||||
},
|
||||
{
|
||||
"token": "LAST_VALUE",
|
||||
"en": "last value",
|
||||
"de": "letzter Wert"
|
||||
"en": "Last value",
|
||||
"de": "Letzter Wert"
|
||||
},
|
||||
{
|
||||
"token": "MAXIMUM",
|
||||
"en": "maximum value",
|
||||
"en": "Maximum value",
|
||||
"de": "Maximalwert"
|
||||
},
|
||||
{
|
||||
"token": "UPDATED",
|
||||
"en": "Updated every",
|
||||
"de": "aktualisiert alle"
|
||||
"de": "Aktualisiert alle"
|
||||
},
|
||||
{
|
||||
"token": "SECONDS",
|
||||
|
|
Loading…
Add table
Reference in a new issue