Tutoriales de IoT

Callback API version de Paho MQTT 2.0: cómo corregir los errores de migración paso a paso

Paho-mqtt 2.0 rompió mucho código Python de la noche a la mañana. Descubre qué cambió, cómo corregir el error de callback_api_version en una línea y cómo migrar cada callback a las firmas de VERSION2 con un ejemplo funcional.

Diff de código que muestra la migración de la callback API version de Paho MQTT 2.0 de callbacks VERSION1 a VERSION2
Pruébalo en tu propio broker MQTTConsigue un broker MQTT privado y gestionado con el plan Free y empieza con ejemplos de código listos para usar.
Pruébalo gratis Ejemplos de código Sin tarjeta

El cambio de la callback API version en Paho MQTT 2.0 es, con diferencia, el motivo más común por el que los scripts MQTT en Python dejan de funcionar de repente después de un pip install --upgrade. Esta guía explica exactamente qué cambió en paho-mqtt 2.0, te da la corrección del error en una línea y después recorre la migración de cada callback a las firmas recomendadas de VERSION2. Terminarás con un ejemplo completo y funcional conectado a broker.connectmq.com que puedes usar como plantilla.

Los errores que probablemente estás viendo

Si has llegado aquí desde un buscador, lo más probable es que tengas uno de estos:

ValueError: Unsupported callback API version: version 2.0 added a callback_api_version, see docs/migrations.rst for details
TypeError: Client.__init__() missing 1 required positional argument: 'callback_api_version'
DeprecationWarning: Callback API version 1 is deprecated, update to latest version
TypeError: on_connect() takes 4 positional arguments but 5 were given

Los dos primeros vienen de crear el cliente al estilo 1.x. El ValueError aparece cuando pasas un client ID como primer argumento posicional, como en mqtt.Client("my-device"), porque esa posición pertenece ahora a la versión de la API de callbacks. El tercero es un aviso de que has elegido explícitamente la API antigua. El cuarto aparece después de cambiar a VERSION2 si dejas un callback con el número de parámetros antiguo.

Qué cambió en paho-mqtt 2.0

La versión 2.0 del cliente Python de Eclipse Paho, publicada a principios de 2024, fue la primera versión mayor en años. Su cambio principal es que los callbacks ahora están versionados. En 1.x, los parámetros que recibían los callbacks diferían entre MQTT 3.1.1 y MQTT 5, y algunos usaban enteros simples mientras otros usaban objetos de reason code. La versión 2 los unifica: cada callback recibe los mismos parámetros sea cual sea la versión del protocolo, y los códigos de resultado son siempre objetos ReasonCode.

Para no romper código en silencio con las nuevas firmas, los mantenedores te obligan a elegir explícitamente. El primer argumento de Client() es ahora callback_api_version, con dos valores:

Valor Comportamiento ¿Recomendado?
CallbackAPIVersion.VERSION1 Firmas de callback antiguas de 1.x, emite un aviso de obsolescencia Solo como puente temporal
CallbackAPIVersion.VERSION2 Nuevas firmas unificadas, objetos ReasonCode en todas partes Sí, para todo el código nuevo y migrado

Otros cambios destacados de la 2.0 son la retirada del soporte para Python 2.7 y para versiones muy antiguas de Python 3, y el cambio de nombre de la clase ReasonCodes a ReasonCode. La lista completa está en las notas de migración oficiales de paho-mqtt.

Solución 1: el puente de una línea (VERSION1)

Si producción está caída y la necesitas de vuelta ya, dile a paho que mantenga el comportamiento antiguo:

import paho.mqtt.client as mqtt

client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION1, client_id="my-device")

Todos tus callbacks existentes siguen funcionando sin cambios. Verás el aviso de obsolescencia, que te recuerda que VERSION1 se eliminará en una futura versión mayor. Trátalo como una tirita, no como una solución.

Otra tirita alternativa es fijar la librería antigua con pip install "paho-mqtt<2.0". Funciona, pero bloquea las correcciones de seguridad y de errores y suele entrar en conflicto con otros paquetes que ya requieren la 2.x. Es preferible el flag VERSION1 a fijar la versión, y planifica la migración real.

Solución 2: migrar a la versión 2 de la callback API de Paho MQTT 2.0

La solución de verdad es pasar VERSION2 y actualizar las firmas de los callbacks. Aquí tienes todos los callbacks, uno al lado del otro:

Callback Firma en 1.x / VERSION1 Firma en 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) sin cambios
on_log (client, userdata, level, buf) sin cambios

Con VERSION1 y MQTT 5, algunos de ellos ya tenían parámetros adicionales; VERSION2 elimina esa diferencia, que es precisamente el objetivo del cambio.

Lista de comprobación de la migración

Recorre tu código en este orden:

  1. Cambia cada llamada mqtt.Client(...) por mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=..., ...), pasando todos los demás argumentos por nombre.
  2. Añade properties a on_connect y renombra rc a reason_code. Sustituye rc == 0 por not reason_code.is_failure (la comparación con enteros sigue funcionando, pero el atributo se lee mejor).
  3. Sustituye flags["session present"] por flags.session_present.
  4. Dale a on_disconnect su nuevo parámetro disconnect_flags y properties.
  5. En on_subscribe, sustituye granted_qos por reason_code_list y comprueba cada entrada: los códigos de éxito tienen valores de 0 a 2 (el QoS concedido) y los fallos tienen is_failure activado.
  6. Añade reason_code y properties a on_publish y on_unsubscribe.
  7. Busca ReasonCodes en el código y cámbialo por ReasonCode.
  8. Ejecuta tus pruebas convirtiendo los avisos en errores (python -W error::DeprecationWarning) para demostrar que no queda ningún cliente VERSION1.

Un ejemplo completo con VERSION2

Este script usa todos los callbacks habituales con las nuevas firmas. Cambia k7f3q2 por tu propia cadena aleatoria para no compartir topics con otros usuarios del servidor MQTT público gratuito:

import time
import uuid

import paho.mqtt.client as mqtt

BROKER = "broker.connectmq.com"
PREFIX = "connectmq/demo/k7f3q2"


def on_connect(client, userdata, flags, reason_code, properties):
    if reason_code.is_failure:
        print(f"Connect failed: {reason_code}")
        return
    print(f"Connected (session present: {flags.session_present})")
    client.subscribe(f"{PREFIX}/#", qos=1)


def on_subscribe(client, userdata, mid, reason_code_list, properties):
    for rc in reason_code_list:
        print("Subscribe rejected:" if rc.is_failure else "Granted QoS:", rc)


def on_publish(client, userdata, mid, reason_code, properties):
    print(f"Publish {mid} acknowledged: {reason_code}")


def on_message(client, userdata, msg):
    print(f"{msg.topic}: {msg.payload.decode()}")


def on_disconnect(client, userdata, disconnect_flags, reason_code, properties):
    print(f"Disconnected: {reason_code}")


client = mqtt.Client(
    mqtt.CallbackAPIVersion.VERSION2,
    client_id=f"migrate-{uuid.uuid4().hex[:8]}",
    protocol=mqtt.MQTTv5,
)
client.on_connect = on_connect
client.on_subscribe = on_subscribe
client.on_publish = on_publish
client.on_message = on_message
client.on_disconnect = on_disconnect

client.connect(BROKER, 1883, keepalive=60)
client.loop_start()
time.sleep(1)

for i in range(3):
    client.publish(f"{PREFIX}/test", f"hello {i}", qos=1)
    time.sleep(1)

client.disconnect()
client.loop_stop()

Ejecútalo y deberías ver la conexión, el QoS concedido, tres confirmaciones de publicación y los tres mensajes devueltos por el broker público de ConnectMQ. Cambia protocol a mqtt.MQTTv311 y los mismos callbacks siguen funcionando sin modificaciones, que es exactamente lo que promete VERSION2. Si eres nuevo con la librería, empieza primero por el tutorial de Paho MQTT en Python.

Problemas tras la migración

Librerías de terceros fijadas a 1.x. Algunos paquetes que envuelven paho declararon paho-mqtt<2.0 en sus requisitos. Compruébalo con pip show y pip check. Las versiones recientes de la mayoría de los envoltorios mantenidos admiten la 2.x; actualízalos o aísla las herramientas heredadas en su propio entorno virtual.

Argumentos posicionales. Cualquier llamada mqtt.Client("id", False, None, mqtt.MQTTv311) queda ahora desplazada una posición. Conviértelas todas en argumentos por nombre; así las futuras actualizaciones serán indoloras.

Callbacks registrados con decoradores. El código que usa @client.connect_callback() y decoradores similares también debe actualizar las firmas de las funciones decoradas.

Excepciones silenciadas. Por defecto, paho registra las excepciones lanzadas dentro de los callbacks en lugar de tumbar tu programa. Una firma incorrecta puede parecer, por tanto, que "no pasa nada". Conecta un logger con client.enable_logger() durante la migración para que el TypeError sea visible.

Lógica de reconexión. Si comparabas enteros rc en on_disconnect para decidir si reconectar, reescríbelo en torno a reason_code. La guía de reconexión de paho-mqtt muestra patrones robustos, y la guía de TLS para MQTT en Python cubre la conexión cifrada que deberías usar en producción.

¿Deberías plantearte aiomqtt en su lugar?

Si tu aplicación está construida sobre asyncio, migrar los callbacks puede ser un buen momento para pasar directamente a una API asíncrona. aiomqtt envuelve paho y sustituye los callbacks por async for message in client.messages. Nuestro tutorial de aiomqtt y asyncio muestra el mismo flujo de publicación y suscripción escrito de esa forma. Para una comparación más amplia de clientes de Python y de otros lenguajes, consulta la visión general de librerías cliente MQTT.

Prueba tu código migrado en ConnectMQ

ConnectMQ ofrece un servidor MQTT público gratuito en broker.connectmq.com, ideal para verificar una migración, y un broker MQTT gestionado para producción con MQTT 3.1.1 y 5.0, TLS en el 8883 y credenciales generadas. Su portal incluye un explorador de topics en vivo para que confirmes que los clientes migrados siguen publicando lo que esperas. Cuando estés listo para dejar el broker público, ejecuta tus clientes en un broker MQTT en la nube privado: regístrate en el plan Serverless gratuito sin tarjeta de crédito.

Preguntas frecuentes

¿Qué es callback_api_version en paho-mqtt?

Es el primer argumento de mqtt.Client() desde paho-mqtt 2.0. Selecciona qué firmas de callback usa el cliente: VERSION1 para el estilo antiguo de 1.x o VERSION2 para el nuevo estilo unificado.

¿Debo usar VERSION1 o VERSION2?

Usa VERSION2. VERSION1 solo existe para que el código antiguo siga funcionando y muestra un aviso de obsolescencia, así que acabará eliminándose.

¿Por qué obtengo "Unsupported callback API version" al pasar un client ID?

Has pasado el client ID como primer argumento posicional, que ahora está reservado para la versión de la API de callbacks. Pasa primero mqtt.CallbackAPIVersion.VERSION2 y el ID como client_id=....

¿Tengo que cambiar on_message al migrar?

No. on_message(client, userdata, message) mantiene la misma firma en ambas versiones. Los cambios afectan a los callbacks de conexión, desconexión, suscripción, cancelación de suscripción y publicación.

Artículos relacionados

Las mejores herramientas CLI de MQTT para pruebas, scripts y depuración

Una comparativa práctica de mosquitto_pub/sub, MQTTX CLI, HiveMQ MQTT CLI, la CLI de MQTT.js, curl y mqttui, con la misma prueba ejecutada contra broker.connectmq.com para que elijas la herramienta adecuada para scripts y depuración.

9 min de lectura

Cómo guardar datos MQTT en InfluxDB con Python

Construye un puente en Python con paho-mqtt e influxdb-client que se suscribe a un broker, convierte los niveles del topic en tags de InfluxDB, escribe los valores numéricos del JSON como fields por lotes y gestiona payloads erróneos y conflictos de tipos.

8 min de lectura

Todos los artículos →

Tu broker MQTT privado, a unos pocos clics

Crea una cuenta, obtén las credenciales de tu broker y conecta tu primer dispositivo, sin servidores que instalar, parchear ni escalar.