Files
Smart-Dashboard/doku/datenbank.md
T
adminandClaude Opus 5 b6ff7c6ad9 Doku: das Meteogramm beschrieben, falsche Quellenangabe richtiggestellt
doku/meteogramm.md: der Weg der Daten, welche Groessen links vom Jetzt-Strich
gemessen und welche vorhergesagt sind, die vier Tabellen samt der Regel, dass
nichts geloescht wird, das Tupel-Schema des Endpunkts, das Katalog-Vokabular
mit dem Dreizeiler "so kommt eine Spur dazu", und die beiden Entscheidungen
beim Zeichnen (ein SVG ueber alle Felder, Wolkenband als Farbverlauf statt
als Raster).

Dazu zwei Fallen, die sonst niemand wiederfindet: die Archiv-Schnittstelle
von Open-Meteo rechnet alle Zeiten mit dem heute gueltigen Zeitzonenversatz
um, und die Spaltenlage des Endpunkts steht an zwei Stellen und muss
zueinander passen.

Richtiggestellt: doku/datenbank.md und README.md nannten Open-Meteo als
Quelle von weatherHours/weatherDays. Das war nie so - es war OpenWeatherMap,
und seit dem 27.10.2024 gar nichts mehr. Beide Zeilen warfen ausserdem
Messung und Vorhersage in einen Topf; sie sind jetzt getrennt, mit dem
richtigen Schreiber je Tabelle.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-21 14:07:07 +02:00

12 KiB
Raw Blame History

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 ~195.000 die eigene Wetterstation, alle 5 min die Station selbst, über /volume1/web/weatherStation.php
weatherHours, weatherDays ~40.000 / 1.600 Wettervorhersage und Archiv, stündlich bzw. täglich (→ meteogramm.md) gatherForecastData.py (Open-Meteo)
weatherTilted, weatherForecastLog je nach Nutzung Einstrahlung in Modulebene; die erste Vorhersage je Stunde gatherForecastData.py
daylight 663 Sonnenauf- und -untergang, nur bis morgen solarManager.py (gerechnet, nicht abgerufen)
simPower ~82.000 Ertragsprognose, 7 Tage voraus Solcast, 4×/Tag über /volume1/web/gatherSolar.php
byd, byd_zellen 1.559 / 492 BYD-Speicher: alle 5 min Ladestand, SOH, Temperaturen; alle 15 min 128 Zellspannungen und 64 Temperaturen (→ byd.md) 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.

Der Rollup-Job

~/Backup-scripts/solarlog-rollup.sh (Schema dazu: solarlog-rollup.sql) hat drei Betriebsarten:

Aufruf Was er tut
fill [--from … --to …] Stunden- und Tageswerte fortschreiben. Idempotent (INSERT … ON DUPLICATE KEY UPDATE), darf jederzeit wiederholt werden
thin [--dry-run] [--months 12] Rohdaten älter als zwölf Monate ausdünnen — nur, wenn für den Monat ein Aggregat vorliegt. Vorher wird der betroffene Zeitraum monatsweise nach /volume1/docker/solarLog_history weggesichert
status zeigt, bis wohin aggregiert und ab wann ausgedünnt ist

Ausgedünnt werden EnergyFlow, Heater und weatherStation; das Zielraster beträgt 15 Minuten. weatherHours und weatherDays stehen bewusst nicht dabei — sie sind schon stündlich, und ihre Strahlungswerte sind die eine Hälfte des Datenpaares für eine künftige Ertragsprognose (die andere ist EnergyFlow_hourly.pv_kwh). Was man beim Rechnen auf den Rohdaten wissen muss, steht ausführlich im Kopf der SQL-Datei — die wichtigsten Punkte:

  • Alles sind Momentanleistungen, gemittelt über rund fünf Minuten. kWh entstehen nur durch Integration über die Zeit, nie durch Mittelwerte über Zeilen.
  • Die Einheiten sind gemischt: die Wallbox-Spalten liefern Kilowatt, alle übrigen Leistungsspalten Watt.
  • Die Vorzeichen sind es auch: totalConsumption negativ = Hausverbrauch, battP negativ = Laden, und PL*_OG kommt negativ, während PL*_EG und PL*_UG positiv sind.
  • Messlücken über 15 Minuten zählen nicht als durchgehende Leistung.

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 ~/Backup-scripts/solarlog-rollup.sh (läuft als root im MariaDB-Container) 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