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>
9.4 KiB
Die Datenbanken
Drei Schemata auf derselben MariaDB der NAS (Port 3310). Die Trennung ist keine Ordnungsliebe, sondern Zuständigkeit:
| Schema | Inhalt | Kennzeichen |
|---|---|---|
homeMesh |
Geräte, Automatiken, Grundriss — was gilt | klein, viele Fremdschlüssel, wird von Hand gepflegt |
solarLog |
Messreihen, Statistik, Preise — was war | groß, schreibt fast nur die NAS, wird verdichtet |
Logins |
Passkeys und Einmal-Links | winzig, sicherheitsrelevant |
Wer von wo verbindet:
| Aufrufer | Funktion / Datei | Zugang |
|---|---|---|
| Weboberfläche | meshDb() (restricted/meshdb.php) |
homeMesh |
| Weboberfläche | solarDb() (restricted/costs.php) |
solarLog |
| Weboberfläche | commandDb() (restricted/commands.php) |
homeMesh, für Kommandos |
| Weboberfläche | checkLogin() (helper.php) |
Logins |
| NAS-Prozesse | config.ini im SolarManager |
beide, eigene Benutzer |
Die Zugangsdaten stehen in restricted/mysql.php (Web) bzw. config.ini
(NAS) — beides nicht im Git.
1. homeMesh — was gilt
erDiagram
floors ||--o{ rooms : "hat"
rooms ||--o{ actors : "room_id"
actors ||--o{ actor_states : "meldet"
actors ||--o{ actor_commands : "kann"
actor_commands ||--o{ command_parameters : "nimmt"
state_types ||--o{ actor_states : "Datentyp"
automations ||--o{ automation_conditions : "wenn"
automations ||--o{ automation_actions : "dann"
automations ||--o{ automation_log : "Protokoll"
automation_conditions }o--|| actor_states : "vergleicht"
automation_actions }o--|| actor_commands : "schickt"
automation_actions ||--o{ automation_action_params : "mit"
floors ||--o{ automations : "Reiter"
energiefluss }o--|| floors : "keine Beziehung, nur Abweichungen"
Geräte
| Tabelle | Inhalt | Geschrieben von |
|---|---|---|
actors |
ein Gerät je Zeile. url entscheidet den Weg: mqtt://, http://, wled://, io:///rts:///internal:///ogp:// (Tahoma), Logic, Automatik. room_id verknüpft mit rooms |
Gerätesuchlauf; room_id von Hand (Einstellungen → Geräte) |
actor_states |
alles Lesbare. url ist entweder ein MQTT-Topic oder ein Feldname im HTTP-JSON, value_path der Schlüssel darin, current_value der letzte Wert |
Suchlauf (Adressen), Runner (Werte) |
actor_commands, command_parameters |
alles Schaltbare und die Parameter dazu (url = Stelle im Befehl) |
Suchlauf |
state_types |
Datentypen: integer, float, bool, string, time, date, datetime, deltatime, elapsed … |
fest |
Zwei Dinge, die immer wieder überraschen:
sensors/sensor_statessind unbenutzt. Der Suchlauf legt auch reine Messgeräte inactors/actor_statesab — ein Gerätebegriff für alles.- Der Suchlauf löscht nie (
clear_tables = false). Er schreibt mitON DUPLICATE KEY UPDATEauf den URLs. EinTRUNCATEwürde neue IDs vergeben und alle Automatiken auf falsche Geräte zeigen lassen. Karteileichen räumt man in den Einstellungen weg (→ einstellungen.md).
Automatiken
Eigenes Dokument: automatiken.md. Kurz:
automations (Rahmen + Laufzustand), automation_conditions (Auslöser,
group_no = UND/ODER), automation_actions + automation_action_params
(was geschickt wird), automation_log (30 Tage), calendar_days (Ferien
und Feiertage, jährlich per fetch_calendar.py).
Grundriss und Anzeige
| Tabelle | Inhalt | Besonderheit |
|---|---|---|
floors |
Etagen: code (fest — steht in URLs, SVG-Ids und automations.floor), Bezeichnung, Reihenfolge, Grundrissbild, Standard-Etage |
Schema homeMesh_grundriss.sql |
rooms |
feste Nummer id, Etage, Name, kuerzel (SVG-Ids), Kachelposition x/y (leer = keine Kachel), thermostat (MQTT-Zweig), werte (JSON, leer = Vorgabe) |
die Nummer überlebt Umbenennen und Umziehen |
energiefluss |
nur Abweichungen der Solar-Übersicht vom Katalog: aktiv, name, x/y, optionen |
keine Zeile = Katalogwert; Schema homeMesh_energiefluss.sql |
Die drei view_*-Objekte sind Lesehilfen aus der Anfangszeit des
Gerätesuchlaufs; die Anwendung benutzt sie nicht.
2. solarLog — was war
Stand heute rund 170 MB, verteilt auf wenige große Reihen:
| Tabelle | Zeilen | Inhalt | Geschrieben von |
|---|---|---|---|
EnergyFlow |
~108.000 | Momentanleistungen alle 5 min: PV, Netz, Batterie, Etagen, Heizstab, Wallbox | solarManager.py |
EnergyFlow_hourly |
~38.000 | Stundenarchiv in kWh — die Langzeitquelle | Rollup-Job |
stats_daily |
~1.500 | Tageswerte für die Jahresstatistik, inklusive vorberechneter Größen | Rollup-Job |
Heater |
~632.000 | Heizung und Heizstab | solarManager.py |
puffertemp |
~247.000 | Puffertemperaturen | solarManager.py |
wasser, zisterne |
~300.000 | Wasserzähler und Zisterne | gatherWaterData.py |
windrad, kiga_windrad |
~255.000 | Windräder | solarManager.py |
weatherStation, weatherHours, weatherDays |
~220.000 | eigene Wetterstation, Vorhersage | Wetterbrücke, Open-Meteo |
daylight |
663 | Sonnenauf- und -untergang je Tag | Vorhersage-Job |
simPower |
~82.000 | Ertragsprognose | Prognose-Job |
byd, byd_zellen |
1.559 / 492 | BYD-Speicher: alle 5 min Ladestand, SOH, Temperaturen; alle 15 min 128 Zellspannungen und 64 Temperaturen | gatherBYDData.py |
skoda, skoda_raw, skoda_ladepunkte |
~1.900 | Fahrzeugzustand, Rohantwort, Ladeverlauf im Minutentakt | gatherSkodaData.py |
gridCosts, gasCosts, fuelCosts |
13 | Preiszeitreihen | Einstellungsseite |
car, Status, actors, sensors, autoActions*, WindradLog |
— | Altlasten aus der Vorgängerfassung | — |
Drei Stufen der Verdichtung
flowchart LR
A["EnergyFlow<br/><small>alle 5 min, Momentanleistung</small>"] -->|"nachts, Rollup"| B["EnergyFlow_hourly<br/><small>Stunden in kWh</small>"]
A --> C["stats_daily<br/><small>Tageswerte + vorberechnete Größen</small>"]
A -. "nach 12 Monaten ausgedünnt" .-> A
B --> H2["Historie: Jahr, Jahrzehnt"]
A --> H1["Historie: Monat<br/><small>der laufende Tag fehlt im Archiv</small>"]
C --> S["Jahresstatistik<br/><small>ajax/getStats.php</small>"]
Wichtig beim Auswerten:
- Für einen Monat auf
EnergyFlowrechnen, sonst fehlt der laufende Tag (das Archiv wird nur nachts fortgeschrieben). - Für Jahre auf
EnergyFlow_hourly, weil die Rohdaten nach zwölf Monaten ausgedünnt werden — wer dort rechnet, zeigt für alte Jahre zu wenig. - Für Kennzahlen auf
stats_daily. Manches lässt sich aus Tagessummen gar nicht rekonstruieren, etwa der Eigenverbrauch oder der Solaranteil der Wallbox: dafür muss je Messwert eine Bedingung ausgewertet werden. - Einspeisung kommt aus
gridPfeed, dem Zählerregister, nicht aus dem SaldogridP. Innerhalb eines Fensters gibt es Bezug und Einspeisung gleichzeitig; der Saldo löscht kurze Bezüge weg. pv_fehlbetrag_kwhgleicht aus, was fehlt, wenn ein Wechselrichter die Verbindung zur OpenDTU verliert: Senken sind dann vollständig gemessen, die Erzeugung nicht — ohne die Korrektur wird der Direktverbrauch negativ.
Die Preistabellen tragen einen Stichtag und kein Enddatum; das Ende holt
sich die Auswertung mit LEAD() beim nächsten Eintrag. Weil ein Stichtag ein
Datum ist, kann ein Tag nie zwei Tarife haben — die Verdichtung auf Tage
verliert hier also nichts.
3. Logins — Zugang
| Tabelle | Inhalt |
|---|---|
users |
Passkeys, authKey, lastAuth |
addUser |
Einmal-Links, mit denen ein neues Gerät einen Passkey anlegen darf |
checkLogin() in helper.php entscheidet in dieser Reihenfolge:
- Heimnetz (
isLocal()) — freier Zugang, ohne Anmeldung. - Gültige Sitzung mit
authKey, höchstens zwei Tage alt. - Sonst: Anmeldeseite.
Daraus folgt die wichtigste Sicherheitsregel dieses Projekts: Was im Heimnetz ohne Anmeldung sichtbar ist, darf keinen Schlüssel preisgeben. Deshalb gehen API-Schlüssel nie an den Browser zurück (→ einstellungen.md), und deshalb schaltet die Oberfläche Geräte über den Server statt direkt.
4. Pflege und Sicherung
| Aufgabe | Wer | Takt |
|---|---|---|
| Rohdaten ausdünnen, Stundenarchiv und Tageswerte fortschreiben | Rollup-Job auf der NAS (solarlog-rollup.sh) |
nachts |
automation_log aufräumen |
Runner selbst | täglich, 30 Tage Aufbewahrung |
calendar_days füllen |
fetch_calendar.py |
jährlich per Cron |
| Datenbanksicherung | backupDB.sh |
per Cron |
| Schema neu aufsetzen | homeMesh_DB-layout.sql, homeMesh_automations.sql, homeMesh_grundriss.sql, homeMesh_energiefluss.sql in dieser Reihenfolge, danach die Nachrüstskripte aus SolarManager/autoActions/ |
einmalig |
5. Wo fange ich an, wenn ich …
| Vorhaben | Ort |
|---|---|
| … eine neue Messreihe aufzeichnen | Tabelle in solarLog anlegen, Schreiber im SolarManager, Leser als ajax/*.php |
| … eine Kennzahl in die Jahresstatistik aufnehmen | Spalte in stats_daily (Rollup) und Eintrag in ajax/getStats.php |
| … wissen, warum eine Zahl in zwei Ansichten abweicht | zuerst prüfen, aus welcher Stufe sie stammt (roh, Stunde, Tag) |
| … ein Gerät endgültig loswerden | Einstellungen → Geräte; per SQL nur, wenn keine Automatik darauf zeigt |
| … eine zweite Installation aufsetzen | Schemata einspielen, dann Einstellungen → Grundriss und → Übersicht; Inhalte stehen in der Datenbank, nicht im Code |