Files
Smart-Dashboard/restricted/energiefluss.php
T
adminandClaude Opus 5 a59277e33e Solar: neue Energiefluss-Uebersicht, anpassbar je Haus
Das feste realtime.svg ist ersetzt durch einen Katalog
(js/solar/energieflussKatalog.js: jeder Kreis einmal, samt Werten aus MQTT)
und einen Renderer (js/solar/energiefluss.js): Ringe mit Segmenten,
Fluesse als Boegen mit laufenden Punkten, Fuellstandswelle fuer Batterie
und Auto, animierte Symbole, Plaketten, Hochkant-Ansicht auf schmalen
Schirmen. Hierarchie und Lage wie bisher: Quellen -> Wechselrichter ->
Verbrauch -> OG/EG/UG -> Wallboxen, Heizstab.

Einstellungen -> Uebersicht: Kreise ein/aus (Kinder ruecken eine Ebene
hoeher), Namen, Lage per Ziehen in der Vorschau, PV-Flaechen. Gespeichert
werden nur Abweichungen in homeMesh.energiefluss.

Der PV-Lageplan hinter der Sonne liegt unveraendert als assets/img/pvplan.svg
und ist ueber den Umschalter Uebersicht/Anlage erreichbar.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 16:42:52 +02:00

106 lines
4.1 KiB
PHP

<?php
/**
* Einstellungen der Energiefluss-Übersicht (Karte "Realtime" auf der
* Solar-Seite), Tabelle homeMesh.energiefluss - siehe homeMesh_energiefluss.sql.
*
* Welche Kreise es gibt und wie sie gezeichnet werden, weiß allein der
* Katalog in js/solar/energieflussKatalog.js. Hier wird nur gespeichert, was
* ein Haus davon abweichend einstellt. PHP kennt deshalb die Kennungen nicht
* und prüft nur ihre Form - ein neuer Kreis im Katalog braucht hier keine
* Zeile.
*/
require_once(__DIR__ . "/meshdb.php");
/**
* Alle Abweichungen, je Kennung.
*
* Fehlt die Tabelle (Installation ohne homeMesh_energiefluss.sql), kommt ein
* leeres Ergebnis zurück: die Übersicht zeichnet dann den Katalog, statt die
* Solar-Seite mit einem Fehler zu verlieren.
*
* @return array<string, array{aktiv: bool, name: ?string, x: ?int, y: ?int, optionen: ?array}>
*/
function energieflussLaden()
{
$ergebnis = [];
try {
$res = meshDb()->query("SELECT id, aktiv, name, x, y, optionen FROM energiefluss");
} catch (Throwable $e) {
return $ergebnis;
}
while ($res && $z = $res->fetch_assoc()) {
$optionen = $z["optionen"] !== null ? json_decode($z["optionen"], true) : null;
$ergebnis[$z["id"]] = [
"aktiv" => (bool)$z["aktiv"],
"name" => $z["name"],
"x" => $z["x"] !== null ? (int)$z["x"] : null,
"y" => $z["y"] !== null ? (int)$z["y"] : null,
"optionen" => is_array($optionen) ? $optionen : null,
];
}
return $ergebnis;
}
/**
* Den ganzen Stand ersetzen.
*
* Die Maske schickt immer alle Kreise, auch unveränderte - so gibt es keinen
* Zwischenstand, in dem die Hälfte gespeichert ist. Einträge, die dem Katalog
* entsprechen (aktiv, ohne Name, Lage und Optionen), werden gar nicht erst
* geschrieben: die Tabelle hält nur echte Abweichungen.
*
* @param array $knoten [{id, aktiv, name, x, y, optionen}, ...]
* @return int Anzahl gespeicherter Abweichungen
*/
function energieflussSpeichern($knoten)
{
if (!is_array($knoten)) {
throw new InvalidArgumentException("Keine Kreise übergeben.");
}
$zeilen = [];
foreach ($knoten as $k) {
$id = (string)($k["id"] ?? "");
if (!preg_match('/^[A-Za-z][A-Za-z0-9_]{0,19}$/', $id)) {
throw new InvalidArgumentException("Ungültige Kennung: " . $id);
}
$name = trim((string)($k["name"] ?? ""));
if (mb_strlen($name) > 40) {
throw new InvalidArgumentException("Der Name „" . $name . "“ ist länger als 40 Zeichen.");
}
$x = isset($k["x"]) && $k["x"] !== null ? (int)round($k["x"]) : null;
$y = isset($k["y"]) && $k["y"] !== null ? (int)round($k["y"]) : null;
foreach ([$x, $y] as $w) {
if ($w !== null && ($w < -2000 || $w > 4000)) {
throw new InvalidArgumentException("Die Lage von „" . $id . "“ liegt außerhalb der Zeichenfläche.");
}
}
$optionen = $k["optionen"] ?? null;
if ($optionen !== null && !is_array($optionen)) {
throw new InvalidArgumentException("Optionen von „" . $id . "“ sind kein Objekt.");
}
$aktiv = !isset($k["aktiv"]) || (bool)$k["aktiv"];
if ($aktiv && $name === "" && $x === null && $y === null && $optionen === null) {
continue;
}
$zeilen[] = [$id, $aktiv ? 1 : 0, $name === "" ? null : $name, $x, $y,
$optionen === null ? null : json_encode($optionen, JSON_UNESCAPED_UNICODE)];
}
$db = meshDb();
$db->begin_transaction();
try {
$db->query("DELETE FROM energiefluss");
$st = $db->prepare("INSERT INTO energiefluss (id, aktiv, name, x, y, optionen) VALUES (?, ?, ?, ?, ?, ?)");
foreach ($zeilen as $z) {
$st->bind_param("sisiis", $z[0], $z[1], $z[2], $z[3], $z[4], $z[5]);
$st->execute();
}
$db->commit();
} catch (Throwable $e) {
$db->rollback();
throw new RuntimeException("Die Übersicht wurde nicht gespeichert: " . $e->getMessage());
}
return count($zeilen);
}