ESP32 et Arduino

MicroPython umqtt sur ESP32 : publier et s'abonner avec umqtt.simple et umqtt.robust

Connectez un ESP32 sous MicroPython à un broker MQTT avec umqtt.simple, ajoutez la reconnexion automatique avec umqtt.robust, recevez des commandes sans bloquer et découvrez ce que ces clients minimalistes ne savent pas faire.

Carte ESP32 exécutant un script MicroPython umqtt ESP32 qui publie des mesures de capteur
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

MicroPython umqtt sur ESP32 vous offre MQTT en quelques lignes de Python, sans étape de compilation et avec une invite interactive pour déboguer. Ce tutoriel connecte un ESP32 au serveur MQTT public gratuit broker.connectmq.com avec umqtt.simple, ajoute la reconnexion automatique avec umqtt.robust, traite les commandes entrantes sans bloquer et explique les limites à connaître avant de s'appuyer sur ces clients en production.

Pourquoi MicroPython pour des appareils MQTT

Le C++ avec le core Arduino est plus rapide et dispose de davantage de bibliothèques, mais MicroPython l'emporte sur la vitesse d'itération. Vous modifiez un fichier, le copiez sur la carte et l'exécutez en quelques secondes. Le REPL vous permet d'explorer le Wi-Fi, les capteurs et MQTT de manière interactive, ce qui est précieux quand vous cherchez pourquoi quelque chose ne fonctionne pas.

Le paquet umqtt de micropython-lib fournit deux modules :

  • umqtt.simple : un client MQTT 3.1.1 minimaliste. Connexion, publication, abonnement, ping, dernière volonté (last will). Rien ne se fait automatiquement.
  • umqtt.robust : une fine sous-classe de simple qui intercepte les erreurs de socket dans publish(), wait_msg() et quelques autres appels, et se reconnecte automatiquement.

Les deux sont suffisamment légers pour tenir sans difficulté sur n'importe quel ESP32.

Préparer la carte

  1. Flashez un firmware MicroPython récent pour votre ESP32 depuis la page de téléchargement officielle, à l'aide de esptool.py.
  2. Installez mpremote sur votre ordinateur : pip install mpremote.
  3. Vérifiez si umqtt est déjà présent. Certaines versions du firmware l'incluent, d'autres non. Si import umqtt.simple échoue dans le REPL, installez-le avec mip :
mpremote connect auto mip install umqtt.simple
mpremote connect auto mip install umqtt.robust

mip nécessite que la carte soit en ligne. Si elle n'est pas encore connectée au Wi-Fi, lancez l'installation depuis un script qui se connecte d'abord, ou copiez les fichiers du module manuellement avec mpremote cp. La documentation MicroPython sur la gestion des paquets décrit toutes les options.

Se connecter au Wi-Fi

Placez ce code dans boot.py pour qu'il s'exécute à chaque démarrage :

import network
import time

WIFI_SSID = "YOUR_WIFI"
WIFI_PASS = "YOUR_WIFI_PASSWORD"

def connect_wifi(timeout=15):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    if not wlan.isconnected():
        wlan.connect(WIFI_SSID, WIFI_PASS)
        start = time.time()
        while not wlan.isconnected():
            if time.time() - start > timeout:
                raise RuntimeError("Wi-Fi connection timed out")
            time.sleep(0.25)
    print("Wi-Fi OK", wlan.ifconfig()[0])
    return wlan

connect_wifi()

Publier avec umqtt.simple

Le premier script se connecte à la zone publique de broker.connectmq.com et publie une mesure toutes les cinq secondes. Le client ID est construit à partir de l'identifiant unique de la puce : il n'est donc jamais vide et n'entre jamais en collision avec celui d'une autre carte.

import time
import ubinascii
import machine
import esp32
from umqtt.simple import MQTTClient

BROKER = "broker.connectmq.com"
PREFIX = b"connectmq/demo/2b7e95"
CLIENT_ID = b"mpy-" + ubinascii.hexlify(machine.unique_id())

client = MQTTClient(CLIENT_ID, BROKER, port=1883, keepalive=30)
client.set_last_will(PREFIX + b"/status", b"offline", retain=True, qos=1)
client.connect()
client.publish(PREFIX + b"/status", b"online", retain=True, qos=1)
print("Connected as", CLIENT_ID)

while True:
    temp_c = (esp32.raw_temperature() - 32) / 1.8   # lecture approximative du capteur interne
    payload = '{"temp":%.1f,"uptime":%d}' % (temp_c, time.ticks_ms() // 1000)
    client.publish(PREFIX + b"/telemetry", payload.encode())
    time.sleep(5)

Sur les variantes d'ESP32 sans raw_temperature(), remplacez cette ligne par la lecture d'un vrai capteur. Observez les messages depuis votre ordinateur :

mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-2b7e95 -t 'connectmq/demo/2b7e95/#' -v

Notez que les topics et les payloads sont des bytes. umqtt accepte des str à de nombreux endroits, mais utiliser systématiquement des bytes évite les surprises, en particulier dans les callbacks, qui reçoivent toujours des bytes.

Recevoir des commandes sans bloquer

Pour recevoir des messages, enregistrez un callback et abonnez-vous. Vous devez ensuite appeler régulièrement soit wait_msg(), qui bloque jusqu'à l'arrivée d'un message, soit check_msg(), qui rend la main immédiatement si rien n'est en attente. Utilisez check_msg() dans une boucle qui effectue aussi d'autres tâches :

import time
import ubinascii
import machine
from umqtt.simple import MQTTClient

BROKER = "broker.connectmq.com"
PREFIX = b"connectmq/demo/2b7e95"
CLIENT_ID = b"mpy-" + ubinascii.hexlify(machine.unique_id())
led = machine.Pin(2, machine.Pin.OUT)

def on_message(topic, msg):
    print("Received", topic, msg)
    if topic == PREFIX + b"/led/set":
        led.value(1 if msg == b"ON" else 0)
        client.publish(PREFIX + b"/led/state", msg, retain=True)

client = MQTTClient(CLIENT_ID, BROKER, port=1883, keepalive=30)
client.set_callback(on_message)
client.connect()
client.subscribe(PREFIX + b"/led/set", qos=1)

last_ping = time.ticks_ms()
last_publish = time.ticks_ms()

while True:
    client.check_msg()                       # traite un éventuel message en attente

    now = time.ticks_ms()
    if time.ticks_diff(now, last_publish) > 10000:
        client.publish(PREFIX + b"/telemetry", b'{"alive":true}')
        last_publish = now
        last_ping = now
    elif time.ticks_diff(now, last_ping) > 20000:
        client.ping()                        # maintient la connexion en période d'inactivité
        last_ping = now

    time.sleep_ms(50)

Testez-le :

mosquitto_pub -h broker.connectmq.com -p 1883 -i pc-2b7e95-pub -t connectmq/demo/2b7e95/led/set -m ON

Deux points passent facilement inaperçus. D'abord, umqtt.simple n'envoie pas de pings keepalive de lui-même. Si vous définissez un keepalive et publiez moins souvent que cet intervalle, vous devez appeler ping(), comme le fait la boucle ci-dessus, sinon le broker vous déconnectera. Ensuite, set_callback() doit être appelé avant subscribe().

Reconnexion automatique avec umqtt.robust

umqtt.simple lève une OSError lorsque la connexion tombe, et votre script s'arrête si vous ne la gérez pas. umqtt.robust encapsule les appels principaux et se reconnecte avec un court délai entre les tentatives. Seul l'import change :

from umqtt.robust import MQTTClient

Il y a un piège. Le client robust reconnecte le socket, mais il ne se réabonne pas à votre place, et avec une clean session le broker oublie vos abonnements. Une approche fiable consiste à encapsuler l'étape de connexion et à l'appeler à la fois au démarrage et après toute erreur que vous interceptez vous-même :

def connect_and_subscribe():
    client.connect(clean_session=True)
    client.subscribe(PREFIX + b"/led/set", qos=1)
    client.publish(PREFIX + b"/status", b"online", retain=True, qos=1)

connect_and_subscribe()

while True:
    try:
        client.check_msg()
        # ... publication périodique ...
    except OSError as e:
        print("MQTT error", e, "- reconnecting")
        time.sleep(5)
        try:
            connect_and_subscribe()
        except OSError:
            pass
    time.sleep_ms(50)

Pour des appareils réellement autonomes, ajoutez machine.WDT(timeout=30000) et alimentez-le dans la boucle, afin qu'un blocage complet redémarre la carte.

Structurer un projet MicroPython umqtt ESP32

Dès qu'un script dépasse quelques dizaines de lignes, découpez-le comme n'importe quel projet Python. Voici une organisation qui fonctionne bien sur la carte :

  • boot.py connecte le Wi-Fi et rien d'autre, afin qu'un bug dans l'application ne vous empêche jamais d'accéder au REPL.
  • config.py contient l'hôte du broker, le port, le préfixe de topic et les identifiants. Gardez-le hors du contrôle de version et copiez-le séparément sur chaque carte.
  • main.py importe config, crée le client et exécute la boucle principale dans un bloc try/except qui journalise l'exception et appelle machine.reset() après un court délai.

Deux habitudes font gagner beaucoup de temps de débogage. Encapsulez la boucle principale de façon qu'un Ctrl+C dans le REPL l'arrête proprement avec KeyboardInterrupt au lieu d'être intercepté par votre gestionnaire d'erreurs générique. Et appelez gc.collect() périodiquement si vous construisez des chaînes de payload dans la boucle : le tas de MicroPython est petit et la fragmentation se manifeste par une MemoryError au bout de plusieurs heures de fonctionnement, pas de quelques minutes.

Le plus simple pour publier du JSON est d'utiliser le module intégré json : client.publish(topic, json.dumps({"temp": 22.4})). L'analyse des commandes fonctionne de la même façon avec json.loads(msg) dans le callback, encadré par un try pour qu'un message malformé ne puisse pas faire planter l'appareil.

Les limites d'umqtt à connaître

Limite Impact Solution de contournement
MQTT 3.1.1 uniquement Pas de propriétés MQTT 5, de reason codes ni d'abonnements partagés Utilisez un autre client si vous avez besoin de MQTT 5
QoS 0 et 1 uniquement QoS 2 n'est pas pris en charge QoS 1 suffit pour la plupart des télémesures et commandes
Pas de pings keepalive automatiques Les connexions inactives sont coupées Appelez ping() périodiquement
Appels de socket bloquants connect() et wait_msg() bloquent le script Utilisez check_msg() ; envisagez un client basé sur asyncio pour les appareils complexes
Pas de réabonnement à la reconnexion Les commandes cessent d'arriver après une reconnexion Réabonnez-vous dans votre propre fonction de reconnexion
Pas de file d'attente hors ligne Les messages publiés pendant la déconnexion sont perdus Mettez vous-même les mesures en tampon si nécessaire

Pour les appareils qui doivent mener plusieurs tâches en parallèle, il existe des clients MQTT communautaires basés sur asyncio qui gèrent keepalive et reconnexion en arrière-plan. Pour des capteurs et actionneurs simples, umqtt reste le choix le plus léger.

Utiliser TLS sur le port 8883

Pour un broker privé, chiffrez la connexion. Les versions récentes de umqtt.simple acceptent un argument ssl qui prend un SSLContext ; les versions plus anciennes utilisent plutôt ssl=True avec un dictionnaire ssl_params, vérifiez donc la version que vous avez installée.

import ssl
from umqtt.simple import MQTTClient

ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="isrg-root-x1.der")   # CA racine Let's Encrypt

client = MQTTClient(
    b"YOUR_CLIENT_ID",
    "broker.connectmq.com",
    port=8883,
    user=b"YOUR_USERNAME",
    password=b"YOUR_PASSWORD",
    keepalive=30,
    ssl=ctx,
)
client.connect()

Copiez d'abord le certificat ISRG Root X1 sur la carte avec mpremote cp. TLS consomme une quantité non négligeable de RAM ; les cartes dotées de PSRAM le gèrent plus confortablement. Utilisez exactement le client ID, le nom d'utilisateur et le mot de passe générés pour votre broker, car ConnectMQ lie le client ID aux identifiants émis.

Guides associés

Si vous préférez travailler en C++, le guide de projet ESP32 PlatformIO MQTT présente une configuration Arduino bien structurée, et le guide ESP32 MQTT Last Will explique le modèle de statut utilisé dans le premier script. Le tutoriel IoT ESP32 couvre l'ensemble du parcours de l'appareil au tableau de bord, et si vous découvrez le protocole, commencez par ce que fait un broker MQTT.

Connectez vos appareils MicroPython à ConnectMQ

Tous les exemples présentés ici fonctionnent avec le serveur MQTT public gratuit, sans inscription ; il convient aux tests et aux prototypes, pas aux données de production. Lorsque vous dépassez le stade des expériences, ConnectMQ vous fournit un broker MQTT managé, c'est-à-dire un broker MQTT privé dans le cloud, avec TLS sur le port 8883 et un client MQTT dans le navigateur pour vérifier ce qu'envoie votre carte. Créez-en un avec l'offre gratuite Serverless sur app.connectmq.com, sans carte bancaire.

Questions fréquentes

umqtt prend-il en charge MQTT 5 ?

Non. umqtt.simple comme umqtt.robust implémentent MQTT 3.1.1. Les brokers compatibles MQTT 5, comme ConnectMQ, acceptent toujours les connexions 3.1.1.

Pourquoi mon client MQTT MicroPython se déconnecte-t-il au bout d'une minute ?

Vous avez défini un keepalive mais n'envoyez rien pendant cet intervalle. umqtt n'envoie pas de ping automatiquement : appelez donc client.ping() régulièrement ou publiez plus souvent que le keepalive.

Quelle est la différence entre wait_msg() et check_msg() ?

wait_msg() bloque jusqu'à l'arrivée d'un message. check_msg() traite un message en attente s'il y en a un et rend la main immédiatement, ce qui convient aux boucles qui lisent aussi des capteurs.

umqtt peut-il utiliser QoS 2 ?

Non. Il prend en charge QoS 0 et 1, ce qui couvre la plupart des cas d'usage des appareils.

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.