ESP32 et Arduino

Station météo ESP32 BME280 MQTT avec un intervalle configurable à distance

Lisez température, humidité et pression depuis un BME280 en I2C, publiez du JSON vers broker.connectmq.com et modifiez l'intervalle de publication à l'exécution grâce à une commande MQTT retained stockée en flash.

Module BME280 relié en I2C à un ESP32 publiant des données météo dans une station météo esp32 bme280 mqtt
Essayez-le sur votre propre broker MQTTObtenez un broker MQTT privé et managé avec l’offre Free et partez d’exemples de code prêts à l’emploi.
Essayer gratuitement Exemples de code Aucune carte bancaire requise

Un nœud ESP32 BME280 MQTT mesure la température, l'humidité et la pression atmosphérique à partir d'une seule petite puce I2C et les publie vers un broker. Dans ce tutoriel, vous allez câbler le capteur, le lire en mode forcé basse consommation, publier du JSON vers broker.connectmq.com, le serveur MQTT public gratuit, et modifier l'intervalle de publication à distance avec un message MQTT qui survit aux redémarrages.

Pourquoi le BME280

Le Bosch BME280 est une montée en gamme populaire par rapport au DHT22. Il communique en I2C, sans timing fragile sur un seul fil, il lit les trois valeurs en quelques millisecondes et sa sortie de pression permet de suivre les tendances météo ou d'estimer l'altitude. Il dispose aussi d'un mode forcé économe en énergie, dans lequel il effectue une mesure puis se rendort, ce qui réduit l'auto-échauffement.

Une mise en garde avant d'acheter : de nombreux modules bon marché étiquetés « BME280 » portent en réalité un BMP280, dépourvu de capteur d'humidité. Les deux se ressemblent trait pour trait. Le sketch ci-dessous lit l'identifiant de la puce et vous indique laquelle vous avez.

Composants et bibliothèques

  • Une carte de développement ESP32.
  • Un module BME280 (version 3,3 V, ou doté d'un régulateur intégré).
  • Quatre fils de connexion.
  • Arduino IDE 2.x avec le paquet de cartes esp32.
  • Bibliothèques : Adafruit BME280 Library, Adafruit Unified Sensor, Adafruit BusIO (installées comme dépendances) et PubSubClient.

Câblage

Broche BME280 Broche ESP32 (classique) Remarques
VIN ou VCC 3V3 N'utilisez pas 5 V sur les modules nus en 3,3 V
GND GND
SCL GPIO 22 Horloge I2C par défaut
SDA GPIO 21 Données I2C par défaut
SDO (si présente) GND ou 3V3 Sélectionne l'adresse 0x76 ou 0x77
CSB (si présente) 3V3 Maintient la puce en mode I2C

Sur l'ESP32-S3, le C3 et le C6, les broches I2C par défaut diffèrent. Passez des broches explicites à Wire.begin(SDA, SCL) comme le fait le sketch, et adaptez les numéros à votre carte.

Conception des topics avec un intervalle distant

Topic Sens Retained Exemple de payload
connectmq/demo/bme5t8n/telemetry L'appareil publie Non {"temperature":21.84,"humidity":46.2,"pressure":1013.6}
connectmq/demo/bme5t8n/config/interval/set Vous publiez Oui 30
connectmq/demo/bme5t8n/config/interval L'appareil publie Oui 30
connectmq/demo/bme5t8n/status L'appareil publie Oui online / offline

La commande est retained : un appareil hors ligne au moment de l'envoi la reçoit quand même à sa prochaine connexion. L'appareil indique ensuite la valeur réellement appliquée sur un topic retained distinct. Séparer les valeurs « souhaitées » et « rapportées » sur des topics différents évite les boucles de rétroaction et permet de voir d'un coup d'œil si un réglage a été accepté. Le guide des niveaux de QoS MQTT explique pourquoi QoS 1 est utilisé pour l'abonnement aux commandes.

Le sketch ESP32 BME280 MQTT

#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";   // utilisez votre propre identifiant aléatoire
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();               // réaffirmer la valeur en vigueur
    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");

  // Mode forcé : une mesure à la demande, puis veille. Recommandé pour le suivi météo.
  bme.setSampling(Adafruit_BME280::MODE_FORCED,
                  Adafruit_BME280::SAMPLING_X1,   // température
                  Adafruit_BME280::SAMPLING_X1,   // pression
                  Adafruit_BME280::SAMPLING_X1,   // humidité
                  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;   // publier immédiatement
}

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();           // degrés 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);
}

Tester les mesures ESP32 BME280 MQTT et modifier l'intervalle

Observez tout ce qui se trouve sous votre préfixe :

mosquitto_sub -h broker.connectmq.com -p 1883 -i bme-view-5t8n -t "connectmq/demo/bme5t8n/#" -v

Passez maintenant l'intervalle à 10 secondes avec une commande retained :

mosquitto_pub -h broker.connectmq.com -p 1883 -i bme-cmd-5t8n -q 1 -r \
  -t "connectmq/demo/bme5t8n/config/interval/set" -m "10"

En un instant, config/interval indique 10 et la télémétrie s'accélère. Réinitialisez la carte : elle relit 10 depuis la flash, et même si la flash était effacée, la commande retained la redéfinirait à la connexion. Envoyez 2 : l'appareil la rejette et republie la valeur en vigueur. Pour supprimer plus tard la commande retained, publiez un message retained vide sur le même topic avec -r -n.

La zone publique du broker est partagée : n'importe qui pourrait publier sur votre topic de commande s'il le devinait. C'est acceptable pour une démo ou un prototype, et c'est précisément pourquoi les appareils de production doivent utiliser leur propre broker authentifié.

Comprendre les mesures

  • La pression est indiquée au niveau de la station, en hPa. Les services météo publient une pression ramenée au niveau de la mer : votre valeur sera donc plus basse en altitude. Les tendances comptent davantage que la valeur absolue : une baisse régulière sur quelques heures précède souvent une dégradation du temps.
  • L'altitude peut être estimée avec bme.readAltitude(1013.25), mais elle ne vaut que ce que vaut la pression au niveau de la mer que vous fournissez, et elle varie avec la météo.
  • L'auto-échauffement est minime en mode forcé à faible fréquence d'échantillonnage. En mode normal avec suréchantillonnage et un temps de veille court, la puce et l'ESP32 voisin peuvent réchauffer sensiblement la mesure.

Dépannage

  1. « No BME280 found ». Lancez un sketch de scan I2C. Aucun périphérique détecté signifie un problème de câblage ; un périphérique à une adresse inattendue concerne le réglage de la broche SDO.
  2. Humidité toujours à 0 ou NaN. Vous avez un BMP280. L'affichage de l'identifiant de puce le confirme.
  3. Pression fausse d'environ 10 %. Vous avez oublié de diviser les pascals par 100, ou vous comparez avec la pression au niveau de la mer.
  4. Commande d'intervalle ignorée. Vérifiez que le topic correspond exactement, casse comprise, et que le payload est un simple nombre.
  5. La carte redémarre pendant la connexion Wi-Fi. Une alimentation USB trop faible provoque un brown-out de l'ESP32. Utilisez un meilleur câble ou une meilleure alimentation.

Configuration de production

Pour de vrais déploiements, créez un broker sur app.connectmq.com et passez le sketch à WiFiClientSecure sur le port 8883 avec le certificat ISRG Root X1, en vous connectant avec YOUR_CLIENT_ID, YOUR_USERNAME et YOUR_PASSWORD. Seuls les clients disposant de vos identifiants pourront alors envoyer des commandes d'intervalle. Les identifiants Wi-Fi stockés dans le code constituent le point faible suivant ; le guide de configuration WiFiManager montre comment les saisir plutôt via un portail captif. Si vous comparez des capteurs, le tutoriel ESP32 DHT22 présente l'option à un fil, moins chère.

Stocker et représenter les données météo

Le broker achemine les messages ; ce n'est pas une base de données. Pour un historique météo, il vous faut un abonné qui écrit chaque mesure dans un stockage durable. Trois approches courantes fonctionnent bien avec ce payload :

  1. Telegraf avec l'input MQTT consumer s'abonne à .../telemetry, analyse les champs JSON et les écrit dans InfluxDB, prêts pour des graphiques Grafana.
  2. Node-RED s'abonne avec un nœud mqtt in, remanie le message et l'écrit dans n'importe quelle base de données pour laquelle il dispose d'un nœud, ce qui est pratique si vous voulez aussi envoyer des notifications.
  3. Un petit script Python utilisant paho-mqtt insère des lignes dans SQLite ou PostgreSQL. C'est l'option la plus transparente pour apprendre.

Dans une maison connectée, la plateforme domotique peut aussi consommer ce payload directement : Home Assistant via un capteur MQTT, et Jeedom et Domoticz savent aussi communiquer en MQTT.

Tous trois se connectent au broker comme des clients MQTT ordinaires avec leur propre client ID, exactement comme l'ESP32. Dans le portail ConnectMQ, ils apparaissent aux côtés de vos appareils, et l'explorateur de topics en direct permet de vérifier rapidement la forme du JSON avant d'écrire le moindre code d'analyse. Comme l'appareil inclut l'interval actif dans chaque message, vos graphiques peuvent aussi montrer quand la fréquence d'échantillonnage a changé, ce qui évite de prendre un trou pour une panne.

Démarrer avec ConnectMQ

Un nœud météo n'est utile que si le broker qui collecte ses données l'est aussi. ConnectMQ est un broker MQTT managé avec TLS, retained messages, MQTT 3.1.1 et 5.0, et un portail doté d'un explorateur de topics en direct et d'alertes à seuil, pour être prévenu quand la pression chute rapidement. L'offre gratuite Serverless ne demande pas de carte bancaire. Si vous découvrez ces notions, lisez ce que fait un broker MQTT, puis explorez toute la série tutoriel IoT ESP32.

Questions fréquentes

Quelle est la différence entre le BME280 et le BMP280 ?

Le BME280 mesure température, humidité et pression ; le BMP280 ne mesure que la température et la pression. Ils partagent la même empreinte et la même bibliothèque, et l'identifiant de puce (0x60 contre 0x58) permet de les distinguer.

Pourquoi rendre l'intervalle de publication configurable via MQTT ?

Cela permet d'échantillonner plus vite pendant le débogage ou lors d'un événement, et plus lentement le reste du temps, sans reflasher. Le stocker en flash conserve le réglage après redémarrage.

La commande d'intervalle doit-elle être retained ?

Oui, pour les réglages. Une commande retained atteint l'appareil même s'il était hors ligne au moment de l'envoi, et l'appareil publie la valeur appliquée sur son propre topic retained.

Puis-je faire fonctionner le nœud BME280 sur batterie ?

Oui. Utilisez le deep sleep entre les mesures, gardez le capteur en mode forcé et ne vous connectez au Wi-Fi que pour publier. Le temps passé en Wi-Fi domine le budget énergétique.

Articles liés

Exemple MQTT ESP-IDF : construire un client esp-mqtt à partir de zéro

Construisez un projet ESP-IDF à partir d'un dossier vide avec le composant esp-mqtt : configuration du Wi-Fi en mode station, gestionnaire d'événements MQTT, publication et abonnement, propriétés MQTT 5 et TLS avec le bundle de certificats.

9 min de lecture

Horodatage NTP ESP32 MQTT : ajouter une heure précise à chaque message

Synchronisez l'horloge de l'ESP32 avec NTP, formatez des horodatages ISO 8601 et epoch en millisecondes, et joignez-les aux payloads MQTT pour que les mesures conservent leur véritable heure de capture même lorsqu'elles sont publiées en retard.

8 min de lecture

Tous les articles →

Votre broker MQTT privé, à quelques clics

Créez un compte, récupérez les identifiants de votre broker et connectez votre premier appareil — aucun serveur à installer, mettre à jour ou dimensionner.