Die Pool-Tagebücher

Ich habe einen Freund mit einem Pool. Dieser Freund – aus Anonymitätsgründen nennen wir ihn Tom – hat diesen Pool selbst gebaut. Er liegt in einem riesigen Garten neben einem zur gleichen Zeit entstandenen Teich. Zwischen Teich und Pool hat Tom eine Holzterrasse mit Sonnenschirm und Liegemöglichkeiten angelegt, auf der man wunderbar Nachmittage verbringen kann.

Tom hat auch eine Frau und Kinder, die diesen Pool ebenfalls benutzen. Seine Familie liebt den Pool und nutzt ihn gerne. Nach einem Jahr im Betrieb wurde jedoch eine gewisse Beschwerde immer lauter: Der Pool war zu kalt. Außerhalb des Hochsommers, also an den ersten und letzten warmen Tagen des Jahres, war die Wassertemperatur für das geschätzte Volk schlicht zu niedrig.

Tom nimmt Beschwerden ernst. Obwohl er sie nicht wirklich teilte (15°C Wassertemperatur empfand er als völlig ausreichend), nahm er sich ihrer an. Jeder vernünftig denkende Mensch wäre wohl in den nächstgelegenen Baumarkt marschiert, hätte eine Wärmepumpen-basierte Poolheizung besorgt und diese verbaut. Die Familie wäre glücklich gewesen und alles wäre gut gewesen.

Nicht so Tom. Der Garten liegt auf einem ehemaligen Bauernhof, bietet also reichlich Platz. Außerdem schweißt Tom gerne. Was lag also näher, als gebrauchte Solarpaneele zur Warmwasserbereitung zu kaufen, diese auf eine selbst geschweißte Stahlkonstruktion an die Garagenfassade zu montieren und den Filterkreis um diese Paneele zu erweitern? Das Resultat waren ca. 10 kW Heizleistung im Filterkreis.

Zunächst war alles gut: Der Pool wurde an sonnigen Tagen spürbar schneller warm und die Familie war zufrieden. Doch schnell stieg der Bedarf nach noch mehr “Pooltagen” und damit nach höherer Heizleistung. Tom besorgte weitere Paneele und erweiterte die Anlage auf beachtliche 20 kW. Der Pool war nun warm – zumindest solange die Pumpe lief. Wurde jedoch vergessen, die Pumpe einzuschalten, blieb der Pool kalt. Schlimmer noch: Wenn das Wasser in den Paneelen bei Sonnenschein nicht zirkulierte, wurde es heißer und heißer – bis die Leitungen explodieren.

Jeder vernünftig denkende Mensch hätte nun eine Zeitschaltuhr und ein Überdruckventil eingebaut. Nicht so Tom. Er beging einen strategischen Fehler: Er fragte mich nach einer Idee.

Dieser Artikel wird etwas länger werden, denn ich beschreibe hier meine Erlebnisse auf einer ungewissen Reise.


Kapitel 1: Der Anfang

Die Anforderungen: Der Pool sollte möglichst effizient beheizt werden. Die Paneele lieferten die nötige Energie, und eine Automatisierung sollte sicherstellen, dass das Wasser zirkulierte, sobald die Sonne schien – oder zumindest warnen, falls dies nicht der Fall war. Die Filterfunktion musste dabei erhalten bleiben.

Die erste Version: Sensoren sollten das Poolwasser (Vorlauf), die Heizung und den Rücklauf überwachen. Sobald die Temperatur in den Paneelen anstieg, sollte die Pumpe automatisch anspringen.

Das Konzept: Wir installierten einen Raspberry Pi 4, OneWire-Thermometer und einen Pimoroni Automation HAT in einem Schaltschrank nahe der Pumpe. Die Steuerung der 230V-Pumpe erfolgte über ein Relais.

KomponenteLinkBeschreibung
Raspberry Pi 4 (4GB)Raspberry Pi FoundationDie zentrale Steuereinheit
Automation HATPimoroniMonitoring & Relais-Steuerung
DS18B20 Sensoren-Wasserfeste OneWire-Thermometer

Die Software: Eine Kernanforderung war, dass Tom die Programmierung selbst anpassen konnte. Da er kein Softwareentwickler ist, brauchten wir eine Lösung, die intuitiv, aber dennoch leistungsfähig war: Node-RED.

[!TIP] Node-RED ist ein flow-basiertes Entwicklungswerkzeug, das Hardware-Geräte, APIs und Online-Dienste über einen Browser-Editor miteinander verknüpft. Mehr erfahren

In der ersten Iteration legten wir GPIO25 des Raspberry Pi auf einen Klemmblock und verbanden das Relais des Automation HAT mit der Pumpe. Das gesamte System wurde auf einer Hutschiene montiert.

Version 1: Der erste Aufbau auf der Hutschiene Version 1: Der erste kompakte Aufbau mit Pi 4 und Automation HAT.

In Node-RED nutzten wir primär zwei Packages:

  • node-red-contrib-1wire für die Temperaturmessung.
  • node-red-contrib-automation-hat für die I/O-Steuerung.

Die IDs der OneWire-Thermometer ließen sich einfach über die Konsole (ls /sys/bus/w1/devices/) ermitteln und in Node-RED zuordnen. Ein Nachmittag später war es vollbracht: Der Pool wurde beheizt, sobald die Paneele heiß genug waren, und die Pumpe schaltete sich abends automatisch aus.


Kapitel 2: Die Erweiterung

Natürlich waren wir damit noch nicht fertig. Das System verschickte noch keine Benachrichtigungen, und Tom musste den Filter immer noch manuell rückspülen – wie ein Tier. Das musste sich ändern.

Neue Anforderungen: Wir benötigten elektrische Ventile für die Rückspülung (Invertierung des Flusses, um Schmutz abzulassen). Zudem installierten wir Durchflussmesser im Vorlauf und im Heizkreis sowie Drucksensoren zur Überwachung des Systemzustands.

Für die Ventile bauten wir ein 24V-Netzteil und zusätzliche Relais (Waveshare Relay Board) ein. Der gesamte Schaltschrank wurde neu verkabelt.

Version 2: Der erweiterte Schaltschrank Version 2: Neu verkabelt mit zusätzlichem Relay Board und 24V-Netzteil für die Ventile.

Dabei stießen wir auf ein Problem: Das Automation-HAT-Package erlaubte kein hochfrequentes Auslesen der analogen Eingänge. Die Durchflussmesser erzeugten jedoch ein Pulssignal, dessen Frequenz wir erfassen mussten. Die Lösung war ein separates Python-Skript, das die Eingänge mit hoher Frequenz ausliest und die Daten als JSON an Node-RED übergibt:

Skript: hat_input.py (Anzeigen)
#!/usr/bin/env python3
"""
Monitors digital pulse frequencies and analog voltages on a Raspberry Pi.

This script uses the pigpio library to count pulses on two specified GPIO pins
and the Adafruit CircuitPython ADS1x15 library to read analog voltages from
three channels of an ADS1015 ADC.

The data is output as a JSON object to standard output at a configurable rate.
"""
import sys
import time
import json
import statistics
import pigpio
import board, busio
import adafruit_ads1x15.ads1015 as ADS
from adafruit_ads1x15.analog_in import AnalogIn

# --- Configuration ---
output_rate_hz = 1.0
if len(sys.argv) > 1:
    try:
        output_rate_hz = float(sys.argv[1])
        if output_rate_hz <= 0:
            sys.exit(1)
    except ValueError:
        sys.exit(1)

integration_time = 1.0 / output_rate_hz
VOLTAGE_COMPENSATION_FACTOR = 5.7

# GPIO pins for the flow sensors (using BCM numbering).
FLOW_SENSOR_PIN_1 = 26
FLOW_SENSOR_PIN_2 = 20

# --- Setup pigpio for pulse counting ---
pi = pigpio.pi()
if not pi.connected:
    sys.exit(1)

pulse_count_1 = 0
pulse_count_2 = 0

def cb1(gpio, level, tick):
    global pulse_count_1
    if level == 1: pulse_count_1 += 1

def cb2(gpio, level, tick):
    global pulse_count_2
    if level == 1: pulse_count_2 += 1

cb1_handle = pi.callback(FLOW_SENSOR_PIN_1, pigpio.RISING_EDGE, cb1)
cb2_handle = pi.callback(FLOW_SENSOR_PIN_2, pigpio.RISING_EDGE, cb2)

pi.set_glitch_filter(FLOW_SENSOR_PIN_1, 100)
pi.set_glitch_filter(FLOW_SENSOR_PIN_2, 100)

# --- Setup I2C ADC ---
i2c = busio.I2C(board.SCL, board.SDA)
ads = ADS.ADS1015(i2c)
ads.gain = 1
ads.data_rate = 128

chan0 = AnalogIn(ads, ADS.P0)
chan1 = AnalogIn(ads, ADS.P1)
chan2 = AnalogIn(ads, ADS.P2)

def get_stable_voltage(channel, num_samples=21):
    readings = []
    for _ in range(num_samples):
        readings.append(channel.voltage)
        time.sleep(0.005)
    return statistics.median(readings)

try:
    last_time = time.monotonic()
    last_pulse_count_1 = 0
    last_pulse_count_2 = 0
    next_output_time = time.monotonic()

    while True:
        next_output_time += integration_time
        sleep_time = next_output_time - time.monotonic()
        if sleep_time > 0:
            time.sleep(sleep_time)

        current_time = time.monotonic()
        elapsed_time = current_time - last_time

        current_pulse_count_1 = pulse_count_1
        current_pulse_count_2 = pulse_count_2
        
        if elapsed_time > 0:
            frequency_1 = (current_pulse_count_1 - last_pulse_count_1) / elapsed_time
            frequency_2 = (current_pulse_count_2 - last_pulse_count_2) / elapsed_time
        else:
            frequency_1 = frequency_2 = 0.0

        last_time, last_pulse_count_1, last_pulse_count_2 = current_time, current_pulse_count_1, current_pulse_count_2

        try:
            v1 = get_stable_voltage(chan0) * VOLTAGE_COMPENSATION_FACTOR
            v2 = get_stable_voltage(chan1) * VOLTAGE_COMPENSATION_FACTOR
            v3 = get_stable_voltage(chan2) * VOLTAGE_COMPENSATION_FACTOR
        except:
            continue

        data = {
             "pin26": round(frequency_1, 2),
             "pin20": round(frequency_2, 2),
             "analog1": round(v1, 2),
             "analog2": round(v2, 2),
             "analog3": round(v3, 2)
        }
        print(json.dumps(data))
        sys.stdout.flush()

except KeyboardInterrupt:
    pass
finally:
    cb1_handle.cancel()
    cb2_handle.cancel()
    pi.stop()

Nun war das System eigentlich perfekt: Tom konnte die Logik selbst anpassen, das System konnte heizen sowie spülen, und wir hatten alle kritischen Werte im Blick. Der erste Winter verging, und im Frühling bestand die Anlage die erste Funktionsprüfung mit Bravour.


Kapitel 3: Die Tücke der Technik

Was passiert jedoch, wenn zwar alle Sensoren funktionieren und die Automatik die Pumpe korrekt ansteuert, aber die mechanischen Ventile nicht zuverlässig schalten?

Richtig: Die Leitung explodiert.