diff --git a/README.md b/README.md index 7b41695..0193307 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,8 @@ Für einige Teile reicht diese Landkarte nicht, weil ihr Verhalten aus dem Zusammenspiel mehrerer Prozesse entsteht. Sie haben eigene, ausführliche Dokumente unter [`doku/`](doku/README.md): **[Automatiken](doku/automatiken.md)**, **[Zeitleiste](doku/zeitleiste.md)**, -**[Datenbanken](doku/datenbank.md)** und -**[Einstellungsseite](doku/einstellungen.md)**. +**[Solar-Übersicht](doku/uebersicht.md)**, **[Datenbanken](doku/datenbank.md)** +und **[Einstellungsseite](doku/einstellungen.md)**. --- diff --git a/doku/README.md b/doku/README.md index 008fdff..e26304c 100644 --- a/doku/README.md +++ b/doku/README.md @@ -13,6 +13,7 @@ abzulesen sind. |---|---| | [automatiken.md](automatiken.md) | Die Automatiken (AutoActions): Datenmodell, Editor, Runner, wie eine Bedingung wirklich ausgewertet wird, Verkettung, Sperren, Fehlerbilder | | [zeitleiste.md](zeitleiste.md) | Die Zeitleiste der Automatiken: wie der Server den Tag ausrechnet und wie der Browser ihn zeichnet | +| [uebersicht.md](uebersicht.md) | Die Solar-Übersicht: Katalog und Renderer, wie aus Watt ein Ring, ein Fluss und ein Füllstand wird | | [datenbank.md](datenbank.md) | Die drei Schemata: wer was schreibt, wie Messreihen verdichtet werden, welche Tabelle man für welche Auswertung nimmt | | [einstellungen.md](einstellungen.md) | Die Einstellungsseite: was jeder Reiter speichert, welche Regeln das Modell durchsetzt und was die Seite bewusst nicht kann | diff --git a/doku/bilder/uebersicht.png b/doku/bilder/uebersicht.png new file mode 100644 index 0000000..669344d Binary files /dev/null and b/doku/bilder/uebersicht.png differ diff --git a/doku/uebersicht.md b/doku/uebersicht.md new file mode 100644 index 0000000..b43edb3 --- /dev/null +++ b/doku/uebersicht.md @@ -0,0 +1,285 @@ +# 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
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 |