mirror of
https://github.com/lumapu/ahoy.git
synced 2025-08-04 00:48:24 +02:00
parent
fa32906707
commit
fa2028e479
10 changed files with 164 additions and 139 deletions
|
@ -1,5 +1,8 @@
|
|||
# Development Changes
|
||||
|
||||
## 0.8.50 - 2024-01-09
|
||||
* merge PR: added history charts to web #1336
|
||||
|
||||
## 0.8.49 - 2024-01-08
|
||||
* fix send total values if inverter state is different from `OFF` #1331
|
||||
* fix german language issues #1335
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
//-------------------------------------
|
||||
#define VERSION_MAJOR 0
|
||||
#define VERSION_MINOR 8
|
||||
#define VERSION_PATCH 49
|
||||
#define VERSION_PATCH 50
|
||||
|
||||
//-------------------------------------
|
||||
typedef struct {
|
||||
|
|
|
@ -794,7 +794,6 @@ class RestApi {
|
|||
void getPowerHistory(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||
obj[F("refresh")] = mConfig->inst.sendInterval;
|
||||
obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH;
|
||||
uint16_t max = 0;
|
||||
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
||||
uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::POWER, fld);
|
||||
|
@ -809,7 +808,6 @@ class RestApi {
|
|||
void getYieldDayHistory(AsyncWebServerRequest *request, JsonObject obj) {
|
||||
getGeneric(request, obj.createNestedObject(F("generic")));
|
||||
obj[F("refresh")] = 86400; // 1 day
|
||||
obj[F("datapoints")] = HISTORY_DATA_ARR_LENGTH;
|
||||
uint16_t max = 0;
|
||||
for (uint16_t fld = 0; fld < HISTORY_DATA_ARR_LENGTH; fld++) {
|
||||
uint16_t value = mApp->getHistoryValue((uint8_t)HistoryStorageType::YIELD, fld);
|
||||
|
|
|
@ -1,135 +1,113 @@
|
|||
<!doctype 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>
|
||||
<title>History</title>
|
||||
{#HTML_HEADER}
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="format-detection" content="telephone=no">
|
||||
</head>
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<h3>Total Power history</h3>
|
||||
<div class="chartDivContainer">
|
||||
<div class="chartDiv" id="phHistoryChart"> </div>
|
||||
<p class="center" style="margin:0px;border:0px;">
|
||||
Maximum day: <span id="phMaximumDay"></span> W. Last value: <span id="phActual"></span> W.<br />
|
||||
Maximum graphics: <span id="phMaximum"></span> W. Updated every <span id="phRefresh"></span> seconds </p>
|
||||
</div>
|
||||
<h3>Yield per day history</h3>
|
||||
<div class="chartDivContainer">
|
||||
<div class="chartDiv" id="ydHistoryChart"> </div>
|
||||
<p class="center" style="margin:0px;border:0px;">
|
||||
Maximum value: <span id="ydMaximum"></span> Wh<br />
|
||||
Updated every <span id="ydRefresh"></span> seconds </p>
|
||||
<body>
|
||||
{#HTML_NAV}
|
||||
<div id="wrapper">
|
||||
<div id="content">
|
||||
<h3>{#TOTAL_POWER}</h3>
|
||||
<div>
|
||||
<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}
|
||||
</p>
|
||||
</div>
|
||||
<h3>{#TOTAL_YIELD_PER_DAY}</h3>
|
||||
<div>
|
||||
<div class="chartDiv" id="ydChart"> </div>
|
||||
<p>
|
||||
{#MAXIMUM}: <span id="ydMax"></span> Wh<br />
|
||||
{#UPDATED} <span id="ydRefresh"></span> {#SECONDS}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{#HTML_FOOTER}
|
||||
|
||||
<h4 style="margin-bottom:0px;">Insert data into Yield per day history</h4>
|
||||
<fieldset style="padding: 1px;">
|
||||
<legend class="des" style="margin-top: 0px;">Insert data (*.json) i.e. from a saved "/api/yieldDayHistory" call </legend>
|
||||
<form id="form" method="POST" action="/api/insertYieldDayHistory" enctype="multipart/form-data" accept-charset="utf-8">
|
||||
<input type="button" class="btn my-4" style="padding: 3px;margin: 3px;" value="Insert" onclick="submit()">
|
||||
<input type="file" name="insert" style="width: 80%;">
|
||||
</form>
|
||||
</fieldset>
|
||||
<p></p>
|
||||
</div>
|
||||
</div>
|
||||
{#HTML_FOOTER}
|
||||
<script type="text/javascript">
|
||||
const svgns = "http://www.w3.org/2000/svg";
|
||||
var pwrExeOnce = true;
|
||||
var ydExeOnce = true;
|
||||
// make a simple rectangle
|
||||
var mRefresh = 60;
|
||||
var mLastValue = 0;
|
||||
const mChartHeight = 250;
|
||||
|
||||
<script type="text/javascript">
|
||||
const svgns = "http://www.w3.org/2000/svg";
|
||||
var phExeOnce = true;
|
||||
var ydExeOnce = true;
|
||||
// make a simple rectangle
|
||||
var mRefresh = 60;
|
||||
var phDatapoints = 512;
|
||||
var mMaximum = 0;
|
||||
var mLastValue = 0;
|
||||
var mDataValues = [];
|
||||
const mChartHight = 250;
|
||||
function parseHistory(obj, namePrefix, execOnce) {
|
||||
mRefresh = obj.refresh
|
||||
var data = Object.assign({}, obj.value)
|
||||
var numDataPts = data.length
|
||||
|
||||
function parseHistory(obj, namePrefix, execOnce) {
|
||||
mRefresh = obj["refresh"];
|
||||
phDatapoints = obj["datapoints"];
|
||||
mDataValues = Object.assign({}, obj["value"]);
|
||||
mMaximum = obj["maximum"];
|
||||
// generate svg
|
||||
if (true == execOnce) {
|
||||
let svg = document.createElementNS(svgns, "svg");
|
||||
svg.setAttribute("class", "chart");
|
||||
svg.setAttribute("width", String((phDatapoints+2) * 2));
|
||||
svg.setAttribute("height", String(mChartHight) + "");
|
||||
svg.setAttribute("aria-labelledby", "title desc");
|
||||
svg.setAttribute("role", "img");
|
||||
t = ml("title");
|
||||
t.innerHTML = "History of day";
|
||||
svg.appendChild(t);
|
||||
let g = document.createElementNS(svgns, "g");
|
||||
svg.appendChild(g);
|
||||
for (var i = 0; i < phDatapoints; i++) {
|
||||
val = mDataValues[i];
|
||||
let rect = document.createElementNS(svgns, "rect");
|
||||
rect.setAttribute("id", namePrefix+"Rect" + i);
|
||||
rect.setAttribute("x", String(i * 2) + "");
|
||||
rect.setAttribute("width", String(2) + "");
|
||||
g.appendChild(rect);
|
||||
if (true == execOnce) {
|
||||
let s = svg(null, (numDataPts + 2) * 2, mChartHeight, "chart");
|
||||
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", String(i * 2) + "");
|
||||
rect.setAttribute("width", String(2) + "");
|
||||
g.appendChild(rect);
|
||||
}
|
||||
document.getElementById(namePrefix+"Chart").appendChild(s);
|
||||
}
|
||||
document.getElementById(namePrefix+"HistoryChart").appendChild(svg);
|
||||
}
|
||||
// normalize data to chart
|
||||
let divider = mMaximum / mChartHight;
|
||||
if (divider == 0)
|
||||
divider = 1;
|
||||
for (var i = 0; i < phDatapoints; i++) {
|
||||
val = mDataValues[i];
|
||||
if (val>0)
|
||||
mLastValue = val
|
||||
val = val / divider
|
||||
rect = document.getElementById(namePrefix+"Rect" + i);
|
||||
rect.setAttribute("height", val);
|
||||
rect.setAttribute("y", mChartHight - val);
|
||||
}
|
||||
document.getElementById(namePrefix + "Maximum").innerHTML = mMaximum;
|
||||
if (mRefresh < 5)
|
||||
mRefresh = 5;
|
||||
document.getElementById(namePrefix + "Refresh").innerHTML = mRefresh;
|
||||
}
|
||||
function parsePowerHistory(obj){
|
||||
if (null != obj) {
|
||||
parseNav(obj["generic"]);
|
||||
parseHistory(obj,"ph", phExeOnce)
|
||||
let maximumDay = obj["maximumDay"];
|
||||
document.getElementById("phActual").innerHTML = mLastValue;
|
||||
document.getElementById("phMaximumDay").innerHTML = maximumDay;
|
||||
}
|
||||
if (true == phExeOnce) {
|
||||
phExeOnce = false;
|
||||
window.setInterval("getAjax('/api/powerHistory', parsePowerHistory)", mRefresh * 1000);
|
||||
// one after the other
|
||||
setTimeout(() => {
|
||||
getAjax("/api/yieldDayHistory", parseYieldDayHistory);
|
||||
} , 20);
|
||||
}
|
||||
}
|
||||
function parseYieldDayHistory(obj) {
|
||||
if (null != obj) {
|
||||
parseNav(obj["generic"]);
|
||||
parseHistory(obj, "yd", ydExeOnce)
|
||||
}
|
||||
if (true == ydExeOnce) {
|
||||
ydExeOnce = false;
|
||||
window.setInterval("getAjax('/api/yieldDayHistory', parseYieldDayHistory)", mRefresh * 500);
|
||||
}
|
||||
}
|
||||
|
||||
getAjax("/api/powerHistory", parsePowerHistory);
|
||||
</script>
|
||||
// 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;
|
||||
}
|
||||
|
||||
</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>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Save</title>
|
||||
<title>{#NAV_SAVE}</title>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<title>Serial Console</title>
|
||||
<title>{#NAV_WEBSERIAL}</title>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -36,21 +36,17 @@ textarea {
|
|||
svg rect {fill: #0000AA;}
|
||||
svg.chart {
|
||||
background: #f2f2f2;
|
||||
border: 2px solid gray;
|
||||
padding: 1px;
|
||||
border: 2px solid gray;
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
div.chartDivContainer {
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
padding: 1px;
|
||||
margin: 1px;
|
||||
}
|
||||
div.chartdivContainer span {
|
||||
color: var(--fg2);
|
||||
}
|
||||
div.chartDiv {
|
||||
padding: 0px;
|
||||
margin: 0px;
|
||||
}
|
||||
|
||||
|
||||
.topnav {
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Live</title>
|
||||
<title>{#NAV_LIVE}</title>
|
||||
{#HTML_HEADER}
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
</head>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Setup Wizard</title>
|
||||
<title>{#NAV_WIZARD}</title>
|
||||
{#HTML_HEADER}
|
||||
</head>
|
||||
<body>
|
||||
|
|
|
@ -3,13 +3,18 @@
|
|||
{
|
||||
"name": "general",
|
||||
"list": [
|
||||
{
|
||||
"token": "NAV_WIZARD",
|
||||
"en": "Setup Wizard",
|
||||
"de": "Daten"
|
||||
},
|
||||
{
|
||||
"token": "NAV_LIVE",
|
||||
"en": "Live",
|
||||
"de": "Daten"
|
||||
},
|
||||
{
|
||||
"token": "{#NAV_HISTORY}",
|
||||
"token": "NAV_HISTORY",
|
||||
"en": "History",
|
||||
"de": "Verlauf"
|
||||
},
|
||||
|
@ -28,6 +33,11 @@
|
|||
"en": "Documentation",
|
||||
"de": "Dokumentation"
|
||||
},
|
||||
{
|
||||
"token": "NAV_SAVE",
|
||||
"en": "save",
|
||||
"de": "speichern"
|
||||
},
|
||||
{
|
||||
"token": "NAV_ABOUT",
|
||||
"en": "About",
|
||||
|
@ -1334,6 +1344,46 @@
|
|||
"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…
Add table
Add a link
Reference in a new issue