Files
Smart-Dashboard/doku/datenbank.md
T
adminandClaude Opus 5 93ec545aaa Doku: Datenbanken und Einstellungsseite ergaenzt
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>
2026-09-21 07:57:35 +02:00

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_states sind unbenutzt. Der Suchlauf legt auch reine Messgeräte in actors/actor_states ab — ein Gerätebegriff für alles.
  • Der Suchlauf löscht nie (clear_tables = false). Er schreibt mit ON DUPLICATE KEY UPDATE auf den URLs. Ein TRUNCATE wü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 EnergyFlow rechnen, 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 Saldo gridP. Innerhalb eines Fensters gibt es Bezug und Einspeisung gleichzeitig; der Saldo löscht kurze Bezüge weg.
  • pv_fehlbetrag_kwh gleicht 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:

  1. Heimnetz (isLocal()) — freier Zugang, ohne Anmeldung.
  2. Gültige Sitzung mit authKey, höchstens zwei Tage alt.
  3. 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