MicroPython bringt einen winzigen MQTT-Client namens umqtt mit, der auf einem ESP32, einem ESP8266 oder einem Raspberry Pi Pico W mit wenigen Kilobyte RAM läuft. Es gibt ihn in zwei Varianten: umqtt.simple, ein minimaler synchroner Client, und umqtt.robust, der automatisches Reconnect obendrauf setzt. Dieses Tutorial installiert beide, verbindet sich per WLAN, publiziert und abonniert, hält die Verbindung am Leben, erholt sich von Abbrüchen und erklärt, worauf du bei TLS achten musst.
Der Code zielt auf MicroPython 1.22 oder neuer und das aktuelle umqtt.simple aus micropython-lib. Er läuft unverändert auf ESP32-Boards und dem Pico W; nur die Pin-Nummern unterscheiden sich, wenn du Sensoren ausliest.
umqtt auf einen Blick
| umqtt.simple | umqtt.robust | |
|---|---|---|
| MQTT-Version | 3.1.1 | 3.1.1 |
| QoS | 0 und 1 (Publish und Subscribe) | 0 und 1 |
| Automatisches Reconnect | Nein | Ja, bei Socket-Fehlern |
| WLAN-Reconnect | Nein | Nein |
| Erneutes Abonnieren nach Reconnect | Nein | Nein (setzt auf eine persistente Session) |
| Keep-Alive-Pings | ping() von Hand | ping() von Hand |
| TLS | Ja, über einen ssl-Kontext | Wie simple |
Beide arbeiten synchron: wait_msg() blockiert, bis eine Nachricht ankommt, check_msg() kehrt sofort zurück. Es gibt keinen Hintergrund-Thread, deine Hauptschleife ist für alles verantwortlich.
1. umqtt installieren
Manche Firmware-Builds enthalten umqtt bereits (der ESP8266-Port meistens). Auf ESP32 und Pico W installierst du es von deinem Rechner aus mit mpremote aus micropython-lib:
pip install mpremote
mpremote mip install umqtt.simple
mpremote mip install umqtt.robustOder, sobald das Board im WLAN ist, über die REPL:
import mip
mip.install("umqtt.simple")
mip.install("umqtt.robust")Prüfe mit from umqtt.simple import MQTTClient – kein ImportError heißt, du kannst loslegen.
2. WLAN verbinden
Das Modul network ist auf ESP32 und Pico W identisch. Leg den Code in wifi.py ab, damit andere Dateien ihn wiederverwenden können:
import network, time
def connect(ssid, password, timeout=20):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(ssid, password)
start = time.time()
while not wlan.isconnected():
if time.time() - start > timeout:
raise OSError("Wi-Fi timeout, status=%s" % wlan.status())
time.sleep(0.5)
print("Wi-Fi OK", wlan.ifconfig()[0])
return wlanBeide Chips unterstützen nur 2,4-GHz-Netze. Bleibt der Status ewig auf "verbinden", prüfe Frequenzband und Passwort, bevor du MQTT verdächtigst.
3. Publizieren und abonnieren mit umqtt.simple
import time, machine, ubinascii
from umqtt.simple import MQTTClient
import wifi
wifi.connect("your-ssid", "your-wifi-password")
CLIENT_ID = b"upy-" + ubinascii.hexlify(machine.unique_id())
BASE = b"testmqtt/upy/" + CLIENT_ID
KEEPALIVE = 60
def on_message(topic, msg):
print("empfangen", topic, msg)
if msg == b"led/on":
machine.Pin("LED", machine.Pin.OUT).on() # Pico W; auf dem ESP32 deinen Pin nehmen
client = MQTTClient(CLIENT_ID, "broker.connectmq.com", port=1883,
user="username", password="password",
keepalive=KEEPALIVE)
client.set_last_will(BASE + b"/status", b"offline", retain=True, qos=1)
client.set_callback(on_message) # muss vor subscribe() gesetzt sein
client.connect()
client.publish(BASE + b"/status", b"online", retain=True, qos=1)
client.subscribe(BASE + b"/cmd", qos=1)
print("verbunden als", CLIENT_ID)
last_pub = last_ping = time.ticks_ms()
while True:
client.check_msg() # nicht blockierend: ruft on_message auf, wenn Daten warten
now = time.ticks_ms()
if time.ticks_diff(now, last_pub) > 10_000:
client.publish(BASE + b"/uptime", str(time.ticks_ms() // 1000))
last_pub = now
if time.ticks_diff(now, last_ping) > KEEPALIVE * 1000 // 2:
client.ping()
last_ping = now
time.sleep_ms(50)Die wichtigsten Punkte:
- Topics und Payloads sind Bytes. Strings funktionieren oft, aber Bytes vermeiden Überraschungen, und
on_messagebekommt immer Bytes. set_callback()vorsubscribe()– sonst wirft umqtt einen Assertion-Fehler.check_msg()vs.wait_msg()–wait_msg()blockiert, bis etwas ankommt. Das passt für einen reinen Befehlsempfänger, hält aber deine Sensorschleife an. Nimmcheck_msg(), wenn du auch publizierst.- Keep-Alive ist deine Aufgabe. Mit
keepalive=60wirft der Broker den Client nach rund 90 Sekunden Stille raus. Die Schleife oben pingt alle 30 Sekunden. Die genauen Zeitregeln stehen in MQTT Keep Alive. - Eindeutige Client-ID aus
machine.unique_id(), damit sich zwei Boards mit derselben Datei nicht gegenseitig rauswerfen.
Testen
Öffne den Online-MQTT-Client, verbinde dich mit broker.connectmq.com und abonniere testmqtt/upy/#. Du solltest die retained status-Nachricht sehen und alle 10 Sekunden einen uptime-Wert. Publiziere led/on an testmqtt/upy/<deine-client-id>/cmd, und die REPL gibt empfangen ... aus.
Sieh dir im Browser an, was dein Board publiziert
4. Richtig neu verbinden
WLAN bricht ab, Router starten neu, Broker starten neu. Bei umqtt.simple zeigt sich jeder Netzwerkfehler als OSError – typischerweise [Errno 104] ECONNRESET, [Errno 113] ECONNABORTED oder [Errno 110] ETIMEDOUT – in dem Aufruf, der gerade den Socket benutzt hat. Umschließe die Schleife, baue die Verbindung neu auf und abonniere erneut:
def mqtt_connect():
c = MQTTClient(CLIENT_ID, "broker.connectmq.com", user="username",
password="password", keepalive=KEEPALIVE)
c.set_last_will(BASE + b"/status", b"offline", retain=True, qos=1)
c.set_callback(on_message)
c.connect()
c.publish(BASE + b"/status", b"online", retain=True, qos=1)
c.subscribe(BASE + b"/cmd", qos=1) # nach einer neuen Clean Session sind die Subscriptions weg
return c
def run_loop(c):
last_pub = last_ping = time.ticks_ms()
while True:
c.check_msg()
now = time.ticks_ms()
if time.ticks_diff(now, last_pub) > 10_000:
c.publish(BASE + b"/uptime", str(time.ticks_ms() // 1000))
last_pub = now
if time.ticks_diff(now, last_ping) > KEEPALIVE * 1000 // 2:
c.ping()
last_ping = now
time.sleep_ms(50)
client = None
delay = 1
while True:
try:
wifi.connect("your-ssid", "your-wifi-password")
client = mqtt_connect()
delay = 1
run_loop(client) # endet nur über einen OSError
except OSError as e:
print("Verbindung verloren:", e, "neuer Versuch in", delay, "s")
try:
client.disconnect()
except Exception:
pass
time.sleep(delay)
delay = min(delay * 2, 60) # exponentielles Backoff mit ObergrenzeExponentielles Backoff zählt, sobald du viele Geräte hast: Startet der Broker neu, erzeugt eine Flotte, die gleichzeitig jede Sekunde neu versucht, einen Reconnect-Sturm. Das Muster beschreibt Best Practices für MQTT-Reconnects.
Für Geräte, die niemals hängen bleiben dürfen, ergänze einen Hardware-Watchdog: wdt = machine.WDT(timeout=30000) und pro Schleifendurchlauf einmal wdt.feed(). Blockiert der Code in einem Socket-Aufruf, setzt sich das Board selbst zurück.
Und umqtt.robust?
umqtt.robust.MQTTClient hat dieselbe API. Trifft publish(), wait_msg() oder check_msg() auf einen OSError, ruft er reconnect() auf, das connect(False) endlos mit wachsender Pause wiederholt. Zwei Einschränkungen:
- Das WLAN fasst er nie an. Ist der Access Point weg, versucht er es weiter mit einem Socket, der nicht funktionieren kann.
- Er verbindet sich mit
clean_session=Falseneu und abonniert nicht erneut. Deine Subscriptions kommen also nur zurück, wenn der Broker die Session behalten hat – ein privater Broker tut das in der Regel, geteilte Test-Endpunkte wiebroker.connectmq.comspeichern aber keine persistenten Sessions. Teste das Verhalten persistenter Sessions gegen deine eigene Broker-Instanz.
umqtt.robust taugt für einen schnellen Prototyp. Für alles, was unbeaufsichtigt läuft, ist die explizite Schleife oben leichter nachzuvollziehen.
5. TLS-Fallstricke
Port 1883 überträgt Benutzername und Passwort im Klartext. Für alles jenseits eines Tischtests verbindest du dich auf 8883 mit TLS. Das aktuelle umqtt.simple nimmt einen ssl.SSLContext entgegen:
import ssl, ntptime
ntptime.settime() # die Zertifikatsgültigkeit braucht eine echte Uhrzeit
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
with open("ca.der", "rb") as f:
ctx.load_verify_locations(cadata=f.read())
client = MQTTClient(CLIENT_ID, "broker.connectmq.com", port=8883,
user="username", password="password",
keepalive=KEEPALIVE, ssl=ctx)
client.connect()Woran Leute oft scheitern:
- Ältere umqtt-Versionen nutzten
ssl=True, ssl_params={...}statt eines Kontexts. Bekommst du einenTypeErrorim Konstruktor, prüfe, welche Version auf dem Board liegt. - Kein CA-Speicher auf dem Gerät. MicroPython bringt keine Root-Zertifikate mit. Kopiere die Root-CA, die das Zertifikat deines Brokers signiert hat, auf das Board (PEM mit
openssl x509 -in ca.pem -outform der -out ca.dernach DER umwandeln und mitmpremote cp ca.der :hochladen). CERT_NONEnur zum Debuggen. Der Verkehr wird verschlüsselt, aber jeder Server akzeptiert – auch der eines Angreifers.- Speicher. Ein TLS-Handshake braucht einige Dutzend Kilobyte freien Heap. ESP32 und Pico W schaffen das; beim ESP8266 wird es oft zu knapp. Ruf vor
connect()gc.collect()auf und verbinde dich früh, bevor deine Anwendung den Heap füllt. - Uhrzeit. Ohne
ntptime.settime()glaubt das Board, es sei das Jahr 2000, und lehnt ein gültiges Zertifikat womöglich als noch nicht gültig ab.
Eine TLS-Verbindung testen zeigt, wie du die Zertifikatskette des Brokers vom Laptop aus prüfst, bevor du auf dem Gerät debuggst.
Häufige Fehler
| Fehler | Bedeutung | Lösung |
|---|---|---|
MQTTException: 5 | CONNACK "not authorized" | Falscher Benutzer oder ACL verweigert den Client |
MQTTException: 4 | Falscher Benutzername oder falsches Passwort | Zugangsdaten prüfen |
MQTTException: 2 | Identifier rejected | Client-ID leer, zu lang oder gesperrt |
OSError: [Errno 104] ECONNRESET | Der Broker hat den Socket geschlossen | Keep-Alive verpasst, doppelte Client-ID, TLS auf einem Klartext-Port |
OSError: -2 oder -202 | DNS-Auflösung fehlgeschlagen (Code je nach Port) | WLAN noch nicht bereit oder falscher Hostname |
MemoryError beim TLS-Connect | Zu wenig Heap | gc.collect(), früher verbinden, kleinere Puffer |
Die Zahlen nach MQTTException sind CONNACK-Return-Codes aus MQTT 3.1.1; das Reason-Code-Tool erklärt jeden einzelnen. Ausführlicher geht es in MicroPython-MQTT-Fehler.
umqtt ist klein genug, um es in einem Rutsch zu lesen, und genau das ist seine Stärke: Sobald du eigene Keep-Alive-Pings, eine Reconnect-Schleife mit Backoff, erneutes Abonnieren und TLS mit einer echten CA ergänzt, ist es ein verlässlicher Client für batterieschonende Sensoren. Bau den Prototyp gegen den geteilten Endpunkt und zieh denselben Code dann auf einen privaten Broker um, indem du nur Host und Zugangsdaten änderst.
Häufig gestellte Fragen
Soll ich umqtt.simple oder umqtt.robust verwenden?
umqtt.robust ist eine dünne Unterklasse von umqtt.simple, die sich bei Socket-Fehlern automatisch neu verbindet. Sie verbindet das WLAN nicht neu und abonniert nicht erneut für dich. Viele Projekte nehmen daher umqtt.simple mit einer eigenen Reconnect-Schleife, die volle Kontrolle über WLAN, Subscriptions und Backoff gibt.
Unterstützt umqtt QoS 2 oder MQTT 5?
Nein. umqtt.simple implementiert MQTT 3.1.1 nur mit QoS 0 und QoS 1. Wenn du MQTT-5-Properties oder QoS 2 auf einem Mikrocontroller brauchst, nimm einen anderen Client oder ein Gateway, das für das Gerät übersetzt.
Warum trennt sich mein MicroPython-MQTT-Client nach etwa einer Minute?
umqtt sendet keine Keep-Alive-Pings von selbst. Setzt du keepalive und publizierst dann länger als das 1,5-Fache dieses Werts nichts, schließt der Broker die Verbindung. Ruf client.ping() regelmäßig in deiner Hauptschleife auf oder setze keepalive=0, wenn dein Broker das erlaubt.


