Bisher loeste eine Automatik in dem Moment aus, in dem die Bedingung wahr wurde. Fuer Dauerzustaende taugt das nicht: "der Wasserzaehler laeuft" ist jedes Haendewaschen, gemeint war "laeuft seit einer halben Stunde ohne Pause". hold_secs schiebt die Flanke nach hinten, statt sie zu verbrauchen - cond_met bleibt bis dahin 0, und alles danach (Tagessperre, Sperrzeit, Protokoll) bleibt unberuehrt. Ein Aussetzer setzt die Zeit zurueck, das Zugehen des Zeitfensters ebenfalls. Gezaehlt wird im Speicher (erfuellt_seit) wie war_aktiv: nach einem Neustart weiss niemand, ob die Bedingung zwischendurch anlag. hold_secs = 0 ist die Vorgabe und heisst: wie bisher. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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
flowchart LR
WR["Wechselrichter<br/><small>GoodWe · OpenDTU · DTU-BI</small>"]
EM3["Stromzähler<br/><small>2× Shelly EM3</small>"]
HEIZ["Heizung"]
BYD["BYD-Speicher<br/><small>BMU</small>"]
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/>gatherBYDData · gatherWaterData<br/>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
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<br/>skoda_ladepunkte · byd<br/>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/#<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,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
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 |
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.