Automatiken koennen andere Automatiken ausloesen
Anlass: "zehn Minuten nach dem Wecker den Rollladen hoch, aber nur wenn es dann schon hell ist". Als Verzoegerung an der Aktion war das nicht zu haben - die Zusatzbedingung gilt erst zum spaeteren Zeitpunkt, und eine verzoegerte Aktion, die selbst noch Bedingungen prueft, braeuchte ein zweites Bedingungssystem neben dem ersten. Der zweite Schritt ist also eine eigene Automatik; was ihr fehlte, war nur ein Bezug auf die erste. Den gibt jetzt das gerechnete Geraet "Automatiken", Gegenstueck zum vorhandenen "Zeitpunkt": jede Automatik ist dort ein Messwert, ihr Wert der Zeitpunkt der letzten Ausloesung. Der Editor braucht dafuer keine Zeile - er listet Geraete und deren Messwerte. Der neue Datentyp `elapsed` verhaelt sich dazu wie `deltatime` zum Sonnenaufgang: "+ 00:10", "ab + 00:10", "vor + 00:10". Ein Minus gibt es nicht. Gerechnet wird mit dem echten Abstand statt mit der Uhrzeit innerhalb des Tages - sonst machte ein Lauf von vorgestern die Bedingung heute wahr. Die offene Form endet trotzdem am Tagesrand, genau wie "ab 16:30". Ausgewertet wird topologisch, Ausloeser vor Nachfolger; nur so wirkt ein Versatz von null noch im selben Takt. Eine pausierte Automatik haelt ihre Nachfolger mit an: geladen werden nur die aktiven, und der Transport liefert fuer alle uebrigen einen leeren Wert. Die Messwerte pflegt ausloeser_nachfuehren() - fuer JEDE Automatik, auch fuer pausierte. Sonst loeschte ein Pausieren ueber fk_cond_state ON DELETE CASCADE die Bedingung des Nachfolgers, still. Geloescht wird nur, was keine Bedingung mehr benutzt. automatik_ausloeser.sql legt Datentyp und Geraet an. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+115
-1
@@ -101,6 +101,9 @@ oder danach liegen — deshalb dieselben drei Fälle mal zwei: `+ 00:30` eine
|
||||
halbe Stunde nach Sonnenaufgang, `ab - 00:30` ab einer halben Stunde davor,
|
||||
`vor + 00:30` bis eine halbe Stunde danach.
|
||||
|
||||
Dieselben drei Formen trägt die Verkettung einer Automatik mit der nächsten,
|
||||
dort aber nur mit Plus — siehe „Eine Automatik löst die nächste aus".
|
||||
|
||||
Über den Tagesrand wird gerechnet, nicht abgeschnitten: „sechs Stunden vor
|
||||
Sonnenaufgang" landet am Vorabend, und das ist so gewollt — abgeschnitten
|
||||
wären solche Angaben gar nicht mehr formulierbar. Verglichen wird die Uhrzeit
|
||||
@@ -140,6 +143,108 @@ davon.
|
||||
`force_once` ist von der Sperre nicht betroffen: es greift nur, wenn im
|
||||
Fenster gar nichts gelaufen ist — dann ist auch keine Sperre aktiv.
|
||||
|
||||
## Eine Automatik löst die nächste aus
|
||||
|
||||
Der Anlass war: *zehn Minuten nach dem Wecker den Rollladen hoch — aber nur,
|
||||
wenn es dann schon hell ist.*
|
||||
|
||||
Der naheliegende Weg wäre eine Verzögerung an der Aktion gewesen. Er trägt
|
||||
nicht: die Zusatzbedingung gilt erst zum **späteren** Zeitpunkt, und eine
|
||||
verzögerte Aktion, die selbst noch Bedingungen prüfen muss, ist keine Aktion
|
||||
mehr — sie bräuchte ein zweites Bedingungssystem neben dem ersten. Der zweite
|
||||
Schritt ist also eine eigene Automatik mit eigenen Rahmenbedingungen, und was
|
||||
ihr fehlte, war nur ein Weg, sich auf die erste zu beziehen.
|
||||
|
||||
Den gibt jetzt das gerechnete Gerät **„Automatiken"**, Gegenstück zum
|
||||
vorhandenen „Zeitpunkt". Jede Automatik ist dort ein Messwert, ihr Wert ist
|
||||
der Zeitpunkt der letzten Auslösung:
|
||||
|
||||
```
|
||||
Wecker Magdalena um 05:50 → Licht auf Wakeup
|
||||
Rollladen Magdalena Wecker Magdalena + 00:10
|
||||
UND Sonnenaufgang ab + 00:00 → Rollladen auf
|
||||
```
|
||||
|
||||
Der Editor braucht dafür keine Zeile Änderung. Er listet Geräte und deren
|
||||
Messwerte — „Automatiken" ist dann ein Gerät wie jedes andere.
|
||||
|
||||
### Der Datentyp `elapsed`
|
||||
|
||||
Er verhält sich zum Auslösezeitpunkt wie `deltatime` zum Sonnenaufgang: der
|
||||
Messwert ist der Bezugspunkt, die Schwelle der Versatz, das Vorzeichen steckt
|
||||
im Operator. Drei Formen, alle „danach" — ein Minus gibt es nicht, vor dem
|
||||
Auslöser kann nichts liegen, das erst der Auslöser anstößt:
|
||||
|
||||
| | wahr, wenn |
|
||||
|---|---|
|
||||
| `+ 00:10` | zehn Minuten nach der Auslösung, noch `catchup_minutes` lang |
|
||||
| `ab + 00:10` | von da an bis Mitternacht |
|
||||
| `vor + 00:10` | in den ersten zehn Minuten danach |
|
||||
|
||||
Gerechnet wird mit dem **echten Abstand**, nicht mit der Uhrzeit innerhalb des
|
||||
Tages. Das ist der eine Unterschied zu `deltatime`, und er ist nötig: sonst
|
||||
machte ein Lauf von vorgestern um 05:50 die Bedingung heute um 06:00 wahr, an
|
||||
einem Tag, an dem der Auslöser gar nicht lief.
|
||||
|
||||
Die offene Form `ab +` endet trotzdem am Tagesrand — sonst wäre sie morgen
|
||||
früh immer noch wahr, obwohl seither nichts geschehen ist. Ein Auslöser um
|
||||
23:55 trägt seinen Nachfolger deshalb nicht über Mitternacht; dieselbe
|
||||
Einschränkung hat `ab 23:55` auch. Die Punktform `+` braucht den
|
||||
Tagesvergleich nicht und trägt darüber hinweg.
|
||||
|
||||
### Reihenfolge, Pause, Kreise
|
||||
|
||||
**Ausgewertet wird topologisch**, Auslöser vor Nachfolger. Nur damit wirkt ein
|
||||
Versatz von null noch im selben Takt; bei jedem anderen Versatz wäre die
|
||||
Reihenfolge gleichgültig, zehn Minuten sind länger als ein Takt. Dazu trägt
|
||||
`ausloesen()` den eigenen Auslösewert sofort nach, statt bis zum nächsten
|
||||
`werte_einsammeln()` zu warten.
|
||||
|
||||
**Eine pausierte Automatik hält ihre Nachfolger mit an.** `Regelwerk.laden()`
|
||||
holt nur `enabled = 1`, der Transport liefert für alle übrigen einen leeren
|
||||
Wert, und der gilt jeder Bedingung als unerfüllt. Wer den Wecker pausiert,
|
||||
will morgens auch den Rollladen unten lassen.
|
||||
|
||||
**Kreise lehnt der Editor beim Speichern ab** (`pruefeKreis()` in
|
||||
`restricted/automations.php`). Im Betrieb wären sie kaum zu bemerken: die
|
||||
Sperrzeit begrenzt sie auf eine Auslösung je `lockout_secs`, und im Protokoll
|
||||
sieht das aus wie eine Automatik, die halt oft läuft. Der Runner meldet einen
|
||||
Kreis trotzdem — falls doch einer an der Datenbank vorbei entsteht — und
|
||||
wertet die Beteiligten dann in ihrer ursprünglichen Reihenfolge aus. Sie
|
||||
stillzulegen wäre schlimmer: eine wortlos abgeschaltete Automatik fällt
|
||||
niemandem auf.
|
||||
|
||||
### Löschen ist die gefährliche Stelle
|
||||
|
||||
`fk_cond_state` steht auf `ON DELETE CASCADE`. Verschwindet der Messwert,
|
||||
verschwindet die Bedingung — still. Bei der **letzten** Bedingung fängt
|
||||
`gruppen_erfuellt()` das ab („eine Automatik ohne Bedingungen löst nie aus").
|
||||
Bei **einer von zweien** fängt es niemand: aus *zehn Minuten nach dem Wecker
|
||||
UND es ist hell* würde ein bloßes *es ist hell*, und der Rollladen führe ab
|
||||
morgen jeden Tag bei Sonnenaufgang hoch.
|
||||
|
||||
Deshalb fragt der Editor vor dem Löschen, und der Runner räumt nur auf, was
|
||||
niemand mehr benutzt:
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Abhängen** | die Bedingung wird entfernt **und der Nachfolger pausiert**. Eine pausierte Automatik mit sichtbarer Lücke ist besser als eine stille Regeländerung. |
|
||||
| **Alle löschen** | der Nachfolger geht mit, und dessen Nachfolger auch. |
|
||||
|
||||
Automatisch mitzulöschen wäre die falsche Vorgabe — „Wecker weg, Rollladen
|
||||
still weg" bemerkt man erst im Winter.
|
||||
|
||||
### Die Messwerte pflegt der Runner
|
||||
|
||||
`ausloeser_nachfuehren()` legt bei jedem Laden des Regelwerks für **jede**
|
||||
Automatik einen Messwert an, auch für pausierte, und zieht den Namen nach.
|
||||
Geführt wird nach der Kennung (`auto:15`) und nicht nach dem Namen: wer
|
||||
umbenennt, soll die abhängigen Bedingungen behalten.
|
||||
|
||||
Für pausierte muss der Messwert stehen bleiben, sonst löschte ein Pausieren
|
||||
über dieselbe Kaskade die Bedingung des Nachfolgers, und beim Fortsetzen wäre
|
||||
sie weg.
|
||||
|
||||
## Transporte
|
||||
|
||||
Welcher Weg zum Gerät führt, entscheidet die URL des Aktors in `actors`:
|
||||
@@ -151,8 +256,9 @@ Welcher Weg zum Gerät führt, entscheidet die URL des Aktors in `actors`:
|
||||
| `wled://…` | Pfad in `/json/state` (`seg[0].col[0]`), gepollt | JSON-Vorlage mit Platzhaltern, als Ganzes gesendet |
|
||||
| Tahoma | Statusname (`core:ClosureState`), gepollt | `exec/apply` an die Box |
|
||||
| `Logic` | gerechnet: Uhrzeit, Datum, Sonne | – |
|
||||
| `Automatik` | gerechnet: je Automatik ihre letzte Auslösung | – |
|
||||
|
||||
Alle fünf stehen in `transports.py`. Eine sechste Geräteart kommt als weitere
|
||||
Alle sechs stehen in `transports.py`. Eine siebte Geräteart kommt als weitere
|
||||
Klasse dazu; sie braucht `passt()`, `zustaende_lesen()` und `senden()`.
|
||||
|
||||
Tahoma ist der einzige, der nicht am URL-Schema erkannt wird, sondern an der
|
||||
@@ -217,10 +323,17 @@ Parameter dort keine eigene URL — ihr Name *ist* der Platzhalter.
|
||||
|
||||
```bash
|
||||
cp config.ini.example config.ini # ausfüllen: Datenbank, MQTT, Tahoma
|
||||
mysql -h 127.0.0.1 -P 3310 -u homeMesh -p homeMesh < automatik_ausloeser.sql
|
||||
python3 fetch_calendar.py # Feiertage und Ferien holen
|
||||
python3 autoaction_runner.py --once --dry-run --verbose # Probelauf
|
||||
```
|
||||
|
||||
`automatik_ausloeser.sql` legt den Datentyp `elapsed` und das gerechnete
|
||||
Gerät „Automatiken" an — beides braucht die Verkettung, siehe oben. Die
|
||||
Messwerte darunter legt der Runner selbst an. Ohne das Skript läuft alles
|
||||
Übrige weiter, es fehlt nur die Möglichkeit, eine Automatik als Auslöser zu
|
||||
wählen.
|
||||
|
||||
`--dry-run` schaltet nichts, protokolliert aber jedes Kommando, das geschickt
|
||||
würde. `--once` macht einen einzigen Durchlauf.
|
||||
|
||||
@@ -237,6 +350,7 @@ nicht im Web-Verzeichnis:
|
||||
├── autoaction_runner.py
|
||||
├── transports.py
|
||||
├── fetch_calendar.py
|
||||
├── automatik_ausloeser.sql einmalig, siehe Einrichten
|
||||
└── config.ini Zugangsdaten, nicht im Git
|
||||
```
|
||||
|
||||
|
||||
Reference in New Issue
Block a user