Files
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

286 lines
11 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](bilder/uebersicht.png)
| 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<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:
```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<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
```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,<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 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 |