doku/byd.md: warum der Umweg über die BMU überhaupt nötig ist (der Gen24 kennt weder Zellen noch SOH noch die Zähler), der Ablauf der sechs Anfragen in einer einzigen Verbindung, und vor allem die Eigenheit, aus der sich der ganze Aufbau des Sammlers ergibt — das Netzwerkmodul startet alle ~102 Sekunden neu und bedient danach nur die ersten ein, zwei Verbindungen. Dazu die Byte-Lagen samt der einen Abweichung vom ioBroker-Adapter (wortvertauschte Gesamtzähler), die Aufteilung der 128 Zellen auf vier Antworten, was unter solarManager/byd/… herauskommt, die beiden Tabellen in solarLog und wie speicher.js daraus die Ansicht baut. Dabei aufgefallen und vermerkt: ajax/speicher.php?was=gesundheit hat keinen Aufrufer. Die Tageswerte für ein Alterungsdiagramm liegen also schon bereit, es fehlt nur die Anzeige. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
11 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 (→ 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
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.
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. 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:
totalConsumptionnegativ = Hausverbrauch,battPnegativ = Laden, undPL*_OGkommt negativ, währendPL*_EGundPL*_UGpositiv 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:
- 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 | ~/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 |