Wie es funktioniert
Architektur
Vier Bauweisen standen zur Wahl. Gewählt wurde die, bei der ein Ausfall von Server oder Netzwerk die Pflanzen nicht gefährdet.
Auf dieser Seite
Die Wahl der Bauweise
| Variante | Bewertung | Ergebnis |
|---|---|---|
| Raspberry Pi, fest verkabelt | Wie beim ersten Beet. SD-Karten sterben, kein Analogeingang, kein Echtzeitverhalten, Linux im feuchten Kasten. | verworfen |
| Reine Netzwerkbrücke, Logik nur auf dem Server | Fällt Server oder WLAN aus, bleibt die Heizung an oder die Pflanzen erfrieren. | verworfen |
| Viele einzelne WLAN-Geräte | Jedes Gerät braucht Zugangsdaten, IP-Adresse und eigenen Empfang. Unübersichtlich. | verworfen |
| Ein Hub mit Modulbus, Server für Oberfläche und Planung | Ein Netzwerkgerät, beliebig viele Module, autonomer Betrieb bei Ausfällen. | gewählt |
Die erste Variante ist keine Theorie. So war das Beet von 2019 gebaut, und die Nachteile stammen aus dem Betrieb. Mehr dazu unter Geschichte.
Bausteine des Servers
Alle Dienste laufen als Container mit Docker Compose.
| Baustein | Wahl | Grund |
|---|---|---|
| TLS-Endpunkt | nginx im Container frontend |
Alles, was über ein Netz geht, ist verschlüsselt: HTTPS, MQTT über TLS, Push. Eigene CA der Installation. |
| Broker | Mosquitto | Klein, stabil, ausreichend. Jeder Hub hat eigene Zugangsdaten und darf nur seine eigenen Topics lesen und schreiben. |
| Backend | TypeScript, Node, Fastify | Eine Sprache für Backend und Oberfläche. |
| Datenbank | PostgreSQL mit TimescaleDB | Stammdaten und Messreihen in einer Datenbank. Alte Daten werden automatisch verdichtet. |
| Oberfläche | React, als Web-App installierbar | Live-Daten per WebSocket, auch auf dem Handy. |
| Benachrichtigung | ntfy, selbst gehostet | Push aufs Handy ohne Fremddienst. |
| Zugriff von außen | WireGuard | Keine Portfreigabe ins Internet. |
Browser ─ HTTPS ─┐ (optional: eigener Proxy oder Caddy davor)
Hub ── HTTPS, ───┼── TLS-Endpunkt (nginx) ── Backend (Fastify)
MQTT/TLS │ 8443, 8883, 8091 │ PostgreSQL + TimescaleDB
ntfy-App ─ HTTPS ┘ │ │ SMTP nur mit TLS, ntfy
└─ Mosquitto (nur im Docker-Netz)
│
Hub (ESP32-S3) / Simulator, CA angeheftet
│ CAN-Bus, 24 V
Module (ESP32-C3 SuperMini)
Im Betrieb sind nach außen nur drei Ports offen, alle mit TLS: HTTPS (8443), MQTT über TLS (8883)
und Push über HTTPS (8091). Oberfläche (8080) und ntfy (8090) ohne TLS hören nur auf 127.0.0.1,
für einen Reverse-Proxy auf demselben Rechner. Datenbank, Broker (1883) und Backend sind nur
innerhalb von Docker erreichbar. Klartext gibt es sonst nur auf dem CAN-Bus und in den offenen
Einrichtungs-WLANs von Hub und Anzeige (WLAN).
Kein Dienst läuft als root
Jeder Container hat einen festen, unprivilegierten Benutzer, auch Datenbank und Broker von Anfang
an. nginx nutzt das Image nginx-unprivileged und hört im Container auf Port 8080.
Geheimnisse
Passwörter für Datenbank und Broker und der Sitzungsschlüssel entstehen beim ersten Start von
selbst in einem eigenen Volume. Es gibt keine Standardpasswörter. Die eigene CA und das
Serverzertifikat legt der Dienst certs an und erneuert das Zertifikat vor dem Ablauf.
Datenmodell
Ein Standort hat Hubs, Zonen und Ressourcen. Ein Hub hat Module, ein Modul hat Kanäle. Ein Kanal ist ein Sensor oder ein Aktor. Eine Zone gruppiert Kanäle, etwa „Kammer A“. Eine Ressource wird von mehreren Zonen geteilt, etwa der Tank. Jede Zone hat Regler und ein Profil.
Ein Terrarium ist ein weiterer Standort mit anderen Zonen und Profilen.
Ein gemeinsamer Vertrag
Das Repository ist ein Monorepo. Das Paket packages/protocol enthält den Vertrag: Topics,
Nachrichten, Konfiguration, Modulkatalog, Anlagenbeschreibung, Layout und Regelgraph. Backend,
Simulator und Oberfläche nutzen dasselbe Paket, damit sie nicht auseinanderlaufen.
| Teil | Ordner | Aufgabe |
|---|---|---|
| Protokoll | packages/protocol |
gemeinsamer Vertrag |
| Backend | apps/backend |
Geräteverwaltung, Übernahme, Messwerte, Regeln, Alarme, Mail, Push, Benutzer, Firmware |
| Oberfläche | apps/frontend |
React-App mit Live-Modell der Anlage |
| Simulator | apps/simulator |
ersetzt den Hub samt Modulen |
| Firmware | firmware |
C++-Kern für Hub, Module und Anzeigen |
Aufbau der Firmware
Hub und Module teilen sich eine Codebasis in C++ mit PlatformIO. Der Kern ist von der Hardware getrennt und wird am Rechner getestet. Nur die unterste Schicht spricht echte Bausteine an.
- Zeit kommt immer als Parameter. Der Kern fragt nie eine Uhr. So lassen sich Abläufe von Minuten in Millisekunden testen.
- Kein Heap in den Regelschleifen, keine Exceptions, kein RTTI.
- Konfiguration in zwei Arenen. Eine neue Konfiguration wird in die zweite Arena gelesen, geprüft und aufgebaut. Erst dann wird umgeschaltet. Eine ungültige Konfiguration ändert nichts.
- Das Hub-eigene Modul läuft als zweiter Modulkern im Hub an der festen Busadresse 1. Für den Hub sieht es aus wie jedes andere Modul.
- Ein Durchlauf je Sekunde: Regler, Vorrang, Wirkungsüberwachung, Versorgung. Danach gehen die Stellwerte an die Module.
Simulator und Firmware tun dasselbe
Das Verhalten von Hub und Modulen ist in einem Dokument festgelegt. Firmware (C++) und Simulator (TypeScript) setzen beide genau dieses Verhalten um. Weicht eines ab, ist das ein Fehler.
Der Preis dafür: Das Verhalten ist doppelt implementiert und kann auseinanderlaufen. Die Tests beider Seiten prüfen deshalb dieselben Fälle.