Ein ESP32-BME280-MQTT-Knoten misst Temperatur, Luftfeuchtigkeit und Luftdruck mit einem einzigen kleinen I2C-Chip und veröffentlicht die Werte an einen Broker. In diesem Tutorial schließen Sie den Sensor an, lesen ihn im stromsparenden Forced Mode aus, veröffentlichen JSON an broker.connectmq.com und ändern das Sendeintervall aus der Ferne mit einer MQTT-Nachricht, die Neustarts übersteht.
Warum der BME280
Der Bosch BME280 ist ein beliebter Aufstieg vom DHT22. Er kommuniziert über I2C, es gibt also kein fragiles Eindraht-Timing, er liest alle drei Werte in Millisekunden aus, und mit seinem Druckwert können Sie Wettertrends verfolgen oder die Höhe schätzen. Außerdem besitzt er einen stromsparenden Forced Mode, in dem er eine Messung durchführt und wieder schlafen geht, was die Eigenerwärmung verringert.
Eine Warnung vor dem Kauf: Viele günstige Breakouts mit der Aufschrift „BME280“ tragen in Wirklichkeit einen BMP280, der keinen Feuchtesensor hat. Beide sehen identisch aus. Der folgende Sketch liest die Chip-ID und sagt Ihnen, welchen Sie haben.
Bauteile und Bibliotheken
- Ein ESP32-Entwicklungsboard.
- Ein BME280-Breakout-Board (3,3-V-Version oder eines mit Spannungsregler an Bord).
- Vier Jumper-Kabel.
- Arduino IDE 2.x mit dem esp32-Board-Paket.
- Bibliotheken: Adafruit BME280 Library, Adafruit Unified Sensor, Adafruit BusIO (werden als Abhängigkeiten installiert) und PubSubClient.
Verdrahtung
| BME280-Pin | ESP32-Pin (klassisch) | Hinweise |
|---|---|---|
| VIN oder VCC | 3V3 | Bei reinen 3,3-V-Modulen keine 5 V verwenden |
| GND | GND | |
| SCL | GPIO 22 | Standard-I2C-Takt |
| SDA | GPIO 21 | Standard-I2C-Daten |
| SDO (falls vorhanden) | GND oder 3V3 | Wählt die Adresse 0x76 oder 0x77 |
| CSB (falls vorhanden) | 3V3 | Hält den Chip im I2C-Modus |
Auf ESP32-S3, C3 und C6 sind die Standard-I2C-Pins andere. Übergeben Sie wie im Sketch explizite Pins an Wire.begin(SDA, SCL) und passen Sie die Nummern an Ihr Board an.
Topic-Design mit fernsteuerbarem Intervall
| Topic | Richtung | Retained | Beispiel-Payload |
|---|---|---|---|
connectmq/demo/bme5t8n/telemetry |
Gerät veröffentlicht | Nein | {"temperature":21.84,"humidity":46.2,"pressure":1013.6} |
connectmq/demo/bme5t8n/config/interval/set |
Sie veröffentlichen | Ja | 30 |
connectmq/demo/bme5t8n/config/interval |
Gerät veröffentlicht | Ja | 30 |
connectmq/demo/bme5t8n/status |
Gerät veröffentlicht | Ja | online / offline |
Der Befehl ist retained, damit ein Gerät, das beim Senden offline war, ihn beim nächsten Verbinden trotzdem erhält. Das Gerät meldet den tatsächlich übernommenen Wert dann auf einem separaten Retained-Topic. Gewünschte und gemeldete Werte auf getrennten Topics zu halten, vermeidet Rückkopplungsschleifen und zeigt Ihnen auf einen Blick, ob eine Einstellung übernommen wurde. Der Leitfaden zu den MQTT-QoS-Stufen erklärt, warum für das Befehlsabonnement QoS 1 verwendet wird.
Der ESP32-BME280-MQTT-Sketch
#include <WiFi.h>
#include <Wire.h>
#include <PubSubClient.h>
#include <Preferences.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>
const char* WIFI_SSID = "YOUR_WIFI_SSID";
const char* WIFI_PASSWORD = "YOUR_WIFI_PASSWORD";
const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* PREFIX = "connectmq/demo/bme5t8n"; // eigene zufällige ID verwenden
const int I2C_SDA = 21, I2C_SCL = 22;
const uint32_t MIN_INTERVAL_S = 5, MAX_INTERVAL_S = 3600;
Adafruit_BME280 bme;
WiFiClient net;
PubSubClient mqtt(net);
Preferences prefs;
String clientId;
String tTelemetry, tIntervalSet, tIntervalState, tStatus;
uint32_t intervalS = 60;
unsigned long lastPublish = 0;
bool hasHumidity = true;
void reportInterval() {
char buf[12];
snprintf(buf, sizeof(buf), "%lu", (unsigned long)intervalS);
mqtt.publish(tIntervalState.c_str(), buf, true);
}
void onMessage(char* topic, byte* payload, unsigned int len) {
if (tIntervalSet != topic) return;
char buf[12];
len = min(len, (unsigned int)sizeof(buf) - 1);
memcpy(buf, payload, len);
buf[len] = '\0';
long requested = atol(buf);
if (requested < (long)MIN_INTERVAL_S || requested > (long)MAX_INTERVAL_S) {
Serial.printf("Ignoring interval %s (allowed %lu-%lu)\n", buf,
(unsigned long)MIN_INTERVAL_S, (unsigned long)MAX_INTERVAL_S);
reportInterval(); // aktuell verwendeten Wert erneut melden
return;
}
if ((uint32_t)requested != intervalS) {
intervalS = requested;
prefs.putUInt("interval", intervalS);
Serial.printf("Interval set to %lu s\n", (unsigned long)intervalS);
}
reportInterval();
}
void connectWifi() {
WiFi.mode(WIFI_STA);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) delay(400);
Serial.printf("Wi-Fi OK, IP %s\n", WiFi.localIP().toString().c_str());
}
void connectMqtt() {
while (!mqtt.connected()) {
if (mqtt.connect(clientId.c_str(), tStatus.c_str(), 1, true, "offline")) {
mqtt.publish(tStatus.c_str(), "online", true);
mqtt.subscribe(tIntervalSet.c_str(), 1);
reportInterval();
Serial.println("MQTT connected");
} else {
Serial.printf("MQTT failed, state %d\n", mqtt.state());
delay(5000);
}
}
}
bool startSensor() {
Wire.begin(I2C_SDA, I2C_SCL);
if (!bme.begin(0x76, &Wire) && !bme.begin(0x77, &Wire)) return false;
uint8_t id = bme.sensorID();
hasHumidity = (id == 0x60);
Serial.printf("Sensor chip ID 0x%02X (%s)\n", id, hasHumidity ? "BME280" : "BMP280, no humidity");
// Forced Mode: eine Messung auf Anforderung, dann Schlaf. Empfohlen für Wetterüberwachung.
bme.setSampling(Adafruit_BME280::MODE_FORCED,
Adafruit_BME280::SAMPLING_X1, // Temperatur
Adafruit_BME280::SAMPLING_X1, // Luftdruck
Adafruit_BME280::SAMPLING_X1, // Luftfeuchtigkeit
Adafruit_BME280::FILTER_OFF);
return true;
}
void setup() {
Serial.begin(115200);
prefs.begin("bme-node", false);
intervalS = prefs.getUInt("interval", 60);
if (!startSensor()) {
Serial.println("No BME280 found at 0x76 or 0x77. Check wiring.");
while (true) delay(1000);
}
clientId = "esp32-bme280-" + String((uint32_t)ESP.getEfuseMac(), HEX);
tTelemetry = String(PREFIX) + "/telemetry";
tIntervalSet = String(PREFIX) + "/config/interval/set";
tIntervalState = String(PREFIX) + "/config/interval";
tStatus = String(PREFIX) + "/status";
connectWifi();
mqtt.setServer(MQTT_HOST, MQTT_PORT);
mqtt.setCallback(onMessage);
mqtt.setBufferSize(512);
connectMqtt();
lastPublish = millis() - intervalS * 1000UL; // sofort veröffentlichen
}
void loop() {
if (WiFi.status() != WL_CONNECTED) connectWifi();
if (!mqtt.connected()) connectMqtt();
mqtt.loop();
if (millis() - lastPublish < intervalS * 1000UL) return;
lastPublish = millis();
if (!bme.takeForcedMeasurement()) {
Serial.println("Forced measurement failed");
return;
}
float t = bme.readTemperature(); // Grad C
float p = bme.readPressure() / 100.0f; // Pa -> hPa
float h = hasHumidity ? bme.readHumidity() : NAN;
char payload[200];
if (hasHumidity) {
snprintf(payload, sizeof(payload),
"{\"temperature\":%.2f,\"humidity\":%.1f,\"pressure\":%.1f,\"interval\":%lu}",
t, h, p, (unsigned long)intervalS);
} else {
snprintf(payload, sizeof(payload),
"{\"temperature\":%.2f,\"pressure\":%.1f,\"interval\":%lu}",
t, p, (unsigned long)intervalS);
}
mqtt.publish(tTelemetry.c_str(), payload);
Serial.println(payload);
}
ESP32-BME280-MQTT-Messwerte testen und das Intervall ändern
Beobachten Sie alles unterhalb Ihres Präfixes:
mosquitto_sub -h broker.connectmq.com -p 1883 -i bme-view-5t8n -t "connectmq/demo/bme5t8n/#" -v
Ändern Sie nun das Intervall mit einem Retained-Befehl auf 10 Sekunden:
mosquitto_pub -h broker.connectmq.com -p 1883 -i bme-cmd-5t8n -q 1 -r \
-t "connectmq/demo/bme5t8n/config/interval/set" -m "10"
Kurz darauf meldet config/interval den Wert 10, und die Telemetrie wird schneller. Setzen Sie das Board zurück: Es liest 10 aus dem Flash, und selbst wenn der Flash gelöscht wäre, würde der Retained-Befehl den Wert beim Verbinden erneut setzen. Senden Sie 2, lehnt das Gerät den Wert ab und meldet erneut den aktuellen. Um den Retained-Befehl später zu entfernen, veröffentlichen Sie mit -r -n eine leere Retained-Nachricht auf demselben Topic.
Der öffentliche MQTT-Broker ist geteilt, sodass jeder, der Ihr Befehls-Topic errät, darauf veröffentlichen könnte. Für eine Demo ist das akzeptabel, aber genau deshalb sollten Produktionsgeräte einen eigenen, authentifizierten Broker verwenden.
Die Messwerte verstehen
- Luftdruck wird auf Stationshöhe in hPa gemeldet. Wetterdienste geben den auf Meereshöhe reduzierten Luftdruck an, Ihr Wert ist in der Höhe also niedriger. Trends sind wichtiger als der absolute Wert: Ein stetiger Abfall über einige Stunden kündigt oft schlechteres Wetter an.
- Höhe lässt sich mit
bme.readAltitude(1013.25)schätzen, ist aber nur so gut wie der angegebene Luftdruck auf Meereshöhe und driftet mit dem Wetter. - Eigenerwärmung ist im Forced Mode bei niedrigen Abtastraten minimal. Im Normal Mode mit Oversampling und kurzer Standby-Zeit können der Chip und der benachbarte ESP32 den Messwert spürbar erwärmen.
Fehlerbehebung
- „No BME280 found“. Führen Sie einen I2C-Scanner-Sketch aus. Gar kein Gerät bedeutet ein Verdrahtungsproblem; ein Gerät an einer unerwarteten Adresse deutet auf die Einstellung des SDO-Pins hin.
- Luftfeuchtigkeit immer 0 oder NaN. Sie haben einen BMP280. Die Ausgabe der Chip-ID bestätigt das.
- Luftdruck um etwa 10 Prozent daneben. Sie haben vergessen, Pascal durch 100 zu teilen, oder vergleichen mit dem Luftdruck auf Meereshöhe.
- Intervall-Befehl wird ignoriert. Prüfen Sie, ob das Topic exakt übereinstimmt, einschließlich Groß- und Kleinschreibung, und ob die Payload eine reine Zahl ist.
- Board startet beim WLAN-Verbindungsaufbau neu. Eine schwache USB-Versorgung lässt die Spannung des ESP32 einbrechen. Verwenden Sie ein besseres Kabel oder Netzteil.
Einrichtung für die Produktion
Für echte Installationen erstellen Sie einen Broker unter app.connectmq.com und stellen den Sketch auf WiFiClientSecure an Port 8883 mit dem Zertifikat ISRG Root X1 um, wobei Sie sich mit YOUR_CLIENT_ID, YOUR_USERNAME und YOUR_PASSWORD verbinden. Dann können nur Clients mit Ihren Zugangsdaten Intervall-Befehle senden. Die nächste Schwachstelle sind WLAN-Zugangsdaten im Code; die Anleitung zur WiFiManager-Einrichtung zeigt, wie Sie sie stattdessen über ein Captive Portal eingeben. Wenn Sie Sensoren vergleichen, behandelt das ESP32-DHT22-Tutorial die günstigere Eindraht-Variante.
Wetterdaten speichern und darstellen
Der Broker stellt Nachrichten zu; er ist keine Datenbank. Für einen Wetterverlauf brauchen Sie einen Subscriber, der jeden Messwert dauerhaft speichert. Drei gängige Muster funktionieren gut mit dieser Payload:
- Telegraf mit dem MQTT-Consumer-Input abonniert
.../telemetry, parst die JSON-Felder und schreibt sie in InfluxDB, bereit für Grafana-Diagramme. - Node-RED abonniert mit einem
mqtt in-Knoten, formt die Nachricht um und schreibt in jede Datenbank, für die es einen Knoten gibt – praktisch, wenn Sie auch Benachrichtigungen versenden möchten. - Ein kleines Python-Skript mit paho-mqtt fügt Zeilen in SQLite oder PostgreSQL ein. Beim Lernen ist das die transparenteste Option.
Alle drei verbinden sich wie der ESP32 als gewöhnliche MQTT-Clients mit eigenen Client IDs mit dem Broker. Im ConnectMQ-Portal erscheinen sie neben Ihren Geräten, und der Live-Topic-Explorer ist ein schneller Weg, die JSON-Struktur zu prüfen, bevor Sie Parsing-Code schreiben. Da das Gerät in jeder Nachricht das aktive interval mitsendet, können Ihre Diagramme auch zeigen, wann sich die Abtastrate geändert hat – so wird eine Lücke nicht fälschlich als Ausfall gedeutet.
Starten Sie mit ConnectMQ
Ein Wetterknoten ist nur so nützlich wie der Broker, der seine Daten sammelt. ConnectMQ ist ein verwalteter MQTT-Broker mit TLS, Retained Messages, MQTT 3.1.1 und 5.0 sowie einem Portal mit Live-Topic-Explorer und Schwellwert-Alarmen, sodass Sie benachrichtigt werden, wenn der Luftdruck schnell fällt. Der kostenlose Serverless-Plan erfordert keine Kreditkarte. Wenn Ihnen die Konzepte neu sind, lesen Sie die Aufgaben eines MQTT Brokers und erkunden Sie dann die gesamte Reihe zum ESP32-IoT-Tutorial.
Häufig gestellte Fragen
Was ist der Unterschied zwischen BME280 und BMP280?
Der BME280 misst Temperatur, Luftfeuchtigkeit und Luftdruck; der BMP280 misst nur Temperatur und Luftdruck. Beide teilen Bauform und Bibliothek, und die Chip-ID (0x60 gegenüber 0x58) unterscheidet sie.
Warum das Sendeintervall per MQTT konfigurierbar machen?
So können Sie beim Debuggen oder während eines Ereignisses schneller und den Rest der Zeit langsamer messen, ohne neu zu flashen. Die Speicherung im Flash erhält die Einstellung über Neustarts hinweg.
Sollte der Intervall-Befehl retained sein?
Ja, bei Einstellungen schon. Ein Retained-Befehl erreicht das Gerät auch dann, wenn es beim Senden offline war, und das Gerät meldet den übernommenen Wert auf seinem eigenen Retained-Topic.
Kann ich den BME280-Knoten mit Batterien betreiben?
Ja. Nutzen Sie Deep Sleep zwischen den Messungen, halten Sie den Sensor im Forced Mode und verbinden Sie sich nur zum Veröffentlichen mit dem WLAN. Die WLAN-Zeit dominiert das Energiebudget.



