Ladefenster neu: Zustand zuerst, Modus als Karten, Aenderungen sofort
Nach dem abgestimmten Entwurf: Ladestand als Ring mit Ziel, Leistung, Sonnenanteil und fertig um; ein grosser Knopf, der sich nach der gemeldeten Freigabe (frc) richtet - "Laden stoppen", "Ladung freigeben" (zurueck zum Modus statt dauerhaft gesperrt), "Jetzt laden", ohne Auto ausgegraut. Modus als Karten (Sofort, Sonne, Geplant), Planung nur bei "Geplant": mit Ladestand des Autos als Ziel-Ladestand, sonst als zusaetzliche Prozent; Abfahrt per Schnellwahl. Ladestrom als Segmente mit kW. Jede Aenderung geht sofort als JSON an carSteuerung.php (frc, modus, strom, plan) statt ueber "Save changes"; die Logik steht in js/solar/ladefenster.js. Am 800er-Display zweispaltig ohne Scrollen. Nebenbei: die Uebersicht las die Restladezeit aus carRemChrg, das der Manager nicht mehr schickt - jetzt evRemChrgTime. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+86
-87
@@ -1,94 +1,93 @@
|
||||
<?php
|
||||
/*
|
||||
* Formular des Auto-Lademodals. carEG.php und carOG.php zeigen dasselbe
|
||||
* Formular; nur die Wallbox dahinter unterscheidet sich - eingebunden wird
|
||||
* es aus carSteuerung.php, das $wallbox und $formId setzt. $formId trennt
|
||||
* die beiden Formulare fuer das JavaScript.
|
||||
* Das Ladefenster, gemeinsam für beide Wallboxen (carEG.php, carOG.php über
|
||||
* carSteuerung.php).
|
||||
*
|
||||
* Der Schieberegler traegt die Wattstunden je Prozent als data-Attribut bei
|
||||
* sich. Frueher stand die Umrechnung als feste 14000 im JavaScript und
|
||||
* passte damit hoechstens zu einem der beiden Autos: eingestellte und
|
||||
* angezeigte Prozente gingen auseinander.
|
||||
* Hier steht nur das Gerüst mit dem, was der Server weiß: welches Auto, wie
|
||||
* groß sein Akku ist. Werte, Zustände und jede Änderung übernimmt
|
||||
* js/solar/ladefenster.js - live aus den MQTT-Werten der Seite, geschrieben
|
||||
* wird sofort per POST an carSteuerung.php.
|
||||
*/
|
||||
$auto = fahrzeuge()[$wallbox];
|
||||
$whProProzent = wallboxWhProProzent($wallbox);
|
||||
$box = wallboxen()[$wallbox];
|
||||
?>
|
||||
<div class="d-grid gap-2 col-6 mx-auto my-4">
|
||||
<button class="btn btn-primary" id="evStart/Stop">Laden starten</button>
|
||||
<div class="lade" data-wallbox="<?= htmlspecialchars($wallbox) ?>"
|
||||
data-wh-pro-prozent="<?= wallboxWhProProzent($wallbox) ?>"
|
||||
data-akku-kwh="<?= htmlspecialchars((string)$auto["akkuKWh"]) ?>">
|
||||
|
||||
<div class="lade-links">
|
||||
<div class="lade-status">
|
||||
<div class="lade-ring" aria-hidden="true">
|
||||
<svg viewBox="0 0 120 120">
|
||||
<circle class="lade-spur" cx="60" cy="60" r="50" pathLength="100"/>
|
||||
<circle class="lade-ziel" cx="60" cy="60" r="50" pathLength="100"/>
|
||||
<circle class="lade-ist" cx="60" cy="60" r="50" pathLength="100"/>
|
||||
</svg>
|
||||
<div class="lade-ring-mitte">
|
||||
<b data-lade="soc">–</b>
|
||||
<span data-lade="ringUnten"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="lade-text">
|
||||
<div class="lade-auto"><?= htmlspecialchars($auto["name"]) ?> <span data-lade="stecker" class="lade-pille"></span></div>
|
||||
<div class="lade-gross" data-lade="leistung">–</div>
|
||||
<div class="lade-zeile" data-lade="quelle"></div>
|
||||
<div class="lade-zeile" data-lade="fertig"></div>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="lade-start" data-lade="start"><span class="lade-punkt"></span><span data-lade="startText">…</span></button>
|
||||
<div class="lade-erklaerung" data-lade="erklaerung"></div>
|
||||
</div>
|
||||
|
||||
<div class="lade-rechts">
|
||||
<div class="lade-abschnitt">
|
||||
<h3 id="lade-modus-<?= $wallbox ?>">Wie laden?</h3>
|
||||
<div class="lade-modi" role="radiogroup" aria-labelledby="lade-modus-<?= $wallbox ?>">
|
||||
<button type="button" class="lade-modus" role="radio" aria-checked="false" data-modus="sofort" style="--c:#6cbe58">
|
||||
<span class="lade-sym"><svg viewBox="0 0 24 24"><path d="M13 3 5 14h6l-1 7 8-11h-6z"/></svg></span>
|
||||
<b>Sofort</b><span>volle Leistung, auch aus dem Netz</span>
|
||||
</button>
|
||||
<button type="button" class="lade-modus" role="radio" aria-checked="false" data-modus="sonne" style="--c:#f7c000">
|
||||
<span class="lade-sym"><svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M2 12h2M20 12h2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg></span>
|
||||
<b>Sonne</b><span>nur Überschuss der Anlage</span>
|
||||
</button>
|
||||
<button type="button" class="lade-modus" role="radio" aria-checked="false" data-modus="plan" style="--c:#4b9fd6">
|
||||
<span class="lade-sym"><svg viewBox="0 0 24 24"><circle cx="12" cy="13" r="8"/><path d="M12 9v4l2.5 2M9 2h6"/></svg></span>
|
||||
<b>Geplant</b><span>bis zur Abfahrt geladen</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lade-planung" data-lade="planung" hidden>
|
||||
<div>
|
||||
<div class="lade-plan-zeile">
|
||||
<label for="lade-ziel-<?= $wallbox ?>" data-lade="zielTitel">Ladestand bei Abfahrt</label>
|
||||
<output data-lade="zielAusgabe"></output>
|
||||
</div>
|
||||
<input type="range" id="lade-ziel-<?= $wallbox ?>" data-lade="ziel" min="0" max="100" step="5" value="80">
|
||||
<div class="lade-marken"><span data-lade="markeLinks">0 %</span><span data-lade="markeMitte"></span><span>100 %</span></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="lade-plan-zeile"><label>Abfahrt</label><output data-lade="abfahrtAusgabe"></output></div>
|
||||
<div class="lade-chips" role="group" aria-label="Abfahrt">
|
||||
<button type="button" class="lade-chip" aria-pressed="false" data-zeit="06:00">06:00</button>
|
||||
<button type="button" class="lade-chip" aria-pressed="false" data-zeit="07:00">07:00</button>
|
||||
<button type="button" class="lade-chip" aria-pressed="false" data-zeit="08:00">08:00</button>
|
||||
<label class="lade-chip" aria-pressed="false" data-lade="chipEigen">andere
|
||||
<input type="time" data-lade="zeitEigen" aria-label="eigene Abfahrtszeit"></label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lade-abschnitt">
|
||||
<h3 id="lade-strom-<?= $wallbox ?>">Höchstens</h3>
|
||||
<div class="lade-strom" role="radiogroup" aria-labelledby="lade-strom-<?= $wallbox ?>">
|
||||
<button type="button" role="radio" aria-checked="false" data-ampere="6"><b>6 A</b><span>≈ 4,1 kW</span></button>
|
||||
<button type="button" role="radio" aria-checked="false" data-ampere="10"><b>10 A</b><span>≈ 6,9 kW</span></button>
|
||||
<button type="button" role="radio" aria-checked="false" data-ampere="16"><b>16 A</b><span>≈ 11 kW</span></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lade-meldung" data-lade="meldung" aria-live="polite">Änderungen gelten sofort · <?= htmlspecialchars($box["titel"]) ?></div>
|
||||
</div>
|
||||
</div>
|
||||
<!--begin::Form-->
|
||||
<form id="<?= $formId ?>">
|
||||
<div class="card m-3">
|
||||
<div class="card-header py-1 px-3">
|
||||
<div class="card-title">Modus</div>
|
||||
</div>
|
||||
<!--begin::Body-->
|
||||
<div class="card-body">
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="evMode" id="EV_Default" value="default">
|
||||
<label class="form-check-label" for="EV_Default"> Immer Laden </label></input>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="evMode" id="EV_Eco" value="eco">
|
||||
<label class="form-check-label" for="EV_Eco"> Überschuss </label></input>
|
||||
</div>
|
||||
<div class="form-check disabled">
|
||||
<input class="form-check-input" type="radio" name="evMode" id="EV_NextTrip" value="NextTrip">
|
||||
<label class="form-check-label" for="EV_NextTrip"> Ladeplanung </label></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card m-3">
|
||||
<div class="card-header py-1 px-3">
|
||||
<div class="card-title">Max. Ladestrom</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="col-sm-10">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="evAmp" id="EV_6A" value="6">
|
||||
<label class="form-check-label" for="EV_6A"> 6A </label></input>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio" name="evAmp" id="EV_10A" value="10">
|
||||
<label class="form-check-label" for="EV_10A"> 10A </label></input>
|
||||
</div>
|
||||
<div class="form-check disabled">
|
||||
<input class="form-check-input" type="radio" name="evAmp" id="EV_16A" value="16">
|
||||
<label class="form-check-label" for="EV_16A"> 16A </label></input>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card m-3">
|
||||
<div class="card-header py-1 px-3">
|
||||
<div class="card-title">Ladeplanung</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="row mb-3">
|
||||
<div class="form-check">
|
||||
<label class="form-label" for="addedCharge">Zusätzlich benötigte Ladung (%): </label>
|
||||
<div style="font-size: 15px;color: #297195;display: inline-block;" id="modal-slider-label">sdf</div>
|
||||
<input type="range" id="modal-slider" name="fte" class="form-range range-color-track" min="1" max="100"
|
||||
data-wh-pro-prozent="<?= $whProProzent ?>" />
|
||||
<div class="small text-secondary">
|
||||
<?= htmlspecialchars($auto["name"]) ?>,
|
||||
<?= htmlspecialchars(rtrim(rtrim(number_format($auto["akkuKWh"], 1, ",", "."), "0"), ",")) ?> kWh nutzbar
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span class="max-amount f-16 font-weight-normal darkGray-color text-right mt-1">1%</span>
|
||||
<span class="max-amount f-16 font-weight-normal darkGray-color text-right mt-1">100%</span>
|
||||
</div>
|
||||
<!--<input name="fte" class="form-control" type="number" max="100" min="1" value="" />-->
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-3">
|
||||
<div class="form-check">
|
||||
<label class="form-label" for="planedTime">Zeitpunkt zudem geladen sein soll: </label>
|
||||
<input id="EV_NextTripTime" name="ftt" class="form-control" size="2" type="time" value=""/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
+89
-68
@@ -2,86 +2,107 @@
|
||||
/**
|
||||
* Ladesteuerung beider Wallboxen.
|
||||
*
|
||||
* carEG.php und carOG.php setzen nur $wallbox und $formId und binden diese
|
||||
* Datei ein - vorher standen hier zwei fast gleiche Dateien, die sich nur im
|
||||
* Transportweg unterschieden. Was die beiden Wallboxen wirklich trennt,
|
||||
* steht in restricted/wallboxen.php.
|
||||
* carEG.php und carOG.php setzen nur $wallbox und binden diese Datei ein.
|
||||
* Was die beiden Wallboxen wirklich trennt, steht in restricted/wallboxen.php.
|
||||
*
|
||||
* Ohne POST wird das Formular ausgeliefert (carForm.php), mit POST wird
|
||||
* geschaltet und nichts zurueckgegeben - so war es vorher auch, das
|
||||
* JavaScript im Modal erwartet es so.
|
||||
* GET das Ladefenster (carForm.php)
|
||||
* POST {"aktion": "frc", "wert": 0|1|2} Freigabe: 0 Modus entscheidet, 1 gesperrt, 2 sofort
|
||||
* POST {"aktion": "modus", "modus": "sofort"|"sonne"}
|
||||
* POST {"aktion": "strom", "ampere": 6..16}
|
||||
* POST {"aktion": "plan", "wh": 1.., "zeit": "HH:MM"} Ladeplanung: Energie bis Uhrzeit
|
||||
*
|
||||
* Seit September 2026 gilt jede Änderung sofort (js/solar/ladefenster.js) -
|
||||
* vorher gingen Modus, Strom und Planung gemeinsam über "Save changes".
|
||||
* Antwort ist JSON: {"ok": true} oder {"error": "..."}. Ob die Wallbox den
|
||||
* Wert genommen hat, meldet sie über MQTT; das Fenster zeigt es beim
|
||||
* nächsten Takt.
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . "/../helper.php");
|
||||
require_once(__DIR__ . "/../restricted/wallboxen.php");
|
||||
|
||||
// lmo, der Betriebsmodus der Wallbox. Die Namen links kommen aus den
|
||||
// Radiobuttons in carForm.php.
|
||||
// lmo, der Betriebsmodus - beide Wallboxen nutzen dieselben Nummern.
|
||||
const CAR_MODI = [
|
||||
"default" => 3, // Immer laden
|
||||
"eco" => 4, // Ueberschuss (Awattar)
|
||||
"NextTrip" => 5, // Ladeplanung (AutomaticStop)
|
||||
"sofort" => 3, // Default: volle Leistung, auch aus dem Netz
|
||||
"sonne" => 4, // Eco (Awattar): Überschuss
|
||||
"plan" => 5, // Next Trip (AutomaticStop): Energie bis Uhrzeit
|
||||
];
|
||||
|
||||
$close = 0;
|
||||
function ladeAntwort($daten, $code = 200)
|
||||
{
|
||||
http_response_code($code);
|
||||
header("Content-Type: application/json; charset=utf-8");
|
||||
echo json_encode($daten, JSON_UNESCAPED_UNICODE);
|
||||
exit;
|
||||
}
|
||||
|
||||
if (checkLogin()) {
|
||||
$konf = wallboxen()[$wallbox];
|
||||
|
||||
if (isset($_POST["evStart/Stop"])) {
|
||||
// frc: 2 gibt das Laden frei, 1 sperrt es, 0 waere "die Wallbox
|
||||
// entscheidet".
|
||||
if ($_POST["evStart/Stop"] === "Laden starten") {
|
||||
wallboxSetzen($wallbox, ["frc" => 2]);
|
||||
} elseif ($_POST["evStart/Stop"] === "Laden stoppen") {
|
||||
wallboxSetzen($wallbox, ["frc" => 1]);
|
||||
}
|
||||
$close = 1;
|
||||
|
||||
} elseif (isset($_POST["evMode"])) {
|
||||
$modus = CAR_MODI[$_POST["evMode"]] ?? null;
|
||||
|
||||
if ($modus !== null) {
|
||||
// Der Schieberegler liefert Prozent zusaetzlich benoetigter
|
||||
// Ladung; die Wallbox will Wattstunden. Der Faktor haengt am
|
||||
// Auto und steht deshalb in restricted/config.php.
|
||||
$prozent = min(100, max(0, intval($_POST["fte"] ?? 0)));
|
||||
$energie = $prozent * wallboxWhProProzent($wallbox);
|
||||
|
||||
$strom = min(16, max(6, intval($_POST["evAmp"] ?? 6)));
|
||||
|
||||
// Das Formularfeld ist ein <input type="time">, liefert also
|
||||
// HH:MM. Alles andere gilt als "keine Ladeplanung" und laesst
|
||||
// Zielzeit und Zielenergie unangetastet. Geprueft wird hier
|
||||
// weiterhin streng: der Wert ging frueher in eine Kommandozeile
|
||||
// und liess sich mit "12:00; befehl" missbrauchen.
|
||||
$sekunden = null;
|
||||
if (isset($_POST["ftt"])
|
||||
&& preg_match('/^([01][0-9]|2[0-3]):([0-5][0-9])$/', $_POST["ftt"], $teile)) {
|
||||
$sekunden = intval($teile[1]) * 3600 + intval($teile[2]) * 60;
|
||||
}
|
||||
|
||||
// Reihenfolge zaehlt: Ziel zuerst, Modus zuletzt.
|
||||
$werte = [];
|
||||
if (!empty($konf["uhrStellen"])) {
|
||||
$werte["utc"] = gmdate("Y-m-d\TH:i:s") . ".000";
|
||||
}
|
||||
if ($sekunden !== null && $energie > 0) {
|
||||
$werte["fte"] = $energie;
|
||||
$werte["ftt"] = $sekunden;
|
||||
}
|
||||
$werte["amp"] = $strom;
|
||||
$werte["fup"] = true;
|
||||
$werte["lmo"] = $modus;
|
||||
|
||||
wallboxSetzen($wallbox, $werte);
|
||||
}
|
||||
$close = 1;
|
||||
if (!checkLogin()) {
|
||||
if ($_SERVER["REQUEST_METHOD"] === "POST") {
|
||||
ladeAntwort(["error" => "Nicht angemeldet."], 403);
|
||||
}
|
||||
} else {
|
||||
$close = 1;
|
||||
exit;
|
||||
}
|
||||
|
||||
if (!$close) {
|
||||
if ($_SERVER["REQUEST_METHOD"] !== "POST") {
|
||||
include __DIR__ . "/carForm.php";
|
||||
exit;
|
||||
}
|
||||
|
||||
$konf = wallboxen()[$wallbox];
|
||||
$body = json_decode(file_get_contents("php://input"), true);
|
||||
if (!is_array($body)) {
|
||||
ladeAntwort(["error" => "Konnte die Anfrage nicht lesen."], 400);
|
||||
}
|
||||
|
||||
switch ($body["aktion"] ?? "") {
|
||||
case "frc":
|
||||
$wert = intval($body["wert"] ?? -1);
|
||||
if (!in_array($wert, [0, 1, 2], true)) {
|
||||
ladeAntwort(["error" => "Unbekannte Freigabe."], 400);
|
||||
}
|
||||
$werte = ["frc" => $wert];
|
||||
break;
|
||||
|
||||
case "modus":
|
||||
$modus = CAR_MODI[$body["modus"] ?? ""] ?? null;
|
||||
if ($modus === null || $modus === CAR_MODI["plan"]) {
|
||||
// Die Planung braucht Energie und Uhrzeit - siehe "plan".
|
||||
ladeAntwort(["error" => "Unbekannter Modus."], 400);
|
||||
}
|
||||
$werte = ["fup" => true, "lmo" => $modus];
|
||||
break;
|
||||
|
||||
case "strom":
|
||||
$werte = ["amp" => min(16, max(6, intval($body["ampere"] ?? 6)))];
|
||||
break;
|
||||
|
||||
case "plan":
|
||||
// Die Uhrzeit ging früher in eine Kommandozeile und ließ sich mit
|
||||
// "12:00; befehl" missbrauchen - deshalb bleibt die Prüfung streng.
|
||||
if (!preg_match('/^([01][0-9]|2[0-3]):([0-5][0-9])$/', (string)($body["zeit"] ?? ""), $teile)) {
|
||||
ladeAntwort(["error" => "Die Uhrzeit ist ungültig."], 400);
|
||||
}
|
||||
$wh = intval($body["wh"] ?? 0);
|
||||
$max = (int) round(fahrzeuge()[$wallbox]["akkuKWh"] * 1000);
|
||||
if ($wh < 100 || $wh > $max) {
|
||||
ladeAntwort(["error" => "Die Energiemenge liegt außerhalb dessen, was der Akku fasst."], 400);
|
||||
}
|
||||
// Reihenfolge zählt: Uhr, Ziel, dann der Modus.
|
||||
$werte = [];
|
||||
if (!empty($konf["uhrStellen"])) {
|
||||
$werte["utc"] = gmdate("Y-m-d\TH:i:s") . ".000";
|
||||
}
|
||||
$werte["fte"] = $wh;
|
||||
$werte["ftt"] = intval($teile[1]) * 3600 + intval($teile[2]) * 60;
|
||||
$werte["fup"] = true;
|
||||
$werte["lmo"] = CAR_MODI["plan"];
|
||||
break;
|
||||
|
||||
default:
|
||||
ladeAntwort(["error" => "Unbekannte Aktion."], 400);
|
||||
}
|
||||
|
||||
if (!wallboxSetzen($wallbox, $werte)) {
|
||||
ladeAntwort(["error" => "Der MQTT-Broker ist nicht erreichbar."], 502);
|
||||
}
|
||||
ladeAntwort(["ok" => true]);
|
||||
|
||||
Reference in New Issue
Block a user