Doku: Benachrichtigungen beschrieben

Web Push ohne App, E-Mail, das gerechnete Geraet und der zweite Weg ueber
MQTT - samt der Stolpersteine (sw.js gehoert ins Wurzelverzeichnis, iPhone
erst ab Home-Bildschirm, Endpoint ist das Geheimnis, Schluessel nie neu
erzeugen solange Abos daran haengen).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 08:38:44 +02:00
co-authored by Claude Opus 5
parent df48432133
commit d3ff959574
3 changed files with 152 additions and 1 deletions
+2 -1
View File
@@ -21,6 +21,7 @@ Für einige Teile reicht diese Landkarte nicht, weil ihr Verhalten aus dem
Zusammenspiel mehrerer Prozesse entsteht. Sie haben eigene, ausführliche Zusammenspiel mehrerer Prozesse entsteht. Sie haben eigene, ausführliche
Dokumente unter [`doku/`](doku/README.md): Dokumente unter [`doku/`](doku/README.md):
**[Automatiken](doku/automatiken.md)**, **[Zeitleiste](doku/zeitleiste.md)**, **[Automatiken](doku/automatiken.md)**, **[Zeitleiste](doku/zeitleiste.md)**,
**[Benachrichtigungen](doku/benachrichtigungen.md)**,
**[Solar-Übersicht](doku/uebersicht.md)**, **[Datenbanken](doku/datenbank.md)** **[Solar-Übersicht](doku/uebersicht.md)**, **[Datenbanken](doku/datenbank.md)**
und **[Einstellungsseite](doku/einstellungen.md)**. und **[Einstellungsseite](doku/einstellungen.md)**.
@@ -196,7 +197,7 @@ genau die Topics, die auf den Kacheln stehen (`tileTopics()` in `rooms.php`).
| `history` | `history.php` | `jahresstatistik.js`, `historyMQTT.js` | `energyHistory` (6 ×), `getStats` | — | | `history` | `history.php` | `jahresstatistik.js`, `historyMQTT.js` | `energyHistory` (6 ×), `getStats` | — |
| `skoda` | `skoda.php` | `skodaMQTT.js` | `skoda.php?was=…`, `skodaCmd.php`, `tile.php` | `solarManager/#` | | `skoda` | `skoda.php` | `skodaMQTT.js` | `skoda.php?was=…`, `skodaCmd.php`, `tile.php` | `solarManager/#` |
| `weather` | `weather.php` | `weatherMQTT.js` | nichts — das Meteogramm ist ein fremdes Dokument im `<iframe>` (`js/meteogram.js`) | `weatherStation/#` | | `weather` | `weather.php` | `weatherMQTT.js` | nichts — das Meteogramm ist ein fremdes Dokument im `<iframe>` (`js/meteogram.js`) | `weatherStation/#` |
| `settings` | `settings.php` | `settings.js`, `grundriss.js`, `energieflussKatalog.js`, `energiefluss.js`, `energieflussEinstellungen.js` | `settings.php?action=…` | — | | `settings` | `settings.php` | `settings.js`, `grundriss.js`, `energieflussKatalog.js`, `energiefluss.js`, `energieflussEinstellungen.js`, `benachrichtigungen.js` | `settings.php?action=…`, `push.php?action=…` | — |
| `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — | | `logs` | `logs.php` | `logs.js` | `logs.php?action=…` | — |
| `einfuehrung` | `einfuehrung.php` | `einfuehrung.js` | nichts — Folien und Bilder stehen in der Seite | — | | `einfuehrung` | `einfuehrung.php` | `einfuehrung.js` | nichts — Folien und Bilder stehen in der Seite | — |
| `anzeige` | `solar.php` im Anzeige-Modus | wie `solar`, dazu `anzeige.js` | wie `solar` | wie `solar` | | `anzeige` | `solar.php` im Anzeige-Modus | wie `solar`, dazu `anzeige.js` | wie `solar` | wie `solar` |
+1
View File
@@ -13,6 +13,7 @@ abzulesen sind.
|---|---| |---|---|
| [automatiken.md](automatiken.md) | Die Automatiken (AutoActions): Datenmodell, Editor, Runner, wie eine Bedingung wirklich ausgewertet wird, Verkettung, Sperren, Fehlerbilder | | [automatiken.md](automatiken.md) | Die Automatiken (AutoActions): Datenmodell, Editor, Runner, wie eine Bedingung wirklich ausgewertet wird, Verkettung, Sperren, Fehlerbilder |
| [zeitleiste.md](zeitleiste.md) | Die Zeitleiste der Automatiken: wie der Server den Tag ausrechnet und wie der Browser ihn zeichnet | | [zeitleiste.md](zeitleiste.md) | Die Zeitleiste der Automatiken: wie der Server den Tag ausrechnet und wie der Browser ihn zeichnet |
| [benachrichtigungen.md](benachrichtigungen.md) | Meldungen aus Automatiken: Web Push ohne App, E-Mail, das Gerät „Benachrichtigungen“ |
| [uebersicht.md](uebersicht.md) | Die Solar-Übersicht: Katalog und Renderer, wie aus Watt ein Ring, ein Fluss und ein Füllstand wird | | [uebersicht.md](uebersicht.md) | Die Solar-Übersicht: Katalog und Renderer, wie aus Watt ein Ring, ein Fluss und ein Füllstand wird |
| [datenbank.md](datenbank.md) | Die drei Schemata: wer was schreibt, wie Messreihen verdichtet werden, welche Tabelle man für welche Auswertung nimmt | | [datenbank.md](datenbank.md) | Die drei Schemata: wer was schreibt, wie Messreihen verdichtet werden, welche Tabelle man für welche Auswertung nimmt |
| [einstellungen.md](einstellungen.md) | Die Einstellungsseite: was jeder Reiter speichert, welche Regeln das Modell durchsetzt und was die Seite bewusst nicht kann | | [einstellungen.md](einstellungen.md) | Die Einstellungsseite: was jeder Reiter speichert, welche Regeln das Modell durchsetzt und was die Seite bewusst nicht kann |
+149
View File
@@ -0,0 +1,149 @@
# Benachrichtigungen
Automatiken können melden: als Nachricht auf dem Handy oder als E-Mail.
Beides ohne eine eigene App — die Meldung aufs Handy kommt über **Web Push**
aus dem Dashboard selbst.
```mermaid
flowchart LR
A["Automatik<br/><small>Aktion: Benachrichtigungen</small>"] --> R
P["Probe aus den Einstellungen"] -->|"MQTT benachrichtigung/#"| R
R["autoaction_runner.py<br/><small>BenachrichtigungTransport</small>"] --> PUSH["Push-Dienst<br/><small>Google, Mozilla, Apple</small>"] --> H["Handy / Browser<br/><small>sw.js zeigt die Meldung</small>"]
R --> MAIL["SMTP"] --> M["Postfach"]
DB[("homeMesh.push_abos")] --> R
KEY[["push_vapid.json<br/><small>Schlüsselpaar</small>"]] --> R
```
---
## 1. Das Gerät „Benachrichtigungen“
Ein gerechnetes Gerät wie „Zeitpunkt“: Es hängt an keinem Netzwerkanschluss,
sondern bündelt Kommandos, die der Runner selbst ausführt. Angelegt wird es
vom Gerätesuchlauf (`benachrichtigung_module.py`), Aktor-URL
`Benachrichtigung`.
| Kommando | `command_url` | Parameter |
|---|---|---|
| Nachricht aufs Handy | `push` | Titel, Text |
| E-Mail | `mail` | Betreff, Text |
**Warum als Gerät und nicht als eigener Aktionstyp?** Weil der Editor, die
Übersicht und die Zeitleiste damit nichts Neues lernen müssen: Für sie ist es
ein Gerät mit Kommandos wie jedes andere. Eine Meldung ist deshalb eine
gewöhnliche Aktion — sie lässt sich mit anderen Aktionen mischen, im Rahmen
begrenzen und verketten.
**Keine Empfängerliste je Automatik.** Wer eine Meldung bekommt, steht an
einer Stelle: die Handys in `homeMesh.push_abos`, die Mailadressen in der
`config.ini` des Runners. Ein Empfängerfeld je Aktion wäre eine zweite
Liste, die mit der ersten auseinanderläuft.
---
## 2. Web Push — ohne App
```mermaid
sequenceDiagram
participant B as Browser (Handy)
participant W as ajax/push.php
participant DB as homeMesh.push_abos
participant R as Runner
participant D as Push-Dienst
B->>B: sw.js registrieren, Erlaubnis holen
B->>D: subscribe(VAPID-Public-Key)
D-->>B: Abo (endpoint + 2 Schlüssel)
B->>W: POST ?action=anmelden
W->>DB: INSERT … ON DUPLICATE KEY UPDATE
Note over R: Automatik löst aus
R->>DB: Abos lesen
R->>D: verschlüsseltes Paket, VAPID-signiert
D->>B: push-Ereignis → showNotification()
```
Drei Teile, die zusammengehören:
| Teil | Wo | Aufgabe |
|---|---|---|
| Service Worker | `sw.js` (Wurzelverzeichnis!) | nimmt das `push`-Ereignis an und zeigt die Meldung; ein Klick holt die Seite nach vorn |
| Abo | `homeMesh.push_abos` | Endpoint plus zwei Schlüssel je Gerät |
| Schlüsselpaar | `SolarManager/push_vapid.json` | der private Teil unterschreibt jede Meldung, der öffentliche steckt im Abo |
Wissenswertes:
* **`sw.js` muss im Wurzelverzeichnis liegen.** Ein Service Worker gilt nur
für Adressen unterhalb seines eigenen Ortes.
* **Er speichert nichts zwischen.** Das Dashboard lebt von aktuellen Werten;
ein Cache wäre hier keine Beschleunigung, sondern eine Fehlerquelle.
* **iPhone:** Safari erlaubt Web Push erst, wenn die Seite über
„Teilen ▸ Zum Home-Bildschirm“ abgelegt und von dort geöffnet wurde.
* **Das Abo gehört dem Browser, nicht dem Konto.** Jedes Gerät meldet sich
einzeln an; in den Einstellungen stehen alle nebeneinander.
* **Der Endpoint ist das Geheimnis.** Wer ihn hat, kann dem Gerät schreiben —
deshalb geht er nie an den Browser zurück, auch nicht in der Liste.
* **Tote Abos räumt der Runner selbst weg:** Antwortet der Dienst mit 404
oder 410, gibt es das Gerät nicht mehr.
* Der Schlüssel wird **nie neu erzeugt**, solange Abos daran hängen —
`vapid_erzeugen.py` überschreibt eine vorhandene Datei nicht.
---
## 3. E-Mail
Ein gewöhnlicher SMTP-Versand aus dem Runner, eingetragen in dessen
`config.ini`:
```ini
[mail]
server = smtp.example.net
port = 587 ; 587 = STARTTLS, 465 = von Anfang an verschlüsselt
benutzer =
passwort =
von =
an = einer@example.net, zweiter@example.net
```
Unverschlüsselten Versand gibt es nicht: Bei 465 wird die Verbindung
verschlüsselt aufgebaut, sonst mit STARTTLS gewechselt. Fehlen Server oder
Empfänger, meldet die Automatik einen Fehler ins Protokoll (`automation_log`),
statt still nichts zu tun.
---
## 4. Der zweite Weg: MQTT
Der Runner hört zusätzlich auf `benachrichtigung/#`:
| Topic | Nutzlast | Wirkung |
|---|---|---|
| `benachrichtigung/push` | `{"titel": "...", "text": "..."}` | Meldung an alle Geräte |
| `benachrichtigung/mail` | `{"betreff": "...", "text": "..."}` | Mail an die eingetragenen Empfänger |
Darüber schickt die **Probe** aus den Einstellungen — die Weboberfläche muss
so nichts verschlüsseln und braucht den privaten Schlüssel nie. Denselben Weg
kann jedes Skript auf der NAS nehmen.
Zugestellt wird im Haupttakt des Runners, nicht im MQTT-Faden: Eine
Datenbankverbindung gehört einem Faden, und der Rückruf des Brokers läuft in
einem eigenen.
---
## 5. Die Pakete
`pywebpush`, `py_vapid` und `http_ece` liegen wie alle übrigen Fremdpakete
**im SolarManager-Ordner**, nicht in `site-packages`. Weil der Runner aus
`autoActions/` startet, hängt `transports.py` diesen Ordner an den Suchpfad.
---
## 6. Wo fange ich an, wenn ich …
| Vorhaben | Ort |
|---|---|
| … einen weiteren Kanal (Telegram, Signal, WhatsApp) | ein Kommando in `benachrichtigung_module.py`, ein `elif` in `BenachrichtigungTransport.senden()` |
| … den Text einer Meldung ändern | in der Automatik selbst — Titel und Text sind Parameter der Aktion |
| … ein Handy loswerden | Einstellungen → Meldungen → Papierkorb; oder auf dem Gerät selbst abmelden |
| … den Schlüssel erneuern | `push_vapid.json` löschen, `vapid_erzeugen.py` laufen lassen, danach müssen sich **alle** Geräte neu anmelden |
| … nachsehen, warum nichts ankam | `autoActions.log` auf der NAS und die Spalten `zuletzt`/`fehler` in `push_abos` |