paho-mqtt 2.0 hat geändert, wie du einen Client erzeugst und was deine Callbacks erhalten. Ist dein Skript nach einem pip install --upgrade kaputt, ist die Lösung fast immer dieselbe: Übergib mqtt.CallbackAPIVersion.VERSION2 als erstes Argument an mqtt.Client() und passe die Callback-Signaturen an. Dieser Leitfaden zeigt die genauen Fehler, was sich geändert hat, und ein vollständiges Vorher-nachher-Beispiel.
Die Fehler, die du wahrscheinlich siehst
| Fehler | Ursache | Lösung |
|---|---|---|
ValueError: Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for details | Die Client-ID wurde als erstes Positionsargument übergeben: mqtt.Client("my-id") | mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="my-id") |
DeprecationWarning: Callback API version 1 is deprecated, update to latest version | mqtt.Client() ohne Version oder mit VERSION1 | VERSION2 übergeben und Callbacks anpassen |
TypeError: on_connect() takes 4 positional arguments but 5 were given | VERSION2 mit einem on_connect(client, userdata, flags, rc) im 1.x-Stil | Den Parameter properties ergänzen (siehe unten) |
TypeError: on_disconnect() takes 3 positional arguments but 5 were given | Dasselbe bei on_disconnect(client, userdata, rc) | Neue Signatur mit disconnect_flags, reason_code, properties |
AttributeError: module 'paho.mqtt.client' has no attribute 'CallbackAPIVersion' | Dein Code ist für 2.x, die Umgebung hat aber noch 1.x | pip install "paho-mqtt>=2.0" und prüfen, welcher Interpreter das Skript ausführt |
Prüfe zuerst die installierte Version — virtuelle Umgebungen und System-Python sind sich oft nicht einig:
python -c "import paho.mqtt; print(paho.mqtt.__version__)"
pip show paho-mqttWas sich in 2.0 geändert hat
Version 1.x hatte einen einzigen Callback-Stil, dessen Argumente von der Protokollversion abhingen: MQTT-3.1.1-Callbacks bekamen einen Integer rc, MQTT-5-Callbacks reason_code und properties. So entstand leicht Code, der mit einem Protokoll lief und mit dem anderen abstürzte.
2.0 führt Callback-API-Versionen ein. Mit CallbackAPIVersion.VERSION2 hat jeder Callback unter MQTT 3.1.1 und MQTT 5 dieselbe Signatur, Reason Codes sind immer ReasonCode-Objekte, und Properties werden immer übergeben (als None, wenn das Protokoll keine kennt). Der Konstruktor sieht jetzt so aus:
mqtt.Client(
callback_api_version, # neu, erstes Positionsargument
client_id="",
clean_session=None,
userdata=None,
protocol=mqtt.MQTTv311,
transport="tcp",
reconnect_on_failure=True,
manual_ack=False,
)Außerdem gut zu wissen: 2.x braucht Python 3.7 oder neuer, und die Helfer publish.single() / subscribe.simple() kümmern sich selbst um die Version — Code, der nur sie nutzt, läuft meist weiter.
Callback-Signaturen: 1.x vs. VERSION2
| Callback | 1.x (MQTT 3.1.1) | 2.x mit VERSION2 |
|---|---|---|
on_connect | (client, userdata, flags, rc) | (client, userdata, flags, reason_code, properties) |
on_disconnect | (client, userdata, rc) | (client, userdata, disconnect_flags, reason_code, properties) |
on_subscribe | (client, userdata, mid, granted_qos) | (client, userdata, mid, reason_code_list, properties) |
on_unsubscribe | (client, userdata, mid) | (client, userdata, mid, reason_code_list, properties) |
on_publish | (client, userdata, mid) | (client, userdata, mid, reason_code, properties) |
on_message | (client, userdata, message) | unverändert |
on_log | (client, userdata, level, buf) | unverändert |
In der Praxis zählen drei Details:
flagsist kein Dict mehr. Inon_connectist es einConnectFlags-Objekt: nutzeflags.session_presentstattflags["session present"].disconnect_flags.is_disconnect_packet_from_serversagt dir, ob der Broker ein DISCONNECT geschickt hat (MQTT 5) oder ob die Bibliothek den Verbindungsverlust selbst erkannt hat.on_subscribeerhält Reason Codes, keine QoS-Integer. Bei einer mit QoS 1 gewährten Subscription istvaluedes Reason Codes1; eine abgelehnte Subscription hat einen Wert ab0x80,rc.is_failureist dann wahr.
Reason Codes sind jetzt Objekte
Mit VERSION2 ist reason_code ein ReasonCode. Er ist im Vergleich gleich einem Integer und seinem Namen, wird als Name ausgegeben und hat die Eigenschaft is_failure:
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Verbindung fehlgeschlagen: {reason_code} ({reason_code.value})")
return
print("verbunden, Session vorhanden:", flags.session_present)reason_code == 0 und reason_code == "Success" funktionieren für den Erfolgsfall weiterhin — die häufigste Prüfung übersteht das Upgrade also unverändert.
Ein vollständiges Vorher-nachher-Beispiel
Typischer 1.x-Code — unter 2.x scheitert er mit dem obigen ValueError:
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, rc):
if rc == 0:
client.subscribe("testmqtt/paho2/#", qos=1)
else:
print("connect failed, rc =", rc)
def on_disconnect(client, userdata, rc):
print("disconnected, rc =", rc)
def on_message(client, userdata, msg):
print(msg.topic, msg.payload.decode())
client = mqtt.Client("testmqtt-paho2-demo")
client.on_connect = on_connect
client.on_disconnect = on_disconnect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, 60)
client.loop_forever()Derselbe Client, migriert auf VERSION2:
import os
import paho.mqtt.client as mqtt
def on_connect(client, userdata, flags, reason_code, properties):
if reason_code.is_failure:
print(f"Verbindung fehlgeschlagen: {reason_code}")
return
print("verbunden, Session vorhanden:", flags.session_present)
client.subscribe("testmqtt/paho2/#", qos=1)
def on_subscribe(client, userdata, mid, reason_code_list, properties):
for rc in reason_code_list:
print("Subscription:", "abgelehnt" if rc.is_failure else f"QoS {rc.value} gewährt")
def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
print(f"getrennt: {reason_code} (vom Server: {disconnect_flags.is_disconnect_packet_from_server})")
def on_message(client, userdata, msg):
print(msg.topic, msg.payload.decode())
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id="testmqtt-paho2-demo")
if os.environ.get("MQTT_USER"):
client.username_pw_set(os.environ["MQTT_USER"], os.environ.get("MQTT_PASS"))
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_disconnect = on_disconnect
client.on_message = on_message
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_forever()Dieselben Callbacks funktionieren unverändert mit protocol=mqtt.MQTTv5 — das ist der Hauptvorteil von VERSION2. Nur die Konstruktor-Argumente unterscheiden sich: MQTT 5 akzeptiert kein clean_session (es wirft ValueError: Clean session is not used for MQTT 5.0); nutze stattdessen clean_start und Session Expiry in connect(). Der Migrationsleitfaden von MQTT 3.1.1 auf MQTT 5 behandelt diese Optionen.
Migrations-Checkliste
- Pinne die Zielversion, etwa
paho-mqtt>=2.1,<3inrequirements.txt, damit dich das nächste Major-Release nicht überrascht. - Durchsuche die Codebasis nach
mqtt.Client(und übergib zuerstmqtt.CallbackAPIVersion.VERSION2, mitclient_id=als Keyword-Argument. - Stelle alle Callbacks auf die neue Signatur um — auch solche, die in anderen Modulen oder Bibliotheken zugewiesen werden.
- Ersetze Integer-Vergleiche auf
rcdurchreason_code.is_failureoder Namensvergleiche. - Ersetze
flags["session present"]durchflags.session_present. - Lass das Programm mit sichtbaren Warnungen laufen (
python -W error::DeprecationWarning app.py), um jeden Client zu finden, der noch auf VERSION1 läuft.
Wenn du ohnehin viel Code umschreibst, überleg, ob ein asynchroner Client besser passt — aiomqtt für asyncio baut auf paho-mqtt 2.x auf. Für andere paho-Exceptions siehe paho-mqtt-Fehler in Python.
Den migrierten Client testen
Starte client_v2.py, öffne den Online-MQTT-Client im Browser, verbinde dich mit broker.connectmq.com und publiziere auf testmqtt/paho2/hello. Dein Skript sollte Topic und Payload ausgeben. Um die Fehlerpfade zu prüfen, verbinde dich mit einem falschen Passwort zu deinem eigenen Broker und kontrolliere, dass on_connect einen lesbaren Grund wie Bad user name or password statt einer nackten Zahl ausgibt.
Testnachricht an den migrierten Client senden
Die Migration auf paho-mqtt 2.0 ist überwiegend mechanisch: ein neues Konstruktor-Argument, fünf Callback-Signaturen und ein paar Integer-Prüfungen, die zu Reason-Code-Prüfungen werden. Einmal sauber auf VERSION2 umgestellt und die Major-Version gepinnt, verhalten sich deine Callbacks unter MQTT 3.1.1 und MQTT 5 identisch. Einen vollständigen Rundgang durch die Bibliothek bietet der Python-MQTT-Leitfaden.
Häufig gestellte Fragen
Was bedeutet 'Unsupported callback API version' in paho-mqtt?
Seit paho-mqtt 2.0 ist das erste Argument von mqtt.Client() die Callback-API-Version. Code für 1.x, der die Client-ID als erstes Positionsargument übergibt, löst jetzt diesen ValueError aus. Übergib zuerst mqtt.CallbackAPIVersion.VERSION2 und die Client-ID als client_id=.
Kann ich mit paho-mqtt 2.x meine alten Callbacks behalten?
Ja, vorübergehend. Mit mqtt.CallbackAPIVersion.VERSION1 funktionieren die Callback-Signaturen aus 1.x weiter, aber die Bibliothek gibt eine DeprecationWarning aus, und Version 1 soll in einem künftigen Major-Release entfernt werden. Steig auf VERSION2 um, sobald es geht.
Warum greift meine Prüfung 'if rc == 5' nach dem Upgrade nicht mehr?
Mit VERSION2 erhält on_connect auch bei MQTT 3.1.1 ein ReasonCode-Objekt, und die Return Codes von 3.1.1 werden auf MQTT-5-Reason-Codes abgebildet. 'Not authorized' ist 135 (0x87), nicht 5. Nutze reason_code.is_failure oder vergleiche mit dem Namen, etwa reason_code == 'Not authorized'.

