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>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<?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);
|
||||
}
|
||||
Reference in New Issue
Block a user