Ein Knopf reicht – einmal Fische füttern, bitte
Wer einen Gartenteich mit Fischen hat und ab und zu zufüttert, kennt das Problem: Kaum rieselt das Futter ins Wasser, saugt der Skimmer die Pellets oder Flocken an, bevor die Fische überhaupt an der Oberfläche sind. Das Futter landet im Filter, die Fische bleiben hungrig, und das Wasser bekommt eine Extraportion Nährstoffe, die man eigentlich nicht haben wollte.
Die Lösung ist so alt wie die Teichpumpe selbst: einfach kurz ausschalten, füttern, nach ein paar Minuten wieder einschalten. Praktisch ist nur, dass man den Schalter am Becken nicht immer parat hat, sich am Ende des Tages nicht mehr erinnert, ihn wieder einzuschalten, und auf dem Weg zur Futterdose sowieso die Hände schmutzig sind.
Genau dafür gibt es den Teich-Skimmer: ein Knopf in einer kleinen Web-App, der die Steckdose der Pumpe pausiert, solange das Futter schwimmt, und sie nach einer einstellbaren Zeit (Standard 30 Minuten) wieder einschaltet. Man tippt, füttert, geht weiter.
Was die App macht
Das Konzept ist bewusst minimal gehalten: Ein-Knopf-Betrieb, kein Login, keine Konfiguration im UI.
- „Skimmer deaktivieren" schreibt einen Zeitstempel in eine kleine Lock-Datei im Docker-Volume. Die Pumpe ist ab sofort aus – egal wer sie wieder einzuschalten versucht.
- Ein zweiter Dienst, der Sync-Loop, liest diese Datei sekündlich und setzt den Zustand gegen Home Assistant durch. Wer auch immer in der Zwischenzeit an der Steckdose dreht – das Handy, eine Automation in HA, der Funktaster am Becken – der Loop schaltet im Zweifel wieder aus.
- Nach Ablauf der Pause (30 Minuten reichen in der Regel locker zum Füttern) schaltet er die Steckdose selbständig wieder ein.
Das ist Last-Writer-Wins in Reinform: die App ist die letzte Instanz, die sagt „jetzt nicht pumpen". Selbst wenn die App inzwischen geschlossen ist, lebt der Zustand im Container weiter, und das Futter hat Zeit, auf den Boden zu sinken, bevor die Fische es finden.
Architektur: zwei Container, eine Wahrheit
Web-App (Smartphone, Terrasse, Sofa)
│ POST action=toggle / status
▼
docker-compose: web + sync
web ────► share/skimmer.lock
sync ◄──── (liest sekündlich, schreibt gegen HA)
│ REST-API
▼
Home Assistant (switch.skimmer_steckdose_1)
Der Clou: Die App spricht nie direkt mit Home Assistant. Sie weiß nicht einmal, dass es HA gibt – sie schreibt nur eine Datei. Der Sync-Dienst ist die einzige Komponente, die einen HA-Token kennt. Das hält die Web-Containeroberfläche sauber und die Angriffsfläche klein.
Die beiden Container im Überblick:
web— Statische PHP-Front-End-App (PWA) +ajax.php. Enthält keinen HA-Token (außer der KonstantePAUSE_MINUTES).sync— PHP-Loop im 1-Sekunden-Takt gegen Home Assistant. Enthält den HA-Token (HA_URL,HA_TOKEN).
So ein Setup passt auf jeden Linux-Server, jeden NAS oder in jeden Container-Cloud-Anbieter – die einzige Voraussetzung ist Docker Compose und ein erreichbarer Home Assistant im Netz.
Pause-Logik im Detail
Die Lock-Datei share/skimmer.lock enthält den Unix-Timestamp der letzten Deaktivierung. Der Sync-Dienst entscheidet jede Sekunde anhand folgender Regeln:
- Pause aktiv, Steckdose an →
turn_off(Pause wird durchgesetzt). - Pause aktiv, Steckdose aus → nichts.
- Pause nicht aktiv, Steckdose aus →
turn_on(Pause beendet / Sollzustand aktiv). - Pause nicht aktiv, Steckdose an → nichts.
„Pause aktiv" heißt: timestamp >= now − PAUSE_MINUTES. Wird die Steckdose während der Pause von einer anderen Automation wieder eingeschaltet, schaltet der Loop sie innerhalb einer Sekunde erneut aus. So entsteht ein robustes Last-Writer-Wins-Verhalten ohne Locking-Tricks – und das Futter hat garantiert Zeit, in Ruhe zu sinken.
Die App selbst zeigt den Status mit einer kleinen Poll-Leiste unten: grün = letzte Antwort vor weniger als 5 Minuten, gelb = unter 15 Minuten, rot = älter oder Fehler. Die Farbe wird per 10-Sekunden-Timer auch dann aktualisiert, wenn gerade kein neuer Poll reinkommt – praktisch, um den Status auch dann zu sehen, wenn die App seit einer Weile im Hintergrund war.
PWA mit echter Offline-Ansicht
Eine PWA ohne Offline ist nur ein Lesezeichen auf dem Home-Bildschirm. Deshalb steckt im aktuellen Release ein durchdachtes Offline-Verhalten, das nicht einfach nur einen „Keine Verbindung"-Toast zeigt:
- Service Worker cached beim ersten Start die App-Shell (HTML, CSS, JS, Manifest). GET-Requests laufen ab dann network-first und fallen nur dann auf den Cache zurück, wenn das Netz wirklich weg ist. Wichtig: Es werden nur erfolgreiche Antworten (
response.ok) gecacht – eine kaputte 500er-Seite landet nicht im Offline-Schatz. - Letzter bekannter Stand wird bei jedem erfolgreichen Status-Poll in
localStoragegespeichert. - Geht das Gerät offline (Terrasse mit schwachem WLAN, Reverse-Proxy hängt kurz), zeigt die App keinen leeren Bildschirm, sondern blendet oben einen Banner ein: „Offline – kein Zugriff auf den Teich. Letzter Stand von HH:MM Uhr". Der Button zum Umschalten ist sichtbar, aber gesperrt.
- Sobald wieder ein
online-Event vom Browser kommt, verschwindet der Banner, die Controls werden freigegeben, und ein sofortiger Status-Poll holt den aktuellen Stand nach.
Heißt konkret: Du kannst auf der Terrasse sitzen und die Fische füttern, ohne dir Gedanken über das WLAN zu machen. Selbst wenn die App den Server gerade nicht erreicht, weiß sie noch, dass der Skimmer bis 14:30 pausiert ist – und blockiert dumme Klicks, die ohnehin ins Leere laufen würden.
Deployment in unter fünf Minuten
git clone https://github.com/jschwind/teich-skimmer.git
cd teich-skimmer
cp .env.example .env
# HA_URL, HA_TOKEN, SKIMMER_ENTITY_ID eintragen
docker compose up -d --build
Die App hört danach auf http://<host>:8080/. Für die PWA-Installation auf dem Handy braucht es HTTPS – die gängigste Lösung ist ein Reverse Proxy vor dem Stack (Caddy, Traefik, Nginx, Cloudflare-Tunnel). Im eigenen LAN reicht auch Tailscale mit HTTPS.
Hinweis zur Sicherheit: Die App hat bewusst keine Anmeldung. Jeder, der die URL kennt, kann den Skimmer pausieren. Im privaten Netz ist das gewollt; wer den Stack öffentlich erreichbar macht, sollte eine Auth-Schicht vor den Reverse Proxy schalten (Basic Auth, mTLS, Tailscale-Access-Controls etc.).
Was im Repo steckt
teich-skimmer/
├── docker-compose.yml # web + sync Services
├── Dockerfile # Schlankes PHP-Image
├── README.md # Setup, Screenshots, Architektur, Troubleshooting
├── app/
│ ├── public/ # PWA (HTML, CSS, JS, Manifest, Service Worker)
│ ├── lib/ha.php # HA-REST-Helper (curl, Timeouts 5/10 s)
│ └── sync.php # 1-Sekunden-Sync-Loop
└── docs/
├── architektur.md # Architekturdiagramm + Sync-Tabelle
└── *.jpeg # Screenshots aus der App
Lizenz: MIT. Das Repo soll ein nützliches Ausgangs-Template für alle sein, die etwas Ähnliches brauchen – eine schaltbare Steckdose, eine simple Pause-Logik, ein hübsches Frontend, eine API-only-Anbindung an HA.
Drei Dinge, die ich beim zweiten Anlauf anders gemacht habe
- HA-Token nur dort, wo er gebraucht wird. Erste Versionen hatten den Long-Lived-Token im Frontend-Container – heute liegt er ausschließlich im
sync-Service. Spart eine unnötige Token-Kopie im Container und reduziert die Angriffsfläche. - Cache-Versionierung explizit mitziehen.
$vinindex.phpundCACHE_NAMEinsw.jsmüssen immer gemeinsam gebumpt werden. Wer das vergisst, hat eine Stunde lang Nutzer mit einer alten App-Shell auf dem Handy und fragt sich, warum die neuen Buttons fehlen. - Telemetrie bewusst entscheiden. Jede offene App meldet beim Poll einen Heartbeat (Zeit, Sichtbarkeit, Bildschirm, Zeitzone, IP) nach
share/heartbeat.json. Das ist zur Diagnose praktisch, sollte aber im README erwähnt sein – wer das nicht möchte, wirft den Block inajax.phpraus.
Fazit
Der Teich-Skimmer ist ein gutes Beispiel für ein „kleines" Tool, das trotzdem ein paar Architektur-Entscheidungen verdient: API-only statt Custom-Component, Lock-Datei als Wahrheit, Container statt Cron auf dem Pi, echte Offline-Ansicht statt nur einem hübschen Manifest. All das passt in ein Repo mit drei PHP-Dateien und ~600 Zeilen Code – und fühlt sich trotzdem wie eine richtige App an.
Wer Lust hat, das Setup an die eigene Steckdose anzupassen, findet das Repo unter github.com/jschwind/teich-skimmer . Pull Requests für ähnliche „Schalte-etwas-für-X-Minuten-aus"-Probleme sind ausdrücklich willkommen – Lüfter, Lichtzonen, Bewässerung, alles, was man einmal kurz ausschalten und dann vergessen will.
Interesse? Lass uns zusammenarbeiten
Du hast eine ähnliche Herausforderung, möchtest einen eigenen kleinen Steuer-Stack bauen oder brauchst Unterstützung bei der Anbindung deiner Geräte an Home Assistant? Dann melde dich gerne:
- E-Mail: web@juergen-schwind.de
- WhatsApp: +49 179 22 11 00 5
Lass uns zusammenarbeiten.
Dieser Beitrag beschreibt den Standalone-Stack des Teich-Skimmers als reines Anwendungsbeispiel. Die Architektur (Lock-Datei + API-Sync-Loop + PWA) lässt sich auf viele ähnliche „Schalte-etwas-für-X-Minuten-aus"-Probleme übertragen – Pumpen, Lüfter, Lichtzonen, Bewässerung.