IoT-Automatisierung

Wetterstation selber bauen mit MQTT: Wind, Regen und Temperatur mit einem ESP32

Verbinden Sie Anemometer, Regenmesser und BME280 mit einem ESP32, veröffentlichen Sie Wind, Regen, Temperatur, Luftfeuchte und Luftdruck als MQTT-JSON und protokollieren und visualisieren Sie die Daten mit einem kurzen Python-Skript.

Mast im Garten mit Anemometer, Regenmesser und Sensorgehäuse, verdrahtet mit einem ESP32 für eine selbstgebaute Wetterstation mit MQTT
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

Wenn Sie eine Wetterstation mit MQTT selber bauen, erhalten Sie lokale Messwerte für Wind, Regen, Temperatur, Luftfeuchte und Luftdruck, die jedes Dashboard, jedes Smart-Home-System und jede Bewässerungssteuerung abonnieren kann. In diesem Tutorial schließen Sie gängige Wettersensoren an einen ESP32 an, veröffentlichen einmal pro Minute eine JSON-Nachricht an einen Broker und nutzen einen Python-Subscriber, um die Messwerte zu protokollieren und ein Diagramm zu zeichnen. Alles wird zuerst auf dem kostenlosen öffentlichen MQTT-Broker getestet und anschließend auf private Zugangsdaten umgestellt.

Bauteile und Verdrahtung für den MQTT-Wetterstationsknoten

Das Design nutzt Bauteile, die leicht erhältlich und gut dokumentiert sind. Wind- und Regensensor stammen aus den klassischen „Weather Meter“-Kits: ein Schalenanemometer und ein Kippwaagen-Regenmesser, die beide Reedkontakte verwenden, die bei jeder Umdrehung bzw. jedem Kippen kurz schließen.

Bauteil Zweck ESP32-Anschluss
ESP32-DevKit-Board WLAN und MQTT USB oder 5-V-Versorgung
BME280-Breakout (I2C) Temperatur, Luftfeuchte, Luftdruck SDA GPIO 21, SCL GPIO 22, 3,3 V, GND
Schalenanemometer (Reedkontakt) Windgeschwindigkeit Ein Draht an GPIO 27, der andere an GND
Kippwaagen-Regenmesser Niederschlag Ein Draht an GPIO 26, der andere an GND
Strahlungsschutz Hält den BME280 aus der direkten Sonne Mechanisch
Wetterfestes Gehäuse Schützt den ESP32 Mechanisch

Die Reedkontakte benötigen nur einen Pull-up-Widerstand; der Sketch aktiviert die internen Pull-ups des ESP32, sodass ein Schließen den Pin auf Low zieht. Viele dieser Kits enthalten auch eine Windfahne; sie nutzt eine Widerstandskette, die Sie über einen ADC-Pin auslesen, und lässt sich später ergänzen, sobald die Grundfunktionen laufen.

Zwei Kalibrierkonstanten stammen aus dem Datenblatt der Sensoren. Beim hier verwendeten gängigen Kit entspricht ein Anemometer-Schließvorgang pro Sekunde 2,4 km/h, und ein Kippen der Waage entspricht 0,2794 mm Niederschlag. Weicht Ihre Hardware ab, ändern Sie die beiden Konstanten am Anfang des Sketches.

Topic- und Payload-Design

Verwenden Sie ein Telemetrie-Topic mit kompaktem JSON-Payload sowie ein retained Status-Topic, das über den MQTT Last Will gesteuert wird:

connectmq/demo/<random-id>/weather/station1/telemetry   (JSON every 60 s)
connectmq/demo/<random-id>/weather/station1/status      (online/offline, retained)
{
  "temp_c": 14.8,
  "hum_pct": 71.2,
  "press_hpa": 1012.6,
  "wind_kmh": 9.6,
  "gust_kmh": 16.8,
  "rain_mm": 0.28,
  "rain_today_mm": 3.07,
  "rssi": -61,
  "uptime_s": 86412
}

Wenn Sie Minutenmittelwerte statt jedes einzelnen Impulses senden, bleibt der Datenverkehr winzig und deutlich innerhalb der Limits des öffentlichen MQTT-Brokers von 10 Nachrichten pro Sekunde und 64 KB pro Nachricht. Ein Böenwert (der höchste 3-Sekunden-Mittelwert innerhalb der Minute) bewahrt die Information, die durch die Mittelung sonst verloren ginge. Der Leitfaden zum kostenlosen öffentlichen MQTT-Broker nennt die übrigen Limits, etwa 8 Topic-Ebenen und 10 Subscriptions pro Client, die dieses Design problemlos einhält.

Warum nicht auch die Telemetrienachricht als retained Message senden? Das ist möglich und bei einer Wetterstation oft sinnvoll: Ein Dashboard, das zwischen zwei Veröffentlichungen geöffnet wird, zeigt sofort den letzten Messwert statt eines leeren Widgets. Der Nachteil: Eine Station, die vor Stunden ausgefallen ist, scheint weiterhin ihre letzten Werte zu melden. Genau deshalb ist das separate retained status-Topic mit Last Will so wichtig. Zeigen Sie beides an und grauen Sie den Messwert aus, wenn der Status offline lautet.

Der ESP32-Sketch

Installieren Sie diese Bibliotheken über den Arduino Library Manager: PubSubClient von Nick O'Leary, Adafruit BME280 Library und Adafruit Unified Sensor. Passen Sie die WLAN-Zugangsdaten und die zufällige ID im Topic-Präfix an.

#include <WiFi.h>
#include <PubSubClient.h>
#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

const char* WIFI_SSID = "YOUR_WIFI";
const char* WIFI_PASS = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/k3x9q2/weather/station1";

const int PIN_WIND = 27;
const int PIN_RAIN = 26;
const float KMH_PER_HZ = 2.4;     // Anemometer: 1 Schließvorgang/s = 2,4 km/h
const float MM_PER_TIP = 0.2794;  // Regenmesser: 1 Kippen = 0,2794 mm

WiFiClient net;
PubSubClient mqtt(net);
Adafruit_BME280 bme;

volatile uint32_t windCount = 0, rainCount = 0;
volatile uint32_t lastWindUs = 0, lastRainMs = 0;

void IRAM_ATTR onWind() {
  uint32_t now = micros();
  if (now - lastWindUs > 5000) { windCount++; lastWindUs = now; }  // 5 ms Entprellung
}
void IRAM_ATTR onRain() {
  uint32_t now = millis();
  if (now - lastRainMs > 200) { rainCount++; lastRainMs = now; }   // 200 ms Entprellung
}

char clientId[32];
char statusTopic[96];
float rainTodayMm = 0;
float maxGust = 0;
uint32_t lastGustCount = 0, lastGustMs = 0, lastPublishMs = 0, windAtPublish = 0;

void connectMqtt() {
  while (!mqtt.connected()) {
    if (mqtt.connect(clientId, nullptr, nullptr, statusTopic, 1, true, "offline")) {
      mqtt.publish(statusTopic, "online", true);
    } else {
      Serial.printf("MQTT failed, state=%d\n", mqtt.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(PIN_WIND, INPUT_PULLUP);
  pinMode(PIN_RAIN, INPUT_PULLUP);
  attachInterrupt(digitalPinToInterrupt(PIN_WIND), onWind, FALLING);
  attachInterrupt(digitalPinToInterrupt(PIN_RAIN), onRain, FALLING);

  if (!bme.begin(0x76) && !bme.begin(0x77)) {
    Serial.println("BME280 not found, check wiring");
  }

  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASS);
  while (WiFi.status() != WL_CONNECTED) { delay(500); Serial.print("."); }

  snprintf(clientId, sizeof(clientId), "wx-%llx", ESP.getEfuseMac());
  snprintf(statusTopic, sizeof(statusTopic), "%s/status", PREFIX);
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  connectMqtt();
  lastPublishMs = lastGustMs = millis();
}

void loop() {
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  uint32_t now = millis();

  // Böe: höchster 3-Sekunden-Mittelwert innerhalb des Sendefensters
  if (now - lastGustMs >= 3000) {
    noInterrupts(); uint32_t c = windCount; interrupts();
    float gust = (c - lastGustCount) / ((now - lastGustMs) / 1000.0) * KMH_PER_HZ;
    if (gust > maxGust) maxGust = gust;
    lastGustCount = c; lastGustMs = now;
  }

  if (now - lastPublishMs >= 60000) {
    noInterrupts();
    uint32_t w = windCount; uint32_t r = rainCount; rainCount = 0;
    interrupts();

    float seconds = (now - lastPublishMs) / 1000.0;
    float windKmh = (w - windAtPublish) / seconds * KMH_PER_HZ;
    float rainMm = r * MM_PER_TIP;
    rainTodayMm += rainMm;   // um Mitternacht im Logger oder per NTP zurücksetzen

    char payload[256];
    snprintf(payload, sizeof(payload),
      "{\"temp_c\":%.1f,\"hum_pct\":%.1f,\"press_hpa\":%.1f,"
      "\"wind_kmh\":%.1f,\"gust_kmh\":%.1f,\"rain_mm\":%.2f,"
      "\"rain_today_mm\":%.2f,\"rssi\":%d,\"uptime_s\":%lu}",
      bme.readTemperature(), bme.readHumidity(), bme.readPressure() / 100.0,
      windKmh, maxGust, rainMm, rainTodayMm, WiFi.RSSI(), now / 1000);

    char topic[96];
    snprintf(topic, sizeof(topic), "%s/telemetry", PREFIX);
    mqtt.publish(topic, payload);
    Serial.println(payload);

    windAtPublish = w;
    maxGust = 0;
    lastPublishMs = now;
  }
}

Öffnen Sie den seriellen Monitor mit 115200 Baud. Drehen Sie das Anemometer von Hand und kippen Sie die Regenwaage ein paar Mal; die Nachricht der nächsten Minute sollte Werte ungleich null zeigen. Prüfen Sie in einem zweiten Terminal, ob die Daten beim Broker ankommen:

mosquitto_sub -h broker.connectmq.com -p 1883 -i wx-viewer-5521 -t "connectmq/demo/k3x9q2/weather/#" -v

Verbindet sich das Board nie, führt Sie das ESP32-MQTT-Tutorial Schritt für Schritt durch die Fehlersuche bei WLAN und Broker.

Messwerte protokollieren und visualisieren

Der Broker liefert Live-Nachrichten aus; er speichert keine Historie für Sie. Ein kurzer Python-Subscriber kann jeden Messwert an eine CSV-Datei anhängen und ein Diagramm erzeugen. Installieren Sie die Abhängigkeiten:

pip install "paho-mqtt>=2.0" matplotlib
import csv
import json
import os
import sys
from datetime import datetime, timezone
import paho.mqtt.client as mqtt

TOPIC = "connectmq/demo/k3x9q2/weather/station1/telemetry"
CSV_FILE = "weather.csv"
FIELDS = ["time", "temp_c", "hum_pct", "press_hpa", "wind_kmh", "gust_kmh", "rain_mm"]


def on_connect(client, userdata, flags, reason_code, properties):
    print("Connected:", reason_code)
    client.subscribe(TOPIC, qos=1)


def on_message(client, userdata, msg):
    data = json.loads(msg.payload)
    row = {"time": datetime.now(timezone.utc).isoformat(timespec="seconds")}
    row.update({k: data.get(k) for k in FIELDS[1:]})
    new_file = not os.path.exists(CSV_FILE)
    with open(CSV_FILE, "a", newline="") as f:
        writer = csv.DictWriter(f, fieldnames=FIELDS)
        if new_file:
            writer.writeheader()
        writer.writerow(row)
    print(row)


def plot():
    import matplotlib.pyplot as plt
    times, temp, wind, gust, rain = [], [], [], [], []
    with open(CSV_FILE) as f:
        for r in csv.DictReader(f):
            times.append(datetime.fromisoformat(r["time"]))
            temp.append(float(r["temp_c"]))
            wind.append(float(r["wind_kmh"]))
            gust.append(float(r["gust_kmh"]))
            rain.append(float(r["rain_mm"]))
    fig, (ax1, ax2, ax3) = plt.subplots(3, 1, sharex=True, figsize=(10, 8))
    ax1.plot(times, temp); ax1.set_ylabel("Temp (C)")
    ax2.plot(times, wind, label="wind"); ax2.plot(times, gust, label="gust")
    ax2.set_ylabel("km/h"); ax2.legend()
    ax3.bar(times, rain, width=0.0007); ax3.set_ylabel("Rain (mm/min)")
    fig.tight_layout()
    fig.savefig("weather.png", dpi=120)
    print("Saved weather.png")


if __name__ == "__main__":
    if len(sys.argv) > 1 and sys.argv[1] == "plot":
        plot()
    else:
        client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="wx-logger-8812")
        client.on_connect = on_connect
        client.on_message = on_message
        client.connect("broker.connectmq.com", 1883, keepalive=60)
        client.loop_forever()

Führen Sie python weather_logger.py aus, um Daten zu sammeln, und python weather_logger.py plot, um weather.png zu erzeugen. Für einen dauerhaften Aufbau ersetzen viele die CSV-Datei durch eine InfluxDB, die von Telegraf befüllt wird, oder durch ein Node-RED-Dashboard; beide verbinden sich als ganz normale MQTT-Clients mit dem Broker. Dasselbe Muster aus Sensorknoten, Broker und protokollierendem Subscriber trägt übrigens auch in der Industrie, etwa beim Klima- oder Energiemonitoring einer Werkhalle.

Checkliste für genaue Messwerte

Eine Wetterstation ist nur so gut wie ihr Standort. Arbeiten Sie diese Liste ab, bevor Sie den Zahlen vertrauen:

  1. Schirmen Sie den Temperatursensor ab. Ein BME280 in direkter Sonne oder in einer geschlossenen Box misst mehrere Grad zu viel. Verwenden Sie einen belüfteten Strahlungsschutz.
  2. Montieren Sie das Anemometer hoch und frei. Dächer und Zäune erzeugen Verwirbelungen; ein Mast oberhalb benachbarter Hindernisse liefert deutlich repräsentativere Windwerte.
  3. Richten Sie den Regenmesser waagerecht aus und halten Sie ihn von Wänden und Bäumen fern, die abschirmen oder tropfen.
  4. Halten Sie den ESP32 vom BME280 fern. Die Eigenwärme des Boards verfälscht die Messwerte, wenn beide im selben Gehäuse sitzen.
  5. Entprellen Sie in Software und prüfen Sie die Verkabelung. Lange ungeschirmte Kabel fangen Störungen ein, die wie Phantom-Kippvorgänge aussehen.
  6. Setzen Sie die Tagesregenmenge konsistent zurück, entweder um Mitternacht Ortszeit im Logger oder auf dem ESP32 mit NTP-Zeit.

Die MQTT-Wetterstation erweitern

Sobald die Grundstation stabil läuft, machen einige Ergänzungen sie spürbar nützlicher.

Windrichtung. Die Windfahne der meisten Kits schaltet beim Drehen zwischen internen Widerständen um. Verdrahten Sie sie als Spannungsteiler mit einem Festwiderstand (das Kit-Datenblatt nennt einen Wert) an einem ADC1-Pin wie GPIO 34, lesen Sie sie mit analogRead() aus und ordnen Sie jedes Spannungsband einer von 16 Himmelsrichtungen zu. Verwenden Sie ausschließlich ADC1-Pins, da ADC2 beim ESP32 nicht nutzbar ist, solange WLAN aktiv ist. Veröffentlichen Sie die Richtung in Grad, das ist für Diagramme einfacher als „NNO“.

Solar- und Akkubetrieb. Ein kleines Solarpanel, eine Lithiumzelle und ein Laderegler können die Station netzunabhängig betreiben, doch ein dauerhaft aktiver ESP32 mit WLAN verbraucht für ein kleines Panel im Winter zu viel. Wechseln Sie zwischen den Veröffentlichungen in den Deep Sleep und zählen Sie die Anemometer-Impulse mit dem ULP-Coprozessor oder einem eigenen Zählerchip, oder akzeptieren Sie ein längeres Sendeintervall. Nehmen Sie die Akkuspannung in das JSON auf, damit Sie Probleme frühzeitig erkennen.

Tageszusammenfassungen. Lassen Sie den Logger kurz nach Mitternacht eine retained Tageszusammenfassung (Minimal- und Maximaltemperatur, Gesamtniederschlag, stärkste Böe) auf einem separaten Topic veröffentlichen. Smart-Home-Tools können dann anhand der Regenmenge vom Vortag einen Bewässerungsdurchlauf auslassen, ohne die komplette Historie zu lesen.

Abgeleitete Werte. Taupunkt und gefühlte Temperatur lassen sich im Logger oder in Node-RED leicht aus Temperatur und Luftfeuchte berechnen. Berechnen Sie sie nachgelagert statt auf dem ESP32, damit Sie die Formel ändern können, ohne neu zu flashen.

Die Station vom öffentlichen MQTT-Broker umziehen

Der öffentliche MQTT-Broker ist ideal, um den Aufbau zu testen, doch alles darauf ist für jeden lesbar, und das Topic-Präfix ist Ihre einzige Abgrenzung. Für eine dauerhafte Station, insbesondere eine, die eine automatische Bewässerungsanlage speist, verwenden Sie einen privaten Broker mit TLS und Zugangsdaten. Ein verwalteter MQTT-Broker wie ConnectMQ bietet Ihnen das, ohne dass Sie zu Hause einen Server betreiben müssen. Stellen Sie den Client auf WiFiClientSecure und Port 8883 um und verwenden Sie Benutzername, Passwort und Client-ID aus dem Portal:

#include <WiFiClientSecure.h>
WiFiClientSecure net;          // ersetzt WiFiClient
// in setup(): das ISRG-Root-X1-Zertifikat für Let's Encrypt laden
// net.setCACert(ISRG_ROOT_X1_PEM);
mqtt.setServer("broker.connectmq.com", 8883);
// verbinden mit: mqtt.connect("YOUR_CLIENT_ID", "YOUR_USERNAME", "YOUR_PASSWORD", statusTopic, 1, true, "offline");

Der Live-Topic-Explorer im ConnectMQ-Portal zeigt dann jede Nachricht beim Eintreffen an, und Schwellenwert-Alarmregeln können Sie ohne zusätzlichen Code vor Frost oder starkem Wind warnen. Weitere Ideen für die Landwirtschaft finden Sie im Artikel zu Smart Agriculture IoT und im umfassenderen Leitfaden zur IoT-Automatisierung.

Ihre Wetterdaten über ConnectMQ veröffentlichen

ConnectMQ ist ein verwalteter MQTT-Broker mit TLS, WebSocket-Zugang für Browser-Dashboards und einem Client im Browser für schnelle Prüfungen. Erstellen Sie unter app.connectmq.com einen kostenlosen Serverless-Broker, ganz ohne Kreditkarte, und ziehen Sie Ihre Station in wenigen Minuten vom öffentlichen MQTT-Broker auf private Zugangsdaten um.

Häufig gestellte Fragen

Wie oft sollte eine selbstgebaute Wetterstation über MQTT senden?

Einmal pro Minute ist ein guter Standardwert für gemittelte Werte. So bleibt der Datenverkehr minimal, und Böen werden trotzdem erfasst, wenn Sie die maximale 3-Sekunden-Windgeschwindigkeit zusammen mit dem Mittelwert senden.

Warum sind meine Temperaturwerte zu hoch?

Fast immer wegen Sonneneinstrahlung oder der Eigenwärme des ESP32. Setzen Sie den BME280 in einen belüfteten Strahlungsschutz, entfernt vom Mikrocontroller und vom Spannungsregler.

Kann ich statt des BME280 einen BME680 oder SHT31 verwenden?

Ja. Tauschen Sie die Bibliothek und die Lesefunktionen aus; die MQTT-Seite bleibt gleich, solange Sie die JSON-Feldnamen beibehalten.

Speichert der Broker meine Wetterhistorie?

Nein. MQTT-Broker liefern Live-Nachrichten und den letzten retained Wert aus. Speichern Sie die Historie mit einem Subscriber wie dem obigen Python-Logger, mit Telegraf und einer Zeitreihendatenbank oder mit Node-RED.

Verwandte Artikel

Zigbee2MQTT einrichten: Vom Koordinator bis zum Dashboard

Installieren Sie Zigbee2MQTT mit Docker, konfigurieren Sie Koordinator und MQTT-Verbindung, lernen Sie Geräte an und benennen Sie sie um, steuern Sie sie per MQTT und zeigen Sie ihre Zustände auf einem Dashboard an, zuerst getestet auf einem kostenlosen öffentlichen MQTT-Broker.

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.