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 :
- 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.
- 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
detectedpeut 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,
cleararrive 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
- 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.
- 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.
- 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.
- L'ESP32 redémarre lors d'un mouvement. Vous effectuez du travail dans le gestionnaire d'interruption. Limitez-vous à positionner des drapeaux.
- 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.



