Runner: Kalender am Vorabend fuer den folgenden Tag pruefen
Mit next_day = 1 gelten Wochentage, Ferien und Feiertage fuer morgen (gemeinter_tag). kalender() merkt sich dafuer heute und morgen und haelt einen fehlgeschlagenen Abruf nicht mehr den ganzen Tag fest. Uhrzeit, Zeitfenster und once_per_day bleiben beim heutigen Tag. vorabend.sql legt die Spalte an. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -147,6 +147,19 @@ vorhandenen Zeilen stehen auf `0` oder `1` und behalten damit genau ihre
|
||||
bisherige Bedeutung. Umzurechnen gibt es nichts, die Migration
|
||||
(`rahmen_erweitern.sql`) setzt nur Kommentare und legt `once_per_day` an.
|
||||
|
||||
### Am Vorabend
|
||||
|
||||
Mit `next_day = 1` gelten Wochentage, Ferien und Feiertage für **morgen**
|
||||
(`gemeinter_tag()`). Gedacht für alles, was abends für den nächsten Tag
|
||||
geschieht: „Kinderrollos zu, wenn morgen Schule ist“ heißt dann Mo–Fr,
|
||||
Ferien nie, Feiertage nie — genau wie der Wecker. Mit dem heutigen Tag ließ
|
||||
sich das nur annähern (So–Do, heute keine Ferien) und ging am letzten
|
||||
Ferientag, am Abend vor einem Feiertag und am Abend eines Feiertags daneben.
|
||||
|
||||
Nur der Rahmen verschiebt sich. Uhrzeit, Zeitfenster und „nur einmal am Tag“
|
||||
bleiben beim heutigen Tag, denn die Automatik läuft ja heute Abend. Die
|
||||
Spalte legt `vorabend.sql` an.
|
||||
|
||||
## Nur einmal am Tag
|
||||
|
||||
`once_per_day` sperrt eine Automatik nach dem Auslösen bis Mitternacht.
|
||||
|
||||
@@ -515,9 +515,30 @@ def im_zeitfenster(jetzt, von, bis):
|
||||
NIE, EGAL, ZUSAETZLICH = 0, 1, 2
|
||||
|
||||
|
||||
def tag_passt(automatik, jetzt, kalender):
|
||||
def gemeinter_tag(automatik, jetzt):
|
||||
"""
|
||||
Faellt der heutige Tag in den Rahmen der Automatik?
|
||||
Fuer welchen Tag gelten Wochentage, Ferien und Feiertage?
|
||||
|
||||
Normalerweise fuer heute. Mit `next_day` fuer morgen - die Vorabend-Form:
|
||||
"Kinderrollos zu, wenn morgen Schule ist" heisst Mo-Fr, Ferien nie,
|
||||
Feiertage nie, und der Runner schaut dafuer auf den folgenden Tag. Mit
|
||||
dem heutigen Tag liess sich das nur annaehern (So-Do, heute keine
|
||||
Ferien), und das ging am letzten Ferientag, am Abend vor einem Feiertag
|
||||
und am Abend eines Feiertags daneben.
|
||||
|
||||
Nur der Rahmen verschiebt sich. Uhrzeit, Zeitfenster und "einmal am Tag"
|
||||
bleiben beim heutigen Tag - die Automatik laeuft ja heute Abend. Ein
|
||||
Zeitfenster ueber Mitternacht meint nach Mitternacht deshalb schon den
|
||||
uebernaechsten Tag; fuer eine Vorabend-Regel ist das kein sinnvoller Fall.
|
||||
"""
|
||||
heute = jetzt.date()
|
||||
return heute + timedelta(days=1) if automatik.get("next_day") else heute
|
||||
|
||||
|
||||
def tag_passt(automatik, tag, kalender):
|
||||
"""
|
||||
Faellt der Tag in den Rahmen der Automatik? `tag` ist heute oder morgen,
|
||||
siehe gemeinter_tag(); `kalender` gehoert zu genau diesem Tag.
|
||||
|
||||
Die Wochentage sind eine Maske, Ferien und Feiertage haben je drei
|
||||
Zustaende. Zwei davon gab es immer: EGAL (der Tag aendert nichts, die
|
||||
@@ -536,7 +557,7 @@ def tag_passt(automatik, jetzt, kalender):
|
||||
den Ferien nicht - ein Verbot schlaegt eine Erweiterung. Anders herum
|
||||
liesse sich "nie" nicht mehr verlassen.
|
||||
"""
|
||||
passt = bool(automatik["weekdays"] & (1 << jetzt.weekday()))
|
||||
passt = bool(automatik["weekdays"] & (1 << tag.weekday()))
|
||||
if kalender["feiertag"] and automatik["on_holiday"] == ZUSAETZLICH:
|
||||
passt = True
|
||||
if kalender["ferien"] and automatik["on_vacation"] == ZUSAETZLICH:
|
||||
@@ -718,7 +739,7 @@ class Runner:
|
||||
self.war_aktiv = {} # automation_id -> war im Zeitfenster
|
||||
self.lief_im_fenster = {} # automation_id -> hat im Fenster ausgeloest
|
||||
self._sonne = (None, "00:00", "00:00") # (datum, aufgang, untergang)
|
||||
self._kalender = (None, {"feiertag": False, "ferien": False})
|
||||
self._kalender = {} # datum -> {"feiertag": .., "ferien": ..}
|
||||
self._letzte_saeuberung = None
|
||||
self.reihenfolge = [] # automation_id, Ausloeser vor Nachfolger
|
||||
self.ausloeser_states = {} # automation_id -> state_id des Ausloesers
|
||||
@@ -991,21 +1012,30 @@ class Runner:
|
||||
return "%02d:%02d" % (minute // 60 % 24, minute % 60)
|
||||
return str(wert)[:5]
|
||||
|
||||
def kalender(self):
|
||||
"""Ferien und Feiertage von heute, einmal je Tag geholt."""
|
||||
heute = date.today()
|
||||
if self._kalender[0] == heute:
|
||||
return self._kalender[1]
|
||||
def kalender(self, tag):
|
||||
"""
|
||||
Ferien und Feiertage eines Tages, je Tag einmal geholt.
|
||||
|
||||
Gefragt wird nach heute und - fuer Vorabend-Regeln - nach morgen.
|
||||
Gemerkt werden deshalb beide; was vor heute liegt, fliegt raus.
|
||||
"""
|
||||
if tag in self._kalender:
|
||||
return self._kalender[tag]
|
||||
stand = {"feiertag": False, "ferien": False}
|
||||
try:
|
||||
with self.db.cursor() as c:
|
||||
c.execute("SELECT holiday, vacation FROM calendar_days WHERE date = %s", (heute,))
|
||||
c.execute("SELECT holiday, vacation FROM calendar_days WHERE date = %s", (tag,))
|
||||
zeile = c.fetchone()
|
||||
if zeile:
|
||||
stand = {"feiertag": bool(zeile["holiday"]), "ferien": bool(zeile["vacation"])}
|
||||
except Exception as fehler:
|
||||
# Nicht merken: beim naechsten Durchlauf noch einmal fragen,
|
||||
# statt den ganzen Tag mit "kein Feiertag" weiterzurechnen.
|
||||
logger.warning("Kalender nicht lesbar: %r", fehler)
|
||||
self._kalender = (heute, stand)
|
||||
return stand
|
||||
heute = date.today()
|
||||
self._kalender = {t: s for t, s in self._kalender.items() if t >= heute}
|
||||
self._kalender[tag] = stand
|
||||
return stand
|
||||
|
||||
# --- Werte -----------------------------------------------------------
|
||||
@@ -1163,13 +1193,13 @@ class Runner:
|
||||
|
||||
def durchlauf(self, fenster=NACHHOLFENSTER):
|
||||
jetzt = datetime.now()
|
||||
kalender = self.kalender()
|
||||
|
||||
for automation_id in self.reihenfolge:
|
||||
automatik = self.regelwerk.automatiken.get(automation_id)
|
||||
if automatik is None:
|
||||
continue
|
||||
aktiv = (tag_passt(automatik, jetzt, kalender)
|
||||
tag = gemeinter_tag(automatik, jetzt)
|
||||
aktiv = (tag_passt(automatik, tag, self.kalender(tag))
|
||||
and im_zeitfenster(jetzt, automatik["window_from"], automatik["window_to"]))
|
||||
vorher_aktiv = self.war_aktiv.get(automatik["id"], aktiv)
|
||||
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
-- ===========================================================================
|
||||
-- Vorabend: Wochentage, Ferien und Feiertage fuer den folgenden Tag.
|
||||
--
|
||||
-- Einspielen:
|
||||
-- mysql -h 127.0.0.1 -P 3310 -u homeMesh -p homeMesh < vorabend.sql
|
||||
--
|
||||
-- Danach den Runner neu starten (startSolarServer.sh) - Daten laedt er
|
||||
-- selbst nach, neuen Code nicht.
|
||||
--
|
||||
-- Gebraucht fuer alles, was abends fuer den naechsten Tag geschieht:
|
||||
-- "Kinderrollos zu, wenn morgen Schule ist". Mit dem heutigen Tag liess sich
|
||||
-- das nur annaehern (So-Do, heute keine Ferien) und ging am letzten
|
||||
-- Ferientag, am Abend vor einem Feiertag und am Abend eines Feiertags
|
||||
-- daneben. Mit next_day = 1 heisst dieselbe Regel einfach Mo-Fr, Ferien nie,
|
||||
-- Feiertage nie - wie der Wecker.
|
||||
--
|
||||
-- Nur der Rahmen verschiebt sich; Uhrzeit, Zeitfenster und "einmal am Tag"
|
||||
-- bleiben beim heutigen Tag. Siehe gemeinter_tag() im Runner.
|
||||
-- ===========================================================================
|
||||
|
||||
-- Idempotent: beim zweiten Lauf steht die Spalte schon da.
|
||||
SET @vorhanden := (SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'automations'
|
||||
AND COLUMN_NAME = 'next_day');
|
||||
|
||||
SET @sql := IF(@vorhanden = 0,
|
||||
'ALTER TABLE automations ADD COLUMN next_day TINYINT(1) NOT NULL DEFAULT 0
|
||||
COMMENT ''Wochentage, Ferien und Feiertage gelten fuer morgen (Vorabend)''
|
||||
AFTER on_holiday',
|
||||
'DO 0');
|
||||
|
||||
PREPARE stmt FROM @sql;
|
||||
EXECUTE stmt;
|
||||
DEALLOCATE PREPARE stmt;
|
||||
|
||||
SELECT COLUMN_NAME, COLUMN_DEFAULT, COLUMN_COMMENT
|
||||
FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'automations'
|
||||
AND COLUMN_NAME = 'next_day';
|
||||
Reference in New Issue
Block a user