Files
Smart-Dashboard/restricted/wallboxen.php
T
adminandClaude Opus 5 61ea98db1d 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>
2026-09-04 08:36:13 +02:00

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;
}