Heizstab- und Bewaesserungsfenster im neuen Look, Bewaesserungszeilen enger
Beide Fenster lesen den Zustand live aus MQTT und uebernehmen jede Wahl sofort (bedienfenster.js). submitFormAjax entfaellt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,249 @@
|
||||
/* ==========================================================================
|
||||
Bedienfenster Heizstab und Bewässerung - im selben Stil wie das
|
||||
Ladefenster (ladefenster.js), mit denselben Bausteinen aus solar.css.
|
||||
|
||||
heizfensterStarten(wurzel, url) Gerüst: ajax/heater.php
|
||||
wasserfensterStarten(wurzel, url) Gerüst: ajax/watering.php
|
||||
|
||||
Beide lesen jede Sekunde aus mqttData und schicken jede Änderung sofort.
|
||||
Was eben geschickt wurde, gilt HALTEN ms lang lokal, bis das Gerät den
|
||||
neuen Stand meldet. Der Takt endet, wenn das Fenster zugeht.
|
||||
========================================================================== */
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
const HALTEN = 8000;
|
||||
const S = " "; // schmales Leerzeichen vor Einheiten
|
||||
const komma = (z, n = 1) => Number(z).toFixed(n).replace(".", ",");
|
||||
const text = t => { const v = window.mqttData && mqttData[t]; return v === undefined ? undefined : String(v); };
|
||||
const zahl = t => { const v = Number(text(t)); return isFinite(v) ? v : null; };
|
||||
|
||||
/** Gemeinsames Gerüst: lokaler Stand, Meldungszeile, Takt bis zum Schließen. */
|
||||
function geruest(wurzel, meldungsFeld, grundText) {
|
||||
const lokal = {};
|
||||
const g = {
|
||||
merke(name, wert) { lokal[name] = { wert, bis: Date.now() + HALTEN }; },
|
||||
gilt(name, gemeldet) { return lokal[name] && lokal[name].bis > Date.now() ? lokal[name].wert : gemeldet; },
|
||||
meldung(inhalt, art) {
|
||||
const m = meldungsFeld();
|
||||
m.textContent = inhalt;
|
||||
m.className = "lade-meldung" + (art ? " " + art : "");
|
||||
clearTimeout(g.uhr);
|
||||
if (art === "gut") g.uhr = setTimeout(() => { m.textContent = grundText; m.className = "lade-meldung"; }, 2200);
|
||||
},
|
||||
takt(zeichnen) {
|
||||
zeichnen();
|
||||
const uhr = setInterval(zeichnen, 1000);
|
||||
const fenster = wurzel.closest(".modal");
|
||||
if (fenster) fenster.addEventListener("hidden.bs.modal", () => clearInterval(uhr), { once: true });
|
||||
},
|
||||
};
|
||||
return g;
|
||||
}
|
||||
|
||||
function setze(el, inhalt) {
|
||||
if (el && el.textContent !== inhalt) el.textContent = inhalt;
|
||||
}
|
||||
|
||||
/** Temperatur als Farbe, wie der Speicher in der Übersicht. */
|
||||
function tempFarbe(t) {
|
||||
const f = Math.max(0, Math.min(1, (t - 20) / 60));
|
||||
const kalt = [35, 137, 186], heiss = [186, 59, 35];
|
||||
return "rgb(" + kalt.map((k, i) => Math.round(k + (heiss[i] - k) * f)).join(",") + ")";
|
||||
}
|
||||
|
||||
/* ======================================================== Heizstab */
|
||||
window.heizfensterStarten = function (wurzel, url) {
|
||||
const $ = name => wurzel.querySelector('[data-heiz="' + name + '"]');
|
||||
const g = geruest(wurzel, () => $("meldung"), "Änderungen gelten sofort");
|
||||
let ziehen = false;
|
||||
|
||||
async function senden(stufe) {
|
||||
g.meldung("wird geschickt …");
|
||||
try {
|
||||
const antwort = await fetch(url, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: "setMode=" + stufe,
|
||||
});
|
||||
const inhalt = (await antwort.text()).trim();
|
||||
if (!antwort.ok || inhalt) throw new Error(inhalt || "HTTP " + antwort.status);
|
||||
g.meldung("✓ an den Heizstab geschickt", "gut");
|
||||
} catch (e) {
|
||||
g.meldung("Nicht übernommen: " + e.message, "fehler");
|
||||
}
|
||||
zeichnen();
|
||||
}
|
||||
|
||||
function stand() {
|
||||
const w = zahl("solarManager/pHeat") || 0;
|
||||
const gemeldetFest = text("solarManager/heatMode") !== undefined && text("solarManager/heatMode") !== "eco";
|
||||
const modus = g.gilt("modus", gemeldetFest ? "fest" : "auto");
|
||||
const stufe = g.gilt("stufe", Math.max(1, Math.min(60, Math.round(w / 100) || 30)));
|
||||
return {
|
||||
w, modus, stufe,
|
||||
temps: ["t_buffT", "t_buffM", "t_buffB"].map(t => zahl("solarManager/" + t)),
|
||||
};
|
||||
}
|
||||
|
||||
function zeichnen() {
|
||||
const s = stand();
|
||||
const heizt = s.w >= 20;
|
||||
|
||||
// Speicher
|
||||
s.temps.forEach((t, i) => {
|
||||
const schicht = $("t" + i);
|
||||
if (t !== null) schicht.style.fill = tempFarbe(t);
|
||||
setze($("tt" + i), t !== null ? Math.round(t) + "°" : "–");
|
||||
});
|
||||
$("spirale").classList.toggle("an", heizt);
|
||||
const namen = ["oben", "Mitte", "unten"];
|
||||
setze($("temps"), s.temps.map((t, i) => t !== null ? namen[i] + " " + komma(t) : null).filter(Boolean).join(" · ") + S + "°C");
|
||||
|
||||
$("leistung").innerHTML = komma(s.w / 1000, 2) + S + "kW<small>" + (heizt ? "heizt" : "aus") + "</small>";
|
||||
const pille = $("pille");
|
||||
setze(pille, s.modus === "auto" ? "Automatik" : "fest");
|
||||
pille.className = "lade-pille" + (heizt ? " an" : "");
|
||||
|
||||
setze($("erklaerung"), s.modus === "auto"
|
||||
? "Heizt nur mit dem Überschuss der Anlage – ohne Sonne bleibt der Heizstab aus."
|
||||
: "Heizt dauerhaft mit " + komma(s.stufe / 10) + S + "kW – reicht die Sonne nicht, kommt der Strom aus dem Netz.");
|
||||
|
||||
wurzel.querySelectorAll("[data-heiz-modus]").forEach(b => b.setAttribute("aria-checked", b.dataset.heizModus === s.modus ? "true" : "false"));
|
||||
$("fest").hidden = s.modus !== "fest";
|
||||
const regler = $("regler");
|
||||
if (!ziehen) regler.value = s.stufe;
|
||||
const wert = Number(regler.value);
|
||||
regler.style.setProperty("--wert", ((wert - 1) / 59 * 100).toFixed(1) + "%");
|
||||
$("reglerAusgabe").innerHTML = komma(wert / 10) + S + "kW";
|
||||
wurzel.querySelectorAll("[data-heiz-stufe]").forEach(c => c.setAttribute("aria-pressed", Number(c.dataset.heizStufe) === wert ? "true" : "false"));
|
||||
}
|
||||
|
||||
wurzel.querySelectorAll("[data-heiz-modus]").forEach(b => b.addEventListener("click", () => {
|
||||
const modus = b.dataset.heizModus;
|
||||
g.merke("modus", modus);
|
||||
senden(modus === "auto" ? 0 : Number($("regler").value));
|
||||
}));
|
||||
$("regler").addEventListener("input", () => { ziehen = true; g.merke("stufe", Number($("regler").value)); zeichnen(); });
|
||||
$("regler").addEventListener("change", () => {
|
||||
ziehen = false;
|
||||
g.merke("modus", "fest");
|
||||
senden(Number($("regler").value));
|
||||
});
|
||||
wurzel.querySelectorAll("[data-heiz-stufe]").forEach(c => c.addEventListener("click", () => {
|
||||
const stufe = Number(c.dataset.heizStufe);
|
||||
g.merke("stufe", stufe);
|
||||
g.merke("modus", "fest");
|
||||
senden(stufe);
|
||||
}));
|
||||
|
||||
g.takt(zeichnen);
|
||||
};
|
||||
|
||||
/* ===================================================== Bewässerung */
|
||||
window.wasserfensterStarten = function (wurzel, url) {
|
||||
const g = geruest(wurzel, () => wurzel.querySelector('[data-w="meldung"]'), "Befehle gehen sofort an die Ventilsteuerung");
|
||||
const bilder = window.ENERGIEFLUSS_ZONENBILDER || {};
|
||||
const dauer = {}; // Zone -> gewählte Minuten
|
||||
|
||||
wurzel.querySelectorAll("[data-bild]").forEach(b => {
|
||||
if (bilder[b.dataset.bild]) b.innerHTML = '<svg viewBox="0 0 24 24">' + bilder[b.dataset.bild] + "</svg>";
|
||||
});
|
||||
wurzel.querySelectorAll("[data-zone]").forEach(k => { dauer[k.dataset.zone] = Number(k.dataset.dauer); });
|
||||
|
||||
const zeitText = sek => sek < 60 ? Math.round(sek) + S + "s" : Math.ceil(sek / 60) + S + "min";
|
||||
const labelText = l => String(l).replace(/^(\d+)m$/, "$1" + S + "min").replace(/^(\d+)s$/, "$1" + S + "s");
|
||||
|
||||
async function senden(topic, payload) {
|
||||
g.meldung("wird geschickt …");
|
||||
try {
|
||||
const antwort = await fetch(url, {
|
||||
method: "POST", credentials: "same-origin",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({ topic, payload: typeof payload === "string" ? payload : JSON.stringify(payload) }),
|
||||
});
|
||||
const d = await antwort.json().catch(() => ({}));
|
||||
if (!antwort.ok || !d.success) throw new Error(d.message || "HTTP " + antwort.status);
|
||||
g.meldung("✓ an die Ventilsteuerung geschickt", "gut");
|
||||
} catch (e) {
|
||||
g.meldung("Nicht übernommen: " + e.message, "fehler");
|
||||
}
|
||||
zeichnen();
|
||||
}
|
||||
|
||||
function zeichnen() {
|
||||
wurzel.querySelectorAll("[data-zone]").forEach(karte => {
|
||||
const key = karte.dataset.zone;
|
||||
const zone = wateringZones[key];
|
||||
if (!zone) return;
|
||||
const z = wateringZonenZustand(zone);
|
||||
const gemeldetLaeuft = z.state !== "aus" && z.state !== "--";
|
||||
const laeuft = g.gilt("laeuft-" + key, gemeldetLaeuft);
|
||||
const timer = wateringValue(wateringJson(zone.topic + "/Timers"), zone.valve);
|
||||
const rest = timer !== null ? Math.abs(wateringSeconds(timer)) : null;
|
||||
const $ = name => karte.querySelector('[data-w="' + name + '"]');
|
||||
|
||||
let zustand;
|
||||
if (laeuft && gemeldetLaeuft) zustand = (z.paused ? "pausiert" : "läuft") + (rest ? " · noch " + zeitText(rest) : "");
|
||||
else if (laeuft) zustand = "startet …";
|
||||
else if (z.bewaessert) zustand = "heute " + labelText(z.heute.label) + " gegossen";
|
||||
else if (z.state === "--") zustand = "Zustand unbekannt";
|
||||
else zustand = "heute noch nicht gegossen";
|
||||
setze($("zustand"), zustand);
|
||||
|
||||
const pille = $("pille");
|
||||
setze(pille, laeuft ? (z.paused ? "pausiert" : "läuft") : z.bewaessert ? "gegossen" : "");
|
||||
pille.className = "lade-pille" + (laeuft ? (z.paused ? " warn" : " an") : z.bewaessert ? " blau" : "");
|
||||
karte.classList.toggle("laeuft", laeuft);
|
||||
|
||||
const knopf = $("knopf");
|
||||
knopf.className = "lade-start wasser-knopf" + (laeuft ? " laeuft" : "");
|
||||
setze($("knopfText"), laeuft ? "Stoppen" : "Gießen · " + dauer[key] + S + "min");
|
||||
knopf.dataset.aktion = laeuft ? "stopp" : "start";
|
||||
|
||||
karte.querySelectorAll("[data-min]").forEach(c => c.setAttribute("aria-pressed", Number(c.dataset.min) === dauer[key] ? "true" : "false"));
|
||||
|
||||
const verlauf = wateringHistory(zone.topic, zone.valve);
|
||||
const vortag = wateringDayTotal(zone.topic, "LastWateringDay", "wateringDurationDaySecs", zone.valve, false);
|
||||
const teile = [];
|
||||
if (verlauf.dateTime !== "--") teile.push("zuletzt " + verlauf.dateTime + (verlauf.duration !== "--" ? " · " + labelText(verlauf.duration) : ""));
|
||||
if (vortag.label !== "--" && vortag.sekunden > 0) teile.push("letzter Gießtag " + labelText(vortag.label));
|
||||
setze($("info"), teile.join(" · "));
|
||||
});
|
||||
|
||||
wurzel.querySelectorAll("[data-ablauf]").forEach(c => {
|
||||
const modi = JSON.parse(c.dataset.ablauf);
|
||||
const aktiv = g.gilt("ablauf-" + c.textContent, Object.entries(modi).every(([t, m]) => wateringAutoActive(t, m)));
|
||||
c.setAttribute("aria-pressed", aktiv ? "true" : "false");
|
||||
});
|
||||
}
|
||||
|
||||
wurzel.querySelectorAll("[data-zone]").forEach(karte => {
|
||||
const key = karte.dataset.zone;
|
||||
const zone = wateringZones[key];
|
||||
karte.querySelectorAll("[data-min]").forEach(c => c.addEventListener("click", () => {
|
||||
dauer[key] = Number(c.dataset.min);
|
||||
zeichnen();
|
||||
}));
|
||||
karte.querySelector('[data-w="knopf"]').addEventListener("click", e => {
|
||||
const start = e.currentTarget.dataset.aktion === "start";
|
||||
g.merke("laeuft-" + key, start);
|
||||
senden(zone.topic + "/set", start
|
||||
? { valve: zone.valve, set: "open", timer: dauer[key] }
|
||||
: { valve: zone.valve, set: "close" });
|
||||
});
|
||||
});
|
||||
|
||||
wurzel.querySelectorAll("[data-ablauf]").forEach(c => c.addEventListener("click", async () => {
|
||||
const modi = JSON.parse(c.dataset.ablauf);
|
||||
const aktiv = c.getAttribute("aria-pressed") === "true";
|
||||
g.merke("ablauf-" + c.textContent, !aktiv);
|
||||
for (const [topic, modus] of Object.entries(modi)) {
|
||||
await senden(topic + "/auto", aktiv ? "Stop" : modus);
|
||||
}
|
||||
}));
|
||||
|
||||
g.takt(zeichnen);
|
||||
};
|
||||
})();
|
||||
@@ -73,46 +73,6 @@ async function getData(chart, url, options = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Formular des Modals per POST abschicken ("Save changes", heute nur noch
|
||||
* der Heizstab). Das Ladefenster schreibt selbst, siehe ladefenster.js.
|
||||
*/
|
||||
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 = "";
|
||||
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.
|
||||
|
||||
@@ -381,7 +381,7 @@
|
||||
wert.remove(); neben.remove();
|
||||
symbol.setAttribute("transform", `translate(0 ${-r * 0.5}) scale(1.4)`);
|
||||
k.zonen.forEach((z, i) => {
|
||||
const zy = -r * 0.1 + i * 25;
|
||||
const zy = -r * 0.06 + i * 20;
|
||||
// Eigenes Symbol (ZONENBILDER) oder ein Zeichen aus Bootstrap Icons.
|
||||
// updateWateringIndicator() färbt es über color und schreibt den
|
||||
// Tooltip in das <title>.
|
||||
@@ -622,4 +622,6 @@
|
||||
}
|
||||
|
||||
window.Energiefluss = Energiefluss;
|
||||
// Auch die Bedienfenster (bedienfenster.js) zeigen die Zonen mit diesen Symbolen.
|
||||
window.ENERGIEFLUSS_ZONENBILDER = ZONENBILDER;
|
||||
})();
|
||||
|
||||
+11
-157
@@ -443,9 +443,9 @@ var chartSettings = {
|
||||
|
||||
|
||||
function openModal(type) {
|
||||
// Das Ladefenster und die Bewaesserung uebernehmen jede Aenderung sofort -
|
||||
// "Save changes" braucht nur noch der Heizstab.
|
||||
document.getElementById("modalSaveBtn").style.display = type === "heater" ? "" : "none";
|
||||
// Alle Fenster der Solar-Seite uebernehmen jede Aenderung sofort -
|
||||
// "Save changes" braucht hier keines mehr.
|
||||
document.getElementById("modalSaveBtn").style.display = "none";
|
||||
switch(type){
|
||||
case "CarEG":
|
||||
document.getElementById("modal-title").innerHTML = "Autoladung EG";
|
||||
@@ -471,8 +471,6 @@ var chartSettings = {
|
||||
|
||||
modalBodyElement = document.getElementById('modal-body');
|
||||
modalBodyElement.innerHTML = loadingHTML("Wird geladen...");
|
||||
document.getElementById("modalSaveBtn").addEventListener("click", submitFormAjax);
|
||||
document.getElementById("modalSaveBtn").contentURL = contentURL;
|
||||
modalEV.show();
|
||||
fetch(contentURL, {
|
||||
method: 'GET',
|
||||
@@ -485,25 +483,17 @@ var chartSettings = {
|
||||
.then(html => {
|
||||
modalBodyElement.innerHTML = html;
|
||||
|
||||
var slider = document.getElementById("modal-slider");
|
||||
var span = document.getElementById("modal-slider-label");
|
||||
// Aufbau und Logik der Fenster: ladefenster.js und bedienfenster.js.
|
||||
switch(type){
|
||||
case "CarEG":
|
||||
case "CarOG":
|
||||
// Alles Weitere in js/solar/ladefenster.js.
|
||||
ladefensterStarten(modalBodyElement.querySelector(".lade"), contentURL);
|
||||
break;
|
||||
case "heater":
|
||||
bindModalSlider(slider, span, value => value == 0 ? "Automatik" : (value / 10) + " kW");
|
||||
if(mqttData["solarManager/heatMode"] == "eco"){
|
||||
document.getElementById("modal-slider").value = 0;
|
||||
}else{
|
||||
document.getElementById("modal-slider").value = Math.round(mqttData["solarManager/pHeat"]/100);
|
||||
}
|
||||
document.getElementById("modal-slider").dispatchEvent(new Event('input'));
|
||||
break;
|
||||
case "heater":
|
||||
heizfensterStarten(modalBodyElement.querySelector(".heiz"), contentURL);
|
||||
break;
|
||||
case "watering":
|
||||
initializeWateringModal();
|
||||
wasserfensterStarten(modalBodyElement.querySelector(".wasser"), contentURL);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -513,146 +503,10 @@ var chartSettings = {
|
||||
});
|
||||
}
|
||||
|
||||
function wateringCommand(topic, command, directTopic = false) {
|
||||
const feedback = document.getElementById("watering-feedback");
|
||||
const targetTopic = directTopic ? topic : topic + "/set";
|
||||
const payload = typeof command === "string" ? command : JSON.stringify(command);
|
||||
const endpoint = window.location.pathname.replace(/\/index\.php$/, "").replace(/\/$/, "") + "/ajax/watering.php";
|
||||
const showFeedback = (message, type) => {
|
||||
if (!feedback) return;
|
||||
feedback.textContent = message;
|
||||
feedback.className = "alert alert-" + type + " mb-3";
|
||||
feedback.style.whiteSpace = "pre-line";
|
||||
};
|
||||
fetch(endpoint, {
|
||||
method: "POST",
|
||||
credentials: "same-origin",
|
||||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||||
body: new URLSearchParams({ topic: targetTopic, payload: payload })
|
||||
}).then(async (response) => {
|
||||
const result = await response.json();
|
||||
if (!response.ok || !result.success) throw new Error(result.message || "Unbekannter MQTT-Fehler.");
|
||||
showFeedback("Befehl gesendet.\nTopic: " + result.topic + "\nInhalt: " + result.payload, "success");
|
||||
}).catch((error) => {
|
||||
showFeedback("Befehl konnte nicht gesendet werden: " + error.message + "\nTopic: " + targetTopic + "\nInhalt: " + payload, "danger");
|
||||
});
|
||||
}
|
||||
|
||||
function initializeWateringModal() {
|
||||
document.querySelectorAll("[data-watering-toggle]").forEach((button) => {
|
||||
const updateButton = () => {
|
||||
const zone = wateringZones[button.dataset.wateringZone];
|
||||
setzeZonenButton(button, wateringLabel(zone.topic, zone.valve));
|
||||
};
|
||||
button.addEventListener("click", () => {
|
||||
const zone = wateringZones[button.dataset.wateringZone];
|
||||
if (wateringLabel(zone.topic, zone.valve) === "aus") {
|
||||
const timerInput = document.querySelector(`[data-watering-timer='${button.dataset.wateringZone}']`);
|
||||
const timer = Math.min(120, Math.max(1, Number(timerInput.value) || 30));
|
||||
wateringCommand(zone.topic, { valve: zone.valve, set: "open", timer: timer });
|
||||
} else {
|
||||
wateringCommand(zone.topic, { valve: zone.valve, set: "close" });
|
||||
}
|
||||
});
|
||||
updateButton();
|
||||
});
|
||||
document.querySelectorAll("[data-watering-auto-toggle]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
const topics = (button.dataset.wateringTopics || button.dataset.wateringTopic).split(",");
|
||||
const modes = button.dataset.wateringAuto.split(",");
|
||||
const active = topics.every((topic, index) => wateringAutoActive(topic, modes[index]));
|
||||
topics.forEach((topic, index) => {
|
||||
wateringCommand(topic + "/auto", active ? "Stop" : modes[index], true);
|
||||
});
|
||||
});
|
||||
updateWateringAutoButton(button);
|
||||
});
|
||||
updateWateringStatus();
|
||||
}
|
||||
|
||||
// Die Zonensymbole im Bewaesserungskreis; das Fenster aktualisiert sich
|
||||
// selbst (bedienfenster.js).
|
||||
function updateWateringStatus() {
|
||||
wateringZoneList.forEach(([key, zone]) => {
|
||||
const state = wateringLabel(zone.topic, zone.valve);
|
||||
const paused = wateringIsPaused(zone.topic, zone.valve);
|
||||
const heute = wateringDayTotal(zone.topic, "WateringToday", "wateringDurationTodaySecs", zone.valve, true);
|
||||
// Die Regenautomatik laeuft zum Sonnenaufgang; wenn jemand auf die
|
||||
// Anzeige schaut, ist die Entscheidung laengst gefallen. Angezeigt wird
|
||||
// deshalb nicht mehr, was geplant ist, sondern was passiert ist. Der
|
||||
// Zaehler unterscheidet nicht, wer ausgeloest hat - eine Bewaesserung
|
||||
// von Hand zaehlt genauso, und das ist so gewollt.
|
||||
const bewaessert = state === "aus" && heute.sekunden > 0;
|
||||
|
||||
// Die Anzeigen liegen im Realtime-SVG und bleiben bestehen -> svgElement.
|
||||
const status = svgElement(zone.statusId);
|
||||
const icon = svgElement(zone.iconId);
|
||||
if (status && icon) updateWateringIndicator(status, icon, zone.label, state, paused, bewaessert);
|
||||
|
||||
// Die Modal-Elemente werden bei jedem Oeffnen neu erzeugt -> nicht cachen.
|
||||
setzeZonenButton(document.querySelector(`[data-watering-toggle][data-watering-zone='${key}']`), state);
|
||||
setzeSchild(`[data-watering-state='${key}']`, state, paused, bewaessert);
|
||||
|
||||
const history = wateringHistory(zone.topic, zone.valve);
|
||||
setzeZelle(`[data-watering-last='${key}']`, history.dateTime);
|
||||
setzeZelle(`[data-watering-duration='${key}']`, history.duration);
|
||||
const vortag = wateringDayTotal(zone.topic, "LastWateringDay", "wateringDurationDaySecs", zone.valve, false);
|
||||
setzeZelle(`[data-watering-today='${key}']`, heute.label, heute.day && "Stand: " + heute.day);
|
||||
setzeZelle(`[data-watering-prevday='${key}']`, vortag.label, vortag.day && "Bewässerungstag: " + vortag.day);
|
||||
});
|
||||
document.querySelectorAll("[data-watering-auto-toggle]").forEach(updateWateringAutoButton);
|
||||
}
|
||||
|
||||
// Der Knopf zeigt die naechste Aktion, nicht den Zustand: laeuft die Zone,
|
||||
// steht "Aus" darauf.
|
||||
function setzeZonenButton(button, state) {
|
||||
if (!button) return;
|
||||
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");
|
||||
}
|
||||
|
||||
// Zustandsschild der Zonenkarte, Farben wie im SVG: laufend gruen,
|
||||
// pausiert gelb, heute schon bewaessert blau, sonst grau.
|
||||
function setzeSchild(selektor, state, paused, bewaessert) {
|
||||
const schild = document.querySelector(selektor);
|
||||
if (!schild) return;
|
||||
let text = "unbekannt";
|
||||
let farbe = "bg-secondary";
|
||||
if (state !== "--" && state !== "aus") {
|
||||
text = paused ? "pausiert " + state : "läuft " + state;
|
||||
farbe = paused ? "bg-warning text-dark" : "bg-success";
|
||||
} else if (state === "aus") {
|
||||
text = bewaessert ? "heute bewässert" : "aus";
|
||||
farbe = bewaessert ? "bg-primary" : "bg-secondary";
|
||||
}
|
||||
if (schild.textContent !== text) schild.textContent = text;
|
||||
const klasse = "badge " + farbe;
|
||||
if (schild.className !== klasse) schild.className = klasse;
|
||||
}
|
||||
|
||||
// Schreibt nur bei echter Aenderung - die Tabelle wird im Sekundentakt
|
||||
// aktualisiert, ihre Werte aendern sich aber hoechstens minuetlich.
|
||||
function setzeZelle(selektor, text, titel) {
|
||||
const element = document.querySelector(selektor);
|
||||
if (!element) return;
|
||||
if (element.textContent !== text) element.textContent = text;
|
||||
if (titel) {
|
||||
if (element.getAttribute("title") !== titel) element.setAttribute("title", titel);
|
||||
} else if (element.hasAttribute("title")) {
|
||||
element.removeAttribute("title");
|
||||
}
|
||||
}
|
||||
|
||||
function updateWateringAutoButton(button) {
|
||||
const topics = (button.dataset.wateringTopics || button.dataset.wateringTopic).split(",");
|
||||
const modes = button.dataset.wateringAuto.split(",");
|
||||
const active = topics.every((topic, index) => wateringAutoActive(topic, modes[index]));
|
||||
if (!button.dataset.wateringLabel) {
|
||||
button.dataset.wateringLabel = button.textContent.replace(/^Automatik\s+/, "");
|
||||
}
|
||||
button.textContent = (active ? "Aus: " : "Ein: ") + button.dataset.wateringLabel;
|
||||
button.classList.toggle("btn-outline-primary", !active);
|
||||
button.classList.toggle("btn-outline-danger", active);
|
||||
wateringSymboleSetzen(svgElement);
|
||||
}
|
||||
|
||||
window.setInterval(updateWateringStatus, 1000);
|
||||
|
||||
Reference in New Issue
Block a user