wecker.py hat genau das getan, was der AutoAction-Runner ohnehin kann - nur mit allem doppelt: eigener Datenbank alarm, eigenem Feiertags- und Ferienkalender neben calendar_days, eigenem UDP-Log auf Port 13377, eigener Endlosschleife und einem Startskript samt naechtlichem Neustart um drei. Punkt fuer Punkt hatte der Runner die bessere Fassung schon: die Zeit als Bedingung "um 05:50" mit Nachholfenster statt eines Vergleichs im 20-Sekunden-Takt, weekdays als Bitmaske statt sieben Spalten, on_holiday/on_vacation gegen calendar_days statt gegen einen zweiten Kalender, die steigende Flanke statt einer wecked-Spalte, und den Versand mit einem Faden je Geraet statt fuenf HTTP-Versuchen im Abstand von vier Sekunden. Umgezogen ist die eine Weckzeit, die scharf war: 05:50, Mo-Fr, nicht in den Ferien, nicht an Feiertagen, WLED-Preset 5 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. Die Datenbank alarm und der Abschnitt [alarm] in der config.ini bleiben vorerst stehen - sie liest nur niemand mehr. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
213 lines
8.5 KiB
Markdown
213 lines
8.5 KiB
Markdown
# 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<br/><small>GoodWe · OpenDTU · DTU-BI</small>"]
|
||
EM3["Stromzähler<br/><small>2× Shelly EM3</small>"]
|
||
HEIZ["Heizung"]
|
||
GOE["go-eCharger"]
|
||
WPILOT["Wattpilot"]
|
||
SKODA["MySkoda-API"]
|
||
STATION["Wetterstation"]
|
||
METEO["Open-Meteo"]
|
||
VENTILE["Ventilsteuerungen<br/><small>2× ESP32</small>"]
|
||
HAUSGER["Thermostate · Shellys · Schalter<br/><small>melden sich per<br/>Home-Assistant-Discovery</small>"]
|
||
|
||
MGR["<b>solarManager.py</b><br/><small>gatherModbusData · gatherOpenDTUData<br/>gatherDTUBIData · gatherShellyEM3Data EG/UG<br/>gatherHeaterData · gatherSkodaData<br/>gatherWaterData · charger_goE</small>"]
|
||
WPB["<b>wattpilot_bruecke.py</b>"]
|
||
WSB["<b>wsMQTTbridge.py</b>"]
|
||
RAIN["<b>gatherRainData.py</b>"]
|
||
|
||
BROKER{{"<b>MQTT-Broker</b>"}}
|
||
SOLARLOG[("<b>solarLog</b>")]
|
||
|
||
WR -->|Modbus · HTTP| MGR
|
||
EM3 -->|HTTP| MGR
|
||
HEIZ -->|HTTP| 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"| 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/#<br/>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,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 acht
|
||
Sammler-Module, jedes für eine Anlage. Sie laufen nicht einzeln, sondern
|
||
werden importiert; ihr gemeinsames Ergebnis geht als ein Baum nach
|
||
`solarManager/#`.
|
||
|
||
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{{"<b>MQTT-Broker</b>"}}
|
||
SOLARLOG[("<b>solarLog</b><br/><small>Verlauf</small>")]
|
||
HOMEMESH[("<b>homeMesh</b><br/><small>Geräte · Automatiken</small>")]
|
||
|
||
RUNNER["<b>autoaction_runner.py</b><br/><small>SolarManager</small>"]
|
||
KALENDER["<b>fetch_calendar.py</b><br/><small>SolarManager · Cronjob, jährlich</small>"]
|
||
DISCOVERY["<b>device_discovery.py</b><br/><small>Web · von Hand gestartet</small>"]
|
||
AJAX["<b>ajax/*.php</b><br/><small>Web</small>"]
|
||
BROWSER["<b>js/solar/*.js</b><br/><small>Web · im Browser</small>"]
|
||
|
||
HAUS["Rollläden · Licht · Schalter<br/><small>Tahoma · WLED · Shelly</small>"]
|
||
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` |
|
||
| **`homeMesh`** | `device_discovery.py`, Runner, Web-Editor | Runner, `ajax/*.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` liest niemand
|
||
mehr; sie steht noch da, mitsamt dem Abschnitt `[alarm]` in der `config.ini`,
|
||
und kann weg, sobald der erste Morgen ohne `wecker.py` durch ist.
|
||
|
||
## Konfiguration
|
||
|
||
Zugangsdaten und Standort stehen in `config.ini` (Vorlage:
|
||
`config.ini.example`), gelesen über `konfig.py`; der Runner hat seine eigene
|
||
unter `autoActions/`. Beide sind per `.gitignore` ausgenommen — nichts davon
|
||
gehört in den Quelltext.
|
||
|
||
Mehr zum Runner selbst, zum Aufbau einer Automatik und zu den Transporten
|
||
steht in [autoActions/README.md](autoActions/README.md).
|