From 2ef946c73285e0188d271ba694f4e2dcfebdacbc Mon Sep 17 00:00:00 2001 From: Moirtz Wagner Date: Fri, 4 Sep 2026 08:53:03 +0200 Subject: [PATCH] Akkugroessen der beiden Autos stimmen und stehen in der Konfiguration Der Schieberegler "Zusaetzlich benoetigte Ladung" rechnet Prozent in Wattstunden um. Dafuer standen bisher drei verschiedene Zahlen im Code: 140 Wh/% beim Schreiben der EG-Wallbox, 90 Wh/% beim go-e und feste 14000 Wh beim Zurueckrechnen im Browser - fuer beide Autos dieselbe. Eine Einstellung von 50 % am go-e schrieb 4500 Wh und las sich beim naechsten Oeffnen als 32 % zurueck. Jetzt kommt die Zahl aus einer Quelle: restricted/config.php nennt Auto und nutzbare Akkukapazitaet je Wallbox, carForm.php gibt die Wattstunden je Prozent als data-Attribut an den Schieberegler weiter, und das JavaScript rechnet damit zurueck statt mit einer festen Konstanten. EG: Skoda Enyaq RS, Modelljahr 2026, 84 kWh brutto / 79 kWh nutzbar OG: Jeep Compass 4xe, 11,4 kWh Das Lademodal zeigt Auto und Kapazitaet jetzt unter dem Regler an - damit sichtbar ist, worauf sich die Prozente beziehen. Co-Authored-By: Claude Opus 5 --- ajax/carForm.php | 19 +++++- ajax/carSteuerung.php | 4 +- js/solar/solarMQTT.js | 129 ++++++++++++++++++++++----------------- restricted/config.php | 41 +++++++++++++ restricted/wallboxen.php | 22 +++++-- 5 files changed, 150 insertions(+), 65 deletions(-) create mode 100644 restricted/config.php diff --git a/ajax/carForm.php b/ajax/carForm.php index c973f89..14a874c 100644 --- a/ajax/carForm.php +++ b/ajax/carForm.php @@ -1,9 +1,17 @@
@@ -62,7 +70,12 @@
- + +
+ , + kWh nutzbar +
1% 100% diff --git a/ajax/carSteuerung.php b/ajax/carSteuerung.php index fa9fd92..8a3f74b 100644 --- a/ajax/carSteuerung.php +++ b/ajax/carSteuerung.php @@ -44,9 +44,9 @@ if (checkLogin()) { if ($modus !== null) { // Der Schieberegler liefert Prozent zusaetzlich benoetigter // Ladung; die Wallbox will Wattstunden. Der Faktor haengt am - // Auto, deshalb steht er je Wallbox in der Tabelle. + // Auto und steht deshalb in restricted/config.php. $prozent = min(100, max(0, intval($_POST["fte"] ?? 0))); - $energie = $prozent * $konf["whProProzent"]; + $energie = $prozent * wallboxWhProProzent($wallbox); $strom = min(16, max(6, intval($_POST["evAmp"] ?? 6))); diff --git a/js/solar/solarMQTT.js b/js/solar/solarMQTT.js index b2a33a2..2f337ef 100644 --- a/js/solar/solarMQTT.js +++ b/js/solar/solarMQTT.js @@ -105,44 +105,44 @@ function svgElement(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; }); } - + +// 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); - // Eine negative Breite heisst "kein Fluss in dieser Richtung" - die - // Aufrufer rechnen 55*(1-exp(x)), was bei umgekehrtem Vorzeichen negativ - // wird. Frueher zeichnete eine negative stroke-width schlicht nichts. - // Math.max haelt das bei; Math.abs wuerde stattdessen einen Fluss in der - // falschen Richtung mit voller Staerke anzeigen. - const breite = Math.max(0, width); - const sichtbar = breite >= 0.5; - flow.style.setProperty("--w", breite + "px"); - flow.style.animationPlayState = sichtbar ? "running" : "paused"; - flow.style.willChange = sichtbar ? "transform" : "auto"; -} - + +// 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); + // Eine negative Breite heisst "kein Fluss in dieser Richtung" - die + // Aufrufer rechnen 55*(1-exp(x)), was bei umgekehrtem Vorzeichen negativ + // wird. Frueher zeichnete eine negative stroke-width schlicht nichts. + // Math.max haelt das bei; Math.abs wuerde stattdessen einen Fluss in der + // falschen Richtung mit voller Staerke anzeigen. + const breite = Math.max(0, width); + const sichtbar = breite >= 0.5; + flow.style.setProperty("--w", breite + "px"); + flow.style.animationPlayState = sichtbar ? "running" : "paused"; + flow.style.willChange = sichtbar ? "transform" : "auto"; +} + const solarSVG = { updateCnt: 99, /** @@ -222,7 +222,7 @@ const solarSVG = { h = "0" + h; if (m < 10) m = "0" + m; - setText("evRemText", h + ":" + m); + setText("evRemText", h + ":" + m); } } else { setText("evRemText", ""); @@ -234,9 +234,9 @@ const solarSVG = { 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("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)); @@ -264,7 +264,7 @@ const solarSVG = { i++; } - setHTML("invList", tttext); + 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))); @@ -602,6 +602,15 @@ var chartSettings = { + // Setzt den Schieberegler aus den Wattstunden, die die Wallbox meldet. + function setzeLadeRegler(wattstunden) { + const regler = document.getElementById("modal-slider"); + const whProProzent = Number(regler.dataset.whProProzent); + if (whProProzent > 0) { + regler.value = Math.round(Number(String(wattstunden)) / whProProzent); + } + } + function openModal(type) { document.getElementById("modalSaveBtn").style.display = type === "watering" ? "none" : ""; switch(type){ @@ -663,7 +672,11 @@ var chartSettings = { else if (mode == "AutomaticStop") { document.getElementById("EV_NextTrip").click(); } else { alert(mode); } document.getElementById("EV_NextTripTime").value = time.toHHMM(); - document.getElementById("modal-slider").value = Math.round(energy * 100 / 14000); + // Wattstunden je Prozent liefert das Formular mit (carForm.php); + // die Akkugroesse steht in restricted/config.php. Frueher standen + // hier feste 14000 Wh - fuer beide Autos dasselbe, und fuer keines + // von beiden richtig. + setzeLadeRegler(energy); document.getElementById("modal-slider").dispatchEvent(new Event('input')); if (amp == "16") { document.getElementById("EV_16A").click(); } else if (amp == "10") { document.getElementById("EV_10A").click(); } @@ -685,7 +698,11 @@ var chartSettings = { else if (mode == "5") { document.getElementById("EV_NextTrip").click(); } else { alert(mode); } document.getElementById("EV_NextTripTime").value = time.toHHMM(); - document.getElementById("modal-slider").value = Math.round(energy * 100 / 14000); + // Wattstunden je Prozent liefert das Formular mit (carForm.php); + // die Akkugroesse steht in restricted/config.php. Frueher standen + // hier feste 14000 Wh - fuer beide Autos dasselbe, und fuer keines + // von beiden richtig. + setzeLadeRegler(energy); document.getElementById("modal-slider").dispatchEvent(new Event('input')); if (amp == "16") { document.getElementById("EV_16A").click(); } else if (amp == "10") { document.getElementById("EV_10A").click(); } @@ -1069,21 +1086,21 @@ 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. - // 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; + // 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"; - 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; + 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); diff --git a/restricted/config.php b/restricted/config.php new file mode 100644 index 0000000..4fcd011 --- /dev/null +++ b/restricted/config.php @@ -0,0 +1,41 @@ + [ + "name" => "Škoda Enyaq RS", + // Modelljahr 2026: 84 kWh brutto, 79 kWh nutzbar. + "akkuKWh" => 79.0, + ], + "og" => [ + "name" => "Jeep Compass 4xe", + // Plug-in-Hybrid. Stellantis nennt 11,4 kWh fuer den Akku, ohne + // zwischen brutto und nutzbar zu unterscheiden. + "akkuKWh" => 11.4, + ], + ]; +} diff --git a/restricted/wallboxen.php b/restricted/wallboxen.php index ff8d911..6fe2a1d 100644 --- a/restricted/wallboxen.php +++ b/restricted/wallboxen.php @@ -26,6 +26,7 @@ */ require_once(__DIR__ . "/../ajax/phpMQTT.php"); +require_once(__DIR__ . "/config.php"); // Der Broker laeuft auf demselben Rechner wie der Webserver. Steht hier und // nicht in commands.php, damit diese Datei nicht die halbe Raumsteuerung @@ -48,9 +49,6 @@ function wallboxen() "praefix" => "wattpilot/properties/", // Die Bruecke haengt "/set" an den Eigenschaftsnamen an. "namen" => [], - // Wattstunden je Prozent des Schiebereglers - Akku des Autos, - // das hier haengt. - "whProProzent" => 140, // Zeichenketten gehen roh hinaus; die Bruecke gibt sie // unveraendert an die Wallbox weiter. "textInJson" => false, @@ -66,7 +64,6 @@ function wallboxen() // Zielzeit und Zielenergie heissen hier anders. Die Wattpilot // kennt "ate"/"att" gar nicht, der go-e umgekehrt "fte"/"ftt". "namen" => ["fte" => "ate", "ftt" => "att"], - "whProProzent" => 90, // Der go-e liest jeden Wert als JSON. Zeichenketten muessen // deshalb in Anfuehrungszeichen stehen, sonst antwortet er mit // "tried to set api key with invalid json". @@ -76,6 +73,23 @@ function wallboxen() ]; } +/** + * Wieviel Wattstunden ein Prozent des Schiebereglers bedeutet. + * + * Der Wert steht nicht in der Tabelle oben, weil er nichts mit der Wallbox + * zu tun hat, sondern mit dem Auto davor: er kommt aus der nutzbaren + * Akkukapazitaet in restricted/config.php. Wechselt das Auto, wird dort eine + * Zeile geaendert. + */ +function wallboxWhProProzent(string $box): int +{ + $autos = fahrzeuge(); + if (!isset($autos[$box])) { + throw new InvalidArgumentException("Kein Auto fuer Wallbox: " . $box); + } + return (int) round($autos[$box]["akkuKWh"] * 1000 / 100); +} + /** * Einen Wert so schreiben, wie die jeweilige Wallbox ihn erwartet. */