# SolarManager Die Hintergrundprozesse des Hauses: Messwerte einsammeln, Wallbox und Heizung regeln, die Automatiken auswerten. Gegenstück ist das Web-Repo `admin/Smart-Dashboard` unter `/volume1/web/smart` — dort liegt die Anzeige, hier liegt alles, was ohne offenen Browser weiterlaufen muss. Beide Seiten reden über zwei Dinge miteinander: den **MQTT-Broker** für alles Aktuelle und die **Datenbanken** für alles, was bleiben soll. Kein Prozess ruft einen anderen direkt auf. ## Woher die Zahlen kommen ```mermaid flowchart LR WR["Wechselrichter
GoodWe · OpenDTU · DTU-BI"] EM3["Stromzähler
2× Shelly EM3"] HEIZ["Heizung"] BYD["BYD-Speicher
BMU"] GOE["go-eCharger"] WPILOT["Wattpilot"] SKODA["MySkoda-API"] STATION["Wetterstation"] METEO["Open-Meteo"] VENTILE["Ventilsteuerungen
2× ESP32"] HAUSGER["Thermostate · Shellys · Schalter
melden sich per
Home-Assistant-Discovery
"] MGR["solarManager.py
gatherModbusData · gatherOpenDTUData
gatherDTUBIData · gatherShellyEM3Data EG/UG
gatherHeaterData · gatherSkodaData
gatherBYDData · gatherWaterData
charger_goE
"] WPB["wattpilot_bruecke.py"] WSB["wsMQTTbridge.py"] RAIN["gatherRainData.py"] BROKER{{"MQTT-Broker"}} SOLARLOG[("solarLog")] WR -->|Modbus · HTTP| MGR EM3 -->|HTTP| MGR HEIZ -->|HTTP| MGR BYD -->|Modbus-RTU in TCP| MGR GOE -->|HTTP| MGR WPILOT -->|WebSocket| MGR SKODA -->|HTTPS| MGR WPILOT -->|WebSocket| WPB STATION -->|WebSocket| WSB METEO -->|HTTPS| RAIN SOLARLOG -->|zisterne| MGR MGR -->|"EnergyFlow · skoda
skoda_ladepunkte · byd
byd_zellen"| SOLARLOG MGR -->|"solarManager/#"| BROKER WPB -->|"wattpilot/#"| BROKER WSB -->|"weatherStation/#"| BROKER RAIN -->|"Wetter/Regen"| BROKER GOE -->|"go-eCharger/#"| BROKER VENTILE -->|"Gartenwasser/#"| BROKER HAUSGER -->|"Raumtemp/# · Power_EG/#
Power_UG/# · wasser/#"| BROKER classDef quelle fill:#eef4fb,stroke:#7f9dc0 classDef skript fill:#fff6e5,stroke:#d0a548 classDef speicher fill:#eaf5ee,stroke:#6fa981 class WR,EM3,HEIZ,BYD,GOE,WPILOT,SKODA,STATION,METEO,VENTILE,HAUSGER quelle class MGR,WPB,WSB,RAIN skript class BROKER,SOLARLOG speicher ``` Vier Prozesse holen aktiv etwas ab. Alles andere meldet sich von selbst: die Ventilsteuerungen, die Wallbox und jedes Gerät, das Home-Assistant-Discovery spricht, schreiben ohne Umweg auf den Broker. Für deren Messwerte ist also **kein Skript** zuständig — wer sie sucht, sucht am Gerät, nicht im Quelltext. `solarManager.py` ist der Sonderfall: ein Prozess, aber neun Sammler-Module, jedes für eine Anlage. Sie laufen nicht einzeln, sondern werden importiert; ihr gemeinsames Ergebnis geht als ein Baum nach `solarManager/#`. `gatherBYDData` liest die BMU der Batterie direkt (192.168.16.254:8080) und legt alles unter `solarManager/byd/…` ab: Ladestand und Gesundheit laut BMU, Zellspannungen (`zellen`, 128 Werte in mV, 16 je Modul), Temperaturen (`temperaturen`, 64 Werte, 8 je Modul), Spreizung, Ausgleich, Fehlerbits und die Gesamtzähler. `ok` fällt auf 0, wenn zehn Minuten nichts kam. Das Netzwerkmodul der BMU startet etwa alle 102 Sekunden neu und antwortet nur kurz danach — der Sammler klopft deshalb jede Sekunde an und bekommt so rund alle 100 Sekunden einen vollständigen Satz. Einzelheiten im Kopf des Moduls. Die Zisterne fällt aus der Reihe — ihr Stand steht in `solarLog`, und `gatherWaterData` liest ihn von dort. Die Daten laufen also durch die Datenbank hindurch von einem Prozess zum nächsten. ## Wer sie benutzt ```mermaid flowchart TB BROKER{{"MQTT-Broker"}} SOLARLOG[("solarLog
Verlauf")] HOMEMESH[("homeMesh
Geräte · Automatiken")] RUNNER["autoaction_runner.py
SolarManager"] KALENDER["fetch_calendar.py
SolarManager · Cronjob, jährlich"] DISCOVERY["device_discovery.py
Web · von Hand gestartet"] AJAX["ajax/*.php
Web"] BROWSER["js/solar/*.js
Web · im Browser"] HAUS["Rollläden · Licht · Schalter
Tahoma · WLED · Shelly"] VENTILE["Ventilsteuerungen"] FERIEN["openholidaysapi.org"] BROKER <-->|"Messwerte ↓ Kommandos ↑"| RUNNER HOMEMESH <-->|"Regelwerk ↓ Verlauf ↑"| RUNNER RUNNER -.->|"HTTP · WLED · Tahoma"| HAUS BROKER -.->|"Gartenwasser/…/set"| VENTILE FERIEN --> KALENDER KALENDER -->|"calendar_days"| HOMEMESH BROKER -->|"homeassistant/#"| DISCOVERY HAUS -->|"mDNS · Tahoma"| DISCOVERY DISCOVERY -->|"Geräte, Messwerte, Befehle"| HOMEMESH SOLARLOG --> AJAX HOMEMESH --> AJAX AJAX <--> BROWSER AJAX -.->|Kommandos| BROKER BROKER -->|wss| BROWSER classDef skript fill:#fff6e5,stroke:#d0a548 classDef speicher fill:#eaf5ee,stroke:#6fa981 classDef geraet fill:#eef4fb,stroke:#7f9dc0 class RUNNER,DISCOVERY,AJAX,BROWSER,KALENDER skript class BROKER,SOLARLOG,HOMEMESH speicher class HAUS,VENTILE,FERIEN geraet ``` **Durchgezogen fließen Daten, gestrichelt gehen Befehle.** Der Browser bekommt seine laufenden Werte direkt vom Broker über `wss://mqtt.nas.el-wa.org`, nicht über PHP. Über PHP läuft nur, was der Broker nicht weiß: der Verlauf aus `solarLog` und alles aus `homeMesh` — und umgekehrt jeder Knopfdruck, denn schalten darf nur der Server. `device_discovery.py` ist kein Dauerläufer. Es sucht per mDNS nach Shellys und WLEDs, hört die Discovery-Nachrichten auf `homeassistant/#` mit, fragt die Tahoma-Box und legt daraus die Geräte in `homeMesh` an. Gestartet wird es von Hand, wenn sich am Bestand etwas geändert hat. ## Wem welche Daten gehören | Daten | wird gefüllt von | wird gelesen von | |---|---|---| | `solarManager/#` | `solarManager.py` | Browser, Runner | | `wattpilot/#` | `wattpilot_bruecke.py` | Browser, Runner | | `weatherStation/#` | `wsMQTTbridge.py` | Browser, Runner | | `Wetter/Regen` | `gatherRainData.py` | Runner, Browser | | `go-eCharger/#` | die Wallbox selbst | Browser, Runner | | `Gartenwasser/#` | die beiden ESP32 | Browser, Runner | | `Raumtemp/#`, `Power_*/#`, `wasser/#` | die Geräte selbst | Browser, Runner | | `homeassistant/#` | die Geräte selbst | `device_discovery.py` | | **`solarLog`** | `solarManager.py` | `ajax/*.php`, `gatherWaterData` | | `byd`, `byd_zellen` (in `solarLog`) | `gatherBYDData.py`, alle 5 bzw. 15 Minuten | Web-Repo (`solarLog_byd.sql`) | | **`homeMesh`** | `device_discovery.py`, Runner, Web-Editor | Runner, `ajax/*.php` | | `skoda.conf` | Einstellungen → Fahrzeug (`restricted/skodaKeys.php`) oder von Hand | `gatherSkodaData.py`, `ajax/skodaCmd.php` | ## Was wann startet | Prozess | gestartet von | |---|---| | `solarManager.py` | `startSolarServer.sh` (Aufgabenplaner, beim Hochfahren) | | `autoActions/autoaction_runner.py` | dito | | `gatherRainData.py` | dito | | `wsMQTTbridge.py` | `startMQTTbridge.sh` | | `wattpilot_bruecke.py` | `startWattpilotMQTT.sh` | | `autoActions/fetch_calendar.py` | Cronjob, einmal im Jahr | | `device_discovery.py` (Web-Repo) | von Hand | `startSolarServer.sh` beendet eine schon laufende Instanz, bevor es neu startet — beim Runner ist das wichtig, zwei Instanzen würden jedes Kommando doppelt schicken. ## Der Wecker ist keiner mehr `wecker.py` gibt es nicht mehr. Es hat genau das getan, was der AutoAction-Runner ohnehin kann — nur mit allem doppelt: eigener Datenbank `alarm`, eigenem Feiertags- und Ferienkalender, eigenem UDP-Log auf Port 13377, eigener Endlosschleife und einem eigenen Startskript samt nächtlichem Neustart um drei. Punkt für Punkt hatte der Runner die bessere Fassung schon: | `wecker.py` | Automatik | |---|---| | `alarmtime.time_1` | Bedingung „Uhrzeit um 05:50" | | Wochentagsspalten `mo` … `so` | `weekdays`, eine Bitmaske | | `is_holiday()` gegen `alarm.feiertage` | `on_holiday`, gegen `calendar_days` | | `is_school_holiday()` gegen `alarm.ferien` | `on_vacation`, ebenso | | `alarmtime.wecked = heute` | `cond_met`, die steigende Flanke | | fünf HTTP-Versuche im Abstand von vier Sekunden | der `Versand`, ein Faden je Gerät | | `send_log()` per UDP | `automation_log` und `autoActions.log` | Der Kalender war der auffälligste Teil davon: `alarm.feiertage` und `alarm.ferien` standen neben `calendar_days`, das `fetch_calendar.py` einmal im Jahr von openholidaysapi.org holt. Zwei Kalender, die dasselbe wissen müssen, gehen früher oder später auseinander. Umgezogen ist die eine Weckzeit, die scharf war: 05:50, Montag bis Freitag, nicht in den Ferien, nicht an Feiertagen, WLED-Preset 5 („Wakeup") auf `MenasHimmel`. Die drei anderen Zeilen in `alarmtime` standen auf `onoff = -1`. `wecker_zu_automatik.sql` legt die Automatik an und beschreibt im Kopf, wie dasselbe im Dashboard von Hand geht. Damit ist die Weckzeit dort einstellbar, wo alles andere auch eingestellt wird — unter „Automatismen", Etage EG. Die Datenbank `alarm` ist gelöscht, und `config.ini.example` hat keinen Abschnitt `[alarm]` mehr. Ein übriggebliebener `[alarm]` in einer echten `config.ini` stört nicht, liest aber auch niemand. ## Konfiguration Zugangsdaten und Standort stehen in `config.ini` (Vorlage: `config.ini.example`), gelesen über `konfig.py`; der Runner hat seine eigene unter `autoActions/`. Die Zugangsschlüssel der MyŠkoda-API stehen getrennt in `skoda.conf` (Vorlage: `skoda.conf.example`), weil auch die Weboberfläche sie liest und im Reiter „Fahrzeug" neue einträgt. Alle drei sind per `.gitignore` ausgenommen — nichts davon gehört in den Quelltext. ## Werkzeuge | Skript | wofür | |---|---| | `skoda_test.py` | prüft Zerlegung und Kontingent-Buchführung von `gatherSkodaData.py` ohne Fahrzeug und ohne Netz | | `skoda_ladepunkte_nachtragen.py` | holt den Wallbox-Verlauf vergangener Ladungen aus `EnergyFlow` nach `skoda_ladepunkte`, solange er dort noch nicht ausgedünnt ist (`--probe` schreibt nichts) | | `wecker_zu_automatik.sql` | hat den alten Wecker als Automatik angelegt; nur noch zum Nachlesen | Mehr zum Runner selbst, zum Aufbau einer Automatik und zu den Transporten steht in [autoActions/README.md](autoActions/README.md).