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>
129 lines
4.8 KiB
PHP
129 lines
4.8 KiB
PHP
<?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;
|
|
}
|