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:
2026-09-15 20:40:29 +02:00
co-authored by Claude Opus 5
parent 2e66bdeff5
commit b0bac3d3ba
9 changed files with 564 additions and 237 deletions
+89 -68
View File
@@ -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]);