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>
106 lines
4.1 KiB
PHP
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);
|
|
}
|