AutoAction-Runner: Automatiken ausfuehren
Bisher konnte man Automatiken nur anlegen - ausgefuehrt hat sie niemand. restricted/autoActions/autoaction_runner.py holt das nach. Dauerlaeufer statt Cronjob, aus zwei Gruenden: Schwellwert-Ausloeser sollen greifen, wenn die MQTT-Nachricht hereinkommt, und actor_states.current_value wird sonst von niemandem fortgeschrieben - beim Discovery einmal gesetzt und danach nie wieder. Ein zustandsloser Lauf haette gar nichts, womit er vergleichen koennte. Der Runner pflegt den Wert nebenbei mit, wovon auch der Editor profitiert: er zeigt neben jedem Messwert den aktuellen Stand. Ausgeloest wird nur auf der steigenden Flanke (automations.cond_met), sonst wuerde "Temperatur ueber 22 Grad" bei jedem Takt erneut feuern. Aus demselben Grund heissen Zeit-Ausloeser jetzt "ab 16:30" statt "gleich 16:30": ein Gleichheitsvergleich waere nur in einer einzigen Minute wahr, und ein Ausfall in genau dieser Minute kostet den ganzen Tag. Dieselbe Ueberlegung steht hinter den breiten Zeitfenstern in auto_watering.py. Der Weg zum Geraet haengt an der URL des Aktors: mqtt:// abonniert und publiziert, http:// pollt und haengt Parameter an, io:// spricht mit der Tahoma-Box, Logic rechnet Uhrzeit, Datum und Sonnenzeiten. Alle vier stehen in transports.py; eine fuenfte Geraeteart ist eine weitere Klasse mit passt(), zustaende_lesen() und senden(). fetch_calendar.py fuellt calendar_days aus openholidaysapi.org, damit "in den Ferien" und "an Feiertagen" eine Grundlage haben. Einmal jaehrlich per Cron. Die Uebersicht blendet auf schmalen Schirmen Spalten aus, statt sie wegzuschieben - auf dem Handy waren die Knoepfe zum Pausieren und Loeschen sonst nur per Seitwaertsscrollen erreichbar. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Fuellt die Tabelle `calendar_days` mit Feiertagen und Schulferien.
|
||||
|
||||
Die Automatiken koennen mit "an Feiertagen ausfuehren" und "in den Ferien
|
||||
ausfuehren" auf besondere Tage reagieren; woher das Wissen kommt, steht hier.
|
||||
Nur besondere Tage werden eingetragen - ein fehlendes Datum ist ein
|
||||
gewoehnlicher Tag.
|
||||
|
||||
Quelle ist openholidaysapi.org: ein offenes Verzeichnis der EU-Kommission, das
|
||||
gesetzliche Feiertage und Schulferien fuer alle deutschen Bundeslaender
|
||||
liefert. Ein Aufruf je Jahr genuegt, deshalb ist das ein Cronjob und kein
|
||||
Dauerlaeufer:
|
||||
|
||||
0 4 1 1 * /usr/bin/python3 /pfad/zu/fetch_calendar.py
|
||||
|
||||
Ein zusaetzlicher Lauf im Herbst schadet nicht - die Ferientermine des
|
||||
uebernaechsten Schuljahres stehen erst spaeter fest.
|
||||
|
||||
Bereits eingetragene Tage werden ueberschrieben, nie doppelt angelegt.
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import sys
|
||||
from datetime import date, timedelta
|
||||
|
||||
import pymysql
|
||||
import requests
|
||||
|
||||
from autoaction_runner import Config, verbinden
|
||||
|
||||
logger = logging.getLogger("kalender")
|
||||
|
||||
BASIS = "https://openholidaysapi.org"
|
||||
|
||||
|
||||
def zeitraum(api, land, region, von, bis):
|
||||
"""Eintraege einer Art (PublicHolidays oder SchoolHolidays) abholen."""
|
||||
antwort = requests.get(
|
||||
BASIS + "/" + api,
|
||||
params={"countryIsoCode": land, "subdivisionCode": region,
|
||||
"languageIsoCode": "DE", "validFrom": von.isoformat(),
|
||||
"validTo": bis.isoformat()},
|
||||
headers={"Accept": "application/json"}, timeout=20)
|
||||
antwort.raise_for_status()
|
||||
return antwort.json()
|
||||
|
||||
|
||||
def name(eintrag):
|
||||
for teil in eintrag.get("name", []):
|
||||
if teil.get("text"):
|
||||
return teil["text"][:80]
|
||||
return "?"
|
||||
|
||||
|
||||
def tage(eintrag):
|
||||
"""Ferien erstrecken sich ueber Wochen; hier wird daraus Tag fuer Tag."""
|
||||
start = date.fromisoformat(eintrag["startDate"])
|
||||
ende = date.fromisoformat(eintrag["endDate"])
|
||||
while start <= ende:
|
||||
yield start
|
||||
start += timedelta(days=1)
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Feiertage und Ferien in calendar_days schreiben.")
|
||||
parser.add_argument("--jahr", type=int, default=date.today().year)
|
||||
parser.add_argument("--jahre", type=int, default=2, help="wie viele Jahre ab --jahr")
|
||||
parser.add_argument("--config", default="config.ini")
|
||||
args = parser.parse_args()
|
||||
|
||||
logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s")
|
||||
config = Config(args.config)
|
||||
land = config.text("calendar", "country", "DE")
|
||||
region = config.text("calendar", "subdivision", "DE-BY")
|
||||
|
||||
von = date(args.jahr, 1, 1)
|
||||
bis = date(args.jahr + args.jahre - 1, 12, 31)
|
||||
logger.info("Hole %s bis %s fuer %s", von, bis, region)
|
||||
|
||||
gesammelt = {}
|
||||
for eintrag in zeitraum("PublicHolidays", land, region, von, bis):
|
||||
for tag in tage(eintrag):
|
||||
gesammelt.setdefault(tag, {})["holiday"] = name(eintrag)
|
||||
for eintrag in zeitraum("SchoolHolidays", land, region, von, bis):
|
||||
for tag in tage(eintrag):
|
||||
gesammelt.setdefault(tag, {})["vacation"] = name(eintrag)
|
||||
|
||||
db = verbinden(config)
|
||||
with db.cursor() as c:
|
||||
c.executemany(
|
||||
"""INSERT INTO calendar_days (date, holiday, vacation) VALUES (%s, %s, %s)
|
||||
ON DUPLICATE KEY UPDATE holiday = VALUES(holiday), vacation = VALUES(vacation)""",
|
||||
[(tag, eintrag.get("holiday"), eintrag.get("vacation"))
|
||||
for tag, eintrag in sorted(gesammelt.items())])
|
||||
logger.info("%d Tage eingetragen", len(gesammelt))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in New Issue
Block a user