Doku: Automatiken und Zeitleiste ausfuehrlich beschrieben
Neues Verzeichnis doku/ mit zwei Tiefenbohrungen: das Regelwerk der Automatiken (Datenmodell, Editor, Runner, Auswertung, Verkettung, Sperren) und die Zeitleiste (Serverrechnung, Zeichnen, Stapeln, Ketten, Karte). Mit Diagrammen und je einem Bild; die Haupt-README verweist darauf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,236 @@
|
||||
# Die Zeitleiste der Automatiken
|
||||
|
||||
Die Übersicht als Tabelle beantwortete nicht, was man eigentlich wissen will:
|
||||
**Läuft der Tag stimmig, und warum ist etwas noch nicht gefahren?** Die
|
||||
Zeitleiste legt deshalb jede Automatik auf die Stunde, zu der sie greift.
|
||||
|
||||

|
||||
|
||||
Zwei Dateien teilen sich die Arbeit, und die Trennung ist scharf:
|
||||
|
||||
| Datei | Aufgabe |
|
||||
|---|---|
|
||||
| `restricted/zeitleiste.php` | **rechnen**: wo liegt eine Automatik auf dem Tag, in welcher Bahn, ist sie heute dran, ist sie gelaufen |
|
||||
| `js/solar/zeitleiste.js` | **zeichnen**: Bahnen, Stapeln, Etiketten, Kettenbögen, Karte, Filter |
|
||||
|
||||
Geliefert wird über `ajax/AutoAction.php?action=zeitleiste&datum=YYYY-MM-DD`
|
||||
als JSON.
|
||||
|
||||
---
|
||||
|
||||
## 1. Was der Server ausrechnet
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A["Alle Automatiken laden<br/><small>loadAutomation() je Id</small>"] --> B["Bedingungen je ODER-Gruppe einsortieren"]
|
||||
B --> C{"Was trägt die Gruppe?"}
|
||||
C -->|"Uhrzeit =, Sonne ±"| P["punkt"]
|
||||
C -->|"Uhrzeit >= / <"| F["start / ende"]
|
||||
C -->|"Automatik + Versatz"| K["kette + versatz"]
|
||||
C -->|"alles andere"| S["sensor = true<br/><small>wartet auf einen Messwert</small>"]
|
||||
P & F & K & S --> D["Art bestimmen:<br/>punkt › kette › fenster › jederzeit"]
|
||||
D --> E["Ketten auflösen<br/><small>bis zu 6 Runden</small>"]
|
||||
E --> G["Bahn, Satz, Tagesprüfung, Läufe"]
|
||||
G --> H[["JSON"]]
|
||||
```
|
||||
|
||||
### 1.1 Die Regeln, wie eine Lage entsteht
|
||||
|
||||
| Bedingung | Ergebnis auf dem Tag |
|
||||
|---|---|
|
||||
| feste Uhrzeit `um 07:00` | **Punkt** |
|
||||
| Sonnenauf-/-untergang ± Versatz | **Punkt**, an diesem Tag ausgerechnet |
|
||||
| andere Automatik `+ 00:10` | **Kette**: Punkt hinter dem Auslöser |
|
||||
| `ab …` / `vor …` oder ein eingeengtes Zeitfenster | **Balken** über das Fenster |
|
||||
| nur Messwerte, ganzer Tag | **„jederzeit“** (eigenes Band unter der Leiste) |
|
||||
|
||||
Mehrere Gruppen (ODER) werden zusammengefasst: Gibt es Punkte, gewinnt der
|
||||
früheste; sonst eine Kette; sonst das umschließende Fenster aus allen
|
||||
Gruppengrenzen. Trägt eine Gruppe einen zeitlich bestimmten Beginn (etwa
|
||||
„Sonnenuntergang + 30 **oder** dunkel“), merkt sich der Eintrag ihn als
|
||||
`punkt` — er erscheint im Hinweis als „frühestens 19:01“.
|
||||
|
||||
### 1.2 Ketten auflösen
|
||||
|
||||
Die Lage eines Nachfolgers hängt vom Auslöser ab, und die Kette kann mehrere
|
||||
Stufen haben (Wecker → Rollos → Schlafzimmer). Deshalb läuft die Auflösung
|
||||
reihum, höchstens sechs Runden:
|
||||
|
||||
| Zustand des Auslösers | Nachfolger |
|
||||
|---|---|
|
||||
| ist an diesem Tag schon **gelaufen** | Punkt bei der echten Laufzeit + Versatz |
|
||||
| hat einen **Punkt** | Punkt dort + Versatz |
|
||||
| ist ein **Fenster** | Fenster ab dessen Beginn + Versatz |
|
||||
| hat selbst keine Lage / Kreis | „jederzeit“ |
|
||||
|
||||
Immer begrenzt auf das eigene Zeitfenster. Und: Der Nachfolger **erbt**, ob
|
||||
der Tag passt — läuft der Wecker heute nicht, steht auch das Rollo
|
||||
gestrichelt da, mit dem Grund „Auslöser ‚Wecker Magdalena‘ ist nicht dran“.
|
||||
|
||||
### 1.3 Ist sie heute überhaupt dran?
|
||||
|
||||
`zlTagPasst()` rechnet **genau wie der Runner** (`tag_passt()` und
|
||||
`gemeinter_tag()`): erst öffnen (Wochentag, „zusätzlich“ an Ferien/Feiertagen),
|
||||
dann sperren (Ferien/Feiertage auf „nie“), und bei `next_day` für morgen.
|
||||
Sonst hieße „heute nicht“ hier etwas anderes als dort.
|
||||
|
||||
> Ausgeblendet wird trotzdem nichts. Wer nicht dran ist, steht gestrichelt
|
||||
> da — mit Grund im Hinweis. Eine Zeitleiste, die Einträge verschweigt,
|
||||
> beantwortet die Frage „warum ist das nicht gefahren?“ gerade nicht.
|
||||
|
||||
### 1.4 Sonnenzeiten für beliebige Tage
|
||||
|
||||
`solarLog.daylight` kennt heute, morgen und die Vergangenheit. Für einen
|
||||
späteren Tag nimmt `zlSonne()` denselben Kalendertag eines Vorjahres — die
|
||||
Sonne verschiebt sich von Jahr zu Jahr um weniger als eine Minute, und so
|
||||
braucht es keinen Standort, der in einem anderen Haus wieder falsch wäre.
|
||||
Im Hinweis steht dann „(Vorjahr)“.
|
||||
|
||||
### 1.5 Die Bahn
|
||||
|
||||
Die Zeile, in der eine Automatik steht, ergibt sich aus den **Geräten, die
|
||||
sie schaltet** — dieselbe Einordnung wie im Raum-Modal (`bedienform()` in
|
||||
`roomControls.php`), Mehrheit gewinnt, bei Gleichstand die Bahn weiter oben.
|
||||
Bewässerung ist dort „sonstiges“ und wird am Gerätetyp erkannt.
|
||||
|
||||
Bahnen: Licht · Beschattung · Heizung · Bewässerung · Schalter · Sonstiges.
|
||||
|
||||
### 1.6 Das JSON
|
||||
|
||||
```jsonc
|
||||
{
|
||||
"datum": "2026-09-21", "heute": true, "jetzt": 964, // Minuten seit Mitternacht
|
||||
"sonne": { "auf": 431, "unter": 1176, "genau": true },
|
||||
"bahnen": { "licht": {"titel": "Licht", "symbol": "bi-lightbulb"}, … },
|
||||
"etagen": [ {"code": "OG", "label": "Obergeschoss"}, … ],
|
||||
"automatiken": [{
|
||||
"id": 18, "name": "Hitzeschutz Süd", "floor": "EG", "enabled": true,
|
||||
"dran": true, "grund": "",
|
||||
"bahn": "beschattung",
|
||||
"satz": "Wozi Schiebetür, Wozi Fensterfront +1: Position+Neigung",
|
||||
"wenn": "Sonne Süd: Helligkeit > 30000 Lux und …",
|
||||
"laeufe": ["12:50"], // aus automation_log
|
||||
"art": "fenster", // punkt | fenster | kette | jederzeit
|
||||
"punkt": 570, "von": 570, "bis": 1080,
|
||||
"kette": null, "versatz": 0,
|
||||
"sonne": false, // Lage kommt vom Sonnenstand
|
||||
"wartet": true // hängt an einem Messwert
|
||||
}]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Was der Browser daraus macht
|
||||
|
||||
```mermaid
|
||||
flowchart TB
|
||||
L["zeitleisteLaden()<br/><small>fetch ?action=zeitleiste</small>"] --> Z["zeitleisteZeichnen()"]
|
||||
Z --> F["Etagenfilter anwenden"]
|
||||
F --> T["Dreiteilung:<br/>gelegt · jederzeit · pausiert"]
|
||||
T --> ST["je Bahn: zlStapeln()<br/><small>Zeilen wie im Kalender</small>"]
|
||||
ST --> HTML["HTML bauen:<br/>Achse, Bahnen, Bänder, Legende"]
|
||||
HTML --> KET["zlKettenZeichnen()<br/><small>SVG-Bögen, nach dem Layout</small>"]
|
||||
KET --> SCR["Scrollstand setzen"]
|
||||
SCR --> KAR["offene Karte wieder öffnen"]
|
||||
```
|
||||
|
||||
Jede Automatik erscheint **genau einmal**: in ihrer Bahn, unter „Jederzeit“
|
||||
oder unter „Pausiert“. Der Zähler oben („22 von 22 · 3 pausiert“) ist die
|
||||
Probe aufs Exempel — fehlte eine, fiele es dort auf.
|
||||
|
||||
### 2.1 Stapeln: warum in Pixeln und nicht in Minuten
|
||||
|
||||
Zwei Punkte zehn Minuten auseinander liegen zeitlich getrennt — ihre
|
||||
**Beschriftungen** aber übereinander. `zlStapeln()` rechnet deshalb in
|
||||
Pixeln: Es misst den Etikett-Text (einmal ein Canvas, dann nur messen) und
|
||||
legt jeden Eintrag in die erste Zeile, in der er nichts überdeckt.
|
||||
|
||||
```
|
||||
Zeile 0 ●07:00 Wecker Magdalena ▭ 09:30–18:00 Hitzeschutz Süd ✓12:50
|
||||
Zeile 1 ●07:08 Wecker Rollos ●15:59 Hitzeschutz Süd zurück
|
||||
└── Nachfolger direkt unter seinem Auslöser
|
||||
```
|
||||
|
||||
Drei Sonderfälle stecken darin:
|
||||
|
||||
* **Punkt am rechten Rand** trägt sein Etikett links von sich — die Marke
|
||||
bleibt, wo die Zeit ist.
|
||||
* **Fenster-Etikett** rückt nie vor seinen Balken; sonst sähe „15:00–18:31“
|
||||
aus, als finge es um halb elf an. Reicht der Platz nicht, wird der Name
|
||||
gekürzt (voll steht er im Hinweis).
|
||||
* **Ketten bleiben beisammen:** Eingesetzt wird familienweise — erst der
|
||||
Auslöser, gleich danach seine Nachfolger in die erste freie Zeile
|
||||
*darunter*. Je Zeile werden belegte Strecken geführt, nicht nur ihr Ende,
|
||||
weil ein Nachfolger links von etwas liegen kann, das schon steht.
|
||||
|
||||
### 2.2 Zustand, Farbe, Form
|
||||
|
||||
`zlZustand()` kennt fünf Zustände — sie bestimmen Farbe und Zeichen:
|
||||
|
||||
| Zustand | Wann | Darstellung |
|
||||
|---|---|---|
|
||||
| `nichtdran` | Tag passt nicht | gestrichelt, Kalendersymbol, Grund im Hinweis |
|
||||
| `gelaufen` | es gibt Läufe an dem Tag | Haken und echte Laufzeit |
|
||||
| `vorbei` | Zeit liegt hinter „jetzt“, nichts gelaufen | matt |
|
||||
| `aktiv` | „jetzt“ liegt im Fenster / hinter dem Punkt | hervorgehoben |
|
||||
| `kommt` | liegt noch vor uns | normal |
|
||||
|
||||
Beim Punkt zeigt das Etikett nach einem Lauf die **echte** Zeit, beim Fenster
|
||||
immer das Fenster und den Lauf hinten mit Haken — sonst stünde „12:50“ am
|
||||
Balkenanfang bei 09:30.
|
||||
|
||||
### 2.3 Kettenbögen
|
||||
|
||||
Erst **nach** dem Einsetzen gezeichnet, weil die Lage beider Enden aus dem
|
||||
Layout kommt. Die Linie verlässt den Auslöser lotrecht nach unten und biegt
|
||||
mit einer kleinen Rundung von links in den Nachfolger ein; liegt der
|
||||
Nachfolger links vom Auslöser, kommt sie von oben an. Jeder Pfad trägt
|
||||
`data-von`/`data-nach`, damit das Überfahren eines Eintrags seine ganze Kette
|
||||
hell schaltet (`zlKetteHervorheben()`).
|
||||
|
||||
### 2.4 Die Karte
|
||||
|
||||
Ein Klick auf einen Eintrag öffnet eine kleine Karte: was die Automatik tut,
|
||||
wann, wie sie heute steht — und die drei Dinge, die man damit tun will
|
||||
(Bearbeiten, Pausieren, Löschen). Pausieren und Löschen gehen über dieselben
|
||||
Funktionen wie in der Liste, samt Rückfrage nach abhängigen Automatiken.
|
||||
|
||||
Zwei Fallen stecken in dieser Karte, beide behoben:
|
||||
|
||||
1. **Die Karte machte eine zweite Bildlaufleiste.** AdminLTE gibt
|
||||
`main.app-main` `overflow: auto` bei Inhaltshöhe. Wurde die Karte zum
|
||||
Messen kurz ohne Lage eingehängt, stand sie unterhalb aller Bahnen und
|
||||
ragte aus `main` — Chrome zeigte dann dauerhaft eine eigene Leiste.
|
||||
Heute wird sie unsichtbar oben links eingehängt, gemessen und erst dann
|
||||
platziert.
|
||||
2. **Die Karte verschwand sofort wieder.** Die neue Leiste machte die
|
||||
Zeitleiste schmaler, der `ResizeObserver` zeichnete neu — und dabei ging
|
||||
die Karte verloren. Heute merkt sich `zeitleisteZeichnen()`, welche Karte
|
||||
offen war, und öffnet sie danach wieder. Außerdem klappt die Karte nach
|
||||
oben, wenn sie unten aus `main` liefe.
|
||||
|
||||
### 2.5 Was ein Neuzeichnen auslöst
|
||||
|
||||
| Anlass | Folge |
|
||||
|---|---|
|
||||
| Tag wechseln, Ansicht öffnen | `zeitleisteLaden()` — neue Daten vom Server |
|
||||
| Etagenfilter | nur `zeitleisteZeichnen()`, Auswahl in `localStorage` |
|
||||
| Breite ändert sich (> 4 px) | `zeitleisteZeichnen()` nach 120 ms |
|
||||
| Minutentakt (nur heute, Tab sichtbar) | `zeitleisteLaden()` — „jetzt“-Linie und neue Läufe |
|
||||
| nach Speichern/Pausieren/Löschen | `refreshAutomations()` lädt die Zeitleiste mit |
|
||||
|
||||
Was sich der Browser merkt: offene Ansicht (Zeitleiste oder Liste),
|
||||
Etagenfilter, Scrollstand innerhalb des Tages.
|
||||
|
||||
---
|
||||
|
||||
## 3. Wo fange ich an, wenn ich …
|
||||
|
||||
| Vorhaben | Ort |
|
||||
|---|---|
|
||||
| … eine Bahn hinzufügen oder umbenennen | `zeitleisteBahnen()` in `restricted/zeitleiste.php`; Farbe in `css/solar.css` (`.zl-bahn-<name>`) |
|
||||
| … das Symbol einer Bahn ändern | ebenda — dasselbe Symbol muss auch im Raum-Modal und im Editor stehen (siehe `GERAETE_SYMBOL` in `autoActionFuncs.js`) |
|
||||
| … eine neue Art von Lage einführen | `zeitleiste()` (Gruppen-Auswertung + Art bestimmen), dann `zlLage()`/`zlEintrag()` im Browser |
|
||||
| … an der Höhe/Dichte schrauben | `ZL_ZEILE`, `ZL_KOPF`, `ZL_MIN_SPUR` in `js/solar/zeitleiste.js` |
|
||||
| … verstehen, warum ein Eintrag „nicht dran“ ist | `zlTagPasst()` — und zum Gegenprüfen `tag_passt()` im Runner |
|
||||
Reference in New Issue
Block a user