Ein Arduino Uno oder Mega mit Ethernet Shield ist nach wie vor einer der zuverlässigsten Wege, einen Sensor oder ein Relais an MQTT zu bringen: kein WLAN, das abbricht, keine Funkstörungen, und eine Kabelverbindung, die im Schaltschrank oder im Keller einfach funktioniert. Der Standard-Stack ist die Ethernet-Bibliothek für den WIZnet-Chip plus PubSubClient für MQTT.
Dieses Tutorial verbindet ein W5100- oder W5500-Shield mit einem Broker, veröffentlicht einen Sensorwert und nimmt Befehle an – mit einer Reconnect-Schleife, die gezogene Kabel und Broker-Neustarts übersteht. Anschließend prüfst du alles Ende-zu-Ende im Browser.
Hardware und Bibliotheken
| Teil | Hinweise |
|---|---|
| Arduino Uno R3 oder Mega 2560 | Uno: 2 KB RAM, Mega: 8 KB RAM |
| Ethernet Shield W5100 oder Ethernet Shield 2 (W5500) | Spricht SPI, Chip Select auf Pin 10 |
| Netzwerkkabel zu einem Router mit DHCP | Oder eine statische IP im LAN |
Installiere zwei Bibliotheken über den Library Manager: Ethernet (von Arduino, unterstützt W5100, W5200 und W5500) und PubSubClient (von Nick O'Leary). Die meisten Shields haben außerdem einen microSD-Slot mit Chip Select auf Pin 4; wenn du keine Karte nutzt, setze Pin 4 auf HIGH, damit er den SPI-Bus nicht stört.
Der komplette Sketch
#include <SPI.h>
#include <Ethernet.h>
#include <PubSubClient.h>
// Pro Board eine eindeutige MAC im Netz verwenden
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); // SD-Karte abwählen
Serial.begin(9600);
Ethernet.init(10); // Chip Select des 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) { // alle 5 s erneut versuchen, ohne zu blockieren
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(); // DHCP-Lease erneuern
}Einige Entscheidungen in diesem Sketch sind wichtiger, als sie aussehen:
F("...")-Strings halten konstanten Text im Flash statt im RAM. Auf dem Uno fressen ein Dutzend Debug-Strings sonst ein Viertel des Speichers.- Der Reconnect blockiert nicht.
loop()läuft zwischen den Versuchen weiter, sodass lokale Logik (Taster, Relais, ein Watchdog) nicht einfriert, während der Broker unerreichbar ist. mqtt.loop()muss oft laufen. Es liest eingehende Pakete und sendet den Keep-Alive-PINGREQ. Ein langesdelay()in deinem Code führt dazu, dass der Broker die Verbindung nach 1,5 × Keep Alive trennt.- Ein Last Will und ein retained
onlinegeben jedem Dashboard einen verlässlichen Online/Offline-Status. Siehe MQTT Last Will.
Statische IP statt DHCP
Industrienetze haben oft kein DHCP. Übergib Adresse, DNS-Server, Gateway und Subnetzmaske explizit; ohne DNS-Eintrag lässt sich der Hostname broker.connectmq.com nicht auflösen:
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);Ist kein DNS verfügbar, akzeptiert mqtt.setServer() auch eine IPAddress – dann bricht aber jede Änderung der Broker-IP alle Geräte. Nutze einen Hostnamen, wann immer das Netz es zulässt.
Ende-zu-Ende testen
- Öffne den Seriellen Monitor mit 9600 Baud. Du solltest eine IP-Adresse sehen und danach
MQTT connecting... connected. - Öffne den Online-MQTT-Client, verbinde dich per Secure WebSocket mit
broker.connectmq.com(Port 8084, Pfad/mqtt) und abonnieretestmqtt/arduino-eth/0001/#. - Das retained
status = onlinekommt sofort an, danach alle zehn Sekunden eintemp-Wert. - Veröffentliche
1auftestmqtt/arduino-eth/0001/cmd/led. Die Board-LED geht an und der Serielle Monitor gibt die Nachricht aus. Mit0schaltest du sie aus. - Zieh das Netzwerkkabel. Nach etwa 45 Sekunden sendet der Broker das Will und der Status wechselt auf
offline. Steck es wieder ein: Der Sketch verbindet sich in wenigen Sekunden neu und veröffentlicht erneutonline.
Den Arduino im Online-Client beobachten
Wenn du lieber im Terminal testest, erledigen mosquitto_pub und mosquitto_sub denselben Job: mosquitto_sub -h broker.connectmq.com -t 'testmqtt/arduino-eth/#' -v.
mqtt.state() richtig lesen
Schlägt connect() fehl, verrät state() den Grund. Diese Codes definiert PubSubClient:
| Code | Konstante | Bedeutung |
|---|---|---|
| -4 | MQTT_CONNECTION_TIMEOUT | Keine Antwort innerhalb des Keep Alive |
| -3 | MQTT_CONNECTION_LOST | Netzwerkverbindung abgebrochen |
| -2 | MQTT_CONNECT_FAILED | TCP-Verbindung fehlgeschlagen (DNS, IP, Port, Firewall) |
| -1 | MQTT_DISCONNECTED | Sauber getrennt |
| 1 | MQTT_CONNECT_BAD_PROTOCOL | Broker hat die Protokollversion abgelehnt |
| 2 | MQTT_CONNECT_BAD_CLIENT_ID | Client-ID abgelehnt |
| 3 | MQTT_CONNECT_UNAVAILABLE | Broker nicht verfügbar |
| 4 | MQTT_CONNECT_BAD_CREDENTIALS | Falscher Benutzername oder falsches Passwort |
| 5 | MQTT_CONNECT_UNAUTHORIZED | Nicht autorisiert |
failed, rc=-2 ist mit Abstand der häufigste Fall: Das Shield hat den Broker per TCP nie erreicht. Prüfe, dass Ethernet.localIP() nicht 0.0.0.0 ist, dass der DNS-Server erreichbar ist und ausgehender Port 1883 erlaubt ist. Der PubSubClient-Fehlerleitfaden geht jeden Code im Detail durch.
Speicher- und Paketgrößengrenzen
PubSubClient reserviert einen einzigen Puffer für eingehende und ausgehende Pakete. Standardmäßig ist er 256 Byte groß, inklusive festem Header, Topic und Payload. Ein Paket, das größer als der Puffer ist, wird beim Empfang stillschweigend verworfen, und publish() gibt beim Senden false zurück.
mqtt.setBufferSize(512); // einmal in setup() aufrufen, vor connect()Halte auf dem Uno den Puffer moderat und die Topics kurz. Wie groß ein Paket genau wird, berechnest du mit dem Paketgrößen-Rechner. Auf einem Mega sind Puffer von 1–2 KB realistisch. Scheitert ein großer Payload trotzdem, behandelt der Leitfaden zu zu großen Paketen auch die Grenzen auf Broker-Seite.
Weitere Grenzen, die du einplanen solltest:
- Nur QoS-0-Publishes. PubSubClient veröffentlicht mit QoS 0 und abonniert mit QoS 0 oder 1. Nutze ein retained State-Topic oder eine Bestätigung auf Anwendungsebene, wenn die Zustellung zählt.
- Nur MQTT 3.1.1. Keine Reason Codes und keine MQTT-5-Properties.
- Anzahl der Sockets. Der W5100 hat 4 Sockets, der W5500 hat 8. DHCP und DNS belegen kurzzeitig einen, ein Sketch mit zusätzlichem Webserver kann also knapp werden.
Wann es mehr als ein Uno sein sollte
Die Kombination aus Uno und Ethernet ist ideal für einfache, kabelgebundene, lokale Geräte. Brauchst du TLS, größere JSON-Payloads oder MQTT-5-Features, lässt sich derselbe PubSubClient-Code fast unverändert auf einen ESP32 mit W5500 oder auf WLAN-Boards übertragen: siehe ESP32 mit Arduino und MQTT. Willst du auf AVR mit QoS 1 veröffentlichen, lohnt ein Blick auf die Bibliothek arduino-mqtt.
Ein kabelgebundener Arduino an MQTT ist vor allem eine Frage der Disziplin: eindeutige MACs und Client-IDs, F()-Strings, ein nicht blockierender Reconnect und eine Schleife, die nie hängt. Bring den seriellen Log dazu, connected zu melden, beweise Publish, Befehle und Last Will im Online-Client – dann läuft das Board jahrelang ohne Eingriff.
Häufig gestellte Fragen
Kann ein Arduino Uno MQTT über TLS nutzen?
Praktisch nicht. Der ATmega328P hat 2 KB RAM, viel zu wenig für einen TLS-Handshake, und die Chips W5100/W5500 beherrschen selbst kein TLS. Nutze unverschlüsseltes MQTT in einem vertrauenswürdigen Netz, ein lokales Gateway, das per TLS weiterleitet, oder wechsle auf einen ESP32 oder ein Board mit mehr Speicher.
Warum empfängt PubSubClient meine längeren Nachrichten nicht?
PubSubClient verwirft jedes Paket, das größer als sein Puffer ist – standardmäßig 256 Byte inklusive Topic und Header. Rufe setBufferSize() mit einem größeren Wert auf und bedenke, dass auf dem Uno jedes Byte von den 2 KB RAM abgeht.
Kann PubSubClient mit QoS 1 veröffentlichen?
Nein. PubSubClient veröffentlicht nur mit QoS 0 und unterstützt MQTT 3.1.1. Abonnieren geht mit QoS 0 oder 1. Brauchst du QoS-1-Publishes, ergänze eine Bestätigung auf Anwendungsebene oder nutze eine andere Bibliothek.

