esp-mqtt est le client MQTT livré avec le framework ESP-IDF d'Espressif. Il tourne dans sa propre tâche FreeRTOS, se reconnecte automatiquement, prend en charge MQTT 3.1.1 et MQTT 5, et gère TCP, TLS, WebSocket et WebSocket sécurisé avec une seule URI. Vous le configurez avec une unique structure esp_mqtt_client_config_t, enregistrez un gestionnaire d'événements et appelez esp_mqtt_client_start().
Ce tutoriel construit pas à pas une application ESP32 complète : d'abord du TCP en clair, puis les événements, la publication, les abonnements, un Last Will et enfin TLS sur le port 8883. À chaque étape, vous vérifiez le résultat depuis un navigateur : quand quelque chose casse, vous savez que le problème vient de l'appareil et non de votre test.
Préparer le projet
Il vous faut ESP-IDF 5.x installé (idf.py --version doit fonctionner dans votre terminal). Partez de l'assistant Wi-Fi utilisé par les exemples officiels pour que le tutoriel se concentre sur MQTT :
idf.py create-project mqtt_demo
cd mqtt_demo
idf.py set-target esp32Rendez le composant Wi-Fi d'exemple visible dans le CMakeLists.txt racine (avant la ligne include(...project.cmake)) et déclarez les dépendances de main. Le composant mqtt fait partie d'ESP-IDF : #include "mqtt_client.h" fonctionne donc sans téléchargement supplémentaire sur les versions 5.x.
set(EXTRA_COMPONENT_DIRS $ENV{IDF_PATH}/examples/common_components/protocol_examples_common)idf_component_register(SRCS "main.c"
INCLUDE_DIRS "."
PRIV_REQUIRES mqtt esp_wifi esp_netif nvs_flash protocol_examples_common)Lancez ensuite idf.py menuconfig, ouvrez Example Connection Configuration et saisissez le SSID et le mot de passe de votre Wi-Fi.
La configuration du client
La configuration minimale est une URI de broker. Le schéma choisit le transport :
| URI | Transport | Port |
|---|---|---|
mqtt://broker.connectmq.com:1883 | TCP | 1883 |
mqtts://broker.connectmq.com:8883 | TLS | 8883 |
ws://broker.connectmq.com:8083/mqtt | WebSocket | 8083 |
wss://broker.connectmq.com:8084/mqtt | WebSocket sécurisé | 8084 |
Une configuration réaliste définit aussi un client ID, des identifiants, le keep alive et un Last Will :
#include "esp_log.h"
#include "mqtt_client.h"
static const char *TAG = "mqtt_demo";
static esp_mqtt_client_handle_t client;
#define BASE_TOPIC "testmqtt/espidf/dev01"
static esp_mqtt_client_handle_t mqtt_create(void)
{
const esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtt://broker.connectmq.com:1883",
.credentials.client_id = "espidf-dev01",
.credentials.username = "username",
.credentials.authentication.password = "password",
.session.keepalive = 30,
.session.last_will = {
.topic = BASE_TOPIC "/status",
.msg = "offline",
.qos = 1,
.retain = 1,
},
.network.reconnect_timeout_ms = 5000,
};
return esp_mqtt_client_init(&cfg);
}Chaque client a besoin d'un client ID unique : deux appareils avec le même ID s'éjectent mutuellement du broker en boucle. En production, dérivez-le de l'adresse MAC, ou générez-en un avec le générateur de client ID pendant vos tests. Gardez les identifiants hors du contrôle de version en les déplaçant dans des options Kconfig.projbuild (CONFIG_MQTT_USER, CONFIG_MQTT_PASS) renseignées dans menuconfig ou sdkconfig.defaults.
Gérer les événements
esp-mqtt signale tout via l'event loop d'ESP. Un seul gestionnaire reçoit tous les événements ; event_data pointe vers un esp_mqtt_event_t :
static void mqtt_event_handler(void *args, esp_event_base_t base,
int32_t event_id, void *event_data)
{
esp_mqtt_event_handle_t event = event_data;
switch ((esp_mqtt_event_id_t)event_id) {
case MQTT_EVENT_CONNECTED:
ESP_LOGI(TAG, "connected");
esp_mqtt_client_publish(client, BASE_TOPIC "/status", "online", 0, 1, 1);
esp_mqtt_client_subscribe(client, BASE_TOPIC "/cmd/#", 1);
break;
case MQTT_EVENT_DISCONNECTED:
ESP_LOGW(TAG, "disconnected, will retry");
break;
case MQTT_EVENT_SUBSCRIBED:
ESP_LOGI(TAG, "subscribed, msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_PUBLISHED:
ESP_LOGI(TAG, "broker acked msg_id=%d", event->msg_id);
break;
case MQTT_EVENT_DATA:
// topic et data ne sont PAS terminés par un caractère nul
ESP_LOGI(TAG, "%.*s = %.*s", event->topic_len, event->topic,
event->data_len, event->data);
break;
case MQTT_EVENT_ERROR:
if (event->error_handle->error_type == MQTT_ERROR_TYPE_TCP_TRANSPORT) {
ESP_LOGE(TAG, "transport error: esp-tls 0x%x, tls stack 0x%x, errno %d",
event->error_handle->esp_tls_last_esp_err,
event->error_handle->esp_tls_stack_err,
event->error_handle->esp_transport_sock_errno);
} else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED) {
ESP_LOGE(TAG, "refused, CONNACK code %d",
event->error_handle->connect_return_code);
}
break;
default:
break;
}
}Trois détails piègent presque tout le monde :
- Abonnez-vous dans
MQTT_EVENT_CONNECTED, pas aprèsstart(). Le client peut se reconnecter de nombreuses fois ; s'abonner à chaque connexion restaure vos abonnements même si le broker n'a pas conservé la session. topicetdatane sont pas des chaînes C. Utilisez toujours les champs_len. Les gros messages arrivent en plusieurs événementsMQTT_EVENT_DATA;event->current_data_offsetetevent->total_data_lenpermettent de les réassembler, sinon augmentez le buffer avec.buffer.size.- Le gestionnaire s'exécute dans la tâche MQTT. Ne le bloquez pas. Confiez les traitements longs à une autre tâche via une file FreeRTOS.
Démarrez le tout depuis app_main :
#include "nvs_flash.h"
#include "esp_netif.h"
#include "esp_event.h"
#include "protocol_examples_common.h"
void app_main(void)
{
ESP_ERROR_CHECK(nvs_flash_init());
ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_event_loop_create_default());
ESP_ERROR_CHECK(example_connect()); // bloque jusqu'à ce que le Wi-Fi ait une IP
client = mqtt_create();
esp_mqtt_client_register_event(client, ESP_EVENT_ANY_ID, mqtt_event_handler, NULL);
esp_mqtt_client_start(client);
}Flashez et suivez le log avec idf.py flash monitor. Vous devriez voir connected suivi de subscribed.
Publier de la télémétrie
esp_mqtt_client_publish(client, topic, data, len, qos, retain) renvoie un ID de message (0 en QoS 0, -1 en cas d'erreur). Passez len = 0 pour que la bibliothèque appelle strlen() sur un payload texte. En QoS 1 et 2, l'appel écrit depuis la tâche appelante et peut attendre le réseau ; esp_mqtt_client_enqueue() prend les mêmes arguments plus un drapeau store et laisse l'envoi à la tâche MQTT :
static void telemetry_task(void *arg)
{
char payload[64];
for (int i = 0;; i++) {
int len = snprintf(payload, sizeof payload,
"{\"seq\":%d,\"heap\":%lu}", i,
(unsigned long)esp_get_free_heap_size());
int id = esp_mqtt_client_enqueue(client, BASE_TOPIC "/telemetry",
payload, len, 1, 0, true);
if (id < 0) ESP_LOGW(TAG, "outbox full or not connected");
vTaskDelay(pdMS_TO_TICKS(5000));
}
}
// dans app_main, après esp_mqtt_client_start() :
// xTaskCreate(telemetry_task, "telemetry", 3072, NULL, 5, NULL);Les messages mis en file pendant que le client est hors ligne restent dans l'outbox (limités par CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS et le heap disponible) et sont envoyés après la reconnexion. Choisissez le QoS en connaissance de cause : voyez les niveaux de QoS MQTT pour le coût de chaque niveau.
Vérifier depuis le navigateur
Ouvrez le client MQTT en ligne, connectez-vous à broker.connectmq.com (WebSocket 8084, chemin /mqtt) et abonnez-vous à testmqtt/espidf/dev01/#. Vous devriez voir :
testmqtt/espidf/dev01/status=online, marqué comme retained.- Un message de télémétrie toutes les cinq secondes.
Publiez maintenant reboot sur testmqtt/espidf/dev01/cmd/system depuis le navigateur : le log de l'ESP32 affiche le topic et le payload. Enfin, débranchez l'alimentation. Après environ 1,5 × keep alive (45 secondes ici), le broker publie le Last Will et le statut passe à offline. Ce seul test prouve que la connexion, le publish, le subscribe et le will fonctionnent. Le fonctionnement du Last Will détaille les délais.
Observer votre ESP32 dans le client en ligne
Ajouter TLS sur le port 8883
Passer à TLS demande deux changements : une URI mqtts:// et un moyen de vérifier le certificat du serveur. Le plus simple est l'ESP x509 certificate bundle, qui contient les CA racines publiques courantes et est activé par défaut (CONFIG_MBEDTLS_CERTIFICATE_BUNDLE) :
#include "esp_crt_bundle.h"
const esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtts://broker.connectmq.com:8883",
.broker.verification.crt_bundle_attach = esp_crt_bundle_attach,
.credentials.client_id = "espidf-dev01",
.credentials.username = "username",
.credentials.authentication.password = "password",
};Si votre broker utilise une CA privée ou si vous voulez épingler une racine précise, intégrez plutôt le fichier PEM :
idf_component_register(SRCS "main.c" INCLUDE_DIRS "."
PRIV_REQUIRES mqtt esp_wifi esp_netif nvs_flash protocol_examples_common
EMBED_TXTFILES "ca.pem")extern const uint8_t ca_pem_start[] asm("_binary_ca_pem_start");
const esp_mqtt_client_config_t cfg = {
.broker.address.uri = "mqtts://broker.connectmq.com:8883",
.broker.verification.certificate = (const char *)ca_pem_start,
};TLS a besoin de l'heure exacte pour contrôler la validité du certificat et d'environ 30 à 40 Ko de heap supplémentaire pendant le handshake ; sur un ESP32 chargé, c'est le premier point à examiner quand les connexions échouent. Notre guide TLS pour ESP32 couvre les certificats client (.credentials.authentication.certificate et .key) pour le TLS mutuel.
Erreurs esp-mqtt courantes
| Ligne de log | Cause | Correction |
|---|---|---|
esp-tls: couldn't get hostname for :broker.connectmq.com: | Échec DNS, généralement Wi-Fi pas encore connecté | Ne démarrer MQTT qu'une fois l'IP obtenue |
mbedtls_ssl_handshake returned -0x2700 | Échec de vérification du certificat | Utiliser le bundle ou la bonne CA ; vérifier l'heure système |
mbedtls_ssl_setup returned -0x7F00 | Mémoire insuffisante pour TLS | Libérer du heap, réduire d'autres buffers |
Error transport connect | Port bloqué ou mauvaise paire schéma/port | Vérifier mqtts:// avec 8883, mqtt:// avec 1883 |
MQTT_ERROR_TYPE_CONNECTION_REFUSED, code 5 | Non autorisé | Vérifier utilisateur, mot de passe et ACL |
Les codes CONNACK numériques et les reason codes MQTT 5 sont répertoriés dans l'outil de recherche de reason codes. Pour les appareils qui se connectent puis décrochent toutes les quelques minutes, lisez déconnexions Wi-Fi et MQTT sur ESP32.
Activer MQTT 5
Activez CONFIG_MQTT_PROTOCOL_5 dans menuconfig (Component config → ESP-MQTT Configurations) et définissez .session.protocol_ver = MQTT_PROTOCOL_V_5. Les appels de base pour publish et subscribe ne changent pas ; les apports de MQTT 5 comme les user properties, le message expiry et les response topics se règlent avec des fonctions telles que esp_mqtt5_client_set_publish_property() avant le publish. Les connexions refusées renvoient alors des reason codes précis au lieu des six codes de retour de MQTT 3.1.1, ce qui facilite beaucoup le débogage de l'authentification.
esp-mqtt vous offre un client de niveau production avec très peu de code : une structure de configuration, un gestionnaire d'événements et un transport choisi par l'URI. Faites-le fonctionner en TCP clair, prouvez le publish, le subscribe et le will depuis le client en ligne, passez ensuite à mqtts:// et conservez les mêmes tests comme contrôle de non-régression avant chaque version du firmware.
Questions fréquentes
Faut-il une bibliothèque supplémentaire pour MQTT dans ESP-IDF ?
Pas pour la plupart des projets. Le client esp-mqtt est fourni avec ESP-IDF (en-tête mqtt_client.h). Certaines versions récentes d'ESP-IDF le distribuent via le component registry sous le nom espressif/mqtt : consultez la documentation de votre version si l'en-tête est introuvable.
Pourquoi esp_mqtt_client_publish bloque-t-il ?
En QoS 1 et 2, il écrit sur le socket depuis la tâche appelante et peut attendre le réseau. Utilisez esp_mqtt_client_enqueue pour placer le message dans l'outbox et laisser la tâche MQTT l'envoyer, ce qui est plus sûr dans du code soumis à des contraintes de temps.
esp-mqtt prend-il en charge MQTT 5 ?
Oui. Activez MQTT 5 dans menuconfig (Component config → ESP-MQTT Configurations) et réglez session.protocol_ver sur MQTT_PROTOCOL_V_5. Vous obtenez alors les reason codes, les user properties et les autres apports de MQTT 5 via des API dédiées.

