Beide Wallboxen werden gleich bedient
carEG.php und carOG.php waren fast identisch und unterschieden sich nur im Transportweg: der go-e bekam seine Kommandos per HTTP, die Wattpilot ueber einen eigenen Python-Prozess je Klick, der sich einen Websocket aufbaute, anmeldete, ein Feld setzte und wieder starb. Beide haengen laengst am selben MQTT-Broker - der go-e mit eigenem Client (mce=true, mcr=false), die Wattpilot ueber wattpilot_bruecke.py. Beide nehmen Kommandos auf <praefix><schluessel>/set entgegen. Die Steuerung steht deshalb jetzt einmal in carSteuerung.php; was die Wallboxen wirklich trennt - Topic-Praefix, die Namen ate/att gegen ftt/fte und die Wattstunden je Prozent - steht als Tabelle in restricted/wallboxen.php. Nebenbei faellt damit /volume1/homes/wagner/wattpilot.py weg, das das Passwort der Wallbox im Klartext enthaelt und ausserhalb jedes Repos liegt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+12
-73
@@ -1,73 +1,12 @@
|
||||
<?php
|
||||
|
||||
require_once("../helper.php");
|
||||
|
||||
if (checkLogin()) {
|
||||
chdir("/volume1/homes/wagner/");
|
||||
putenv('PYTHONPATH="/var/services/homes/wagner/.local/lib/python3.8/site-packages/');
|
||||
$close = 0;
|
||||
if (isset($_POST["evStart/Stop"])) {
|
||||
if ($_POST["evStart/Stop"] == "Laden starten") {
|
||||
$close = 1;
|
||||
exec("python wattpilot.py -start 2>&1", $output, $return_var);
|
||||
} elseif ($_POST["evStart/Stop"] == "Laden stoppen") {
|
||||
$close = 1;
|
||||
exec("python wattpilot.py -stop 2>&1", $output, $return_var);
|
||||
}
|
||||
$close = 1;
|
||||
} else if (isset($_POST["evMode"])) {
|
||||
// Eingaben pruefen, bevor sie in die Kommandozeile gehen. ftt kam bisher
|
||||
// ungeprueft aus dem Formular und liess sich mit "12:00; befehl" zum
|
||||
// Anhaengen beliebiger Shell-Befehle missbrauchen.
|
||||
$fte = intval($_POST["fte"]);
|
||||
if ($fte > 100)
|
||||
$fte = 100;
|
||||
else if ($fte < 1)
|
||||
$fte = 1;
|
||||
$fte = $fte * 140;
|
||||
|
||||
$evAmp = intval($_POST["evAmp"]);
|
||||
if ($evAmp > 16)
|
||||
$evAmp = 16;
|
||||
if ($evAmp < 6)
|
||||
$evAmp = 6;
|
||||
|
||||
// Das Formularfeld ist ein <input type="time">, liefert also HH:MM.
|
||||
// Alles andere gilt als "keine Ladeplanung" und faellt in den else-Zweig.
|
||||
$ftt = "";
|
||||
if (isset($_POST["ftt"]) && preg_match('/^([01][0-9]|2[0-3]):[0-5][0-9]$/', $_POST["ftt"])) {
|
||||
$ftt = $_POST["ftt"];
|
||||
}
|
||||
$setTime = gmdate("Y-m-d\TH:i:s", time()) . ".000";
|
||||
|
||||
if ($_POST["evMode"] == "eco") {
|
||||
$close = 1;
|
||||
if ($ftt !== "" && $fte > 0) {
|
||||
exec("python wattpilot.py -eco -time " . escapeshellarg($ftt) . " -energy " . $fte . " -maxCurr " . $evAmp . " -setTime " . escapeshellarg($setTime) . " 2>&1", $output, $return_var);
|
||||
} else {
|
||||
exec("/bin/python wattpilot.py -e 2>&1", $output, $return_var);
|
||||
}
|
||||
} else if ($_POST["evMode"] == "default") {
|
||||
$close = 1;
|
||||
if ($ftt !== "" && $fte > 0) {
|
||||
exec("python wattpilot.py -default -time " . escapeshellarg($ftt) . " -energy " . $fte . " -maxCurr " . $evAmp . " -setTime " . escapeshellarg($setTime) . " 2>&1", $output, $return_var);
|
||||
} else {
|
||||
exec("python wattpilot.py -d 2>&1", $output, $return_var);
|
||||
}
|
||||
} else if ($_POST["evMode"] == "NextTrip") {
|
||||
$close = 1;
|
||||
if ($ftt !== "" && $fte > 0) {
|
||||
exec("python wattpilot.py -trip -time " . escapeshellarg($ftt) . " -energy " . $fte . " -maxCurr " . $evAmp . " -setTime " . escapeshellarg($setTime) . " 2>&1", $output, $return_var);
|
||||
} else {
|
||||
exec("python wattpilot.py -t 2>&1", $output, $return_var);
|
||||
}
|
||||
}
|
||||
$close = 1;
|
||||
}
|
||||
} else {
|
||||
$close = 1;
|
||||
}
|
||||
if (!$close) {
|
||||
$formId = "carEG_form";
|
||||
include __DIR__ . "/carForm.php";
|
||||
}
|
||||
<?php
|
||||
/*
|
||||
* Autoladung EG - die Wattpilot in der Garage.
|
||||
*
|
||||
* Die Ansteuerung ist fuer beide Wallboxen dieselbe und steht in
|
||||
* carSteuerung.php; hier bleibt nur, welche gemeint ist.
|
||||
*/
|
||||
|
||||
$wallbox = "eg";
|
||||
$formId = "carEG_form";
|
||||
|
||||
require __DIR__ . "/carSteuerung.php";
|
||||
|
||||
+12
-58
@@ -1,58 +1,12 @@
|
||||
<?php
|
||||
|
||||
require_once("../helper.php");
|
||||
|
||||
|
||||
if (checkLogin()) {
|
||||
$close = 0;
|
||||
if (isset($_POST["evStart/Stop"])) {
|
||||
if ($_POST["evStart/Stop"] == "Laden starten") {
|
||||
$close = 1;
|
||||
httpGetJson("http://192.168.179.122/api/set?frc=2");
|
||||
} else if ($_POST["evStart/Stop"] == "Laden stoppen") {
|
||||
$close = 1;
|
||||
httpGetJson("http://192.168.179.122/api/set?frc=1");
|
||||
}
|
||||
$close = 1;
|
||||
} else if (isset($_POST["evMode"])) {
|
||||
if ($_POST["fte"] > 100)
|
||||
$_POST["fte"] = 100;
|
||||
else if ($_POST["fte"] < 0)
|
||||
$_POST["fte"] = 0;
|
||||
if ($_POST["evAmp"] > 16)
|
||||
$_POST["evAmp"] = 16;
|
||||
if ($_POST["evAmp"] < 6)
|
||||
$_POST["evAmp"] = 6;
|
||||
$_POST["fte"] = $_POST["fte"] * 90;
|
||||
$seconds = strtotime('1970-01-01 ' . $_POST["ftt"] . ':00GMT');
|
||||
if ($_POST["evMode"] == "eco") {
|
||||
$close = 1;
|
||||
if ($_POST["ftt"] && $_POST["fte"] > 0) {
|
||||
httpGetJson("http://192.168.179.122/api/set?ate=" . $_POST["fte"] . "&att=" . $seconds . "&=" . $_POST["evAmp"] . "&fup=true&lmo=4");
|
||||
} else {
|
||||
httpGetJson("http://192.168.179.122/api/set?amp=" . $_POST["evAmp"] . "&fup=true&lmo=4");
|
||||
}
|
||||
} else if ($_POST["evMode"] == "default") {
|
||||
$close = 1;
|
||||
if ($_POST["ftt"] && $_POST["fte"] > 0) {
|
||||
httpGetJson("http://192.168.179.122/api/set?ate=" . $_POST["fte"] . "&att=" . $seconds . "&=" . $_POST["evAmp"] . "&fup=true&lmo=3");
|
||||
} else {
|
||||
httpGetJson("http://192.168.179.122/api/set?amp=" . $_POST["evAmp"] . "&fup=true&lmo=3");
|
||||
}
|
||||
} else if ($_POST["evMode"] == "NextTrip") {
|
||||
$close = 1;
|
||||
if ($_POST["ftt"] && $_POST["fte"] > 0) {
|
||||
httpGetJson("http://192.168.179.122/api/set?ate=" . $_POST["fte"] . "&att=" . $seconds . "&=" . $_POST["evAmp"] . "&fup=true&lmo=5");
|
||||
} else {
|
||||
httpGetJson("http://192.168.179.122/api/set?amp=" . $_POST["evAmp"] . "&fup=true&lmo=5");
|
||||
}
|
||||
}
|
||||
$close = 1;
|
||||
}
|
||||
} else {
|
||||
$close = 1;
|
||||
}
|
||||
if (!$close) {
|
||||
$formId = "carOG_form";
|
||||
include __DIR__ . "/carForm.php";
|
||||
}
|
||||
<?php
|
||||
/*
|
||||
* Autoladung OG - der go-e Charger.
|
||||
*
|
||||
* Die Ansteuerung ist fuer beide Wallboxen dieselbe und steht in
|
||||
* carSteuerung.php; hier bleibt nur, welche gemeint ist.
|
||||
*/
|
||||
|
||||
$wallbox = "og";
|
||||
$formId = "carOG_form";
|
||||
|
||||
require __DIR__ . "/carSteuerung.php";
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
/**
|
||||
* 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.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
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.
|
||||
const CAR_MODI = [
|
||||
"default" => 3, // Immer laden
|
||||
"eco" => 4, // Ueberschuss (Awattar)
|
||||
"NextTrip" => 5, // Ladeplanung (AutomaticStop)
|
||||
];
|
||||
|
||||
$close = 0;
|
||||
|
||||
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, deshalb steht er je Wallbox in der Tabelle.
|
||||
$prozent = min(100, max(0, intval($_POST["fte"] ?? 0)));
|
||||
$energie = $prozent * $konf["whProProzent"];
|
||||
|
||||
$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;
|
||||
}
|
||||
} else {
|
||||
$close = 1;
|
||||
}
|
||||
|
||||
if (!$close) {
|
||||
include __DIR__ . "/carForm.php";
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
/**
|
||||
* Beide Wallboxen ueber denselben Weg ansteuern: MQTT.
|
||||
*
|
||||
* Bis September 2026 liefen die beiden voellig verschieden. Der go-e bekam
|
||||
* seine Kommandos per HTTP (`api/set?frc=2`), die Wattpilot ueber einen
|
||||
* eigenen Python-Prozess je Klick, der sich einen Websocket aufbaute,
|
||||
* anmeldete, ein Feld setzte und wieder starb - inklusive Passwort im
|
||||
* Klartext in `/volume1/homes/wagner/wattpilot.py`.
|
||||
*
|
||||
* Warum MQTT und nicht HTTP fuer beide: die Wattpilot hat keine lokale
|
||||
* REST-API. Sie beantwortet auf Port 80 jeden Pfad mit 404 und spricht nur
|
||||
* ueber ihren Websocket. Fronius liefert die go-e-Firmware ohne die
|
||||
* HTTP-Schnittstelle aus.
|
||||
*
|
||||
* Warum es umgekehrt geht: der go-e hat einen eigenen MQTT-Client (`mce`),
|
||||
* haengt an unserem Broker (`mcu`) und laesst Schreibzugriffe zu
|
||||
* (`mcr = false`). Die Wattpilot erreicht denselben Broker ueber
|
||||
* `wattpilot_bruecke.py` im SolarManager. Beide nehmen Kommandos auf
|
||||
* `<praefix><schluessel>/set` entgegen und antworten auf
|
||||
* `<praefix><schluessel>/result` mit `success` oder einer Fehlermeldung.
|
||||
*
|
||||
* Der Lesestand liegt ohnehin schon auf demselben Broker - der Browser
|
||||
* abonniert `go-eCharger/270003/...` und `wattpilot/properties/.../state`
|
||||
* nebeneinander (js/solar/solarMQTT.js).
|
||||
*/
|
||||
|
||||
require_once(__DIR__ . "/../ajax/phpMQTT.php");
|
||||
|
||||
// Der Broker laeuft auf demselben Rechner wie der Webserver. Steht hier und
|
||||
// nicht in commands.php, damit diese Datei nicht die halbe Raumsteuerung
|
||||
// mitzieht, nur um zwei Konstanten zu erben.
|
||||
const WALLBOX_BROKER = "localhost";
|
||||
const WALLBOX_PORT = 1883;
|
||||
|
||||
/**
|
||||
* Die beiden Wallboxen und worin sie sich noch unterscheiden.
|
||||
*
|
||||
* Die Schluessel `frc`, `lmo`, `amp` und `fup` heissen auf beiden gleich -
|
||||
* es ist beides go-e-Firmware. Uebrig bleiben drei echte Unterschiede, und
|
||||
* die stehen hier statt in zwei getrennten Codepfaden.
|
||||
*/
|
||||
function wallboxen()
|
||||
{
|
||||
return [
|
||||
"eg" => [
|
||||
"titel" => "Wattpilot (EG)",
|
||||
"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,
|
||||
// Die Ladeplanung rechnet mit der Uhr der Wallbox. Beim alten
|
||||
// Python-Aufruf wurde sie vor jedem Moduswechsel gestellt
|
||||
// (-setTime); das bleibt so, damit die Funktion nicht verloren
|
||||
// geht. Der go-e hat das nie gebraucht.
|
||||
"uhrStellen" => true,
|
||||
],
|
||||
"og" => [
|
||||
"titel" => "go-e (OG)",
|
||||
"praefix" => "go-eCharger/270003/",
|
||||
// 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".
|
||||
"textInJson" => true,
|
||||
"uhrStellen" => false,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Einen Wert so schreiben, wie die jeweilige Wallbox ihn erwartet.
|
||||
*/
|
||||
function wallboxNutzlast($wert, array $konf)
|
||||
{
|
||||
if (is_bool($wert)) {
|
||||
return $wert ? "true" : "false";
|
||||
}
|
||||
if (is_string($wert) && $konf["textInJson"]) {
|
||||
return json_encode($wert);
|
||||
}
|
||||
return (string) $wert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mehrere Eigenschaften einer Wallbox setzen.
|
||||
*
|
||||
* Die Reihenfolge von $werte bleibt erhalten - sie ist nicht beliebig: der
|
||||
* Modus (`lmo`) kommt zuletzt, damit Zielzeit und Zielenergie schon stehen,
|
||||
* wenn er greift.
|
||||
*
|
||||
* Liefert true, wenn alles beim Broker war. Ob die Wallbox die Werte
|
||||
* genommen hat, steht anschliessend auf den `/result`-Topics; darauf wird
|
||||
* hier nicht gewartet, damit ein Klick nicht an einer stummen Wallbox
|
||||
* haengenbleibt.
|
||||
*/
|
||||
function wallboxSetzen(string $box, array $werte): bool
|
||||
{
|
||||
$alle = wallboxen();
|
||||
if (!isset($alle[$box])) {
|
||||
throw new InvalidArgumentException("Unbekannte Wallbox: " . $box);
|
||||
}
|
||||
$konf = $alle[$box];
|
||||
|
||||
$mqtt = new Bluerhinos\phpMQTT(WALLBOX_BROKER, WALLBOX_PORT,
|
||||
"WallboxWeb-" . bin2hex(random_bytes(4)));
|
||||
if (!$mqtt->connect(true, null, "", "")) {
|
||||
error_log("wallboxSetzen: kein Kontakt zum MQTT-Broker");
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ($werte as $schluessel => $wert) {
|
||||
$name = $konf["namen"][$schluessel] ?? $schluessel;
|
||||
$mqtt->publish($konf["praefix"] . $name . "/set",
|
||||
wallboxNutzlast($wert, $konf), 0, false);
|
||||
}
|
||||
|
||||
$mqtt->close();
|
||||
return true;
|
||||
}
|
||||
Reference in New Issue
Block a user