Raspberry Pi

Raspberry Pi Abstandssensor MQTT: HC-SR04 für Füllstand und Anwesenheitserkennung

Schließen Sie einen HC-SR04-Ultraschallsensor mit sicherem Spannungsteiler an einen Raspberry Pi an, filtern Sie verrauschte Messwerte in Python und veröffentlichen Sie Tankfüllstand und Anwesenheits-Events per MQTT für Dashboards und Automationen.

HC-SR04-Ultraschallsensor über einem Wassertank, verbunden mit einem Raspberry Pi, der Füllstandswerte sendet – ein Raspberry Pi Abstandssensor MQTT Projekt
Probieren Sie es auf Ihrem eigenen MQTT-Broker ausHolen Sie sich im Free-Tarif einen privaten, verwalteten MQTT-Broker und starten Sie mit fertigen Codebeispielen.
Kostenlos testen Codebeispiele Keine Kreditkarte erforderlich

Ein Raspberry Pi Abstandssensor MQTT Projekt mit dem HC-SR04 löst zwei häufige Aufgaben mit einem einzigen günstigen Bauteil: Wie voll ist ein Tank, und ist etwas (oder jemand) da? In diesem Tutorial schließen Sie den Ultraschallsensor sicher an, erfassen mit gpiozero stabile Messwerte in Python und veröffentlichen sowohl einen Füllstand in Prozent als auch Anwesenheits-Events per MQTT, damit ein Dashboard oder eine Automation darauf reagieren kann.

So misst der HC-SR04 Abstände

Der HC-SR04 sendet einen kurzen 40-kHz-Ultraschallimpuls, sobald sein TRIG-Pin einen Puls von 10 Mikrosekunden erhält, und hält dann seinen ECHO-Pin auf High, bis die Reflexion zurückkommt. Die Länge dieses Echopulses, multipliziert mit der Schallgeschwindigkeit und geteilt durch zwei, ergibt den Abstand. Der Sensor arbeitet von wenigen Zentimetern bis zu einigen Metern, am besten gegen flache, harte Oberflächen im rechten Winkel, etwa eine Wasseroberfläche oder eine Wand.

Für reale Projekte sind zwei Folgen wichtig. Erstens muss der Pi den Echopuls in Software messen, daher fügt das Linux-Scheduling etwas Jitter hinzu; den filtern Sie heraus. Zweitens ändert sich die Schallgeschwindigkeit mit der Lufttemperatur, rechnen Sie also mit einer kleinen Drift zwischen einem kalten Morgen und einem heißen Nachmittag. Für einen Füllstands- oder Anwesenheitsmelder sind beide Effekte beherrschbar.

Verdrahtung mit Spannungsteiler

Der HC-SR04 läuft mit 5 V, und auch sein ECHO-Ausgang liefert 5 V, was einen GPIO-Pin des Raspberry Pi beschädigen kann. Verwenden Sie an ECHO immer einen Spannungsteiler:

HC-SR04-Pin Anschluss
VCC Pi 5 V (physischer Pin 2)
GND Pi GND (physischer Pin 6)
TRIG GPIO23 (physischer Pin 16), direkt
ECHO 1-k-Widerstand zu GPIO24 (physischer Pin 18); 2-k-Widerstand von GPIO24 nach GND

Das Paar aus 1 k und 2 k teilt 5 V auf etwa 3,3 V herunter. Eine Kombination aus 1 k und 1,8 k oder 2,2 k funktioniert ebenfalls. TRIG kann direkt angeschlossen werden, weil der 3,3-V-Ausgang des Pi ausreicht, um den Sensor auszulösen. Wenn Sie die 3,3-V-kompatible Variante des Moduls kaufen, prüfen Sie deren Dokumentation; möglicherweise brauchen Sie den Teiler dann nicht.

Die Software installieren

sudo apt update
sudo apt install -y python3-venv python3-lgpio
mkdir -p ~/distance-mqtt && cd ~/distance-mqtt
python3 -m venv --system-site-packages .venv
source .venv/bin/activate
pip install "paho-mqtt>=2.0" gpiozero

Schnelltest ohne MQTT:

python -c "from gpiozero import DistanceSensor; from time import sleep; s = DistanceSensor(echo=24, trigger=23, max_distance=4); sleep(1); print(round(s.distance * 100, 1), 'cm')"

Richten Sie den Sensor auf eine Wand und vergleichen Sie das Ergebnis mit einem Maßband. Ein Messwert, der immer exakt dem Wert von max_distance entspricht, bedeutet, dass kein Echo erkannt wird – meist ein Verdrahtungsproblem an ECHO.

Zwei Modi: Füllstand und Anwesenheit

Derselbe Messwert lässt sich auf zwei Arten interpretieren, und das Skript veröffentlicht beide:

Modus Berechnung Topic Veröffentlichung Retained
Tankfüllstand (empty_distance - distance) / (empty_distance - full_distance) .../level Alle 30 s Ja
Anwesenheit Abstand unter einem Schwellwert, mit Hysterese .../presence Nur bei Änderung Ja
Rohwert Median-Abstand in cm in beiden Payloads

Da beide Topics retained sind, zeigt ein jederzeit geöffnetes Dashboard sofort den aktuellen Füllstand und ob jemand anwesend ist. Anwesenheit nur bei Änderung zu veröffentlichen hält den Datenverkehr winzig und macht Automationen einfach: abonnieren und auf jede Nachricht reagieren.

Den Tank kalibrieren

Montieren Sie den Sensor oben am Tank, senkrecht nach unten gerichtet, mit Abstand zu Zulauf und Wänden. Messen Sie dann zwei Abstände:

  1. EMPTY_CM: Sensor bis zum Boden (oder bis zum niedrigsten nutzbaren Pegel).
  2. FULL_CM: Sensor bis zum Wasser an der Voll-Markierung. Halten Sie zwischen Sensorfläche und höchstem Wasserstand mindestens den Mindestmessabstand des Sensors von einigen Zentimetern ein.

Setzen Sie beide Werte als Umgebungsvariablen. Das Skript begrenzt das Ergebnis auf 0 bis 100 Prozent, damit Spritzer oder ein leerer Tank keine unmöglichen Werte erzeugen.

Das Raspberry Pi Abstandssensor MQTT Skript

Speichern Sie es als distance_mqtt.py:

#!/usr/bin/env python3
"""HC-SR04 tank level and presence detection over MQTT."""
import json
import os
import statistics
import time
import uuid

import paho.mqtt.client as mqtt
from gpiozero import DistanceSensor

BROKER = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "1883"))
USERNAME = os.getenv("MQTT_USERNAME")
PASSWORD = os.getenv("MQTT_PASSWORD")
CLIENT_ID = os.getenv("MQTT_CLIENT_ID", f"distance-{uuid.uuid4().hex[:8]}")
PREFIX = os.getenv("MQTT_PREFIX", "connectmq/demo/9c3d55/tank1")

EMPTY_CM = float(os.getenv("EMPTY_CM", "120"))
FULL_CM = float(os.getenv("FULL_CM", "20"))
PRESENCE_ON_CM = float(os.getenv("PRESENCE_ON_CM", "60"))
PRESENCE_OFF_CM = float(os.getenv("PRESENCE_OFF_CM", "80"))
LEVEL_INTERVAL = 30
SAMPLES = 7

sensor = DistanceSensor(echo=24, trigger=23, max_distance=4, queue_len=5)


def measure_cm():
    """Median of several readings, ignoring out-of-range values."""
    values = []
    for _ in range(SAMPLES):
        d = sensor.distance * 100
        if 2 < d < 399:  # 400 cm bedeutet: kein Echo innerhalb von max_distance
            values.append(d)
        time.sleep(0.06)  # Echos zwischen den Pings abklingen lassen
    return statistics.median(values) if values else None


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connection refused: {reason_code}")
        return
    print(f"Connected as {CLIENT_ID}")
    client.publish(f"{PREFIX}/status", "online", qos=1, retain=True)


client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=CLIENT_ID)
client.on_connect = on_connect
client.will_set(f"{PREFIX}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
if USERNAME:
    client.username_pw_set(USERNAME, PASSWORD)
if PORT == 8883:
    client.tls_set()
client.connect(BROKER, PORT, keepalive=30)
client.loop_start()

present = None
last_level = 0.0

try:
    while True:
        distance = measure_cm()
        now = time.time()
        if distance is None:
            print("No valid echo")
            time.sleep(1)
            continue

        # Anwesenheit mit Hysterese: unter ON einschalten, über OFF ausschalten.
        new_present = present
        if distance < PRESENCE_ON_CM:
            new_present = True
        elif distance > PRESENCE_OFF_CM:
            new_present = False
        if new_present is not None and new_present != present:
            present = new_present
            payload = {"present": present, "distance_cm": round(distance, 1), "ts": int(now)}
            client.publish(f"{PREFIX}/presence", json.dumps(payload), qos=1, retain=True)
            print("presence", payload)

        if now - last_level >= LEVEL_INTERVAL:
            span = EMPTY_CM - FULL_CM
            percent = max(0.0, min(100.0, (EMPTY_CM - distance) / span * 100))
            payload = {"level_pct": round(percent, 1), "distance_cm": round(distance, 1), "ts": int(now)}
            client.publish(f"{PREFIX}/level", json.dumps(payload), qos=1, retain=True)
            print("level", payload)
            last_level = now

        time.sleep(0.5)
except KeyboardInterrupt:
    pass
finally:
    client.publish(f"{PREFIX}/status", "offline", qos=1, retain=True).wait_for_publish(2)
    client.loop_stop()
    client.disconnect()
    sensor.close()

In einem echten Einsatz verwenden Sie meist einen Modus pro Sensor: Ein Tanksensor braucht keine Anwesenheits-Events, und ein Sensor an einer Tür braucht keinen Füllstand. Beide in einem Skript zu haben erleichtert das Experimentieren, und Sie können den nicht benötigten Block einfach löschen.

Warum Medianfilter und Hysterese wichtig sind

Ultraschall-Messwerte springen gelegentlich: ein Streuecho von der Tankwand, eine Welle auf dem Wasser oder ein Timing-Aussetzer unter Linux. Beim Mitteln zieht ein einziger falscher Wert das Ergebnis mit; der Median aus sieben Messwerten ignoriert ihn einfach. Die Hysterese löst ein anderes Problem. Ohne sie würde eine Person genau an der Schwelle die Anwesenheit mehrmals pro Sekunde ein- und ausschalten und Abonnenten mit Events fluten. Mit getrennten Ein- und Ausschaltabständen ändert sich der Zustand einmal und bleibt stabil.

Die Ausgabe testen

mosquitto_sub -h broker.connectmq.com -p 1883 -i watch-9c3d55 \
  -t 'connectmq/demo/9c3d55/tank1/#' -v

Bewegen Sie Ihre Hand auf den Sensor zu: Eine presence-Nachricht mit "present": true erscheint, wenn Sie 60 cm unterschreiten, und false, sobald Sie sich über 80 cm entfernen. Alle 30 Sekunden meldet eine level-Nachricht den Füllstand in Prozent auf Basis Ihrer Kalibrierung. Denken Sie daran, dass der öffentliche MQTT-Broker für alle lesbar ist; verwenden Sie daher ein zufälliges Präfix und halten Sie echte Standortnamen aus Test-Topics heraus.

Automatisieren mit den Daten

Liegen Füllstand und Anwesenheit auf MQTT, wird Automation zu einer Frage des Subscribers statt des Sensors:

  1. Pumpensteuerung: Ein zweites Skript oder ein Node-RED-Flow abonniert level und schaltet über die Steuerung aus Raspberry Pi Relais per MQTT eine Pumpe ein, wenn der Füllstand unter 20 Prozent fällt, und über 90 Prozent wieder aus.
  2. Alarme: Auf einem privaten ConnectMQ-Broker warnt Sie eine Schwellwert-Alarmregel auf level_pct vor einem fast leeren Tank, wie unter MQTT-Schwellwert-Alarme beschrieben.
  3. Hausautomation: Home Assistant kann das Retained-Topic presence als Belegungssensor lesen und Licht einschalten.

Brauchen Sie einen batteriebetriebenen Füllstandssensor an einem entlegenen Tank ohne Netzstrom, nutzt der Aufbau ESP32-Wassertank-Füllstandssensor denselben Payload mit Deep Sleep, sodass beide dasselbe Dashboard speisen können.

Fehlerbehebung

Problem Wahrscheinliche Ursache und Lösung
Misst immer 400 cm Kein Echo erkannt; Teiler und ECHO-Verdrahtung prüfen
Messwerte springen stark Sensor sieht die Tankwand; mittig und mit Abstand zu den Rändern montieren
Messwerte driften im Tagesverlauf Schallgeschwindigkeit ändert sich mit der Temperatur; neu kalibrieren oder Temperaturkompensation ergänzen
Kondensationsfehler im Tank Feuchtigkeit auf den Wandlern; wasserdichtes Ultraschallmodul verwenden
GPIO busy Ein anderes Skript nutzt GPIO23 oder GPIO24

Einrichtung für den Produktivbetrieb

Für einen Tank auf einem Hof, in einer Werkstatt oder einem Gebäude wechseln Sie vom öffentlichen MQTT-Broker auf einen privaten. ConnectMQ ist ein verwalteter MQTT-Broker mit TLS auf Port 8883 und generierten Zugangsdaten. Setzen Sie MQTT_PORT=8883, MQTT_USERNAME=YOUR_USERNAME, MQTT_PASSWORD=YOUR_PASSWORD, MQTT_CLIENT_ID=YOUR_CLIENT_ID und ein aussagekräftiges Präfix wie farm1/water/tank1 als Umgebungsvariablen und betreiben Sie das Skript als systemd-Dienst. Das Raspberry Pi MQTT Tutorial enthält die Vorlage für die Dienstdatei und erklärt das hier verwendete Reconnect-Verhalten.

Jetzt mit ConnectMQ starten

Legen Sie Ihre Füllstands- und Anwesenheitsdaten auf einen Cloud-MQTT-Broker, den jedes Dashboard und jede Automation erreicht. Registrieren Sie sich bei ConnectMQ für den kostenlosen Tarif Serverless (Free) ohne Kreditkarte, erstellen Sie einen Broker und verbinden Sie Ihren Pi in wenigen Minuten über TLS.

Häufig gestellte Fragen

Brauche ich für den HC-SR04 wirklich einen Spannungsteiler?

Ja, beim Standardmodul mit 5 V. Sein ECHO-Pin gibt 5 V aus, und die GPIO-Pins des Raspberry Pi sind nur für 3,3 V ausgelegt.

Wie genau ist der HC-SR04 am Raspberry Pi?

Unter guten Bedingungen auf etwa einen Zentimeter genau, sobald die Messwerte mit einem Medianfilter bereinigt sind. Software-Timing unter Linux und Temperaturänderungen sorgen für etwas Streuung.

Kann der HC-SR04 den Wasserstand in einem geschlossenen Tank messen?

Ja, solange er senkrecht auf die Oberfläche zeigt und trocken bleibt. In feuchten Tanks hält ein wasserdichtes Ultraschallmodul länger.

Warum wird die Anwesenheit nur bei Änderung veröffentlicht?

Das hält den Datenverkehr minimal und macht Automationen einfach. Da die Nachricht retained ist, erhalten neue Abonnenten trotzdem sofort den aktuellen Zustand.

Verwandte Artikel

Raspberry Pi RFID MQTT: Zutrittsprotokollierung mit dem RC522-Leser

Schließen Sie einen RC522-RFID-Leser an einen Raspberry Pi an, lesen Sie Tags mit Python, prüfen Sie sie gegen eine lokale Allowlist und veröffentlichen Sie jeden Zutrittsversuch als MQTT-Ereignis, das Logger, Dashboards und Alarmwerkzeuge verarbeiten können.

7 Min. Lesezeit

Pico W vs. Pico 2 W für MQTT: RP2040 vs. RP2350 bei Leistung, Speicher und TLS

Der Pico 2 W behält Formfaktor und WLAN-Chip des Pico W, ersetzt aber den RP2040 durch den schnelleren RP2350. Dieser Vergleich zeigt, was das für MQTT-Geräte bedeutet: CPU, Speicher, TLS, Sicherheitsfunktionen, Softwareunterstützung und welches Board für welches Projekt.

8 Min. Lesezeit

Alle Artikel →

Ihr privater MQTT-Broker ist nur wenige Klicks entfernt

Konto anlegen, Broker-Zugangsdaten erhalten und das erste Gerät verbinden — ohne Server, die installiert, gepatcht oder skaliert werden müssen.