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
|
bisherige Bedeutung. Umzurechnen gibt es nichts, die Migration
|
||||||
(`rahmen_erweitern.sql`) setzt nur Kommentare und legt `once_per_day` an.
|
(`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
|
## Nur einmal am Tag
|
||||||
|
|
||||||
`once_per_day` sperrt eine Automatik nach dem Auslösen bis Mitternacht.
|
`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
|
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
|
Die Wochentage sind eine Maske, Ferien und Feiertage haben je drei
|
||||||
Zustaende. Zwei davon gab es immer: EGAL (der Tag aendert nichts, die
|
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
|
den Ferien nicht - ein Verbot schlaegt eine Erweiterung. Anders herum
|
||||||
liesse sich "nie" nicht mehr verlassen.
|
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:
|
if kalender["feiertag"] and automatik["on_holiday"] == ZUSAETZLICH:
|
||||||
passt = True
|
passt = True
|
||||||
if kalender["ferien"] and automatik["on_vacation"] == ZUSAETZLICH:
|
if kalender["ferien"] and automatik["on_vacation"] == ZUSAETZLICH:
|
||||||
@@ -718,7 +739,7 @@ class Runner:
|
|||||||
self.war_aktiv = {} # automation_id -> war im Zeitfenster
|
self.war_aktiv = {} # automation_id -> war im Zeitfenster
|
||||||
self.lief_im_fenster = {} # automation_id -> hat im Fenster ausgeloest
|
self.lief_im_fenster = {} # automation_id -> hat im Fenster ausgeloest
|
||||||
self._sonne = (None, "00:00", "00:00") # (datum, aufgang, untergang)
|
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._letzte_saeuberung = None
|
||||||
self.reihenfolge = [] # automation_id, Ausloeser vor Nachfolger
|
self.reihenfolge = [] # automation_id, Ausloeser vor Nachfolger
|
||||||
self.ausloeser_states = {} # automation_id -> state_id des Ausloesers
|
self.ausloeser_states = {} # automation_id -> state_id des Ausloesers
|
||||||
@@ -991,21 +1012,30 @@ class Runner:
|
|||||||
return "%02d:%02d" % (minute // 60 % 24, minute % 60)
|
return "%02d:%02d" % (minute // 60 % 24, minute % 60)
|
||||||
return str(wert)[:5]
|
return str(wert)[:5]
|
||||||
|
|
||||||
def kalender(self):
|
def kalender(self, tag):
|
||||||
"""Ferien und Feiertage von heute, einmal je Tag geholt."""
|
"""
|
||||||
heute = date.today()
|
Ferien und Feiertage eines Tages, je Tag einmal geholt.
|
||||||
if self._kalender[0] == heute:
|
|
||||||
return self._kalender[1]
|
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}
|
stand = {"feiertag": False, "ferien": False}
|
||||||
try:
|
try:
|
||||||
with self.db.cursor() as c:
|
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()
|
zeile = c.fetchone()
|
||||||
if zeile:
|
if zeile:
|
||||||
stand = {"feiertag": bool(zeile["holiday"]), "ferien": bool(zeile["vacation"])}
|
stand = {"feiertag": bool(zeile["holiday"]), "ferien": bool(zeile["vacation"])}
|
||||||
except Exception as fehler:
|
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)
|
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
|
return stand
|
||||||
|
|
||||||
# --- Werte -----------------------------------------------------------
|
# --- Werte -----------------------------------------------------------
|
||||||
@@ -1163,13 +1193,13 @@ class Runner:
|
|||||||
|
|
||||||
def durchlauf(self, fenster=NACHHOLFENSTER):
|
def durchlauf(self, fenster=NACHHOLFENSTER):
|
||||||
jetzt = datetime.now()
|
jetzt = datetime.now()
|
||||||
kalender = self.kalender()
|
|
||||||
|
|
||||||
for automation_id in self.reihenfolge:
|
for automation_id in self.reihenfolge:
|
||||||
automatik = self.regelwerk.automatiken.get(automation_id)
|
automatik = self.regelwerk.automatiken.get(automation_id)
|
||||||
if automatik is None:
|
if automatik is None:
|
||||||
continue
|
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"]))
|
and im_zeitfenster(jetzt, automatik["window_from"], automatik["window_to"]))
|
||||||
vorher_aktiv = self.war_aktiv.get(automatik["id"], aktiv)
|
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