Browse Source

0.8.50

* merge PR: added history charts to web #1336
pull/1347/head
lumapu 1 year ago
parent
commit
fa2028e479
  1. 3
      src/CHANGES.md
  2. 2
      src/defines.h
  3. 2
      src/web/RestApi.h
  4. 222
      src/web/html/history.html
  5. 2
      src/web/html/save.html
  6. 2
      src/web/html/serial.html
  7. 12
      src/web/html/style.css
  8. 2
      src/web/html/visualization.html
  9. 2
      src/web/html/wizard.html
  10. 52
      src/web/lang.json

3
src/CHANGES.md

@ -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

2
src/defines.h

@ -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 {

2
src/web/RestApi.h

@ -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);

222
src/web/html/history.html

@ -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
// normalize data to chart let divider = obj.max / mChartHeight;
let divider = mMaximum / mChartHight; if (divider == 0)
if (divider == 0) divider = 1;
divider = 1; for (var i = 0; i < numDataPts; i++) {
for (var i = 0; i < phDatapoints; i++) { val = data[i];
val = mDataValues[i]; if (val > 0)
if (val>0) mLastValue = val
mLastValue = val val = val / divider
val = val / divider rect = document.getElementById(namePrefix+"Rect" + i);
rect = document.getElementById(namePrefix+"Rect" + i); rect.setAttribute("height", val);
rect.setAttribute("height", val); rect.setAttribute("y", mChartHeight - val);
rect.setAttribute("y", mChartHight - val); }
} document.getElementById(namePrefix + "Max").innerHTML = obj.max;
document.getElementById(namePrefix + "Maximum").innerHTML = mMaximum; if (mRefresh < 5)
if (mRefresh < 5) mRefresh = 5;
mRefresh = 5; document.getElementById(namePrefix + "Refresh").innerHTML = mRefresh;
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); function parsePowerHistory(obj){
</script> 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);
</body> 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);
}
}
</html> getAjax("/api/powerHistory", parsePowerHistory);
</script>
</body>
</html>

2
src/web/html/save.html

@ -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>

2
src/web/html/serial.html

@ -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>

12
src/web/html/style.css

@ -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 {

2
src/web/html/visualization.html

@ -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>

2
src/web/html/wizard.html

@ -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>

52
src/web/lang.json

@ -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"
}
]
} }
] ]
} }

Loading…
Cancel
Save