ESP32 et Arduino

Alertes MQTT avec un capteur de mouvement PIR sur ESP32 : anti-rebond et heure du dernier mouvement

Détectez les mouvements avec un PIR HC-SR501 ou AM312 sur un ESP32, filtrez les déclenchements parasites, publiez les événements de mouvement via MQTT et conservez un horodatage retained du dernier mouvement pour les tableaux de bord et les automatisations.

Module PIR HC-SR501 câblé à un ESP32 envoyant des alertes de mouvement vers le cloud dans un projet de capteur de mouvement PIR ESP32 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 capteur de mouvement PIR ESP32 MQTT transforme un détecteur infrarouge bon marché en source d'événements à laquelle n'importe quel tableau de bord ou automatisation peut réagir. Dans ce tutoriel, vous allez câbler un HC-SR501 ou un AM312, capter le mouvement par interruption, filtrer les déclenchements parasites, publier les événements vers broker.connectmq.com et conserver un horodatage retained du dernier mouvement qui indique aux nouveaux abonnés exactement quand quelque chose a bougé pour la dernière fois.

Le comportement des capteurs PIR

Un capteur infrarouge passif (PIR) détecte les variations de rayonnement infrarouge dans son champ de vision, typiquement un corps chaud en mouvement. Le module les traduit en une simple sortie numérique : niveau haut tant qu'un mouvement est détecté, niveau bas sinon. Les détails comptent pour MQTT, car la sortie n'est pas un signal propre à une impulsion par événement.

Module Alimentation Sortie Réglages Remarques
HC-SR501 5 V (broche VIN) 3,3 V à l'état haut Sensibilité, temps de maintien, cavalier de mode de déclenchement Grande lentille, portée de quelques mètres, nécessite un préchauffage
AM312 2,7 à 12 V 3,3 V à l'état haut Aucun Minuscule, temps de maintien fixe de quelques secondes, adapté aux montages en 3,3 V
HC-SR505 4,5 à 20 V 3,3 V à l'état haut Aucun Compact, temps de maintien fixe de plusieurs secondes

Ce qu'il faut savoir avant d'écrire le code :

  • Préchauffage. Après la mise sous tension, les modules PIR produisent des déclenchements aléatoires pendant jusqu'à une minute, le temps que le capteur se stabilise. Ignorez les mouvements pendant cette fenêtre.
  • Temps de maintien. La sortie reste à l'état haut pendant une durée configurée après le dernier mouvement détecté. Sur le HC-SR501, un potentiomètre la règle de quelques secondes à plusieurs minutes.
  • Mode de redéclenchement. Avec le cavalier du HC-SR501 en position « H » (répétition), un mouvement continu maintient la sortie à l'état haut. En mode « L », la sortie retombe après le temps de maintien même si le mouvement continue, puis se redéclenche. « H » est généralement ce que vous voulez.
  • Faux déclenchements. Le soleil, les radiateurs, les bouches d'aération et les antennes Wi-Fi placées juste à côté du capteur peuvent tous provoquer des impulsions parasites.

Câblage

Pour le HC-SR501 : VCC sur la broche 5V (VIN) de l'ESP32, GND sur GND, OUT sur GPIO 27. La sortie est en 3,3 V, elle se branche donc directement sur l'entrée de l'ESP32. Pour l'AM312 : VCC sur 3V3, GND sur GND, OUT sur GPIO 27. Éloignez un peu le capteur de l'antenne de l'ESP32 ; une trop grande proximité est une source connue de déclenchements fantômes.

Conception des événements : anti-rebond, délai de carence et topics

Publier chaque front de la sortie PIR inonde les abonnés et gaspille le débit par client du serveur MQTT public gratuit. Le sketch transforme le signal brut en deux états propres :

  1. Mouvement détecté lorsque la sortie passe à l'état haut et que l'événement précédent est plus ancien qu'un délai de carence.
  2. Mouvement terminé lorsque la sortie est restée à l'état bas pendant une période calme.
Topic Retained Payload
connectmq/demo/pir9d4k/motion Oui {"state":"detected","ts":"2026-10-16T18:03:11Z","count":14}
connectmq/demo/pir9d4k/motion/last Oui 2026-10-16T18:03:11Z
connectmq/demo/pir9d4k/status Oui online / offline

Le topic motion/last est l'idée clé : tout client qui s'abonne, même des heures plus tard, reçoit instantanément l'heure de la dernière détection. Les horodatages proviennent de NTP ; le guide d'horodatage NTP ESP32 MQTT explique les détails de synchronisation horaire utilisés ici.

Le sketch capteur de mouvement PIR ESP32 MQTT

#include <WiFi.h>
#include <PubSubClient.h>
#include <time.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/pir9d4k";   // utilisez votre propre identifiant aléatoire

const uint8_t  PIR_PIN      = 27;
const uint32_t WARMUP_MS    = 60000;   // ignorer les déclenchements après la mise sous tension
const uint32_t COOLDOWN_MS  = 10000;   // délai minimal entre deux événements « detected »
const uint32_t CLEAR_MS     = 30000;   // sortie basse pendant cette durée -> « clear »

WiFiClient   net;
PubSubClient mqtt(net);
String clientId, tMotion, tLast, tStatus;

volatile bool     edgeSeen = false;
volatile uint32_t lastEdgeMs = 0;
bool     motionActive = false;
uint32_t lastDetectedMs = 0;
uint32_t lastHighMs = 0;
uint32_t motionCount = 0;

void IRAM_ATTR onPirRise() {
  edgeSeen = true;
  lastEdgeMs = millis();
}

bool isoNow(char* buf, size_t len) {
  time_t now = time(nullptr);
  if (now < 1700000000) return false;              // pas encore synchronisé
  struct tm utc;
  gmtime_r(&now, &utc);
  strftime(buf, len, "%Y-%m-%dT%H:%M:%SZ", &utc);
  return true;
}

void publishState(const char* state) {
  char ts[24] = "unknown";
  bool synced = isoNow(ts, sizeof(ts));
  char payload[128];
  snprintf(payload, sizeof(payload), "{\"state\":\"%s\",\"ts\":\"%s\",\"count\":%lu}",
           state, ts, (unsigned long)motionCount);
  mqtt.publish(tMotion.c_str(), payload, true);
  if (strcmp(state, "detected") == 0 && synced) {
    mqtt.publish(tLast.c_str(), ts, true);
  }
  Serial.println(payload);
}

void connectWifi() {
  WiFi.mode(WIFI_STA);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  while (WiFi.status() != WL_CONNECTED) delay(300);
  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);
      Serial.println("MQTT connected");
    } else {
      Serial.printf("MQTT failed, state %d\n", mqtt.state());
      delay(5000);
    }
  }
}

void setup() {
  Serial.begin(115200);
  pinMode(PIR_PIN, INPUT_PULLDOWN);
  attachInterrupt(digitalPinToInterrupt(PIR_PIN), onPirRise, RISING);

  clientId = "esp32-pir-" + String((uint32_t)ESP.getEfuseMac(), HEX);
  tMotion  = String(PREFIX) + "/motion";
  tLast    = String(PREFIX) + "/motion/last";
  tStatus  = String(PREFIX) + "/status";

  connectWifi();
  configTime(0, 0, "pool.ntp.org", "time.cloudflare.com");
  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  connectMqtt();
  Serial.println("Warming up PIR sensor...");
}

void loop() {
  if (WiFi.status() != WL_CONNECTED) connectWifi();
  if (!mqtt.connected()) connectMqtt();
  mqtt.loop();

  uint32_t now = millis();
  bool pinHigh = digitalRead(PIR_PIN) == HIGH;
  if (pinHigh) lastHighMs = now;

  if (now < WARMUP_MS) {               // écarter le bruit du préchauffage
    edgeSeen = false;
    return;
  }

  // Front montant (capté par l'interruption) ou sortie toujours haute -> mouvement
  if (edgeSeen || pinHigh) {
    edgeSeen = false;
    if (!motionActive && now - lastDetectedMs >= COOLDOWN_MS) {
      motionActive = true;
      lastDetectedMs = now;
      motionCount++;
      publishState("detected");
    }
  }

  // Sortie basse assez longtemps -> terminé
  if (motionActive && !pinHigh && now - lastHighMs >= CLEAR_MS) {
    motionActive = false;
    publishState("clear");
  }
}

La routine d'interruption se contente de positionner un drapeau et une heure. Ne publiez jamais depuis une interruption : le code réseau n'est pas compatible avec les interruptions et l'ESP32 plantera. Le drapeau garantit qu'une impulsion même très brève d'un AM312 ne sera pas manquée pendant que mqtt.loop() est occupé.

Tester les événements du capteur de mouvement PIR ESP32 MQTT

Abonnez-vous à votre préfixe et agitez la main devant le capteur après la minute de préchauffage :

mosquitto_sub -h broker.connectmq.com -p 1883 -i pir-view-9d4k -t "connectmq/demo/pir9d4k/#" -v

Vous voyez un état detected, puis clear environ 30 secondes après le retour au calme. Arrêtez l'abonné, patientez, puis relancez-le : la valeur retained motion/last arrive immédiatement. La zone publique est partagée et lisible par tous : ne l'utilisez pas pour un vrai domicile ou un vrai bureau. Les données de mouvement révèlent quand un lieu est vide. Consultez la page du serveur MQTT public gratuit pour connaître son usage prévu.

Ajuster les temporisations

Trois constantes pilotent le comportement :

  • COOLDOWN_MS limite la fréquence à laquelle detected peut se déclencher. Dix secondes conviennent pour l'éclairage d'un couloir ; pour un journal de sécurité, vous voudrez peut-être chaque nouvelle présence après un court intervalle.
  • CLEAR_MS détermine quand la pièce est considérée comme vide. Ajoutez-y le temps de maintien propre au module : avec un temps de maintien HC-SR501 de 5 secondes et un CLEAR_MS de 30 secondes, clear arrive environ 35 secondes après le dernier mouvement.
  • WARMUP_MS doit couvrir le temps de stabilisation du module. Soixante secondes est une valeur sûre pour le HC-SR501.

Réglez le potentiomètre de temps de maintien du HC-SR501 au minimum et laissez le firmware gérer les temporisations. Une temporisation logicielle se modifie plus facilement à distance et reste cohérente d'un module à l'autre.

Réagir aux événements de mouvement

Le broker achemine les événements ; c'est un autre composant qui décide quoi en faire. Dans une maison connectée, les consommateurs typiques sont Home Assistant avec un capteur binaire MQTT, un flow Node-RED qui allume une lumière et envoie une notification, ou une caméra qui capture une image lorsqu'un mouvement est détecté ; le projet ESP32-CAM MQTT se marie naturellement avec ce capteur. Jeedom et Domoticz savent aussi communiquer en MQTT. Chaque consommateur s'abonne à motion comme un client MQTT ordinaire.

Le message will retained offline est tout aussi important pour les usages de sécurité. Un capteur de mouvement qui tombe en panne silencieusement ressemble exactement à une pièce calme. Le tutoriel ESP32 MQTT Last Will explique comment le broker le publie à votre place.

Dépannage

  1. Déclenchements permanents. Le capteur est trop près de l'antenne de l'ESP32 ou orienté vers une source de chaleur. Déplacez-le, baissez la sensibilité et vérifiez la logique de préchauffage.
  2. Aucun déclenchement. Vérifiez que le HC-SR501 est alimenté en 5 V et non en 3,3 V, et que OUT est relié à la broche configurée.
  3. Détecté mais jamais terminé. Mode de redéclenchement avec un mouvement continu, ou temps de maintien plus long que prévu. Réduisez le potentiomètre de maintien.
  4. L'ESP32 redémarre lors d'un mouvement. Vous effectuez du travail dans le gestionnaire d'interruption. Limitez-vous à positionner des drapeaux.
  5. L'horodatage affiche unknown. NTP n'est pas encore synchronisé ; autorisez le port UDP 123 sortant.

Configuration de production

Pour tout local réel, utilisez un broker authentifié et chiffré. Créez-en un sur app.connectmq.com, passez à WiFiClientSecure sur le port 8883 avec le certificat ISRG Root X1 et connectez-vous avec YOUR_CLIENT_ID, YOUR_USERNAME et YOUR_PASSWORD. Dans le portail ConnectMQ, vous pouvez suivre le topic motion dans l'explorateur de topics en direct pendant que vous réglez le capteur.

Si vous faites tourner plusieurs capteurs, donnez à chaque pièce son propre niveau de préfixe, comme site/floor1/hallway/motion, plutôt qu'un seul topic à plat. Les consommateurs peuvent alors s'abonner à une pièce ou utiliser un joker pour tout l'étage, et une règle d'alerte à seuil sur le compteur de mouvements d'une pièce sensible vous fournit un avertissement simple en dehors des heures d'ouverture, sans code backend supplémentaire.

Faites tourner vos alertes de mouvement sur ConnectMQ

Les alertes de mouvement ne sont utiles que si le chemin du capteur au téléphone est fiable et privé. ConnectMQ est un broker MQTT managé, un broker MQTT cloud avec TLS, retained messages, prise en charge du last will et une offre gratuite Serverless sans carte bancaire. Connectez ce nœud, branchez l'outil d'automatisation de votre choix et retrouvez d'autres projets d'appareils dans la collection tutoriel IoT ESP32.

Questions fréquentes

Pourquoi mon capteur PIR se déclenche-t-il juste après la mise sous tension ?

Les modules PIR ont besoin de jusqu'à une minute pour se stabiliser et produisent une sortie aléatoire pendant ce temps. Ignorez les déclenchements pendant une fenêtre de préchauffage, comme le fait le sketch.

Les événements de mouvement doivent-ils être des retained messages ?

Conservez en retained l'état courant et l'horodatage du dernier mouvement pour que les nouveaux abonnés obtiennent immédiatement le contexte. Ne conservez pas les notifications ponctuelles qui ne doivent se déclencher qu'une seule fois.

Puis-je utiliser le deep sleep avec un capteur PIR ?

Oui. Configurez la sortie PIR comme source de réveil ext0, connectez-vous au Wi-Fi après le réveil, publiez l'événement et rendormez-vous. Comptez quelques secondes de délai avant l'arrivée du message.

Une interruption est-elle vraiment nécessaire ?

Pour un HC-SR501 avec un long temps de maintien, une scrutation suffit. Les modules à impulsion courte comme l'AM312 peuvent être manqués pendant l'exécution du code réseau : le drapeau d'interruption rend donc la détection fiable.

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.