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:
- 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.
- Montieren Sie das Anemometer hoch und frei. Dächer und Zäune erzeugen Verwirbelungen; ein Mast oberhalb benachbarter Hindernisse liefert deutlich repräsentativere Windwerte.
- Richten Sie den Regenmesser waagerecht aus und halten Sie ihn von Wänden und Bäumen fern, die abschirmen oder tropfen.
- Halten Sie den ESP32 vom BME280 fern. Die Eigenwärme des Boards verfälscht die Messwerte, wenn beide im selben Gehäuse sitzen.
- Entprellen Sie in Software und prüfen Sie die Verkabelung. Lange ungeschirmte Kabel fangen Störungen ein, die wie Phantom-Kippvorgänge aussehen.
- 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.



