Tutoriels

MQTT sur Raspberry Pi : des capteurs au broker en 15 minutes

Publiez les mesures d'un BME280 ou DHT22 depuis un Raspberry Pi vers un broker MQTT avec Python et paho-mqtt 2.x, puis lancez-le en service systemd au boot.

Mis à jour · 8 min de lecture

MQTT sur Raspberry Pi : des capteurs au broker en 15 minutes

Faire passer les mesures d'un Raspberry Pi dans MQTT demande trois éléments : une bibliothèque pour le capteur, le client paho-mqtt et un gestionnaire de services qui maintient le script en vie. Ce tutoriel branche un BME280 (ou un DHT22) sur un Pi, publie température, humidité et pression en JSON toutes les 10 secondes et exécute le publisher comme service systemd qui se reconnecte tout seul et démarre au boot.

Tout ce qui suit a été écrit pour Raspberry Pi OS Bookworm (ou plus récent) avec Python 3.11+, paho-mqtt 2.x et les pilotes de capteurs CircuitPython d'Adafruit. Cela fonctionne sur Pi Zero 2 W, Pi 3, Pi 4 et Pi 5.

Ce qu'il vous faut

ÉlémentRemarques
Raspberry Pi connecté au réseauWi-Fi ou Ethernet, SSH activé
Module BME280 (I2C)Température, humidité, pression. Adresse 0x76 ou 0x77
ou DHT22 / AM2302Température et humidité sur une seule broche GPIO
4 fils de connexion3,3 V, GND, SDA, SCL (BME280)
Un broker MQTTN'importe quel broker joignable ; les exemples utilisent broker.connectmq.com

Reliez le BME280 à 3V3 (broche 1), GND (broche 6), SDA (broche 3, GPIO2) et SCL (broche 5, GPIO3). Un DHT22 se branche sur 3V3, GND et une broche de données comme GPIO4 (broche 7), avec une résistance de pull-up de 10 kΩ si votre module n'en intègre pas déjà une.

Inutile d'avoir un broker sur le Pi. Si vous en voulez un malgré tout, consultez faire tourner un broker sur Raspberry Pi ; cet article ne traite que du côté client.

1. Activer l'I2C et vérifier le capteur

Activer l'I2C et trouver le capteur
sudo raspi-config nonint do_i2c 0
sudo apt update && sudo apt install -y i2c-tools python3-venv
i2cdetect -y 1

i2cdetect affiche une grille. Vous devriez y voir 76 ou 77 : c'est l'adresse du BME280. Une grille vide signale un problème de câblage (SDA et SCL inversés, le grand classique) ou un I2C pas encore activé ; redémarrez une fois après raspi-config si besoin.

2. Créer un environnement virtuel

Bookworm refuse un pip install global avec error: externally-managed-environment. Utilisez plutôt un venv dans un dossier de projet :

Préparer le projet
mkdir -p ~/sensors && cd ~/sensors
python3 -m venv .venv
.venv/bin/pip install paho-mqtt adafruit-circuitpython-bme280
# pour un DHT22 à la place :
# .venv/bin/pip install paho-mqtt adafruit-circuitpython-dht

Les pilotes Adafruit installent Blinka, la couche de compatibilité qui fournit board et busio sous Linux.

Un test rapide avant d'écrire la moindre ligne MQTT :

Lire le capteur une fois
.venv/bin/python -c "import board; from adafruit_bme280 import basic as b; s=b.Adafruit_BME280_I2C(board.I2C(), address=0x76); print(s.temperature, s.relative_humidity, s.pressure)"

Si vous obtenez ValueError: No I2C device at address: 0x76, essayez address=0x77.

3. Le script publisher

Le script se connecte avec un Last Will pour que les abonnés sachent quand le Pi disparaît, publie un statut online retenu, puis envoie une mesure JSON toutes les 10 secondes. Les identifiants viennent de variables d'environnement : le même fichier fonctionne sur votre bureau et en production.

publish.py
import json, os, socket, time
import board
import paho.mqtt.client as mqtt
from adafruit_bme280 import basic as adafruit_bme280

HOST = os.getenv("MQTT_HOST", "broker.connectmq.com")
PORT = int(os.getenv("MQTT_PORT", "8883"))
DEVICE = socket.gethostname()
BASE = f"testmqtt/pi/{DEVICE}"
INTERVAL = 10

sensor = adafruit_bme280.Adafruit_BME280_I2C(board.I2C(), address=0x76)

def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"échec de connexion : {reason_code}", flush=True)
        return
    print("connecté", flush=True)
    client.publish(f"{BASE}/status", "online", qos=1, retain=True)

def on_disconnect(client, userdata, flags, reason_code, properties):
    print(f"déconnecté : {reason_code}", flush=True)

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"pi-{DEVICE}")
client.username_pw_set(os.getenv("MQTT_USER"), os.getenv("MQTT_PASS"))
if PORT == 8883:
    client.tls_set()                      # magasin de CA du système
client.will_set(f"{BASE}/status", "offline", qos=1, retain=True)
client.reconnect_delay_set(min_delay=1, max_delay=60)
client.on_connect = on_connect
client.on_disconnect = on_disconnect

client.connect_async(HOST, PORT, keepalive=60)
client.loop_start()                       # thread réseau, réessaie jusqu'à la connexion

while True:
    reading = {
        "temperature": round(sensor.temperature, 2),
        "humidity": round(sensor.relative_humidity, 1),
        "pressure": round(sensor.pressure, 1),
        "ts": int(time.time()),
    }
    info = client.publish(f"{BASE}/bme280", json.dumps(reading), qos=1)
    print(f"publish mid={info.mid} rc={info.rc} {reading}", flush=True)
    time.sleep(INTERVAL)

Quelques choix méritent une explication :

  • connect_async() + loop_start() : au démarrage, le réseau n'est pas forcément prêt. connect() lèverait une exception immédiatement ; connect_async() laisse le thread d'arrière-plan de paho réessayer, et ce même thread se reconnecte après toute coupure ultérieure avec le backoff défini par reconnect_delay_set().
  • QoS 1 pour les mesures, afin qu'une courte coupure ne perde pas de données en silence. paho met en file d'attente les messages QoS 1 pendant la déconnexion et les envoie dès que la connexion revient. QoS 0 serait plus léger, mais abandonne tout ce qui est envoyé pendant la coupure.
  • Statut retenu + Last Will : tout abonné arrivant plus tard voit immédiatement online ou offline. Le principe est expliqué dans MQTT Last Will.
  • Client ID dérivé du hostname : stable et unique par Pi. Deux appareils partageant un ID s'éjectent mutuellement du broker en boucle.

Utiliser un DHT22 à la place

Le protocole du DHT22 est sensible au timing sous Linux, donc les lectures échouent régulièrement avec RuntimeError (par exemple "Checksum did not validate" ou "A full buffer was not returned"). C'est normal : ignorez l'échantillon et réessayez :

lecture dht22 (remplace la partie capteur)
import adafruit_dht
dht = adafruit_dht.DHT22(board.D4)

def read_dht():
    for _ in range(5):
        try:
            return {"temperature": dht.temperature, "humidity": dht.humidity}
        except RuntimeError:
            time.sleep(2)              # le DHT22 a besoin d'environ 2 s entre deux lectures
    return None                        # rien à publier à ce tour

Appelez read_dht() dans la boucle principale et ne publiez que lorsqu'elle renvoie une valeur.

4. Tester à la main

Placez les identifiants dans un fichier lisible uniquement par votre utilisateur, puis lancez le script au premier plan :

~/sensors/mqtt.env
MQTT_HOST=broker.connectmq.com
MQTT_PORT=8883
MQTT_USER=username
MQTT_PASS=password
Lancer une fois
chmod 600 ~/sensors/mqtt.env
set -a; . ~/sensors/mqtt.env; set +a
.venv/bin/python publish.py

Vous devriez voir connecté, puis une ligne publish mid=... toutes les 10 secondes. Observez maintenant depuis une autre machine :

S'abonner depuis votre portable
mosquitto_sub -h broker.connectmq.com -p 8883 --capath /etc/ssl/certs \
  -u "$MQTT_USER" -P "$MQTT_PASS" -t 'testmqtt/pi/#' -v

Ou évitez l'installation : ouvrez le client MQTT en ligne dans votre navigateur, connectez-vous au même broker et abonnez-vous à testmqtt/pi/#. Vous verrez d'abord le message status retenu, puis un nouveau payload JSON bme280 à chaque intervalle.

Voir les mesures de votre Pi dans le navigateur

Si rien n'arrive, regardez le reason code affiché. Bad user name or password et Not authorized sont des refus du broker ; consultez-les dans l'outil des reason codes MQTT. Un timeout signifie en général un port bloqué : le guide des erreurs de connexion passe chaque cas en revue.

5. L'exécuter comme service systemd

Un script lancé dans une session SSH meurt à la déconnexion. systemd le démarre au boot, le relance s'il plante et collecte sa sortie dans le journal.

/etc/systemd/system/bme280-mqtt.service
[Unit]
Description=BME280 to MQTT publisher
Wants=network-online.target
After=network-online.target

[Service]
User=pi
WorkingDirectory=/home/pi/sensors
EnvironmentFile=/home/pi/sensors/mqtt.env
ExecStart=/home/pi/sensors/.venv/bin/python -u /home/pi/sensors/publish.py
Restart=always
RestartSec=5

[Install]
WantedBy=multi-user.target

Remplacez pi par votre vrai nom d'utilisateur : les images récentes de Raspberry Pi OS vous en font choisir un à l'installation. L'utilisateur doit appartenir au groupe i2c (ou gpio pour un DHT22) ; l'utilisateur par défaut en fait déjà partie.

Activer et suivre les logs
sudo systemctl daemon-reload
sudo systemctl enable --now bme280-mqtt
journalctl -u bme280-mqtt -f

L'option -u sur la ligne de commande Python désactive la mise en tampon de la sortie, pour que les lignes apparaissent immédiatement dans le journal (le flush=True du script a le même effet).

Prouver qu'il survit aux pannes

  1. sudo reboot : les mesures doivent reprendre en moins d'une minute, sans aucune session SSH ouverte.
  2. Débranchez le réseau pendant une minute. Les abonnés voient offline dès que le broker constate l'absence de keep-alive (jusqu'à 1,5 × 60 secondes), puis de nouveau online après la reconnexion, suivi des mesures mises en file pendant la coupure.
  3. sudo systemctl kill bme280-mqtt : systemd relance le script au bout de 5 secondes.

Pour aller plus loin

  • Structurez le payload pour vos dashboards et bases de données : payloads JSON en Python.
  • Ajoutez des capteurs et un écran : la station météo MQTT part de ce script.
  • Alimentez Home Assistant en publiant des messages de discovery, comme expliqué dans Home Assistant MQTT.

Un Pi, un capteur et quarante lignes de Python suffisent pour une télémétrie fiable, à condition de traiter les parties ennuyeuses : un venv pour les dépendances, un client ID stable, un Last Will pour la présence et systemd pour les redémarrages. Testez chaque panne une fois sur votre bureau, et le Pi du jardin se comportera de la même façon.

Questions fréquentes

Faut-il faire tourner un broker MQTT sur le Raspberry Pi lui-même ?

Non. Le Pi a seulement besoin d'une bibliothèque cliente comme paho-mqtt. Il peut publier vers n'importe quel broker joignable : un broker installé sur le même Pi, une autre machine du réseau local ou un broker managé dans le cloud.

Pourquoi pip échoue avec "externally-managed-environment" sur Raspberry Pi OS ?

Raspberry Pi OS Bookworm et les versions suivantes protègent le Python système (PEP 668). Créez un environnement virtuel avec python3 -m venv, installez-y paho-mqtt et les bibliothèques du capteur, puis faites pointer votre service systemd vers le python du venv.

BME280 ou DHT22 : quel capteur utiliser avec MQTT ?

Le BME280 communique en I2C, renvoie une mesure fiable à chaque fois et ajoute la pression atmosphérique. Le DHT22 est bon marché, mais son protocole à un fil est sensible au timing : des lectures échouent régulièrement et votre code doit réessayer. Pour une télémétrie continue, le BME280 pose moins de problèmes.