# 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.

| 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.
```mermaid
flowchart LR
PV["Sonne
pv"] --> WR
NETZ["Netz
netz"] --> WR
AKKU["Batterie
akku"] --> WR(("Autarkie
wr · Pflicht"))
WR --> VERB(("Verbrauch
verbrauch · Pflicht"))
VERB --> OG["Obergeschoss"] --> WBOG["Wallbox OG"]
VERB --> EG["Erdgeschoss"] --> WBEG["Wallbox EG"]
VERB --> UG["Untergeschoss"]
VERB --> HEIZ["Heizstab"]
BEW["Bewässerung
frei stehend"]
```
Ein Eintrag im Katalog sieht so aus:
```js
{
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](einstellungen.md#3-übersicht-energiefluss)).
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.
```mermaid
flowchart TB
W["Leistung in Watt
aus lesen()"] --> Z["Zahl im Kreis
W bis 999, danach kW"]
W --> R["Ring
Anteil an skala, gestaucht"]
W --> F["Fluss zum Elternkreis
Punktgröße + Tempo"]
W --> S["Symbolzustand
an/aus, Füllstand, Temperaturen"]
```
### 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 i−1)
```
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
```mermaid
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
```mermaid
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,
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 hinsieht** — `IntersectionObserver`
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 |