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);
|
||||
}
|
||||
@@ -33,6 +33,7 @@ require_once(__DIR__ . "/skodaKeys.php");
|
||||
$reiter = [
|
||||
["id" => "preise", "titel" => "Preise", "symbol" => "bi-currency-euro"],
|
||||
["id" => "grundriss", "titel" => "Grundriss", "symbol" => "bi-house"],
|
||||
["id" => "energiefluss", "titel" => "Übersicht", "symbol" => "bi-diagram-3"],
|
||||
["id" => "kacheln", "titel" => "Home-Kacheln", "symbol" => "bi-grid-1x2"],
|
||||
["id" => "geraete", "titel" => "Geräte", "symbol" => "bi-house-gear"],
|
||||
["id" => "fahrzeug", "titel" => "Fahrzeug", "symbol" => "bi-ev-front"],
|
||||
@@ -162,6 +163,48 @@ try {
|
||||
</div>
|
||||
<?= reiterFlaecheZu() ?><!-- /Grundriss -->
|
||||
|
||||
<!-- ================================================== Übersicht ==== -->
|
||||
<!-- Die Energiefluss-Übersicht der Solar-Seite. Welche Kreise es gibt,
|
||||
steht im Katalog (js/solar/energieflussKatalog.js); hier wird nur
|
||||
eingestellt, was dieses Haus davon abweicht - gespeichert in
|
||||
homeMesh.energiefluss. Vorschau und Liste zeichnet
|
||||
js/solar/energieflussEinstellungen.js. -->
|
||||
<?= reiterFlaecheAuf("energiefluss", $reiterOpt) ?>
|
||||
<div class="card mb-4" id="energieflussKarte">
|
||||
<div class="card-header pb-2 pt-2">
|
||||
<h3 class="card-title"><i class="bi bi-diagram-3 me-2"></i>Energiefluss-Übersicht</h3>
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn btn-tool" id="energieflussNeuLaden" title="Änderungen verwerfen">
|
||||
<i class="bi bi-arrow-counterclockwise"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<p class="text-secondary small mb-3">
|
||||
Kreise, die es in diesem Haus nicht gibt, werden ausgeschaltet – hängen andere
|
||||
daran, rücken sie eine Ebene nach oben. In der Vorschau lassen sich die Kreise
|
||||
mit der Maus verschieben. Auf schmalen Bildschirmen dreht die Übersicht sich
|
||||
von selbst hochkant. Die Werte in der Vorschau sind Beispiele.
|
||||
</p>
|
||||
<div class="row g-4">
|
||||
<div class="col-12 col-xl-7">
|
||||
<div class="enf-vorschau" id="energieflussVorschau"></div>
|
||||
</div>
|
||||
<div class="col-12 col-xl-5">
|
||||
<div id="energieflussListe">
|
||||
<p class="text-secondary small mb-0">Wird geladen …</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-footer d-flex flex-wrap align-items-center gap-3">
|
||||
<button type="button" class="btn btn-primary" id="energieflussSpeichern">Speichern</button>
|
||||
<button type="button" class="btn btn-outline-secondary" id="energieflussZuruecksetzen">Alles auf Katalog zurücksetzen</button>
|
||||
<span class="small" id="energieflussHinweis"></span>
|
||||
</div>
|
||||
</div>
|
||||
<?= reiterFlaecheZu() ?><!-- /Übersicht -->
|
||||
|
||||
<!-- ================================================ Home-Kacheln ==== -->
|
||||
<!-- Der Inhalt kommt aus js/solar/settings.js: eine Karte je Etage, darin
|
||||
je Raum eine Zeile mit ihren Werten. Gezeichnet wird aus dem Modell,
|
||||
|
||||
+24
-9
@@ -1,3 +1,4 @@
|
||||
<?php require_once(__DIR__ . "/energiefluss.php"); ?>
|
||||
<style>
|
||||
.datepicker-native {
|
||||
position: relative;
|
||||
@@ -47,22 +48,36 @@
|
||||
<!--begin::Row-->
|
||||
<div class="row">
|
||||
<!-- Start col -->
|
||||
<!-- Energiefluss. Gezeichnet von js/solar/energiefluss.js aus dem Katalog
|
||||
(energieflussKatalog.js) und den Einstellungen dieses Hauses
|
||||
(Einstellungen -> Übersicht). Bis September 2026 stand hier
|
||||
assets/img/realtime.svg mit festen Kreisen. -->
|
||||
<div class="col-xxl-12 col-xl-12 d-flex align-items-stretch">
|
||||
<div class="card p-0 mb-4" style="width:100%">
|
||||
<div class="card-header pb-0 pt-1">
|
||||
<h3 class="card-title">Realtime</h3>
|
||||
<div class="card p-0 mb-4" style="width:100%" id="energieflussKarte">
|
||||
<div class="card-header pb-1 pt-1 d-flex align-items-center">
|
||||
<h3 class="card-title me-auto">Realtime</h3>
|
||||
<div class="enf-umschalter me-2" role="tablist" aria-label="Ansicht">
|
||||
<button type="button" class="btn btn-sm active" role="tab" aria-selected="true" data-enf-ansicht="fluss">Übersicht</button>
|
||||
<button type="button" class="btn btn-sm" role="tab" aria-selected="false" data-enf-ansicht="anlage">Anlage</button>
|
||||
</div>
|
||||
<div class="card-tools">
|
||||
<button type="button" class="btn btn-tool" data-lte-toggle="card-remove">
|
||||
<i class="bi bi-x-lg"></i>
|
||||
</button>
|
||||
<a class="btn btn-tool" href="index.php?action=settings#energiefluss" title="Übersicht einstellen">
|
||||
<i class="bi bi-sliders"></i>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-0 d-flex flex-column" id="realtimeSVGcontainer" style="position:relative;max-height:800px;">
|
||||
<?php readfile(__DIR__ . "/../assets/img/realtime.svg"); ?>
|
||||
<div class="card-body p-2 p-md-3">
|
||||
<div class="enf-ansicht enf-behaelter" data-enf-ansicht-flaeche="fluss" id="energiefluss"></div>
|
||||
<div class="enf-ansicht" data-enf-ansicht-flaeche="anlage" hidden>
|
||||
<?php readfile(__DIR__ . "/../assets/img/pvplan.svg"); ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.card -->
|
||||
</div>
|
||||
<script>
|
||||
// Abweichungen dieses Hauses vom Katalog, siehe restricted/energiefluss.php.
|
||||
const energieflussEinstellungen = <?= json_encode((object)energieflussLaden(), JSON_UNESCAPED_UNICODE) ?>;
|
||||
</script>
|
||||
|
||||
<div class="col-xxl-12 col-xl-12 col-lg-12 col-md-12 d-flex align-items-stretch">
|
||||
<div class="card p-0 mb-4" style="width:100%">
|
||||
|
||||
Reference in New Issue
Block a user