sdf
-
+
+
+ = htmlspecialchars($auto["name"]) ?>,
+ = htmlspecialchars(rtrim(rtrim(number_format($auto["akkuKWh"], 1, ",", "."), "0"), ",")) ?> 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.
*/