Files
Smart-Dashboard/doku/uebersicht.md
T
adminandClaude Opus 5 061dfa3fc5 Doku: Solar-Uebersicht beschrieben
Katalog und Renderer, die Elternbeziehung als Struktur, und wie aus Watt
eine Zahl, ein gestauchter Ring, ein Fluss mit Punktgroesse und Tempo, ein
Fuellstand und Plaketten werden. Dazu quer/hochkant, der Sekundentakt mit
seinen drei Sparmassnahmen und der Bearbeitungsmodus.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 08:11:39 +02:00

11 KiB
Raw Blame History

Die Solar-Übersicht (Energiefluss)

Die Karte „Realtime“ auf der Solar-Seite: Wo kommt der Strom her, wo geht er hin, wie viel ist es gerade. Sie ist kein gezeichnetes Bild, sondern ein Katalog plus ein Renderer — und genau diese Trennung macht sie erweiterbar.

Die Übersicht

Datei Aufgabe
js/solar/energieflussKatalog.js Was es gibt: jeder Kreis genau einmal — Name, Symbol, Farbe, Elternkreis, Lage, Ringskala, Klickziel und lesen()
js/solar/energiefluss.js Wie gezeichnet wird: Kreise, Ringe, Flüsse, Symbole, Plaketten, Hochkant, Bearbeitungsmodus. Kennt keinen Kreis beim Namen
restricted/energiefluss.php speichert Abweichungen je Haus (homeMesh.energiefluss)
js/solar/solarMQTT.js hängt beides an den MQTT-Strom und an die Reiter

1. Der Aufbau: Kreise und ihre Eltern

Jeder Kreis nennt seinen Elternkreis; daraus entsteht der Fluss zwischen beiden. Es gibt keine getrennte Liste von Verbindungen — die Struktur ist die Elternbeziehung.

flowchart LR
  PV["Sonne<br/><small>pv</small>"] --> WR
  NETZ["Netz<br/><small>netz</small>"] --> WR
  AKKU["Batterie<br/><small>akku</small>"] --> WR(("Autarkie<br/><small>wr · Pflicht</small>"))
  WR --> VERB(("Verbrauch<br/><small>verbrauch · Pflicht</small>"))
  VERB --> OG["Obergeschoss"] --> WBOG["Wallbox OG"]
  VERB --> EG["Erdgeschoss"] --> WBEG["Wallbox EG"]
  VERB --> UG["Untergeschoss"]
  VERB --> HEIZ["Heizstab"]
  BEW["Bewässerung<br/><small>frei stehend</small>"]

Ein Eintrag im Katalog sieht so aus:

{
  id: "heizstab", name: "Heizstab", symbol: "speicher", farbe: "#c2614e",
  eltern: "verbrauch",               // woher der Fluss kommt
  x: 1120, y: 380,                   // Lage in der Queransicht
  hochX: 670, hochY: 600,            // optional: eigene Lage hochkant
  skala: 6000,                       // W, bei denen der Ring voll ist
  klick: { modal: "heater" },        // Ziel beim Antippen
  lesen(m, opt) {                    // aus MQTT-Werten wird ein Zustand
    const w = m.zahl("solarManager/pHeat");
    return { leistung: w, fluss: w, ruhe: w < 20, neben: "…", zeichen: [...],
             zustand: { an: w >= 20, temps: [...] } };
  },
}

Was lesen() zurückgeben darf:

Feld Wirkung
leistung die große Zahl und der Ring
wert Text statt der Leistung (z. B. „93 %“)
neben die kleine Zeile darunter
segmente [{w, farbe, knoten?}] statt eines einfarbigen Rings
fluss Watt vom Elternkreis hierher; negativ = Gegenrichtung
ruhe Kreis blass zeichnen — es ist nichts los
zeichen Plaketten am Rand: [{art, text?, titel?}]
zustand was das Symbol braucht: {an, stand, laden, temps}

Ein neuer Kreis ist ein neuer Eintrag hier — sonst nichts. Kein Markup, keine CSS-Regel, keine Zeile im Renderer. Nur ein neues Symbol braucht einen Eintrag in SYMBOLE (energiefluss.js).

Abweichungen je Haus

Der Katalog ist der Normalfall, homeMesh.energiefluss hält nur, was davon abweicht: Kreis aus, anderer Name, andere Lage, andere Optionen. Eingestellt wird das unter Einstellungen → Übersicht (→ einstellungen.md).

Ist ein Kreis aus, rückt sein Kind automatisch an den nächsten eingeschalteten Vorfahren: elternWirksam sucht die Kette hinauf. Und ein Ringsegment, das zu einem ausgeschalteten Kreis gehört, fällt in das letzte verbleibende Segment — die Summe bleibt also richtig, auch wenn jemand die Wallbox ausblendet.


2. Die Leistungsvisualisierung

Drei Dinge zeigen dieselbe Leistung auf drei Arten. Das ist Absicht: Die Zahl ist genau, der Ring vergleicht, der Fluss zeigt die Richtung.

flowchart TB
  W["Leistung in Watt<br/><small>aus lesen()</small>"] --> Z["Zahl im Kreis<br/><small>W bis 999, danach kW</small>"]
  W --> R["Ring<br/><small>Anteil an skala, gestaucht</small>"]
  W --> F["Fluss zum Elternkreis<br/><small>Punktgröße + Tempo</small>"]
  W --> S["Symbolzustand<br/><small>an/aus, Füllstand, Temperaturen</small>"]

2.1 Der Ring — gestauchte Skala

Linear wäre der Ring unbrauchbar: Bei skala = 15000 wären 300 W ein Strich von zwei Grad, und genau in diesem Bereich steht die Anlage die meiste Zeit.

   Anteil = log10(1 + 9 · w/skala)

   w/skala    0 %   10 %   25 %   50 %   100 %
   Ring       0 %   28 %   47 %   70 %   100 %

Der volle Ausschlag bleibt exakt bei skala, aber das untere Zehntel bekommt knapp ein Drittel des Rings.

Mehrere Segmente (etwa Verbrauch = Etagen + Heizstab + Rest) werden über die Summe gerechnet und nicht je Segment — die gestauchte Skala ist nicht additiv, sonst reichten drei Viertel-Segmente weiter als ein volles:

   Segment i = ringAnteil(Σ bis i)  ringAnteil(Σ bis i1)

Zwischen zwei Segmenten bleibt eine Lücke von 0,6 %; Segmente unter 0,4 % verschwinden ganz, sonst stünde dort ein Punkt statt eines Bogens.

2.2 Der Fluss — Punkte statt Linie

Ein Fluss ist ein Bogen vom Eltern- zum Kindkreis (flussPfad()): senkrecht los, dann waagerecht hinein — hochkant umgekehrt. Gezeichnet wird er als gestrichelte Linie mit runden Enden, also als Punktreihe, die über stroke-dashoffset wandert.

Größe Formel Wirkung
Punktgröße 2,4 + 11 · (1 e^(W/5000)) 2,4 px bei ein paar hundert Watt, ~13 px bei Volllast
Tempo 1 / (0,7 + 1,9 · e^(W/3000)) 2,6 s je Abschnitt bei wenig, 0,7 s bei viel
Richtung Vorzeichen von fluss enf-rueck dreht die Animation um
Stillstand unter 15 W Punkte ausgeblendet

Zwei Feinheiten, die teuer erkauft waren:

  • Das Tempo wird über Animation.updatePlaybackRate() gesetzt, nicht über eine neue animation-duration. Eine geänderte Dauer rechnet die Position neu — die Punkte sprängen bei jeder Wertänderung.
  • Keine Bahn unter den Punkten. Die dunkle Linie gibt es nur im Bearbeitungsmodus, wo sie zeigt, was mit was verbunden ist. In der Ansicht erzählen die Punkte den Weg; wo nichts fließt, steht nichts.

2.3 Füllstände

Batterie und Wallbox haben statt eines bloßen Rings eine Welle im Kreis, die mit dem Ladestand steigt (zustand.stand, 0…1). Die Fläche liegt bei 22 % Deckung, beim Laden 32 %, und die Wasserlinie ist zusätzlich als Kante gezeichnet — sonst muss man den Stand suchen. Meldet ein Kreis keinen Stand, bleibt die Füllung ganz weg: Eine Wallbox ohne Auto zeigte sonst eine Linie am unteren Rand.

2.4 Plaketten und Symbolzustände

Kleine runde Zeichen am Kreisrand, aus lesen().zeichen:

art Zeichen Bedeutung
eco Blatt Eco-Modus (Wallbox, Heizstab)
uhr Uhr „Nächste Fahrt“
manuell Regler fester Modus
stecker, schloss Stecker, Schloss Fahrzeug angesteckt / verriegelt
heizung Flamme Heizung im Geschoss an
tank Zapfsäule Tankfüllung, mit Text
warnung Dreieck Störung

Die Glyphen sind Bootstrap-Icons und stehen als \uF…-Escapes im Quelltext — die Zeichen selbst liegen im privaten Unicode-Bereich, sind im Editor unsichtbar und werden beim Kopieren leicht verfälscht.

zustand steuert dagegen das Symbol selbst: an schaltet die Zustandsklasse (leuchtender Kern), laden die Ladeanimation, temps färbt die Schichten des Pufferspeichers zwischen kalt und heiß.

2.5 Die Bewässerung

Der einzige Kreis ohne Leistung: Statt Zahl und Ring stehen darin drei Zeilen, je Zone ein Symbol, der Name und der Zustand. Gezeichnet werden sie aus katalog.zonen, gefüllt von bewaesserungAnzeige.js, das die Ids kennt, die der Renderer vergibt.


3. Zwei Ansichten aus einer Lage

flowchart LR
  subgraph Q["quer (≥ 720 px)"]
    direction LR
    Q1["Quellen links"] --> Q2["Mitte"] --> Q3["Verbraucher rechts"]
  end
  subgraph H["hochkant (< 720 px)"]
    direction TB
    H1["Quellen oben"] --> H2["Mitte"] --> H3["Verbraucher unten"]
  end

Hochkant werden schlicht x und y vertauscht. So braucht es keine zweite Lage je Kreis, und eine eigene Lage aus den Einstellungen gilt in beiden Ansichten. Wo das Vertauschen unglücklich steht, nennt der Katalog mit hochX/hochY eine eigene Lage — so stehen UG und Heizstab hochkant getauscht, damit die drei Geschosse untereinander liegen.

Umgeschaltet wird über einen ResizeObserver am Behälter, und nur dann, wenn die Ansicht wirklich wechselt. Das Kiosk-Display (anzeige.js) setzt hochkantUnter: 0 und bleibt damit immer quer.

Der viewBox wird nach dem Zeichnen aus den äußersten Kreisen gerechnet (rahmenSetzen()), inklusive Platz für den Namen unter dem Kreis — ein ausgeschalteter Randkreis verkleinert das Bild also automatisch.


4. Wie die Werte hereinkommen

sequenceDiagram
  participant M as MQTT-Broker
  participant S as solarMQTT.js
  participant E as Energiefluss
  participant K as Katalog

  M->>S: solarManager/# (viele Nachrichten je Sekunde)
  Note over S: Werte in mqttData ablegen,<br/>höchstens 1× pro Sekunde weiterreichen
  S->>E: aktualisieren(mqttData)
  loop je sichtbarem Kreis
    E->>K: lesen(m, optionen)
    K-->>E: {leistung, fluss, zeichen, zustand, …}
    Note over E: nur schreiben, was sich geändert hat
  end

Drei Sparmaßnahmen, die zusammen die Karte flüssig halten:

  1. Ein Takt je Sekunde. Der Broker schickt deutlich öfter.
  2. Nur geänderte Werte schreiben. Jeder Schreibzugriff macht Style und Paint ungültig; gemessen waren rund 71 % der Zugriffe reine Wiederholungen.
  3. Animationen stehen still, wenn niemand hinsiehtIntersectionObserver für „Karte im Bild“ und visibilitychange für „Tab im Vordergrund“.

Ein Fehler in einem lesen() fängt der Renderer je Kreis ab: Er meldet ihn auf der Konsole und zeichnet die übrigen weiter. Eine kaputte Zeile im Katalog nimmt also nicht die ganze Übersicht mit.


5. Antippen und Bearbeiten

klick im Katalog sagt, was ein Tipp auf einen Kreis tut:

Eintrag Wirkung
{ ansicht: "anlage" } schaltet auf die Anlagen-Ansicht (anlage.js)
{ ansicht: "speicher" } schaltet auf die Speicher-Ansicht (speicher.js)
{ modal: "heater" } öffnet das passende Bedienfenster (bedienfenster.js, ladefenster.js)

Klickbare Kreise bekommen ein Pfeil-Abzeichen, tabindex, role="button" und reagieren auf Enter und Leertaste.

Im Bearbeitungsmodus (bearbeiten: true, nur in den Einstellungen) sind die Kreise ziehbar, die Verbindungslinien sichtbar und die Lage wird beim Loslassen auf zehn Einheiten gerundet an onVerschoben(id, x, y) gemeldet. Die Vorschau dort läuft mit festen Beispielwerten, damit man auch nachts sieht, was man einstellt.


6. Wo fange ich an, wenn ich …

Vorhaben Ort
… einen Kreis hinzufügen ein Eintrag in ENERGIEFLUSS_KATALOG.knoten — mehr nicht
… ein neues Symbol SYMBOLE in energiefluss.js (24er Raster, Mitte 0/0, currentColor)
… eine neue Plakette ZEICHEN in energiefluss.js, Glyph als \uF…-Escape
… die Ringspreizung ändern ringAnteil() — der Faktor 9 bestimmt die Stauchung
… Punktgröße oder Tempo flussSetzen() in energiefluss.js
… die Lage hochkant korrigieren hochX/hochY im Katalog (gilt nur dort)
… einen Kreis ausblenden oder verschieben Einstellungen → Übersicht, keine Codeänderung