Dashboard: Doppelungen zusammengelegt, Lade- und Renderlast gesenkt

Zusammenlegung
- Die sechs Endpunkte getConsData_/getProdData_{month,year,decade}.php sind
  ajax/energyHistory.php?series=&range= gewichen, der gemeinsame Aufbau der
  Chart-Daten steht in ajax/chartData.php. Die erzeugte SQL wurde gegen alle
  sechs Originale abgeglichen.
- Die 13 Raeume der Home-Ansicht stehen nur noch in restricted/rooms.php.
  home.php rendert die SVG-Kacheln daraus, homeMQTT.js bekommt dieselbe
  Tabelle als JSON (346 -> 222 bzw. 428 -> 197 Zeilen).
- Gemeinsame JS-Helfer in js/solar/common.js; geteilte Bausteine fuer
  Wetterkacheln, Chart-Skripte und das Formular der Ladesteuerung.
- Seitenvorlagen sind jetzt .php und werden eingebunden, statt per
  file_get_contents zusammengesetzt zu werden.

Fehlerbehebungen
- ?action=weather fuehrte auf eine leere Seite, es gibt jetzt weather.php.
- homeMQTT.js griff auf ein #meteogram zu, das home.php nie hatte, und brach
  damit den readystatechange-Handler ab.
- Die Heizungsseite abonnierte weatherStation/#, zeigte die Werte aber nie an.

Ladelast
- Chart.js, MQTT und Meteogramm werden nur noch auf den Seiten geladen, die
  sie brauchen; die Seitentabelle dafuer steht in index.php.
- mqtt.js durch die minifizierte Fassung ersetzt (859 -> 359 KB). MQTT.js
  5.14.1, byte-identisch mit dem signierten npm-Tarball.
- Rund 3,1 MB ungenutzte Vendor-Reste und AdminLTE-Demobilder entfernt.
- Solar 1796 -> 1240 KB, Home 1796 -> 873 KB, Historie 1796 -> 843 KB.

Speicher im Browser
- Chart.defaults.devicePixelRatio auf 1.5 gedeckelt. Die Zeichenpuffer der
  Diagramme waren mit 20-45 MB der groesste Posten der Seite.
- Das Meteogramm-iframe laedt erst beim Hinscrollen.

Realtime-SVG
- Filterbereiche auf das noetige Mass verkleinert und den wirkungslosen
  feOffset-Durchgang entfernt: 60 % weniger gerasterte Filterflaeche bei
  pixelgleichem Ergebnis.
- Die Fluss-Animation laeuft ueber transform statt stroke-dashoffset, mit
  Punkten statt Strichmuster; sie ruht, wo kein Fluss anliegt.
- Der Wertupdate schreibt nur noch bei echter Aenderung und benutzt
  textContent statt innerHTML: 9 statt 86 Elemente pro Update, davon keines
  mit Filter. describeArc rundet auf volle Grad.
- waterInfo auf scale(0.90) wie die uebrigen Info-Gruppen.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-26 22:54:28 +02:00
co-authored by Claude Opus 5
parent 29f752eafe
commit 6b8556567b
68 changed files with 1555 additions and 60085 deletions
-2
View File
File diff suppressed because one or more lines are too long
-1191
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
-14
View File
File diff suppressed because one or more lines are too long
Binary file not shown.
-7
View File
@@ -1,7 +0,0 @@
/*!
* chartjs-adapter-luxon v1.3.1
* https://www.chartjs.org
* (c) 2023 chartjs-adapter-luxon Contributors
* Released under the MIT license
*/
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(require("chart.js"),require("luxon")):"function"==typeof define&&define.amd?define(["chart.js","luxon"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Chart,e.luxon)}(this,(function(e,t){"use strict";const n={datetime:t.DateTime.DATETIME_MED_WITH_SECONDS,millisecond:"h:mm:ss.SSS a",second:t.DateTime.TIME_WITH_SECONDS,minute:t.DateTime.TIME_SIMPLE,hour:{hour:"numeric"},day:{day:"numeric",month:"short"},week:"DD",month:{month:"short",year:"numeric"},quarter:"'Q'q - yyyy",year:{year:"numeric"}};e._adapters._date.override({_id:"luxon",_create:function(e){return t.DateTime.fromMillis(e,this.options)},init(e){this.options.locale||(this.options.locale=e.locale)},formats:function(){return n},parse:function(e,n){const i=this.options,r=typeof e;return null===e||"undefined"===r?null:("number"===r?e=this._create(e):"string"===r?e="string"==typeof n?t.DateTime.fromFormat(e,n,i):t.DateTime.fromISO(e,i):e instanceof Date?e=t.DateTime.fromJSDate(e,i):"object"!==r||e instanceof t.DateTime||(e=t.DateTime.fromObject(e,i)),e.isValid?e.valueOf():null)},format:function(e,t){const n=this._create(e);return"string"==typeof t?n.toFormat(t):n.toLocaleString(t)},add:function(e,t,n){const i={};return i[n]=t,this._create(e).plus(i).valueOf()},diff:function(e,t,n){return this._create(e).diff(this._create(t)).as(n).valueOf()},startOf:function(e,t,n){if("isoWeek"===t){n=Math.trunc(Math.min(Math.max(0,n),6));const t=this._create(e);return t.minus({days:(t.weekday-n+7)%7}).startOf("day").valueOf()}return t?this._create(e).startOf(t).valueOf():e},endOf:function(e,t){return this._create(e).endOf(t).valueOf()}})}));
-7
View File
File diff suppressed because one or more lines are too long
-23895
View File
File diff suppressed because one or more lines are too long
+19
View File
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+182
View File
@@ -0,0 +1,182 @@
/*
* Gemeinsame Bausteine der Dashboard-Seiten.
* Wird vor solarMQTT.js / homeMQTT.js / heatMQTT.js / historyMQTT.js geladen.
*/
/** Sekunden als "HH:MM". */
String.prototype.toHHMM = function () {
var sec_num = parseInt(this, 10);
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
if (hours < 10) { hours = "0" + hours; }
if (minutes < 10) { minutes = "0" + minutes; }
return hours + ':' + minutes;
};
function powerToString(power) {
if (Math.abs(power) > 999) {
power = power / 1000;
return power.toPrecision(3) + " kW";
} else {
return Math.round(power) + " W";
}
}
function loadingHTML(msg) {
return "<div class='m-4'><div class='spinner-border' role='status'><span class='visually-hidden'>Loading...</span></div>&nbsp;&nbsp;&nbsp;" + msg + "</div>";
}
/**
* Diagrammdaten holen und eintragen.
* params Funktion, die {FROM, TO} liefert (Zeitraum aendert sich zur Laufzeit)
* sunrise true -> Sonnenstands-Markierungen fuer denselben Zeitraum
* Objekt {FROM, TO} -> fester Zeitraum
* autoUpdate alle 5 Minuten neu laden
* updateMode an chart.update() durchgereicht ("none" = ohne Animation)
*/
async function getData(chart, url, options = {}) {
const {
params = null,
sunrise = false,
autoUpdate = true,
updateMode = undefined,
refreshMs = 5 * 60 * 1000
} = options;
const range = params ? params() : null;
const query = range ? "?FROM=" + range.FROM + "&TO=" + range.TO : "";
try {
chart.options.events = []; // waehrend des Ladens keine Tooltips
const response = await fetch(url + query);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
chart.data = await response.json();
if (sunrise) {
const sunRange = sunrise === true ? range : sunrise;
const response2 = await fetch("ajax/getSunrise.php?FROM=" + sunRange.FROM + "&TO=" + sunRange.TO);
if (!response2.ok) {
throw new Error(`Response status: ${response2.status}`);
}
chart.options.plugins.annotation.annotations = await response2.json();
}
chart.options.events = ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'];
chart.update(updateMode);
} catch (error) {
console.log("getData " + url + ": " + error.message);
}
if (autoUpdate) {
setTimeout(function () { getData(chart, url, options); }, refreshMs);
}
}
/** Vorgerendertes HTML-Schnipsel in ein Element laden. */
async function getStats(elem_id, url) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
document.getElementById(elem_id).innerHTML = await response.text();
} catch (error) {
console.log("getStats " + url + ": " + error.message);
}
}
/**
* Formular des Modals per POST abschicken.
* Der Start/Stop-Knopf der Ladesteuerung schickt nur sich selbst.
*/
function submitFormAjax(event) {
let xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200) {
setTimeout(function () { modalEV.hide(); }, 700);
if (xmlhttp.responseText) {
alert(xmlhttp.responseText);
}
}
};
let post = "";
if (event.currentTarget.id == "evStart/Stop") {
post = "evStart/Stop=" + event.currentTarget.innerHTML;
} else {
const form = document.getElementById('modalEV').querySelector('form');
for (const element of Array.from(form.elements)) {
if (!element.name) continue;
if (element.type == "radio" || element.type == "checkbox") {
if (element.checked) {
post += element.name + "=" + encodeURIComponent(element.value) + "&";
}
} else if (element.type == "button") {
if (!element.classList.contains("accordion-button")) {
post += element.name + "=" + encodeURIComponent(element.innerHTML) + "&";
}
} else {
post += element.name + "=" + encodeURIComponent(element.value) + "&";
}
}
}
xmlhttp.open("POST", event.currentTarget.contentURL, true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
document.getElementById('modalEV').querySelector('.modal-body').innerHTML = loadingHTML("Änderungen werden übernommen...");
xmlhttp.send(post);
return false;
}
/**
* Schieberegler im Modal mit seiner Beschriftung verbinden.
* label(wert) liefert den anzuzeigenden Text.
*/
function bindModalSlider(slider, span, label) {
slider.oninput = function () {
const min = Number(slider.min);
const max = Number(slider.max);
const percent = (Number(slider.value) - min) * 100 / (max - min);
slider.style.setProperty("--background-size", `${percent}%`);
// Beschriftung mitlaufen lassen, an den Raendern etwas gebremst
let margin = percent;
if (margin < 10) {
margin = margin - 0.4 * margin;
} else if (margin < 85) {
margin = margin - 4;
} else {
margin = margin - 4 - 0.5 * (margin - 85);
}
span.setAttribute('style', 'margin-left:' + margin + '%;');
span.innerHTML = label(slider.value);
};
}
/** Meteoblue-Meteogramm einhängen, sofern die Seite einen Platz dafür hat. */
function mountMeteogram() {
const target = document.getElementById("meteogram");
if (!target) return;
// loading="lazy": das Meteogramm ist ein vollstaendiges Fremd-Dokument und
// liegt unterhalb der Falz - es wird erst geladen, wenn man hinscrollt.
target.innerHTML = "<iframe loading='lazy' src='" + meteogramURL + "' frameborder='0' scrolling=NO' allowtransparency='true' sandbox='allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox' style='width: 100%;height: 500px;border: 0;overflow: hidden;'></iframe><!-- DO NOT REMOVE THIS LINK --><a href='https://www.meteoblue.com/de/wetter/woche/index' target='_blank' rel='noopener'></a>";
}
/**
* Wetterkacheln aktualisieren (restricted/weather_cards.html).
* value(name) liefert den Wert zu weatherStation/<name> - die Seiten legen
* die MQTT-Werte unterschiedlich ab, deshalb der Zugriff über eine Funktion.
*/
function updateWeatherCards(value) {
const windDir = document.getElementById("windDir");
if (!windDir) return; // Seite ohne Wetterkacheln
windDir.style.transform = "rotate(" + (90 + Number(value("windDeg"))) + "deg)";
document.getElementById("windSpd").innerHTML = value("avgWindspeed");
document.getElementById("humidity").innerHTML = value("hum");
document.getElementById("temp").innerHTML = value("tempAmb");
document.getElementById("windDirGust").style.transform = "rotate(" + (90 + Number(value("gustDeg"))) + "deg)";
document.getElementById("windGust").innerHTML = value("maxgust");
document.getElementById("ambPress").innerHTML = Math.round(value("qff") * 10) / 10;
}
+5 -48
View File
@@ -54,7 +54,9 @@ const solarMQTT = {
function messageReceived(topic, message) {
setNestedProp(mqttData, topic, message);
if (topic == "solarManager/P_Load") {
setTimeout(function () { solarSVG.updateValuesMQTT(mqttData) }, 200); //give the object tree some time to build up and receive all values
setTimeout(function () { solarSVG.updateValuesMQTT(mqttData) }, 200); //give the object tree some time to build up and receive all values
} else if (topic == "weatherStation/windDeg") {
setTimeout(function () { updateWeatherCards(name => mqttData["weatherStation"]?.[name]) }, 200);
}
}
}
@@ -185,52 +187,7 @@ var chartSettings = {
document.addEventListener('readystatechange', function () {
if (event.target.readyState === "complete") {
solarMQTT.getMQTT();
getData(heatChart, 'ajax/getHeaterData.php');
getData(waterChart, 'ajax/getWaterData.php');
getData(heatChart, 'ajax/getHeaterData.php', { sunrise: { FROM: -24, TO: 0 } });
getData(waterChart, 'ajax/getWaterData.php', { sunrise: { FROM: -24, TO: 0 } });
}
});
String.prototype.toHHMM = function () {
var sec_num = parseInt(this, 10); // don't forget the second param
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) { hours = "0" + hours; }
if (minutes < 10) { minutes = "0" + minutes; }
return hours + ':' + minutes;
}
async function getData(chart, url, sunrise=true) {
try {
console.log("fetching");
const response = await fetch(url);
if (!response.ok) {
console.log("err");
throw new Error(`Response status: ${response.status}`);
}
chart.data = await response.json();
if(sunrise){
const response2 = await fetch("ajax/getSunrise.php?FROM=-24&TO=0");
if (!response2.ok) {
console.log("err");
throw new Error(`Response status: ${response2.status}`);
}
chart.options.plugins.annotation.annotations = await response2.json();
}
chart.update();
} catch (error) {
console.log(error.message);
}
setTimeout(function () { getData(chart, url, sunrise) }, 5 * 60 * 1000); //renew data every 5 min.
}
function powerToString(power) {
if (Math.abs(power) > 999) {
power = power / 1000
return power.toPrecision(3) + "kW"
} else {
return Math.round(power) + "W"
}
}
+6 -53
View File
@@ -139,61 +139,14 @@ Chart.defaults.plugins.legend.labels.generateLabels = legendLabels;
);
document.addEventListener('readystatechange', function () {
if (event.target.readyState === "complete") {
getData(prodChart, 'ajax/getProdData_month.php');
getData(consChart, 'ajax/getConsData_month.php');
getData(prodChartYear, 'ajax/getProdData_year.php');
getData(consChartYear, 'ajax/getConsData_year.php');
getData(prodChartDecade, 'ajax/getProdData_decade.php');
getData(consChartDecade, 'ajax/getConsData_decade.php');
//getData(prodChart, 'ajax/getProdData.php');
//getData(foreChart,'ajax/getForecastData.php', false);
getData(prodChart, 'ajax/energyHistory.php?series=prod&range=month');
getData(consChart, 'ajax/energyHistory.php?series=cons&range=month');
getData(prodChartYear, 'ajax/energyHistory.php?series=prod&range=year');
getData(consChartYear, 'ajax/energyHistory.php?series=cons&range=year');
getData(prodChartDecade, 'ajax/energyHistory.php?series=prod&range=decade');
getData(consChartDecade, 'ajax/energyHistory.php?series=cons&range=decade');
getStats("Stats-Year","ajax/getStats.php?type=ThisYear");
getStats("Stats-Lastyear","ajax/getStats.php?type=LastYear");
getStats("Stats-Prelastyear","ajax/getStats.php?type=PreLastYear");
}
});
async function getData(chart, url, sunrise=true) {
try {
console.log("fetching"+chart);
const response = await fetch(url);
if (!response.ok) {
console.log("err");
throw new Error(`Response status: ${response.status}`);
}
chart.data = await response.json();
chart.update();
//chart.options.scales.x.min = chart.data.labels[0]-(chart.data.labels[1]-chart.data.labels[0])/2;
//chart.options.scales.x.max = chart.data.labels[chart.data.labels.length-1]+(chart.data.labels[1]-chart.data.labels[0])/2;
chart.update();
} catch (error) {
console.log(error.message);
}
setTimeout(function () { getData(chart, url, sunrise) }, 5 * 60 * 1000); //renew data every 5 min.
}
async function getStats(elem_id, url) {
try {
console.log("fetching");
const response = await fetch(url);
if (!response.ok) {
console.log("err");
throw new Error(`Response status: ${response.status}`);
}
document.getElementById(elem_id).innerHTML = await response.text();
} catch (error) {
console.log(error.message);
}
setTimeout(function () { getData(chart, url, sunrise) }, 5 * 60 * 1000); //renew data every 5 min.
}
function powerToString(power) {
if (Math.abs(power) > 999) {
power = power / 1000
return power.toPrecision(3) + "kW"
} else {
return Math.round(power) + "W"
}
}
+22 -254
View File
@@ -51,205 +51,26 @@ const homeSVG = {
fillElementArray: function () {
},
updateValuesMQTT: function (mqttData) {
var htmlNode = document
//OG
if(typeof(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["Heating"] == "false") htmlNode.getElementById('OG_wozi_heater').setAttribute("display", "none");
else htmlNode.getElementById('OG_wozi_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["mode"] == "Overheating") htmlNode.getElementById('OG_wozi_buffer').setAttribute("display", "");
else htmlNode.getElementById('OG_wozi_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_pres_wozi').innerHTML = Math.floor(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_tmp_wozi').innerHTML = mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('OG_hum_wozi').innerHTML = mqttData["Raumtemp"]["OG"]["Wohnzimmer"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["OG"]["Bad"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Bad"]["Heating"] == "false") htmlNode.getElementById('OG_bad_heater').setAttribute("display", "none");
else htmlNode.getElementById('OG_bad_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Bad"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Bad"]["mode"] == "Overheating") htmlNode.getElementById('OG_bad_buffer').setAttribute("display", "");
else htmlNode.getElementById('OG_bad_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Bad"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_pres_bad').innerHTML = Math.floor(mqttData["Raumtemp"]["OG"]["Bad"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Bad"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_tmp_bad').innerHTML = mqttData["Raumtemp"]["OG"]["Bad"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Bad"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('OG_hum_bad').innerHTML = mqttData["Raumtemp"]["OG"]["Bad"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["Heating"] == "false") htmlNode.getElementById('OG_schlafen_heater').setAttribute("display", "none");
else htmlNode.getElementById('OG_schlafen_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["mode"] == "Overheating") htmlNode.getElementById('OG_schlafen_buffer').setAttribute("display", "");
else htmlNode.getElementById('OG_schlafen_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_pres_schlafen').innerHTML = Math.floor(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_tmp_schlafen').innerHTML = mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('OG_hum_schlafen').innerHTML = mqttData["Raumtemp"]["OG"]["Schlafzimmer"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["OG"]["KiZi"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["KiZi"]["Heating"] == "false") htmlNode.getElementById('OG_kizi_heater').setAttribute("display", "none");
else htmlNode.getElementById('OG_kizi_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["OG"]["KiZi"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["KiZi"]["mode"] == "Overheating") htmlNode.getElementById('OG_kizi_buffer').setAttribute("display", "");
else htmlNode.getElementById('OG_kizi_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["OG"]["KiZi"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_pres_kizi').innerHTML = Math.floor(mqttData["Raumtemp"]["OG"]["KiZi"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["KiZi"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_tmp_kizi').innerHTML = mqttData["Raumtemp"]["OG"]["KiZi"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["KiZi"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('OG_hum_kizi').innerHTML = mqttData["Raumtemp"]["OG"]["KiZi"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["OG"]["Buero"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Buero"]["Heating"] == "false") htmlNode.getElementById('OG_buero_heater').setAttribute("display", "none");
else htmlNode.getElementById('OG_buero_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Buero"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["OG"]["Buero"]["mode"] == "Overheating") htmlNode.getElementById('OG_buero_buffer').setAttribute("display", "");
else htmlNode.getElementById('OG_buero_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["OG"]["Buero"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_pres_buero').innerHTML = Math.floor(mqttData["Raumtemp"]["OG"]["Buero"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Buero"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('OG_tmp_buero').innerHTML = mqttData["Raumtemp"]["OG"]["Buero"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["OG"]["Buero"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('OG_hum_buero').innerHTML = mqttData["Raumtemp"]["OG"]["Buero"]["rHum[%]"]+"%rF";
//EG
if(typeof(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["Heating"] == "false") htmlNode.getElementById('EG_wozi_heater').setAttribute("display", "none");
else htmlNode.getElementById('EG_wozi_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["mode"] == "Overheating") htmlNode.getElementById('EG_wozi_buffer').setAttribute("display", "");
else htmlNode.getElementById('EG_wozi_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_pres_wozi').innerHTML = Math.floor(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_tmp_wozi').innerHTML = mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('EG_hum_wozi').innerHTML = mqttData["Raumtemp"]["EG"]["Wohnzimmer"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["EG"]["Bad"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Bad"]["Heating"] == "false") htmlNode.getElementById('EG_bad_heater').setAttribute("display", "none");
else htmlNode.getElementById('EG_bad_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Bad"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Bad"]["mode"] == "Overheating") htmlNode.getElementById('EG_bad_buffer').setAttribute("display", "");
else htmlNode.getElementById('EG_bad_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Bad"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_pres_bad').innerHTML = Math.floor(mqttData["Raumtemp"]["EG"]["Bad"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Bad"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_tmp_bad').innerHTML = mqttData["Raumtemp"]["EG"]["Bad"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Bad"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('EG_hum_bad').innerHTML = mqttData["Raumtemp"]["EG"]["Bad"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["EG"]["Schlafen"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Schlafen"]["Heating"] == "false") htmlNode.getElementById('EG_schlafen_heater').setAttribute("display", "none");
else htmlNode.getElementById('EG_schlafen_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Schlafen"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Schlafen"]["mode"] == "Overheating") htmlNode.getElementById('EG_schlafen_buffer').setAttribute("display", "");
else htmlNode.getElementById('EG_schlafen_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Schlafen"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_pres_schlafen').innerHTML = Math.floor(mqttData["Raumtemp"]["EG"]["Schlafen"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Schlafen"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_tmp_schlafen').innerHTML = mqttData["Raumtemp"]["EG"]["Schlafen"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Schlafen"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('EG_hum_schlafen').innerHTML = mqttData["Raumtemp"]["EG"]["Schlafen"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["EG"]["Florian"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Florian"]["Heating"] == "false") htmlNode.getElementById('EG_kizi_heater').setAttribute("display", "none");
else htmlNode.getElementById('EG_kizi_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Florian"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Florian"]["mode"] == "Overheating") htmlNode.getElementById('EG_kizi_buffer').setAttribute("display", "");
else htmlNode.getElementById('EG_kizi_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Florian"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_pres_kizi').innerHTML = Math.floor(mqttData["Raumtemp"]["EG"]["Florian"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Florian"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_tmp_kizi').innerHTML = mqttData["Raumtemp"]["EG"]["Florian"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Florian"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('EG_hum_kizi').innerHTML = mqttData["Raumtemp"]["EG"]["Florian"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["EG"]["Magdalena"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Magdalena"]["Heating"] == "false") htmlNode.getElementById('EG_buero_heater').setAttribute("display", "none");
else htmlNode.getElementById('EG_buero_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Magdalena"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["EG"]["Magdalena"]["mode"] == "Overheating") htmlNode.getElementById('EG_buero_buffer').setAttribute("display", "");
else htmlNode.getElementById('EG_buero_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["EG"]["Magdalena"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_pres_buero').innerHTML = Math.floor(mqttData["Raumtemp"]["EG"]["Magdalena"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Magdalena"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('EG_tmp_buero').innerHTML = mqttData["Raumtemp"]["EG"]["Magdalena"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["EG"]["Magdalena"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('EG_hum_buero').innerHTML = mqttData["Raumtemp"]["EG"]["Magdalena"]["rHum[%]"]+"%rF";
//UG
if(typeof(mqttData["Raumtemp"]["UG"]["Kueche"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["UG"]["Kueche"]["Heating"] == "false") htmlNode.getElementById('UG_kueche_heater').setAttribute("display", "none");
else htmlNode.getElementById('UG_kueche_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["UG"]["Kueche"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["UG"]["Kueche"]["mode"] == "Overheating") htmlNode.getElementById('UG_kueche_buffer').setAttribute("display", "");
else htmlNode.getElementById('UG_kueche_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["UG"]["Kueche"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('UG_pres_kueche').innerHTML = Math.floor(mqttData["Raumtemp"]["UG"]["Kueche"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["UG"]["Kueche"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('UG_tmp_kueche').innerHTML = mqttData["Raumtemp"]["UG"]["Kueche"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["UG"]["Kueche"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('UG_hum_kueche').innerHTML = mqttData["Raumtemp"]["UG"]["Kueche"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["UG"]["Buero"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["UG"]["Buero"]["Heating"] == "false") htmlNode.getElementById('UG_buero_heater').setAttribute("display", "none");
else htmlNode.getElementById('UG_buero_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["UG"]["Buero"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["UG"]["Buero"]["mode"] == "Overheating") htmlNode.getElementById('UG_buero_buffer').setAttribute("display", "");
else htmlNode.getElementById('UG_buero_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["UG"]["Buero"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('UG_pres_buero').innerHTML = Math.floor(mqttData["Raumtemp"]["UG"]["Buero"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["UG"]["Buero"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('UG_tmp_buero').innerHTML = mqttData["Raumtemp"]["UG"]["Buero"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["UG"]["Buero"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('UG_hum_buero').innerHTML = mqttData["Raumtemp"]["UG"]["Buero"]["rHum[%]"]+"%rF";
if(typeof(mqttData["Raumtemp"]["UG"]["Bad"]["Heating"]) != "undefined"){
if(mqttData["Raumtemp"]["UG"]["Bad"]["Heating"] == "false") htmlNode.getElementById('UG_bad_heater').setAttribute("display", "none");
else htmlNode.getElementById('UG_bad_heater').setAttribute("display", "");
}
if(typeof(mqttData["Raumtemp"]["UG"]["Bad"]["mode"]) != "undefined"){
if(mqttData["Raumtemp"]["UG"]["Bad"]["mode"] == "Overheating") htmlNode.getElementById('UG_bad_buffer').setAttribute("display", "");
else htmlNode.getElementById('UG_bad_buffer').setAttribute("display", "none");
}
if(typeof(mqttData["Raumtemp"]["UG"]["Bad"]["Set Temp[degC]"]) != "undefined")
htmlNode.getElementById('UG_pres_bad').innerHTML = Math.floor(mqttData["Raumtemp"]["UG"]["Bad"]["Set Temp[degC]"])+" °C";
if(typeof(mqttData["Raumtemp"]["UG"]["Bad"]["Temp[degC]"]) != "undefined")
htmlNode.getElementById('UG_tmp_bad').innerHTML = mqttData["Raumtemp"]["UG"]["Bad"]["Temp[degC]"]+" °C";
if(typeof(mqttData["Raumtemp"]["UG"]["Bad"]["rHum[%]"]) != "undefined")
htmlNode.getElementById('UG_hum_bad').innerHTML = mqttData["Raumtemp"]["UG"]["Bad"]["rHum[%]"]+"%rF";
updateValuesMQTT: function (mqttData) {
// homeRooms liefert restricted/rooms.php (in home.php eingebettet).
// Jeder Raum bekommt die gleiche Behandlung - Raeume kommen dort dazu,
// nicht hier.
homeRooms.forEach(function (room) {
const values = mqttData["Raumtemp"]?.[room.floor]?.[room.mqtt];
if (!values) return; // Topic noch nicht empfangen
const el = (part) => document.getElementById(room.floor + "_" + part);
if (typeof (values["Heating"]) != "undefined")
el(room.id + "_heater").setAttribute("display", values["Heating"] == "false" ? "none" : "");
if (typeof (values["mode"]) != "undefined")
el(room.id + "_buffer").setAttribute("display", values["mode"] == "Overheating" ? "" : "none");
if (typeof (values["Set Temp[degC]"]) != "undefined")
el("pres_" + room.id).innerHTML = Math.floor(values["Set Temp[degC]"]) + "\u2009\u00b0C";
if (typeof (values["Temp[degC]"]) != "undefined")
el("tmp_" + room.id).innerHTML = values["Temp[degC]"] + "\u2009\u00b0C";
if (typeof (values["rHum[%]"]) != "undefined")
el("hum_" + room.id).innerHTML = values["rHum[%]"] + "\u2009%rF";
});
}
}
@@ -336,13 +157,10 @@ function switchFloor(floor){
document.addEventListener('readystatechange', function () {
if (event.target.readyState === "complete") {
homeMQTT.getMQTT();
this.getElementById("meteogram").innerHTML = "<iframe src='"+meteogramURL+"' frameborder='0' scrolling=NO' allowtransparency='true' sandbox='allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox' style='width: 100%;height: 500px;border: 0;overflow: hidden;'></iframe><!-- DO NOT REMOVE THIS LINK --><a href='https://www.meteoblue.com/de/wetter/woche/index' target='_blank' rel='noopener'></a>";
mountMeteogram();
}
});
function loadingHTML(msg) {
return "<div class='m-4'><div class='spinner-border' role='status'><span class='visually-hidden'>Loading...</span></div>&nbsp;&nbsp;&nbsp;" + msg + "</div>"
}
function openModal(heater) {
var contentURL = "./ajax/roomtemp.php?heater="+heater;
@@ -365,21 +183,7 @@ function switchFloor(floor){
modalBodyElement.innerHTML = html;
var slider = document.getElementById("modal-slider");
var span = document.getElementById("modal-slider-label");
slider.oninput = function () {
sliderPrcnt=(slider.value-10)*5;
slider.style.setProperty("--background-size", `${sliderPrcnt}%`);
marginValue = sliderPrcnt;
if (marginValue < 10) {
marginValue = marginValue - 0.4 * marginValue;
} else if (marginValue < 85) {
marginValue = marginValue - 4;
}
else {
marginValue = marginValue - 4 - 0.5 * (marginValue - 85);
}
span.setAttribute('style', 'margin-left:' + marginValue + '%;');
span.innerHTML = this.value + " °C";
}
bindModalSlider(slider, span, value => value + " °C");
if(typeof(mqttData["Raumtemp"][heaterMQTT[0]][heaterMQTT[1]]["Set Temp[degC]"])!= "undefined")
slider.setAttribute("value",mqttData["Raumtemp"][heaterMQTT[0]][heaterMQTT[1]]["Set Temp[degC]"]);
@@ -391,39 +195,3 @@ function switchFloor(floor){
modalBodyElement.innerHTML += error.message;
});
}
function submitFormAjax(event) {
let xmlhttp = window.XMLHttpRequest ?
new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200){
setTimeout(function () { modalEV.hide() }, 700);
if(xmlhttp.responseText){
alert(xmlhttp.responseText);
}
}
}
const form = document.getElementById('modalEV').querySelector('form');
post = "";
// Get interesting form elements
const formElements = Array.from(form.elements);
for (let i = 0; i < formElements.length; i++) {
if(formElements[i].name){
if (formElements[i].type == "radio" && formElements[i].checked) {
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].value) + "&";
} else if(formElements[i].type == "checkbox" && formElements[i].checked){
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].value) + "&";
} else if(formElements[i].type == "button" && !formElements[i].classList.contains("accordion-button")){
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].innerHTML) + "&";
}else if(formElements[i].type != "checkbox" && formElements[i].type != "radio") {
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].value) + "&";
}
}
}
xmlhttp.open("POST", event.currentTarget.contentURL, true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
document.getElementById('modalEV').querySelector('.modal-body').innerHTML = loadingHTML("Änderungen werden übernommen...");
xmlhttp.send(post);
return false;
}
+230 -331
View File
@@ -77,7 +77,7 @@ const solarMQTT = {
}, 1000);
}
}else if(topic == "weatherStation/windDeg"){
setTimeout(function () { updateValuesWeather() }, 200); //give the object tree some time to build up and receive all values
setTimeout(function () { updateWeatherCards(name => mqttData["weatherStation/" + name]) }, 200); //give the object tree some time to build up and receive all values
}
}
},
@@ -89,10 +89,54 @@ const solarMQTT = {
}
}
// Die Knoten im eingebetteten Realtime-SVG bleiben ueber die Lebensdauer der
// Seite bestehen. updateValuesMQTT fasst 104 davon an - bis zu einmal pro
// Sekunde -, deshalb werden sie einmal nachgeschlagen und dann behalten.
const svgElements = {};
function svgElement(id) {
let node = svgElements[id];
if (node === undefined) {
node = svgElements[id] = document.getElementById(id);
}
return node;
}
// Nur schreiben, wenn sich der Wert wirklich geaendert hat. Jeder Schreibzugriff
// macht Style und Paint des Elements ungueltig - bei einem gefilterten Element
// wird dabei sein Filter neu gerastert. Gemessen waren rund 71 % der Zugriffe
// pro Update reine Wiederholungen desselben Werts; die entfallen damit.
const letzterWert = new Map();
function schreibe(schluessel, wert, anwenden) {
const s = String(wert);
if (letzterWert.get(schluessel) === s) return;
letzterWert.set(schluessel, s);
anwenden(s);
}
// textContent statt innerHTML: setzt einen Textknoten, ohne den HTML-Parser zu bemuehen.
function setText(id, wert) { schreibe("t:" + id, wert, s => { svgElement(id).textContent = s; }); }
function setHTML(id, markup) { schreibe("h:" + id, markup, s => { svgElement(id).innerHTML = s; }); }
function setAttr(id, name, wert) { schreibe("a:" + id + ":" + name, wert, s => { svgElement(id).setAttribute(name, s); }); }
function setDisplay(id, wert) { schreibe("d:" + id, wert, s => { svgElement(id).style.display = s; }); }
// Punktgroesse einer Flusslinie setzen und die Animation anhalten, wenn kein
// Fluss anliegt. --w ist der Durchmesser, die CSS-Regel macht daraus den Radius
// aller Punkte der Gruppe. will-change nur solange die Gruppe laeuft: es fordert
// eine eigene Compositor-Ebene an, und die kostet Speicher.
function setFlowWidth(id, width) {
const flow = svgElement(id);
const sichtbar = Math.abs(width) >= 0.5;
flow.style.setProperty("--w", (sichtbar ? Math.abs(width) : 0) + "px");
flow.style.animationPlayState = sichtbar ? "running" : "paused";
flow.style.willChange = sichtbar ? "transform" : "auto";
}
const solarSVG = {
updateCnt: 99,
/** Cache verwerfen - noetig, falls das SVG neu gezeichnet wird. */
fillElementArray: function () {
for (const id in svgElements) delete svgElements[id];
},
updateValuesMQTT: function (mqttData) {
@@ -143,16 +187,15 @@ const solarSVG = {
common = 0;
}
var htmlNode = document
htmlNode.getElementById('consumerArc').setAttribute("d", describeArc(100, 100, 95, 0, Math.round(-cons / angleFactor)));
setAttr('consumerArc', "d", describeArc(100, 100, 95, 0, Math.round(-cons / angleFactor)));
htmlNode.getElementById("ogArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(-og / angleFactor)));
htmlNode.getElementById("consumerText").innerHTML = powerToString(-cons);
setAttr("ogArc", "d", describeArc(100, 100, 95, 0, Math.round(-og / angleFactor)));
setText("consumerText", powerToString(-cons));
htmlNode.getElementById("consumerTextAllg").innerHTML = "Gemein: "+powerToString(common);
setText("consumerTextAllg", "Gemein: "+powerToString(common));
if (evPower > 0) {
htmlNode.getElementById("evCharge").style.display = "";
setDisplay("evCharge", "");
if (carRemChrg > -1) {
var today = new Date();
today.setMinutes(today.getMinutes() + carRemChrg);
@@ -162,31 +205,31 @@ const solarSVG = {
h = "0" + h;
if (m < 10)
m = "0" + m;
htmlNode.getElementById("evRemText").innerHTML = h + ":" + m
setText("evRemText", h + ":" + m);
}
} else {
htmlNode.getElementById("evRemText").innerHTML = "";
setText("evRemText", "");
}
htmlNode.getElementById("ogText").innerHTML = powerToString(-og);
htmlNode.getElementById("egText").innerHTML = powerToString(eg);
htmlNode.getElementById("ugText").innerHTML = powerToString(ug);
htmlNode.getElementById("pvText").innerHTML = powerToString(pv);
htmlNode.getElementById("pv1txt").innerHTML = powerToString(pv1);
htmlNode.getElementById("pv2txt").innerHTML = powerToString(pv2);
htmlNode.getElementById("pv3txt").innerHTML = powerToString(pv3);
htmlNode.getElementById("il1txt").innerHTML = Math.round(i_l1evu * 10) / 10 + "A"
htmlNode.getElementById("il2txt").innerHTML = Math.round(i_l2evu * 10) / 10 + "A"
htmlNode.getElementById("il3txt").innerHTML = Math.round(i_l3evu * 10) / 10 + "A"
htmlNode.getElementById("batText").innerHTML = powerToString(pbatt);
htmlNode.getElementById("heatText").innerHTML = powerToString(Pheat);
htmlNode.getElementById("evText").innerHTML = powerToString(evPower * 1000);
htmlNode.getElementById("evTextOG").innerHTML = powerToString(evPowerOG * 1000);
htmlNode.getElementById("genText").innerHTML = "Autarkie: " + Math.round(aut) + "%";
htmlNode.getElementById("genInfoText").innerHTML = "Wirkungsgrad: " + Math.round(eff) + "%";// powerToString(p_wr);
htmlNode.getElementById("gridText").innerHTML = powerToString(grid);
setText("ogText", powerToString(-og));
setText("egText", powerToString(eg));
setText("ugText", powerToString(ug));
setText("pvText", powerToString(pv));
setText("pv1txt", powerToString(pv1));
setText("pv2txt", powerToString(pv2));
setText("pv3txt", powerToString(pv3));
setText("il1txt", Math.round(i_l1evu * 10) / 10 + "A");
setText("il2txt", Math.round(i_l2evu * 10) / 10 + "A");
setText("il3txt", Math.round(i_l3evu * 10) / 10 + "A");
setText("batText", powerToString(pbatt));
setText("heatText", powerToString(Pheat));
setText("evText", powerToString(evPower * 1000));
setText("evTextOG", powerToString(evPowerOG * 1000));
setText("genText", "Autarkie: " + Math.round(aut) + "%");
setText("genInfoText", "Wirkungsgrad: " + Math.round(eff) + "%");// powerToString(p_wr);
setText("gridText", powerToString(grid));
for (i = 0; i < pvn.length; i++) {
htmlNode.getElementById("det_pv" + i + "P").innerHTML = powerToString(pvn[i]);
setText("det_pv" + i + "P", powerToString(pvn[i]));
}
tttext = ""
@@ -204,127 +247,126 @@ const solarSVG = {
i++;
}
htmlNode.getElementById("invList").innerHTML = tttext
htmlNode.getElementById("egArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(eg / angleFactor)));
htmlNode.getElementById("ugArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(ug / angleFactor)));
htmlNode.getElementById("pvArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(pv1 / angleFactor)));
htmlNode.getElementById("pv2Arc").setAttribute("d", describeArc(100, 100, 95, Math.round(pv1 / angleFactor), Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor)));
htmlNode.getElementById("pv3Arc").setAttribute("d", describeArc(100, 100, 95, Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor), Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor) + Math.round(pv3 / angleFactor)));
setHTML("invList", tttext);
setAttr("egArc", "d", describeArc(100, 100, 95, 0, Math.round(eg / angleFactor)));
setAttr("ugArc", "d", describeArc(100, 100, 95, 0, Math.round(ug / angleFactor)));
setAttr("pvArc", "d", describeArc(100, 100, 95, 0, Math.round(pv1 / angleFactor)));
setAttr("pv2Arc", "d", describeArc(100, 100, 95, Math.round(pv1 / angleFactor), Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor)));
setAttr("pv3Arc", "d", describeArc(100, 100, 95, Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor), Math.round(pv1 / angleFactor) + Math.round(pv2 / angleFactor) + Math.round(pv3 / angleFactor)));
if (pbatt < 0) {
if (limbatt < 100) {
htmlNode.getElementById("charge").style.display = "none";
htmlNode.getElementById("batLim").style.display = "";
htmlNode.getElementById("batLimText").innerHTML = Math.round(limbatt) + "%";
setDisplay("charge", "none");
setDisplay("batLim", "");
setText("batLimText", Math.round(limbatt) + "%");
} else {
htmlNode.getElementById("charge").style.display = "";
htmlNode.getElementById("batLim").style.display = "none";
setDisplay("charge", "");
setDisplay("batLim", "none");
}
htmlNode.getElementById("batArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(-pbatt / angleFactor)));
htmlNode.getElementById("battani").setAttribute("class", "stream-rev");
htmlNode.getElementById("battani").setAttribute("stroke-width", 55 * (1 - Math.exp(pbatt / 2000)));
setAttr("batArc", "d", describeArc(100, 100, 95, 0, Math.round(-pbatt / angleFactor)));
setAttr("battani", "class", "stream-rev");
setFlowWidth("battani", 55 * (1 - Math.exp(pbatt / 2000)));
} else {
htmlNode.getElementById("charge").style.display = "none";
htmlNode.getElementById("batLim").style.display = "none";
htmlNode.getElementById("batArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(pbatt / angleFactor)));
htmlNode.getElementById("battani").setAttribute("class", "stream");
htmlNode.getElementById("battani").setAttribute("stroke-width", 55 * (1 - Math.exp(-pbatt / 2000)));
setDisplay("charge", "none");
setDisplay("batLim", "none");
setAttr("batArc", "d", describeArc(100, 100, 95, 0, Math.round(pbatt / angleFactor)));
setAttr("battani", "class", "stream");
setFlowWidth("battani", 55 * (1 - Math.exp(-pbatt / 2000)));
}
htmlNode.getElementById("batChargeState").setAttribute("y", (100 - soc) + "%");
//htmlNode.getElementById("heatChargeState").setAttribute("y", 100 - ((((puffO + puffM) / 2) - 30) * 1.66) + "%");
htmlNode.getElementById('tmpH').setAttribute("stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffO));
htmlNode.getElementById('tmpM').setAttribute("stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffM));
htmlNode.getElementById('tmpL').setAttribute("stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffU));
htmlNode.getElementById("batSOC").innerHTML = soc + "%";
htmlNode.getElementById("evArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(evPower * 2000 / angleFactor)));
htmlNode.getElementById("evChargeState").setAttribute("y", (100 - evsoc) + "%");
htmlNode.getElementById("evSOC").innerHTML = evsoc + "%";
htmlNode.getElementById("evFuel").innerHTML = fuelev + "%";
setAttr("batChargeState", "y", (100 - soc) + "%");
//setAttr("heatChargeState", "y", 100 - ((((puffO + puffM) / 2) - 30) * 1.66) + "%");
setAttr('tmpH', "stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffO));
setAttr('tmpM', "stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffM));
setAttr('tmpL', "stop-color", assignColor("#2389BA", "#BA3B23", 20, 80, puffU));
setText("batSOC", soc + "%");
setAttr("evArc", "d", describeArc(100, 100, 95, 0, Math.round(evPower * 2000 / angleFactor)));
setAttr("evChargeState", "y", (100 - evsoc) + "%");
setText("evSOC", evsoc + "%");
setText("evFuel", fuelev + "%");
htmlNode.getElementById("heatArc").setAttribute("d", describeArc(100, 100, 95, 0, Math.round(Pheat / sixkWangleFactor)));
htmlNode.getElementById("heatUp").innerHTML = Math.round(puffO * 10) / 10 + " °C";
htmlNode.getElementById("heatDown").innerHTML = Math.round(puffM * 10) / 10 + " °C";
setAttr("heatArc", "d", describeArc(100, 100, 95, 0, Math.round(Pheat / sixkWangleFactor)));
setText("heatUp", Math.round(puffO * 10) / 10 + " °C");
setText("heatDown", Math.round(puffM * 10) / 10 + " °C");
if (evlock == false) {
htmlNode.getElementById("evLock").style.display = "none";
setDisplay("evLock", "none");
} else {
htmlNode.getElementById("evLock").style.display = "";
setDisplay("evLock", "");
}
if (heatMode == "eco") {
htmlNode.getElementById("heatEco").style.display = "";
htmlNode.getElementById("heatDefault").style.display = "none";
setDisplay("heatEco", "");
setDisplay("heatDefault", "none");
} else {
htmlNode.getElementById("heatEco").style.display = "none";
htmlNode.getElementById("heatDefault").style.display = "";
setDisplay("heatEco", "none");
setDisplay("heatDefault", "");
}
if (evMode == "Eco") {
htmlNode.getElementById("evEco").style.display = "";
htmlNode.getElementById("evNextTrip").style.display = "none";
htmlNode.getElementById("evDefault").style.display = "none";
setDisplay("evEco", "");
setDisplay("evNextTrip", "none");
setDisplay("evDefault", "none");
} else if (evMode == "Next Trip") {
htmlNode.getElementById("evEco").style.display = "none";
htmlNode.getElementById("evNextTrip").style.display = "";
htmlNode.getElementById("evDefault").style.display = "none";
setDisplay("evEco", "none");
setDisplay("evNextTrip", "");
setDisplay("evDefault", "none");
} else {
htmlNode.getElementById("evEco").style.display = "none";
htmlNode.getElementById("evNextTrip").style.display = "none";
htmlNode.getElementById("evDefault").style.display = "";
setDisplay("evEco", "none");
setDisplay("evNextTrip", "none");
setDisplay("evDefault", "");
}
if (heatOG) {
htmlNode.getElementById("heatOG").style.display = "";
setDisplay("heatOG", "");
} else {
htmlNode.getElementById("heatOG").style.display = "none";
setDisplay("heatOG", "none");
}
if (heatEG) {
htmlNode.getElementById("heatEG").style.display = "";
setDisplay("heatEG", "");
} else {
htmlNode.getElementById("heatEG").style.display = "none";
setDisplay("heatEG", "none");
}
if (plugev != "no car") {
htmlNode.getElementById("evPlug").style.display = "";
setDisplay("evPlug", "");
} else {
htmlNode.getElementById("evPlug").style.display = "none";
setDisplay("evPlug", "none");
}
if (evModeOG == "Eco") {
htmlNode.getElementById("evEcoOG").style.display = "";
htmlNode.getElementById("evNextTripOG").style.display = "none";
htmlNode.getElementById("evDefaultOG").style.display = "none";
setDisplay("evEcoOG", "");
setDisplay("evNextTripOG", "none");
setDisplay("evDefaultOG", "none");
} else if (evModeOG == "Next Trip") {
htmlNode.getElementById("evEcoOG").style.display = "none";
htmlNode.getElementById("evNextTripOG").style.display = "";
htmlNode.getElementById("evDefaultOG").style.display = "none";
setDisplay("evEcoOG", "none");
setDisplay("evNextTripOG", "");
setDisplay("evDefaultOG", "none");
} else {
htmlNode.getElementById("evEcoOG").style.display = "none";
htmlNode.getElementById("evNextTripOG").style.display = "none";
htmlNode.getElementById("evDefaultOG").style.display = "";
setDisplay("evEcoOG", "none");
setDisplay("evNextTripOG", "none");
setDisplay("evDefaultOG", "");
}
if (evPlugOG > 0) {
htmlNode.getElementById("evPlugOG").style.display = "";
setDisplay("evPlugOG", "");
} else {
htmlNode.getElementById("evPlugOG").style.display = "none";
setDisplay("evPlugOG", "none");
}
if (grid < 0) {
htmlNode.getElementById("gridArc").setAttribute("d", describeArc(100, 100, 95, 0, -grid / angleFactor));
htmlNode.getElementById("consGridani").setAttribute("class", "stream-rev");
htmlNode.getElementById("consGridani").setAttribute("stroke-width", 55 * (1 - Math.exp(grid / 2000)));
setAttr("gridArc", "d", describeArc(100, 100, 95, 0, -grid / angleFactor));
setAttr("consGridani", "class", "stream-rev");
setFlowWidth("consGridani", 55 * (1 - Math.exp(grid / 2000)));
} else {
htmlNode.getElementById("gridArc").setAttribute("d", describeArc(100, 100, 95, 0, grid / angleFactor));
htmlNode.getElementById("consGridani").setAttribute("class", "stream");
htmlNode.getElementById("consGridani").setAttribute("stroke-width", 55 * (1 - Math.exp(-grid / 2000)));
setAttr("gridArc", "d", describeArc(100, 100, 95, 0, grid / angleFactor));
setAttr("consGridani", "class", "stream");
setFlowWidth("consGridani", 55 * (1 - Math.exp(-grid / 2000)));
}
htmlNode.getElementById("consEVani").setAttribute("stroke-width", 55 * (1 - Math.exp(-evPower)));
htmlNode.getElementById("consEVOGani").setAttribute("stroke-width", 55 * (1 - Math.exp(-evPowerOG)));
htmlNode.getElementById("consOGani").setAttribute("stroke-width", 55 * (1 - Math.exp(og / 2000)));
setFlowWidth("consEVani", 55 * (1 - Math.exp(-evPower)));
setFlowWidth("consEVOGani", 55 * (1 - Math.exp(-evPowerOG)));
setFlowWidth("consOGani", 55 * (1 - Math.exp(og / 2000)));
htmlNode.getElementById("consUGani").setAttribute("stroke-width", 55 * (1 - Math.exp( -ug / 2000)));
setFlowWidth("consUGani", 55 * (1 - Math.exp( -ug / 2000)));
htmlNode.getElementById("consHeatAni").setAttribute("stroke-width", 55 * (1 - Math.exp(-Pheat / 2000)));
htmlNode.getElementById("consAni").setAttribute("stroke-width", 55 * (1 - Math.exp(cons / 2000)));
htmlNode.getElementById("consEGani").setAttribute("stroke-width", 55 * (1 - Math.exp(-eg / 2000)));
htmlNode.getElementById("pvani").setAttribute("stroke-width", 55 * (1 - Math.exp(-pv / 2000)));
htmlNode.getElementById("pvani");
}
setFlowWidth("consHeatAni", 55 * (1 - Math.exp(-Pheat / 2000)));
setFlowWidth("consAni", 55 * (1 - Math.exp(cons / 2000)));
setFlowWidth("consEGani", 55 * (1 - Math.exp(-eg / 2000)));
setFlowWidth("pvani", 55 * (1 - Math.exp(-pv / 2000)));
}
}
@@ -472,6 +514,7 @@ var chartSettings = {
var mqttData = {};
var timeFrom = -24;
var timeTo = 12;
const dayRange = () => ({ FROM: timeFrom, TO: timeTo });
const consChart = new Chart(
document.querySelector('#consumption-chart'),
Object.assign({}, chartSettings)
@@ -487,8 +530,8 @@ var chartSettings = {
);
function updateCharts(){
getData(consChart, 'ajax/getConsData.php',true,false,false);
getData(prodChart, 'ajax/getProdData.php',true,false,false);
getData(consChart, 'ajax/getConsData.php', { params: dayRange, sunrise: true, autoUpdate: false, updateMode: "none" });
getData(prodChart, 'ajax/getProdData.php', { params: dayRange, sunrise: true, autoUpdate: false, updateMode: "none" });
}
function prevDay(){
@@ -531,29 +574,16 @@ var chartSettings = {
document.addEventListener('readystatechange', function () {
if (event.target.readyState === "complete") {
solarMQTT.getMQTT();
getData(consChart, 'ajax/getConsData.php');
getData(prodChart, 'ajax/getProdData.php');
getData(foreChart,'ajax/getForecastData.php', false);
getData(consChart, 'ajax/getConsData.php', { params: dayRange, sunrise: true, updateMode: "none" });
getData(prodChart, 'ajax/getProdData.php', { params: dayRange, sunrise: true, updateMode: "none" });
getData(foreChart, 'ajax/getForecastData.php', { params: dayRange, updateMode: "none" });
getStats("Stats-Year","ajax/getStats.php?type=ThisYear");
solarSVG.fillElementArray();
this.getElementById("meteogram").innerHTML = "<iframe src='"+meteogramURL+"' frameborder='0' scrolling=NO' allowtransparency='true' sandbox='allow-same-origin allow-scripts allow-popups allow-popups-to-escape-sandbox' style='width: 100%;height: 500px;border: 0;overflow: hidden;'></iframe><!-- DO NOT REMOVE THIS LINK --><a href='https://www.meteoblue.com/de/wetter/woche/index' target='_blank' rel='noopener'></a>";
mountMeteogram();
}
});
String.prototype.toHHMM = function () {
var sec_num = parseInt(this, 10); // don't forget the second param
var hours = Math.floor(sec_num / 3600);
var minutes = Math.floor((sec_num - (hours * 3600)) / 60);
//var seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) { hours = "0" + hours; }
if (minutes < 10) { minutes = "0" + minutes; }
return hours + ':' + minutes;
}
function loadingHTML(msg) {
return "<div class='m-4'><div class='spinner-border' role='status'><span class='visually-hidden'>Loading...</span></div>&nbsp;&nbsp;&nbsp;" + msg + "</div>"
}
function openModal(type) {
document.getElementById("modalSaveBtn").style.display = type === "watering" ? "none" : "";
@@ -600,20 +630,7 @@ var chartSettings = {
var span = document.getElementById("modal-slider-label");
switch(type){
case "CarEG":
slider.oninput = function () {
marginValue = parseInt(this.value);
slider.style.setProperty("--background-size", `${marginValue}%`);
if (marginValue < 10) {
marginValue = marginValue - 0.4 * marginValue;
} else if (marginValue < 85) {
marginValue = marginValue - 4;
}
else {
marginValue = marginValue - 4 - 0.5 * (marginValue - 85);
}
span.setAttribute('style', 'margin-left:' + marginValue + '%;');
span.innerHTML = this.value + "%";
}
bindModalSlider(slider, span, value => value + "%");
document.getElementById("evStart/Stop").addEventListener("click", submitFormAjax);
document.getElementById("evStart/Stop").contentURL = contentURL;
mode = new TextDecoder().decode(mqttData["wattpilot/properties/lmo/state"]);
@@ -636,20 +653,7 @@ var chartSettings = {
else if (amp == "6") { document.getElementById("EV_6A").click(); }
break;
case "CarOG":
slider.oninput = function () {
marginValue = parseInt((this.value-this.min)*100/this.max);
slider.style.setProperty("--background-size", `${marginValue}%`);
if (marginValue < 10) {
marginValue = marginValue - 0.4 * marginValue;
} else if (marginValue < 85) {
marginValue = marginValue - 4;
}
else {
marginValue = marginValue - 4 - 0.5 * (marginValue - 85);
}
span.setAttribute('style', 'margin-left:' + marginValue + '%;');
span.innerHTML = this.value + "%";
}
bindModalSlider(slider, span, value => value + "%");
document.getElementById("evStart/Stop").addEventListener("click", submitFormAjax);
document.getElementById("evStart/Stop").contentURL = contentURL;
mode = mqttData["go-eCharger/270003/lmo"];
@@ -671,24 +675,7 @@ var chartSettings = {
else if (amp == "6") { document.getElementById("EV_6A").click(); }
break;
case "heater":
slider.oninput = function () {
marginValue = parseInt((this.value-this.min)*100/this.max);
slider.style.setProperty("--background-size", `${marginValue}%`);
if (marginValue < 10) {
marginValue = marginValue - 0.4 * marginValue;
} else if (marginValue < 85) {
marginValue = marginValue - 4;
}
else {
marginValue = marginValue - 4 - 0.5 * (marginValue - 85);
}
span.setAttribute('style', 'margin-left:' + marginValue + '%;');
if(this.value == 0){
span.innerHTML = "Automatik";
}else{
span.innerHTML = (this.value/10) + " kW";
}
}
bindModalSlider(slider, span, value => value == 0 ? "Automatik" : (value / 10) + " kW");
if(mqttData["solarManager/heatMode"] == "eco"){
document.getElementById("modal-slider").value = 0;
}else{
@@ -744,7 +731,8 @@ var chartSettings = {
const updateButton = () => {
const zone = wateringZones[button.dataset.wateringZone];
const state = wateringLabel(zone.topic, zone.valve);
button.textContent = state === "aus" ? "Ein" : "Aus";
const beschriftung = state === "aus" ? "Ein" : "Aus";
if (button.textContent !== beschriftung) button.textContent = beschriftung;
button.classList.toggle("btn-success", state === "aus");
button.classList.toggle("btn-danger", state !== "aus");
};
@@ -774,14 +762,27 @@ var chartSettings = {
updateWateringStatus();
}
// Die Anzeige laeuft im Sekundentakt, die Nutzlasten aendern sich aber nur,
// wenn eine neue MQTT-Nachricht kommt. messageReceived legt dabei jedes Mal
// ein neues Buffer-Objekt ab - der Identitaetsvergleich reicht also als
// Gueltigkeitspruefung, ein Invalidieren von Hand braucht es nicht.
const wateringJsonCache = {};
const wateringDecoder = new TextDecoder();
function wateringJson(topic) {
const value = mqttData[topic];
if (!value) return null;
const cached = wateringJsonCache[topic];
if (cached && cached.raw === value) return cached.parsed;
let parsed = null;
try {
return JSON.parse(typeof value === "string" ? value : new TextDecoder().decode(value));
parsed = JSON.parse(typeof value === "string" ? value : wateringDecoder.decode(value));
} catch (error) {
return null;
parsed = null;
}
wateringJsonCache[topic] = { raw: value, parsed: parsed };
return parsed;
}
function wateringHistory(topic, valve) {
@@ -872,15 +873,24 @@ var chartSettings = {
return !!(status && status.would_water && !status.watered_recently);
}
// Konstante Tabellen - stehen ausserhalb, damit der Sekundentakt sie nicht
// jedes Mal neu aufbaut.
const wateringIndicators = [
["wateringFrontStatus", "wateringFrontIcon", "Vorn", "Gartenwasser/vorn", 6, "garten_vorn"],
["wateringTroughStatus", "wateringTroughIcon", "Tröge", "Gartenwasser/vorn", 1, "troege"],
["wateringBedsStatus", "wateringBedsIcon", "Beete", "Gartenwasser/hinten", 6, "hochbeete"]
];
const wateringHistoryRows = [
["troughs", "Gartenwasser/vorn", 1],
["frontGarden", "Gartenwasser/vorn", 6],
["raisedBeds", "Gartenwasser/hinten", 6]
];
function updateWateringStatus() {
const indicators = [
["wateringFrontStatus", "wateringFrontIcon", "Vorn", "Gartenwasser/vorn", 6, "garten_vorn"],
["wateringTroughStatus", "wateringTroughIcon", "Tröge", "Gartenwasser/vorn", 1, "troege"],
["wateringBedsStatus", "wateringBedsIcon", "Beete", "Gartenwasser/hinten", 6, "hochbeete"]
];
indicators.forEach(([statusId, iconId, name, topic, valve, autoZoneKey]) => {
const status = document.getElementById(statusId);
const icon = document.getElementById(iconId);
// Die Anzeigen liegen im Realtime-SVG und bleiben bestehen -> svgElement.
wateringIndicators.forEach(([statusId, iconId, name, topic, valve, autoZoneKey]) => {
const status = svgElement(statusId);
const icon = svgElement(iconId);
if (!status || !icon) return;
const state = wateringLabel(topic, valve);
const planned = state === "aus" && wateringAutoPlanned(autoZoneKey);
@@ -889,16 +899,14 @@ var chartSettings = {
document.querySelectorAll("[data-watering-toggle]").forEach((button) => {
const zone = wateringZones[button.dataset.wateringZone];
const state = wateringLabel(zone.topic, zone.valve);
button.textContent = state === "aus" ? "Ein" : "Aus";
const beschriftung = state === "aus" ? "Ein" : "Aus";
if (button.textContent !== beschriftung) button.textContent = beschriftung;
button.classList.toggle("btn-success", state === "aus");
button.classList.toggle("btn-danger", state !== "aus");
});
document.querySelectorAll("[data-watering-auto-toggle]").forEach(updateWateringAutoButton);
[
["troughs", "Gartenwasser/vorn", 1],
["frontGarden", "Gartenwasser/vorn", 6],
["raisedBeds", "Gartenwasser/hinten", 6]
].forEach(([zone, topic, valve]) => {
// Die Modal-Elemente werden bei jedem Oeffnen neu erzeugt -> nicht cachen.
wateringHistoryRows.forEach(([zone, topic, valve]) => {
const history = wateringHistory(topic, valve);
const dateElement = document.querySelector(`[data-watering-last='${zone}']`);
const durationElement = document.querySelector(`[data-watering-duration='${zone}']`);
@@ -923,132 +931,29 @@ var chartSettings = {
// "Aus" bleibt bei nicht geplant stehen; bei geplant ersetzt die
// Stoppuhr (U+23F1, ohne Emoji-Variationsselektor -> einfarbig, folgt
// der Icon-Farbe) den Text, um Platz auf der textPath zu sparen.
status.textContent = state === "aus" ? (planned ? "⏱" : "Aus") : state;
// Laeuft im Sekundentakt, aendert sich aber selten - ohne Pruefung
// wuerde jede Sekunde derselbe Wert geschrieben und das Icon samt
// Filter neu gezeichnet.
const text = state === "aus" ? (planned ? "⏱" : "Aus") : state;
if (status.textContent !== text) status.textContent = text;
// Geplant-Blau ("#3A6292") entspricht dem Wassertropfen-Icon (id="water").
const color = state !== "aus" ? (paused ? "#ffc107" : "#20c997") : planned ? "#3A6292" : "#6c757d";
icon.setAttribute("color", color);
icon.setAttribute("fill", color);
icon.style.color = color;
icon.querySelector("title").textContent = name + ": " + (state === "aus" && planned ? "Aus (Automatik heute noch geplant)" : state);
if (icon.getAttribute("color") !== color) {
icon.setAttribute("color", color);
icon.setAttribute("fill", color);
icon.style.color = color;
}
const titel = name + ": " + (state === "aus" && planned ? "Aus (Automatik heute noch geplant)" : state);
const titelKnoten = icon.querySelector("title");
if (titelKnoten.textContent !== titel) titelKnoten.textContent = titel;
}
window.setInterval(updateWateringStatus, 1000);
function submitFormAjax(event) {
let xmlhttp = window.XMLHttpRequest ?
new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
xmlhttp.onreadystatechange = function () {
if (this.readyState === 4 && this.status === 200)
modalEV.hide();
}
const form = document.getElementById('modalEV').querySelector('form');
post = "";
// ✅ Get interesting form elements
if (event.currentTarget.id == "evStart/Stop") {
post = "evStart/Stop=" + event.currentTarget.innerHTML;
} else {
const formElements = Array.from(form.elements);
for (let i = 0; i < formElements.length; i++) {
if(formElements[i].name){
if (formElements[i].type == "radio" && formElements[i].checked) {
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].value) + "&";
} else if(formElements[i].type == "checkbox" && formElements[i].checked){
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].value) + "&";
} else if(formElements[i].type == "button" && !formElements[i].classList.contains("accordion-button")){
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].innerHTML) + "&";
}else if(formElements[i].type != "checkbox" && formElements[i].type != "radio") {
post += formElements[i].name + "=" + encodeURIComponent(formElements[i].value) + "&";
}
}
}
}
xmlhttp.open("POST", event.currentTarget.contentURL, true);
xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
document.getElementById('modalEV').querySelector('.modal-body').innerHTML = loadingHTML("Änderungen werden übernommen...");
xmlhttp.send(post);
return false;
}
async function getData(chrt, url, sunrise=true, autoUpdate=true, animation=true) {
try {
chrt.options.events = [];
console.log("fetching "+url+"?FROM="+timeFrom+"&TO="+timeTo);
const response = await fetch(url+"?FROM="+timeFrom+"&TO="+timeTo);
if (!response.ok) {
console.log("err");
throw new Error(`Response status: ${response.status}`);
}
chrt.data = await response.json();
if(sunrise){
const response2 = await fetch("ajax/getSunrise.php?FROM="+timeFrom+"&TO="+timeTo);
if (!response2.ok) {
console.log("err");
throw new Error(`Response status: ${response2.status}`);
}
chrt.options.plugins.annotation.annotations = await response2.json();
}
chrt.options.events = ['mousemove', 'mouseout', 'click', 'touchstart', 'touchmove'];
chrt.update("none");
} catch (error) {
console.log(error.message);
}
if(autoUpdate){
setTimeout(function () { getData(chrt, url, sunrise,autoUpdate,animation) }, 5 * 60 * 1000); //renew data every 5 min.
}
}
async function getDataOnly(chart, url, sunrise=true, autoUpdate=true, animation=true) {
try {
console.log("fetching "+url+"?FROM="+timeFrom+"&TO="+timeTo);
const response = await fetch(url+"?FROM="+timeFrom+"&TO="+timeTo);
if (!response.ok) {
console.log("err");
throw new Error(`Response status: ${response.status}`);
}
chart.data = await response.json();
if(sunrise){
const response2 = await fetch("ajax/getSunrise.php?FROM="+timeFrom+"&TO="+timeTo);
if (!response2.ok) {
console.log("err");
throw new Error(`Response status: ${response2.status}`);
}
chart.options.plugins.annotation.annotations = await response2.json();
}
} catch (error) {
console.log(error.message);
}
if(autoUpdate){
setTimeout(function () { getData(chart, url, sunrise,autoUpdate,animation) }, 5 * 60 * 1000); //renew data every 5 min.
}
}
async function getStats(elem_id, url) {
try {
console.log("fetching");
const response = await fetch(url);
if (!response.ok) {
console.log("err");
throw new Error(`Response status: ${response.status}`);
}
document.getElementById(elem_id).innerHTML = await response.text();
} catch (error) {
console.log(error.message);
}
}
function powerToString(power) {
if (Math.abs(power) > 999) {
power = power / 1000
return power.toPrecision(3) + "kW"
} else {
return Math.round(power) + "W"
}
}
function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
var angleInRadians = (angleInDegrees - 90) * Math.PI / 180.0;
@@ -1090,29 +995,23 @@ var chartSettings = {
return color;
}
function describeArc(x, y, radius, startAngle, endAngle) {
if (endAngle > 288) {
endAngle = 288;
function describeArc(x, y, radius, startAngle, endAngle) {
if (endAngle > 288) {
endAngle = 288;
}
// Auf volle Grad runden: bei Radius 95 ist ein Grad rund 1,7 Einheiten,
// feiner sieht niemand. Dafuer bleibt das d-Attribut bei kleinen
// Schwankungen unveraendert und setAttr unterdrueckt den Schreibvorgang -
// sonst laesst jeder Zappler den Bogen samt Filter neu zeichnen.
startAngle = Math.round(startAngle);
endAngle = Math.round(endAngle);
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
// zwei Nachkommastellen genuegen und halten das Attribut kurz
var rnd = (v) => Math.round(v * 100) / 100;
return [
"M", rnd(start.x), rnd(start.y),
"A", radius, radius, 0, largeArcFlag, 0, rnd(end.x), rnd(end.y)
].join(" ");
}
var start = polarToCartesian(x, y, radius, endAngle);
var end = polarToCartesian(x, y, radius, startAngle);
var largeArcFlag = endAngle - startAngle <= 180 ? "0" : "1";
var d = [
"M", start.x, start.y,
"A", radius, radius, 0, largeArcFlag, 0, end.x, end.y
].join(" ");
return d;
}
function updateValuesWeather(){
var div=document.getElementById("windDir");
div.style.transform = "rotate("+(90+Number(mqttData["weatherStation/windDeg"]))+"deg)";
document.getElementById("windSpd").innerHTML = mqttData["weatherStation/avgWindspeed"];
document.getElementById("humidity").innerHTML = mqttData["weatherStation/hum"];
document.getElementById("temp").innerHTML = mqttData["weatherStation/tempAmb"];
document.getElementById("windDirGust").style.transform = "rotate("+(90+Number(mqttData["weatherStation/gustDeg"]))+"deg)";
document.getElementById("windGust").innerHTML = mqttData["weatherStation/maxgust"];
document.getElementById("ambPress").innerHTML = Math.round(mqttData["weatherStation/qff"]*10)/10;
}
+33
View File
@@ -0,0 +1,33 @@
/*
* Wetterseite: Messwerte der eigenen Wetterstation und das Meteogramm.
* Die Anzeige selbst steckt in updateWeatherCards() / mountMeteogram()
* in common.js, hier bleibt nur die MQTT-Anbindung.
*/
var mqttData = {};
const weatherMQTT = {
getMQTT: function () {
const client = mqtt.connect("wss://mqtt.nas.el-wa.org:443", { rejectUnauthorized: false });
client.on("connect", function () {
client.subscribe("weatherStation/#");
});
client.on("message", function (topic, message) {
mqttData[topic] = message;
if (topic == "weatherStation/windDeg") {
// etwas warten, bis die uebrigen Werte eingetroffen sind
setTimeout(function () { updateWeatherCards(name => mqttData["weatherStation/" + name]) }, 200);
}
});
client.on("error", function (error) {
console.log("MQTT: " + error.message);
});
}
};
document.addEventListener('readystatechange', function () {
if (event.target.readyState === "complete") {
weatherMQTT.getMQTT();
mountMeteogram();
}
});