datenbank.md: die drei Schemata, wer was schreibt, die drei Stufen der Verdichtung und welche Tabelle fuer welche Auswertung taugt. einstellungen.md: was jeder Reiter speichert, welche Regeln das Modell durchsetzt (feste Etagenkuerzel, Loeschsperren, Schluessel gehen nie zurueck) und was die Seite bewusst nicht kann. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 KiB
Die Einstellungsseite
Alles, was das Haus beschreibt statt es zu programmieren: Preise, Etagen und Räume, die Solar-Übersicht, die Werte auf den Kacheln, die Zuordnung der Geräte und der Zugang zum Fahrzeug. Was hier eingetragen wird, steht in der Datenbank — nicht im Quelltext. Eine zweite Installation braucht deshalb keine Codeänderung, sondern einen Nachmittag auf dieser Seite.
| Reiter | Speichert nach | Modell (PHP) | Maske (JS) |
|---|---|---|---|
| Preise | solarLog.gridCosts, gasCosts, fuelCosts |
restricted/costs.php |
settings.js |
| Grundriss | homeMesh.floors, rooms |
restricted/grundriss.php, rooms.php |
grundriss.js |
| Übersicht | homeMesh.energiefluss |
ajax/energiefluss.php |
energieflussEinstellungen.js |
| Home-Kacheln | homeMesh.rooms.werte (JSON) |
restricted/kacheln.php |
settings.js |
| Geräte | homeMesh.actors.room_id |
restricted/automations.php |
settings.js |
| Fahrzeug | skoda.conf (außerhalb des Web-Roots) |
restricted/skodaKeys.php |
settings.js |
Ein Endpunkt für alles: ajax/settings.php?action=…. Die Maske prüft nichts,
was das Modell nicht auch prüfte — sie zeigt nur die Meldung, die von dort
kommt.
flowchart LR
UI["settings.php<br/><small>Reiter aus reiter.php</small>"] --> JS["settings.js · grundriss.js<br/>energieflussEinstellungen.js"]
JS -->|"GET ?action=…"| EP["ajax/settings.php"]
JS -->|"POST ?action=…"| EP
EP --> M1["costs.php"] --> SL[("solarLog")]
EP --> M2["grundriss.php / rooms.php"] --> HM[("homeMesh")]
EP --> M3["kacheln.php"] --> HM
EP --> M4["automations.php<br/><small>geraeteListe, geraetLoeschen</small>"] --> HM
EP --> M5["skodaKeys.php"] --> CONF[["skoda.conf<br/><small>/volume1/homes/wagner/SolarManager</small>"]]
1. Preise
Drei Tabellen desselben Zuschnitts: je Zeile ein Stichtag, kein Enddatum.
Das Ende holt sich die Auswertung beim nächsten Eintrag (LEAD()), damit
eine Preiserhöhung eine Zeile ist und nicht zwei Datumsfelder, die
auseinanderlaufen können.
| Reihe | Felder | Wofür |
|---|---|---|
| Strom | Arbeitspreis, Einspeisung (ct/kWh), Grundpreis (€/Jahr) | Jahresstatistik |
| Gas | Gaspreis (ct/l), Energieinhalt (kWh/l) | Ersparnis des Heizstabs |
| Benzin | Preis (€/l), Verbrauch Verbrenner (l/100 km), Verbrauch Elektro (kWh/100 km) | „Benzin gespart“ |
Zwei Entscheidungen, die man beim Erweitern kennen sollte:
- Gespeichert wird in Euro, eingegeben in Cent (
faktorje Feld). Auf der Rechnung stehen 31,63 ct/kWh — niemand tippt gern 0,3163. - Nicht jedes Feld ist ein Preis. Energieinhalt und Verbräuche stehen hier, weil sie am Brennstoff und am Fahrzeug hängen, nicht an der Physik. Beim Auto bewusst als zwei bekannte Verbräuche statt als „kWh je Liter“ — diese Zahl steht auf keinem Datenblatt.
Eine weitere Tabelle desselben Zuschnitts ist ein Eintrag in
preisReihen(); Endpunkt, Maske und Speicherlogik bleiben unverändert.
2. Grundriss
Der Reiter pflegt floors und rooms — und damit gleichzeitig das Menü, die
Startseite, die Kacheln, die MQTT-Abos und die Etagen-Auswahl im
Automatik-Editor.
flowchart TB
E["Etage<br/><small>code, Bezeichnung, Reihenfolge, Bild, Standard</small>"] --> R["Raum<br/><small>Name, Kürzel, Thermostat-Zweig</small>"]
R --> K["Kachel<br/><small>x/y auf der Zeichenfläche 400 × 300</small>"]
R --> G["Geräte<br/><small>actors.room_id</small>"]
E --> A["Automatiken<br/><small>automations.floor</small>"]
Regeln, die das Modell durchsetzt:
- Das Kürzel einer Etage ist nach dem Anlegen fest. Es steht in URLs,
SVG-Ids und
automations.floor; bis zu vier Großbuchstaben oder Ziffern. - Gelöscht wird nur, was leer ist. Eine Etage mit Räumen oder Automatiken wird abgelehnt — mit der Liste dessen, was im Weg steht. Stilles Mitlöschen wäre Datenverlust.
- Die Raumnummer überlebt alles. Umbenennen und Etagenwechsel ändern sie nicht, deshalb behalten Geräte, Regler und Automatiken ihren Raum.
- Kein
x/yheißt: keine Kachel. Die Position wird im Plan gezogen, Maße wie inhome.php(Fläche 400 × 300, Kachel 24 × 19). - Thermostat-Zweig ist der MQTT-Pfad des Raums. Vorschläge kommen aus den
Topics, die gerade auf
/Temp[degC]enden. - Grundrissbilder: PNG, JPEG oder WebP, geprüft am Inhalt
(
getimagesize), nicht an der Endung. Hochgeladene landen untertiles/grundriss— dem einen Verzeichnis, in das der Webserver schreiben darf, und bewusst außerhalb von Git: Ein Grundriss ist Inhalt dieses Hauses, nicht Teil der Anwendung. Der Dateiname trägt einen Teil der Prüfsumme, damit ein neues Bild eine neue Adresse bekommt und nicht im Browser-Zwischenspeicher hängen bleibt.
3. Übersicht (Energiefluss)
Die Solar-Übersicht hat einen Katalog im Code
(js/solar/energieflussKatalog.js) — jeder Kreis genau einmal, mit Name,
Symbol, Farbe, Elternkreis, Lage und Ringskala. In der Datenbank steht nur,
was davon abweicht.
flowchart LR
KAT["energieflussKatalog.js<br/><small>der Normalfall</small>"] --> MIX["setzeEinstellungen()"]
DB[("homeMesh.energiefluss<br/><small>nur Abweichungen</small>")] --> MIX
MIX --> BILD["gezeichnete Übersicht"]
Änderbar sind: Kreis an/aus, Name, Lage (im Vorschaubild ziehen) und
Optionen, etwa welche Einträge von P_PVn zu welcher PV-Fläche gehören.
„Alles auf Katalog zurücksetzen“ löscht schlicht alle Zeilen.
Der Vorteil dieser Trennung: Eine neue Installation zeigt sofort etwas Sinnvolles, ohne dass jemand eine Tabelle füllt — und ein Update des Katalogs erreicht alle Installationen, ohne die Anpassungen zu überschreiben.
Die Hochkant-Lage (
hochX/hochY) steht nur im Katalog. Wer Kreise verschiebt, ändert also die Queransicht; siehe README → Energiefluss.
4. Home-Kacheln
Welche Werte auf einer Kachel stehen, steht als JSON in rooms.werte —
höchstens drei je Kachel. Ohne Eintrag gilt kachelVorgabe(): mit
Thermostat Soll, Ist und Feuchte, sonst nichts.
Eine Wertdefinition hat überall dieselbe Form, damit zwischen Maske, Datenbank und Anzeige nichts übersetzt werden muss:
| Feld | Bedeutung |
|---|---|
topic |
MQTT-Topic, aus dem der Wert kommt |
pfad |
Schlüssel in einer JSON-Nachricht (die Shelly-Zähler schicken mehrere Werte in einer) |
wechselrichter, feld |
statt topic: Summe benannter Wechselrichter unter solarManager/invertersN |
format |
leistung (Quelle meldet Watt) oder leistung_kw (Quelle meldet Kilowatt) — skaliert selbst zwischen W und kW |
einheit, stellen |
sonst: Einheit dahinter, Nachkommastellen |
negativ |
Vorzeichen drehen, für verkehrt herum eingebaute Zähler |
gross |
die Zahl steht groß auf der Kachel |
icon |
Name eines Bootstrap-Icons ohne bi- |
Gelesen wird das im Browser von homeSVG.kachelWert(). Warum die
Wechselrichter über ihren Namen und nicht über die Nummer gesucht
werden: inverters0 ist eine Position in einer Liste und sagt nichts über
das Dach — ändert sich die Reihenfolge, stünde sonst stillschweigend das
falsche Dach auf der Kachel.
5. Geräte
Die Liste zeigt je Gerät: Beschriftung, Art, Raum, einen Raumvorschlag aus dem MQTT-Topic, die Zahl der Messwerte und Kommandos, wann zuletzt etwas gemeldet wurde — und in welchen Automatiken es steckt.
Genau deshalb liegt die Liste im Modell und nicht in der Maske: Wer über das Löschen entscheidet, muss die Verweise kennen.
| Handlung | Regel |
|---|---|
| Raum zuordnen | actors.room_id; ordnet die Geräteliste im Automatik-Editor und bestimmt, welche Messwerte bei einer Kachel oben stehen |
| Gerät löschen | nur, wenn keine Automatik darauf zeigt. Sonst Ablehnung mit den Namen der Automatiken. Gelöscht wird von innen nach außen: Parameter, Kommandos, Messwerte, Gerät — in einer Transaktion |
| Wieder da? | Steht das Gerät noch im Netz, findet es der nächste Suchlauf wieder. Das ist kein Fehler, sondern sein Zweck |
Der Suchlauf selbst löscht nie (→ datenbank.md) — deshalb braucht es diesen Papierkorb überhaupt.
6. Fahrzeug
Hier werden die Schlüssel der MyŠkoda-API hinterlegt. Sie stehen in
skoda.conf außerhalb des Web-Roots, weil sie auch gatherSkodaData.py
im SolarManager liest.
Ein Schlüssel geht nie an den Browser zurück. Die Seite steht im Heimnetz ohne Anmeldung offen, und so ein Schlüssel steuert das Fahrzeug von überall auf der Welt — Laden, Klimatisierung, Standort. Die Maske sieht nur, ob einer hinterlegt ist und auf welche vier Zeichen er endet. Das genügt, um zwei auseinanderzuhalten, und taugt zu nichts sonst. Geschrieben wird nur in eine Richtung.
Dazu gehört die Aufteilung des Kontingents (20 Anfragen je Stunde für das ganze Konto, nicht je Schlüssel):
| Zweck | Anteil |
|---|---|
Abruf des Fahrzeugzustands (gatherSkodaData.py) |
14 / Stunde |
Befehle aus der Oberfläche (ajax/skodaCmd.php) |
4 / Stunde |
| Reserve | 2 / Stunde |
Ein eigener Befehlsschlüssel trennt deshalb nicht die Kontingente, sondern nur, wer womit fragt — er lässt sich einzeln widerrufen.
7. Was die Seite bewusst nicht kann
- Geräte anlegen. Das macht der Suchlauf
(
restricted/deviceDiscovery/); von Hand angelegte Geräte hätten keine Verbindung zur Wirklichkeit. - Automatiken bearbeiten. Dafür gibt es den Editor auf der Home-Seite (→ automatiken.md).
- Die Standheizung des Fahrzeugs starten. Das verlangt die Sicherheits-PIN; sie dauerhaft auf der NAS zu hinterlegen ist eine andere Größenordnung als ein API-Schlüssel und soll bewusst entschieden werden.
8. Wo fange ich an, wenn ich …
| Vorhaben | Ort |
|---|---|
| … eine weitere Preistabelle | Eintrag in preisReihen() (restricted/costs.php) |
| … ein neues Feld auf einer Kachel | kachelWertPruefen()/kachelKatalog() in kacheln.php und homeSVG.kachelWert() in homeMQTT.js |
| … eine Etage, einen Raum, ein Grundrissbild | nur diese Seite — kein Code |
| … einen weiteren Reiter | restricted/settings.php (Fläche über reiter.php), Endpunkt in ajax/settings.php, Modell in restricted/, Maske in settings.js |
| … einen weiteren Zugangsschlüssel verwalten | restricted/skodaKeys.php als Vorbild: Modell kennt die Datei, Maske sieht nur den Stand |