Tutoriels

MQTT sur Arduino avec un shield Ethernet (W5100/W5500) et PubSubClient

Connectez un Arduino Uno ou Mega avec shield Ethernet W5100/W5500 à un broker MQTT via PubSubClient : câblage, DHCP, reconnexion, limites de RAM et tests.

Mis à jour · 8 min de lecture

MQTT sur Arduino avec un shield Ethernet (W5100/W5500) et PubSubClient

Un Arduino Uno ou Mega équipé d'un shield Ethernet reste l'un des moyens les plus fiables de raccorder un capteur ou un relais à MQTT : pas de Wi-Fi qui décroche, pas d'interférences radio, et une liaison filaire qui fonctionne tout simplement dans une armoire ou une cave. La pile standard associe la bibliothèque Ethernet pour la puce WIZnet et PubSubClient pour MQTT.

Ce tutoriel connecte un shield W5100 ou W5500 à un broker, publie la valeur d'un capteur et accepte des commandes, avec une boucle de reconnexion qui résiste aux câbles débranchés et aux redémarrages du broker. Vous vérifiez ensuite le tout de bout en bout depuis le navigateur.

Matériel et bibliothèques

ÉlémentRemarques
Arduino Uno R3 ou Mega 2560Uno : 2 Ko de RAM, Mega : 8 Ko de RAM
Shield Ethernet W5100 ou Ethernet Shield 2 (W5500)Communique en SPI, chip select sur la broche 10
Câble réseau vers un routeur avec DHCPOu une IP statique sur votre LAN

Installez deux bibliothèques depuis le Library Manager : Ethernet (par Arduino, compatible W5100, W5200 et W5500) et PubSubClient (par Nick O'Leary). La plupart des shields comportent aussi un emplacement microSD dont le chip select est la broche 4 ; si vous n'utilisez pas de carte, mettez la broche 4 à l'état haut pour qu'elle ne perturbe pas le bus SPI.

Le sketch complet

ethernet_mqtt.ino
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>

// Une adresse MAC unique par carte sur votre réseau
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0x10, 0x01 };

const char* MQTT_HOST = "broker.connectmq.com";
const uint16_t MQTT_PORT = 1883;
const char* CLIENT_ID = "arduino-eth-0001";
const char* MQTT_USER = "username";
const char* MQTT_PASS = "password";

const char* T_STATUS = "testmqtt/arduino-eth/0001/status";
const char* T_TEMP   = "testmqtt/arduino-eth/0001/temp";
const char* T_CMD    = "testmqtt/arduino-eth/0001/cmd/led";

EthernetClient ethClient;
PubSubClient mqtt(ethClient);

unsigned long lastReconnect = 0;
unsigned long lastPublish = 0;

void onMessage(char* topic, byte* payload, unsigned int length) {
  Serial.print(F("<- "));
  Serial.print(topic);
  Serial.print(F(" = "));
  Serial.write(payload, length);
  Serial.println();

  if (strcmp(topic, T_CMD) == 0 && length > 0) {
    digitalWrite(LED_BUILTIN, payload[0] == '1' ? HIGH : LOW);
  }
}

bool connectMqtt() {
  Serial.print(F("MQTT connecting... "));
  // id, user, pass, willTopic, willQos, willRetain, willMessage
  if (mqtt.connect(CLIENT_ID, MQTT_USER, MQTT_PASS, T_STATUS, 1, true, "offline")) {
    Serial.println(F("connected"));
    mqtt.publish(T_STATUS, "online", true);
    mqtt.subscribe(T_CMD, 1);
    return true;
  }
  Serial.print(F("failed, rc="));
  Serial.println(mqtt.state());
  return false;
}

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(4, OUTPUT);
  digitalWrite(4, HIGH);           // désélectionne la carte SD
  Serial.begin(9600);

  Ethernet.init(10);               // chip select du W5100/W5500
  Serial.println(F("DHCP..."));
  if (Ethernet.begin(mac) == 0) {
    if (Ethernet.hardwareStatus() == EthernetNoHardware) {
      Serial.println(F("No Ethernet shield found"));
    } else {
      Serial.println(F("DHCP failed - check the cable or use a static IP"));
    }
    while (true) delay(1000);
  }
  Serial.print(F("IP: "));
  Serial.println(Ethernet.localIP());

  mqtt.setServer(MQTT_HOST, MQTT_PORT);
  mqtt.setCallback(onMessage);
  mqtt.setKeepAlive(30);
}

void loop() {
  if (!mqtt.connected()) {
    unsigned long now = millis();
    if (now - lastReconnect > 5000) {   // réessaie toutes les 5 s sans bloquer
      lastReconnect = now;
      connectMqtt();
    }
  } else {
    mqtt.loop();
  }

  if (mqtt.connected() && millis() - lastPublish > 10000) {
    lastPublish = millis();
    char buf[8];
    int raw = analogRead(A0);
    itoa(raw, buf, 10);
    mqtt.publish(T_TEMP, buf);
  }

  Ethernet.maintain();              // renouvelle le bail DHCP
}

Certains choix de ce sketch comptent plus qu'il n'y paraît :

  • Les chaînes F("...") gardent le texte constant en flash plutôt qu'en RAM. Sur un Uno, une douzaine de chaînes de débogage peuvent sinon consommer un quart de la mémoire.
  • La reconnexion n'est pas bloquante. loop() continue de tourner entre les tentatives, si bien que la logique locale (boutons, relais, watchdog) ne se fige pas quand le broker est injoignable.
  • mqtt.loop() doit tourner souvent. Il lit les paquets entrants et envoie le PINGREQ du keep alive. Un long delay() dans votre code pousse le broker à couper la connexion après 1,5 × keep alive.
  • Un Last Will et un online retained donnent à n'importe quel dashboard un indicateur fiable en ligne/hors ligne. Voir MQTT Last Will.

IP statique au lieu du DHCP

Les réseaux industriels n'ont souvent pas de DHCP. Indiquez explicitement l'adresse, le serveur DNS, la passerelle et le masque ; sans DNS, le nom d'hôte broker.connectmq.com ne peut pas être résolu :

Static configuration
IPAddress ip(192, 168, 1, 50);
IPAddress dns(192, 168, 1, 1);
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 255, 0);

Ethernet.begin(mac, ip, dns, gateway, subnet);

Si aucun DNS n'est disponible, mqtt.setServer() accepte aussi une IPAddress, mais un changement d'IP du broker casse alors tous les appareils. Préférez un nom d'hôte chaque fois que le réseau le permet.

Tester de bout en bout

  1. Ouvrez le Moniteur série à 9600 bauds. Vous devriez voir une adresse IP puis MQTT connecting... connected.
  2. Ouvrez le client MQTT en ligne, connectez-vous à broker.connectmq.com en WebSocket sécurisé (port 8084, chemin /mqtt) et abonnez-vous à testmqtt/arduino-eth/0001/#.
  3. Le status = online retained arrive immédiatement, suivi d'une valeur temp toutes les dix secondes.
  4. Publiez 1 sur testmqtt/arduino-eth/0001/cmd/led. La LED de la carte s'allume et le Moniteur série affiche le message. Publiez 0 pour l'éteindre.
  5. Débranchez le câble réseau. Après environ 45 secondes, le broker envoie le will et le statut passe à offline. Rebranchez-le : le sketch se reconnecte en quelques secondes et republie online.

Observer l'Arduino dans le client en ligne

Si vous préférez tester depuis un terminal, mosquitto_pub et mosquitto_sub font le même travail : mosquitto_sub -h broker.connectmq.com -t 'testmqtt/arduino-eth/#' -v.

Lire mqtt.state()

Quand connect() échoue, state() vous en donne la raison. Voici les codes définis par PubSubClient :

CodeConstanteSignification
-4MQTT_CONNECTION_TIMEOUTPas de réponse dans le délai du keep alive
-3MQTT_CONNECTION_LOSTLa connexion réseau a été coupée
-2MQTT_CONNECT_FAILEDÉchec de la connexion TCP (DNS, IP, port, pare-feu)
-1MQTT_DISCONNECTEDDéconnexion propre
1MQTT_CONNECT_BAD_PROTOCOLLe broker a refusé la version du protocole
2MQTT_CONNECT_BAD_CLIENT_IDClient ID refusé
3MQTT_CONNECT_UNAVAILABLEBroker indisponible
4MQTT_CONNECT_BAD_CREDENTIALSNom d'utilisateur ou mot de passe incorrect
5MQTT_CONNECT_UNAUTHORIZEDNon autorisé

failed, rc=-2 est de loin le plus fréquent : le shield n'a jamais atteint le broker en TCP. Vérifiez que Ethernet.localIP() ne vaut pas 0.0.0.0, que le serveur DNS est joignable et que le port 1883 sortant est autorisé. Le guide de dépannage PubSubClient détaille chaque code.

Limites de mémoire et de taille de paquet

PubSubClient alloue un seul buffer pour les paquets entrants et sortants. Sa taille par défaut est de 256 octets, en-tête fixe, topic et payload compris. Un paquet plus grand que le buffer est rejeté silencieusement à la réception, et publish() renvoie false à l'envoi.

Raising the buffer
mqtt.setBufferSize(512);   // à appeler une fois dans setup(), avant connect()

Sur un Uno, gardez un buffer modeste et des topics courts. Vous pouvez calculer précisément la taille d'un paquet avec le calculateur de taille de paquet. Sur un Mega, des buffers de 1 à 2 Ko sont réalistes. Si un gros payload échoue toujours, le guide sur les paquets trop grands couvre aussi les limites côté broker.

Autres limites à prévoir :

  • Publications QoS 0 uniquement. PubSubClient publie en QoS 0 et s'abonne en QoS 0 ou 1. Utilisez un topic d'état retained ou un accusé de réception applicatif quand la livraison compte.
  • MQTT 3.1.1 uniquement. Pas de reason codes ni de propriétés MQTT 5.
  • Nombre de sockets. Le W5100 dispose de 4 sockets, le W5500 de 8. DHCP et DNS en occupent un brièvement : un sketch qui sert aussi une page web peut en manquer.

Quand dépasser l'Uno

Le duo Uno + Ethernet est parfait pour des appareils locaux, simples et filaires. Si vous avez besoin de TLS, de payloads JSON plus volumineux ou de fonctionnalités MQTT 5, le même code PubSubClient se porte presque sans modification sur un ESP32 avec W5500 ou sur des cartes Wi-Fi : voir ESP32 avec Arduino et MQTT. Pour publier en QoS 1 sur AVR, la bibliothèque arduino-mqtt mérite un coup d'œil.

Un Arduino filaire sur MQTT est surtout une affaire de discipline : MAC et client ID uniques, chaînes F(), reconnexion non bloquante et boucle qui ne se fige jamais. Faites afficher connected au log série, prouvez la publication, les commandes et le Last Will depuis le client en ligne, et la carte tournera des années sans intervention.

Questions fréquentes

Un Arduino Uno peut-il utiliser MQTT sur TLS ?

Pas en pratique. L'ATmega328P dispose de 2 Ko de RAM, bien trop peu pour un handshake TLS, et les puces W5100/W5500 ne gèrent pas TLS elles-mêmes. Utilisez MQTT en clair sur un réseau de confiance, une passerelle locale qui relaie en TLS, ou passez à un ESP32 ou une carte plus généreuse en mémoire.

Pourquoi PubSubClient ne reçoit-il pas mes messages plus longs ?

PubSubClient rejette tout paquet plus grand que son buffer, de 256 octets par défaut en comptant le topic et les en-têtes. Appelez setBufferSize() avec une valeur plus grande, en gardant à l'esprit que sur un Uno chaque octet est pris sur les 2 Ko de RAM.

PubSubClient peut-il publier en QoS 1 ?

Non. PubSubClient publie uniquement en QoS 0 et prend en charge MQTT 3.1.1. Il peut s'abonner en QoS 0 ou 1. Si vous avez besoin de publications QoS 1, ajoutez un accusé de réception applicatif ou utilisez une autre bibliothèque.