Bewaesserung und Regen werden Geraete im Automatik-Editor
Erster Teil der Ablosung von restricted/gartenbewaesserung/auto_watering.py.
Das Skript holt heute stuendlich den Regen, vergleicht ihn mit Schwellen
aus einer ZONES-Tabelle im Quelltext und schaltet selbst. Kuenftig
entscheidet der AutoAction-Runner, und die Schwellen stehen im Editor.
Dafuer legt ein neues Discovery-Modul drei Geraete an. Es sucht nichts,
sondern schreibt sie fest hin - wie das Logic-Modul auch:
* Die beiden ESP32-Ventilsteuerungen sprechen zwar MQTT, aber ohne
Home-Assistant-Discovery; das MQTT-Modul findet sie deshalb nicht.
* "Regen" ist gar kein Geraet, sondern das, was gatherRainData.py im
SolarManager von Open-Meteo holt und nach Wetter/Regen legt.
Je Steuerung entsteht ein Kommando "Automatik" mit dem Modus als
Parameter - ein Kommando ohne Parameter schickt im Runner eine leere
Nutzlast, der Klartext ("Hoch", "Trog", "Vorn") muss also der Parameter
sein. Dazu je Zone zwei Messwerte:
Bewaessert heute ersetzt die Zwoelf-Stunden-Sperre des Skripts;
"heute noch nicht" heisst hier "= 0". Die
Steuerung setzt den Wert um Mitternacht zurueck,
der Runner kann den mitgelieferten "wateringDay"
naemlich nicht pruefen.
Tage seit Bewaesserung ersetzt max_dry_days. Ein Zeitstempel nuetzte
nichts: der Runner vergleicht Datumsangaben nur
gegen einen festen Wert, "laenger als fuenf Tage
her" ist damit nicht formulierbar. Die Firmware
liefert deshalb gleich die Anzahl Tage.
Die Regensummen kommen als tage1 bis tage7 herein; welche davon als
Messwert auftauchen, entscheidet allein dieses Modul. Der Sammler
veroeffentlicht immer alle sieben, damit sich die beiden Repositorien
nicht ueber eine Fensterliste einigen muessen.
Die Logseite kennt rainOutput.log jetzt ebenfalls.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,7 @@ from modules.tahoma_module import TahomaModule
|
||||
from modules.wled_module import WLEDModule
|
||||
from modules.mqtt_module import MQTTModule
|
||||
from modules.shelly_module import ShellyModule
|
||||
from modules.gartenwasser_module import GartenwasserModule
|
||||
from modules.logic_module import LogicModule #nicht Vergessen neue Module auch in Zeile 546 einzufügen!!
|
||||
|
||||
# Logging
|
||||
@@ -149,6 +150,13 @@ class Config:
|
||||
if not self.config.has_section('logic'):
|
||||
return False
|
||||
return self._get_bool('logic', 'enable', False)
|
||||
|
||||
# GARTENWASSER
|
||||
@property
|
||||
def gartenwasser_enable(self) -> bool:
|
||||
if not self.config.has_section('gartenwasser'):
|
||||
return False
|
||||
return self._get_bool('gartenwasser', 'enable', False)
|
||||
|
||||
|
||||
# MQTT
|
||||
@@ -561,7 +569,8 @@ def main():
|
||||
WLEDModule(config),
|
||||
MQTTModule(config),
|
||||
ShellyModule(config),
|
||||
LogicModule(config)
|
||||
LogicModule(config),
|
||||
GartenwasserModule(config)
|
||||
]
|
||||
|
||||
# Jedes Modul durchlaufen
|
||||
|
||||
@@ -0,0 +1,206 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Gartenwasser Module
|
||||
Die beiden Ventilsteuerungen und der Regen - fest beschrieben, nicht gesucht
|
||||
KEINE Datenbank-Operationen!
|
||||
"""
|
||||
|
||||
import logging
|
||||
from typing import List, Dict, Tuple
|
||||
from modules.base_module import BaseModule
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Topic, auf das gatherRainData.py im SolarManager seine Regensummen legt.
|
||||
# Dasselbe Topic steht dort noch einmal im Quelltext. Bewusst kein
|
||||
# Konfigurationseintrag: er müsste in beiden Repositorien gleich lauten, und
|
||||
# eine Einstellung, die man an zwei Orten gleich halten muss, ist keine.
|
||||
REGEN_TOPIC = "Wetter/Regen"
|
||||
|
||||
# Welche Regenfenster als Messwert im Editor auftauchen. gatherRainData.py
|
||||
# veröffentlicht immer tage1 bis tage7; welche davon jemand braucht, entscheidet
|
||||
# allein diese Liste. Die drei Zonen der alten auto_watering.py brauchten 1, 2
|
||||
# und 5 Tage - der Rest steht da, damit eine neue Zone keinen Eingriff im
|
||||
# SolarManager kostet.
|
||||
REGEN_FENSTER = [1, 2, 3, 5, 7]
|
||||
|
||||
# Die beiden ESP32-Ventilsteuerungen. Sie melden sich nicht per
|
||||
# Home-Assistant-Discovery, das MQTT-Modul findet sie also nicht.
|
||||
#
|
||||
# topic Wurzel aller Topics dieser Steuerung
|
||||
# modi Nutzlast des /auto-Topics -> Beschriftung im Editor. Dieselben
|
||||
# Klartexte stehen in der Whitelist von ajax/watering.php.
|
||||
# zonen Ventilnummer -> Name der Zone. Eine Steuerung bedient mehrere
|
||||
# Zonen; "vorn" zum Beispiel Ventil 1 für die Tröge und Ventil 6
|
||||
# für den Garten. Aus jeder Zone werden zwei Messwerte.
|
||||
STEUERUNGEN = [
|
||||
{
|
||||
"name": "Gartenwasser vorn",
|
||||
"topic": "Gartenwasser/vorn",
|
||||
"modi": [{"Trog": "Tröge"}, {"Vorn": "Garten vorn"}, {"Stop": "Stopp"}],
|
||||
"zonen": [(1, "Tröge"), (6, "Garten vorn")],
|
||||
},
|
||||
{
|
||||
"name": "Gartenwasser hinten",
|
||||
"topic": "Gartenwasser/hinten",
|
||||
"modi": [{"Hoch": "Hochbeete"}, {"Stop": "Stopp"}],
|
||||
"zonen": [(6, "Hochbeete")],
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class GartenwasserModule(BaseModule):
|
||||
"""
|
||||
Gartenwasser Modul - Implementiert BaseModule Interface
|
||||
|
||||
Anders als die übrigen Module sucht dieses nichts, es schreibt drei feste
|
||||
Geräte hin - wie das Logic-Modul auch:
|
||||
|
||||
* Die Ventilsteuerungen sprechen MQTT, aber ohne Home-Assistant-
|
||||
Discovery. Ohne dieses Modul kennt die Datenbank sie nicht, und im
|
||||
Automatik-Editor lässt sich die Bewässerung nicht auswählen.
|
||||
* "Regen" ist gar kein Gerät, sondern das, was gatherRainData.py im
|
||||
SolarManager von Open-Meteo holt.
|
||||
|
||||
Von Hand in die Datenbank geschrieben würden die Zeilen zwar jeden
|
||||
Discovery-Lauf überleben (es wird nur mit ON DUPLICATE KEY UPDATE
|
||||
geschrieben, nie gelöscht), aber niemand wüsste mehr, woher sie kommen.
|
||||
|
||||
Zusammen ersetzt das restricted/gartenbewaesserung/auto_watering.py: die
|
||||
Zeitfenster werden Bedingungen auf Sonnenauf- und -untergang, die
|
||||
Regenschwellen Bedingungen auf "Regen ... Tage", und geschaltet wird über
|
||||
das Kommando "Automatik".
|
||||
|
||||
Zwei Zusagen der Ventilsteuerung stecken in den Messwerten unten, und ohne
|
||||
sie stimmt die Automatik nicht mehr:
|
||||
|
||||
* LastWatering trägt je Ventil ein "daysSinceWatering<n>" - die Anzahl
|
||||
Tage, nicht den Zeitstempel.
|
||||
* WateringToday wird um Mitternacht zurückgesetzt.
|
||||
"""
|
||||
|
||||
def is_enabled(self) -> bool:
|
||||
"""Prüft ob Gartenwasser aktiviert ist"""
|
||||
return self.config.gartenwasser_enable
|
||||
|
||||
def discover(self) -> Tuple[List[Dict], List[Dict]]:
|
||||
"""
|
||||
Erzeugt die beiden Ventilsteuerungen als Actors und den Regen als Sensor
|
||||
|
||||
Returns:
|
||||
Tuple (actors, sensors)
|
||||
"""
|
||||
logger.info("\n" + "=" * 60)
|
||||
logger.info("GARTENWASSER-GERÄTE WERDEN ERZEUGT")
|
||||
logger.info("=" * 60)
|
||||
|
||||
actors = [self._steuerung(s) for s in STEUERUNGEN]
|
||||
sensors = [self._regen()]
|
||||
return actors, sensors
|
||||
|
||||
@staticmethod
|
||||
def _steuerung(steuerung: Dict) -> Dict:
|
||||
"""
|
||||
Eine Ventilsteuerung: ein Kommando, zwei Messwerte je Zone.
|
||||
|
||||
Das Kommando trägt seinen Klartext im Parameter und nicht im Namen.
|
||||
Ein Kommando ohne Parameter schickt im Runner eine leere Nutzlast
|
||||
(siehe MQTTTransport.senden), der Modus muss also der Parameter sein -
|
||||
im Editor liest sich das dann als "Automatik Modus = Hochbeete".
|
||||
"""
|
||||
topic = steuerung["topic"]
|
||||
|
||||
states = [{
|
||||
# Klartext der gerade laufenden Automatik, leer wenn keine läuft.
|
||||
# Ohne Werteliste: was die Steuerung bei Stillstand genau schickt,
|
||||
# steht in keiner Dokumentation, und eine falsche Übersetzung wäre
|
||||
# schlimmer als der rohe Wert.
|
||||
"name": "Laufende Automatik",
|
||||
"url": topic + "/Timers",
|
||||
"value_path": "auto",
|
||||
"type": "string",
|
||||
}]
|
||||
|
||||
for valve, zone in steuerung["zonen"]:
|
||||
states.append({
|
||||
# Ersetzt die Zwölf-Stunden-Sperre von auto_watering.py:
|
||||
# "heute noch nicht bewässert" heißt hier schlicht "= 0".
|
||||
#
|
||||
# Dass das trägt, liegt an der Steuerung: sie setzt den Wert um
|
||||
# Mitternacht zurück. Die Nachricht ist retained, und der Runner
|
||||
# sieht nur die Zahl hinter dem value_path - den Tag, den die
|
||||
# Nutzlast als "wateringDay" mitführt, kann er nicht prüfen.
|
||||
# Ohne den Reset stünde nach einer bewässerungsfreien Nacht die
|
||||
# Summe von gestern als "heute" da, und die Zone bliebe trocken.
|
||||
"name": "Bewässert heute " + zone,
|
||||
"url": topic + "/WateringToday",
|
||||
"value_path": "wateringDurationTodaySecs%d" % valve,
|
||||
"type": "integer",
|
||||
"unit": "s",
|
||||
})
|
||||
states.append({
|
||||
# Der Trockenheits-Notlauf (max_dry_days in auto_watering.py).
|
||||
# Ein Zeitstempel nützte hier nichts: der Runner vergleicht
|
||||
# Datumsangaben nur gegen einen festen Wert, "länger als fünf
|
||||
# Tage her" ist damit nicht formulierbar. Die Steuerung liefert
|
||||
# deshalb gleich die Anzahl Tage - dafür wurde ihre Firmware
|
||||
# erweitert, das Feld gab es vorher nicht.
|
||||
"name": "Tage seit Bewässerung " + zone,
|
||||
"url": topic + "/LastWatering",
|
||||
"value_path": "daysSinceWatering%d" % valve,
|
||||
"type": "integer",
|
||||
"unit": "Tage",
|
||||
})
|
||||
|
||||
return {
|
||||
"type": "Bewässerung",
|
||||
"name": steuerung["name"],
|
||||
"url": "mqtt://" + topic,
|
||||
"commands": [{
|
||||
"command": "Automatik",
|
||||
"url": topic + "/auto",
|
||||
"parameters": [{
|
||||
"name": "Modus",
|
||||
"type": "string",
|
||||
"url": topic + "/auto",
|
||||
"values": steuerung["modi"],
|
||||
}],
|
||||
}],
|
||||
"states": states,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _regen() -> Dict:
|
||||
"""
|
||||
Der Regen als Gerät: je Zeitraum ein Messwert, dazu der Stand.
|
||||
|
||||
"Regen heute" ist der bereits gefallene Regen des laufenden Tages, die
|
||||
übrigen zählen ihn plus die abgeschlossenen Tage davor - so hat auch
|
||||
auto_watering.py gerechnet.
|
||||
|
||||
Der Stand ist kein Messwert zum Vergleichen, sondern zum Nachsehen: im
|
||||
Editor steht er hinter dem Namen ("= 04.09.2026 09:00:00") und verrät,
|
||||
ob der Sammler noch läuft. Bleibt er länger aus, leert gatherRainData.py
|
||||
die Zahlen von sich aus, und die Bewässerung setzt aus.
|
||||
"""
|
||||
states = [{
|
||||
"name": "Regen heute" if tage == 1 else "Regen %d Tage" % tage,
|
||||
"url": REGEN_TOPIC,
|
||||
"value_path": "tage%d" % tage,
|
||||
"type": "float",
|
||||
"unit": "mm",
|
||||
} for tage in REGEN_FENSTER]
|
||||
|
||||
states.append({
|
||||
"name": "Stand",
|
||||
"url": REGEN_TOPIC,
|
||||
"value_path": "stand",
|
||||
"type": "string",
|
||||
})
|
||||
|
||||
return {
|
||||
"type": "Wetter",
|
||||
"name": "Regen",
|
||||
"url": "mqtt://" + REGEN_TOPIC,
|
||||
"states": states,
|
||||
}
|
||||
@@ -48,6 +48,14 @@ function logDateien()
|
||||
"symbol" => "bi-diagram-3",
|
||||
"hinweis" => "Wertet die Automatiken aus und schickt die Kommandos.",
|
||||
],
|
||||
"regen" => [
|
||||
"titel" => "Regen",
|
||||
"datei" => "rainOutput.log",
|
||||
"symbol" => "bi-cloud-rain",
|
||||
"hinweis" => "Holt die Niederschlagsdaten von Open-Meteo und legt "
|
||||
. "sie nach Wetter/Regen, damit die Bewässerungs-"
|
||||
. "Automatiken sie als Messwert bekommen.",
|
||||
],
|
||||
"wattpilot" => [
|
||||
"titel" => "Wattpilot",
|
||||
"datei" => "wattpilotshell.log",
|
||||
|
||||
Reference in New Issue
Block a user