Tutorials

Migration auf Paho MQTT 2.0: CallbackAPIVersion-Fehler beheben

Von paho-mqtt 1.x auf 2.x: CallbackAPIVersion-ValueError beheben, on_connect und andere Callback-Signaturen anpassen, neue ReasonCode-Objekte nutzen.

Aktualisiert · 8 Min. Lesezeit

Migration auf Paho MQTT 2.0: CallbackAPIVersion-Fehler beheben

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

FehlerUrsacheLösung
ValueError: Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for detailsDie 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 versionmqtt.Client() ohne Version oder mit VERSION1VERSION2 übergeben und Callbacks anpassen
TypeError: on_connect() takes 4 positional arguments but 5 were givenVERSION2 mit einem on_connect(client, userdata, flags, rc) im 1.x-StilDen Parameter properties ergänzen (siehe unten)
TypeError: on_disconnect() takes 3 positional arguments but 5 were givenDasselbe 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.xpip 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:

Welches paho-mqtt ist installiert?
python -c "import paho.mqtt; print(paho.mqtt.__version__)"
pip show paho-mqtt

Was 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:

python
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

Callback1.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:

  • flags ist kein Dict mehr. In on_connect ist es ein ConnectFlags-Objekt: nutze flags.session_present statt flags["session present"].
  • disconnect_flags.is_disconnect_packet_from_server sagt dir, ob der Broker ein DISCONNECT geschickt hat (MQTT 5) oder ob die Bibliothek den Verbindungsverlust selbst erkannt hat.
  • on_subscribe erhält Reason Codes, keine QoS-Integer. Bei einer mit QoS 1 gewährten Subscription ist value des Reason Codes 1; eine abgelehnte Subscription hat einen Wert ab 0x80, rc.is_failure ist 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:

python
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:

client_v1.py — paho-mqtt 1.x
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:

client_v2.py — paho-mqtt 2.x
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

  1. Pinne die Zielversion, etwa paho-mqtt>=2.1,<3 in requirements.txt, damit dich das nächste Major-Release nicht überrascht.
  2. Durchsuche die Codebasis nach mqtt.Client( und übergib zuerst mqtt.CallbackAPIVersion.VERSION2, mit client_id= als Keyword-Argument.
  3. Stelle alle Callbacks auf die neue Signatur um — auch solche, die in anderen Modulen oder Bibliotheken zugewiesen werden.
  4. Ersetze Integer-Vergleiche auf rc durch reason_code.is_failure oder Namensvergleiche.
  5. Ersetze flags["session present"] durch flags.session_present.
  6. 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'.