* merge PR: added history charts to web #1336
This commit is contained in:
lumapu 2024-01-09 22:12:06 +01:00
parent fa32906707
commit fa2028e479
10 changed files with 164 additions and 139 deletions

View file

@ -1,5 +1,8 @@
# Development Changes # Development Changes
## 0.8.50 - 2024-01-09
* merge PR: added history charts to web #1336
## 0.8.49 - 2024-01-08 ## 0.8.49 - 2024-01-08
* fix send total values if inverter state is different from `OFF` #1331 * fix send total values if inverter state is different from `OFF` #1331
* fix german language issues #1335 * fix german language issues #1335

View file

@ -13,7 +13,7 @@
//------------------------------------- //-------------------------------------
#define VERSION_MAJOR 0 #define VERSION_MAJOR 0
#define VERSION_MINOR 8 #define VERSION_MINOR 8
#define VERSION_PATCH 49 #define VERSION_PATCH 50
//------------------------------------- //-------------------------------------
typedef struct { typedef struct {

View file

@ -794,7 +794,6 @@ 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")));
obj[F("refresh")] = mConfig->inst.sendInterval; obj[F("refresh")] = mConfig->inst.sendInterval;
obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH;
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);
@ -809,7 +808,6 @@ class RestApi {
void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) { void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) {
getGeneric(request, obj.createNestedObject(F("generic"))); getGeneric(request, obj.createNestedObject(F("generic")));
obj[F("refresh")] = 86400; // 1 day obj[F("refresh")] = 86400; // 1 day
obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH;
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::YIELD, fld); uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::YIELD, fld);

View file

@ -1,135 +1,113 @@
<!doctype html> <!doctype html>
<html> <html>
<head>
<title>{#NAV_HISTORY}</title>
{#HTML_HEADER}
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="format-detection" content="telephone=no">
<head> </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}
<body> <div id="wrapper">
{#HTML_NAV} <div id="content">
<div id="wrapper"> <h3>{#TOTAL_POWER}</h3>
<div id="content"> <div>
<h3>Total Power history</h3> <div class="chartDiv" id="pwrChart"> </div>
<div class="chartDivContainer"> <p>
<div class="chartDiv" id="phHistoryChart"> </div> {#MAX_DAY}: <span id="pwrMaxDay"></span> W. {#LAST_VALUE}: <span id="pwrLast"></span> W.<br />
<p class="center" style="margin:0px;border:0px;"> {#MAXIMUM}: <span id="pwrMax"></span> W. {#UPDATED} <span id="pwrRefresh"></span> {#SECONDS}
Maximum day: <span id="phMaximumDay"></span> W. Last value: <span id="phActual"></span> W.<br /> </p>
Maximum graphics: <span id="phMaximum"></span> W. Updated every <span id="phRefresh"></span> seconds </p> </div>
</div> <h3>{#TOTAL_YIELD_PER_DAY}</h3>
<h3>Yield per day history</h3> <div>
<div class="chartDivContainer"> <div class="chartDiv" id="ydChart"> </div>
<div class="chartDiv" id="ydHistoryChart"> </div> <p>
<p class="center" style="margin:0px;border:0px;"> {#MAXIMUM}: <span id="ydMax"></span> Wh<br />
Maximum value: <span id="ydMaximum"></span> Wh<br /> {#UPDATED} <span id="ydRefresh"></span> {#SECONDS}
Updated every <span id="ydRefresh"></span> seconds </p> </p>
</div>
</div>
</div> </div>
{#HTML_FOOTER}
<h4 style="margin-bottom:0px;">Insert data into Yield per day history</h4> <script type="text/javascript">
<fieldset style="padding: 1px;"> const svgns = "http://www.w3.org/2000/svg";
<legend class="des" style="margin-top: 0px;">Insert data (*.json) i.e. from a saved "/api/yieldDayHistory" call </legend> var pwrExeOnce = true;
<form id="form" method="POST" action="/api/insertYieldDayHistory" enctype="multipart/form-data" accept-charset="utf-8"> var ydExeOnce = true;
<input type="button" class="btn my-4" style="padding: 3px;margin: 3px;" value="Insert" onclick="submit()"> // make a simple rectangle
<input type="file" name="insert" style="width: 80%;"> var mRefresh = 60;
</form> var mLastValue = 0;
</fieldset> const mChartHeight = 250;
<p></p>
</div>
</div>
{#HTML_FOOTER}
<script type="text/javascript"> function parseHistory(obj, namePrefix, execOnce) {
const svgns = "http://www.w3.org/2000/svg"; mRefresh = obj.refresh
var phExeOnce = true; var data = Object.assign({}, obj.value)
var ydExeOnce = true; var numDataPts = data.length
// 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) { if (true == execOnce) {
mRefresh = obj["refresh"]; let s = svg(null, (numDataPts + 2) * 2, mChartHeight, "chart");
phDatapoints = obj["datapoints"]; s.setAttribute("role", "img");
mDataValues = Object.assign({}, obj["value"]); let g = document.createElementNS(svgns, "g");
mMaximum = obj["maximum"]; s.appendChild(g);
// generate svg for (var i = 0; i < numDataPts; i++) {
if (true == execOnce) { val = data[i];
let svg = document.createElementNS(svgns, "svg"); let rect = document.createElementNS(svgns, "rect");
svg.setAttribute("class", "chart"); rect.setAttribute("id", namePrefix+"Rect" + i);
svg.setAttribute("width", String((phDatapoints+2) * 2)); rect.setAttribute("x", String(i * 2) + "");
svg.setAttribute("height", String(mChartHight) + ""); rect.setAttribute("width", String(2) + "");
svg.setAttribute("aria-labelledby", "title desc"); g.appendChild(rect);
svg.setAttribute("role", "img"); }
t = ml("title"); document.getElementById(namePrefix+"Chart").appendChild(s);
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); // normalize data to chart
</script> 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;
}
</body> function parsePowerHistory(obj){
if (null != obj) {
parseHistory(obj,"pwr", pwrExeOnce)
document.getElementById("pwrLast").innerHTML = mLastValue
document.getElementById("pwrMaxDay").innerHTML = obj.maxDay
}
if (pwrExeOnce) {
pwrExeOnce = false;
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000);
</html> setTimeout(() => {
getAjax("/api/yieldDayHistory", parseYieldDayHistory);
} , 20);
}
}
function parseYieldDayHistory(obj) {
if (null != obj) {
parseNav(obj.generic);
parseHistory(obj, "yd", ydExeOnce)
}
if (ydExeOnce) {
ydExeOnce = false;
window.setInterval("getAjax('/api/yieldDayHistory', parseYieldDayHistory)", mRefresh * 500);
}
}
getAjax("/api/powerHistory", parsePowerHistory);
</script>
</body>
</html>

View file

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<title>Save</title> <title>{#NAV_SAVE}</title>
{#HTML_HEADER} {#HTML_HEADER}
</head> </head>
<body> <body>

View file

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<title>Serial Console</title> <title>{#NAV_WEBSERIAL}</title>
{#HTML_HEADER} {#HTML_HEADER}
</head> </head>
<body> <body>

View file

@ -36,21 +36,17 @@ textarea {
svg rect {fill: #0000AA;} svg rect {fill: #0000AA;}
svg.chart { svg.chart {
background: #f2f2f2; background: #f2f2f2;
border: 2px solid gray; border: 2px solid gray;
padding: 1px; padding: 1px;
} }
div.chartDivContainer { div.chartDivContainer {
padding: 1px; padding: 1px;
margin: 1px; margin: 1px;
} }
div.chartdivContainer span { div.chartdivContainer span {
color: var(--fg2); color: var(--fg2);
} }
div.chartDiv {
padding: 0px;
margin: 0px;
}
.topnav { .topnav {

View file

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<title>Live</title> <title>{#NAV_LIVE}</title>
{#HTML_HEADER} {#HTML_HEADER}
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
</head> </head>

View file

@ -1,7 +1,7 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<title>Setup Wizard</title> <title>{#NAV_WIZARD}</title>
{#HTML_HEADER} {#HTML_HEADER}
</head> </head>
<body> <body>

View file

@ -3,13 +3,18 @@
{ {
"name": "general", "name": "general",
"list": [ "list": [
{
"token": "NAV_WIZARD",
"en": "Setup Wizard",
"de": "Daten"
},
{ {
"token": "NAV_LIVE", "token": "NAV_LIVE",
"en": "Live", "en": "Live",
"de": "Daten" "de": "Daten"
}, },
{ {
"token": "{#NAV_HISTORY}", "token": "NAV_HISTORY",
"en": "History", "en": "History",
"de": "Verlauf" "de": "Verlauf"
}, },
@ -28,6 +33,11 @@
"en": "Documentation", "en": "Documentation",
"de": "Dokumentation" "de": "Dokumentation"
}, },
{
"token": "NAV_SAVE",
"en": "save",
"de": "speichern"
},
{ {
"token": "NAV_ABOUT", "token": "NAV_ABOUT",
"en": "About", "en": "About",
@ -1334,6 +1344,46 @@
"de": "Fehler beim Speichern" "de": "Fehler beim Speichern"
} }
] ]
},
{
"name": "history.html",
"list": [
{
"token": "TOTAL_POWER",
"en": "Total Power",
"de": "Gesamtleistung"
},
{
"token": "TOTAL_YIELD_PER_DAY",
"en": "Total Yield per day",
"de": "Gesamtertrag pro Tag"
},
{
"token": "MAX_DAY",
"en": "maximum day",
"de": "Tagesmaximum"
},
{
"token": "LAST_VALUE",
"en": "last value",
"de": "letzter Wert"
},
{
"token": "MAXIMUM",
"en": "maximum value",
"de": "Maximalwert"
},
{
"token": "UPDATED",
"en": "Updated every",
"de": "aktualisiert alle"
},
{
"token": "SECONDS",
"en": "seconds",
"de": "Sekunden"
}
]
} }
] ]
} }