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>
11 KiB
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.
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 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 neueanimation-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:
- Ein Takt je Sekunde. Der Broker schickt deutlich öfter.
- Nur geänderte Werte schreiben. Jeder Schreibzugriff macht Style und Paint ungültig; gemessen waren rund 71 % der Zugriffe reine Wiederholungen.
- Animationen stehen still, wenn niemand hinsieht —
IntersectionObserverfür „Karte im Bild“ undvisibilitychangefü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 |
