MicroPython incluye un cliente MQTT diminuto llamado umqtt que funciona en un ESP32, un ESP8266 o una Raspberry Pi Pico W con unos pocos kilobytes de RAM. Viene en dos variantes: umqtt.simple, un cliente síncrono mínimo, y umqtt.robust, que añade reconexión automática encima. Este tutorial instala ambos, se conecta por Wi-Fi, publica y se suscribe, mantiene viva la conexión, se recupera de las caídas y explica qué vigilar con TLS.
El código está pensado para MicroPython 1.22 o superior y el umqtt.simple actual de micropython-lib. Funciona sin cambios en placas ESP32 y en la Pico W; solo cambian los números de pin si lees sensores.
umqtt de un vistazo
| umqtt.simple | umqtt.robust | |
|---|---|---|
| Versión MQTT | 3.1.1 | 3.1.1 |
| QoS | 0 y 1 (publish y subscribe) | 0 y 1 |
| Reconexión automática | No | Sí, ante errores de socket |
| Reconexión Wi-Fi | No | No |
| Resuscripción tras reconectar | No | No (depende de una sesión persistente) |
| Pings de keep-alive | ping() manual | ping() manual |
| TLS | Sí, mediante un contexto ssl | Igual que simple |
Ambos son síncronos: wait_msg() bloquea hasta que llega un mensaje y check_msg() vuelve de inmediato. No hay hilo en segundo plano, así que tu bucle principal se encarga de todo.
1. Instala umqtt
Algunas builds del firmware ya incluyen umqtt (el port de ESP8266 suele hacerlo). En ESP32 y Pico W, instálalo desde micropython-lib con mpremote desde tu ordenador:
pip install mpremote
mpremote mip install umqtt.simple
mpremote mip install umqtt.robustO, cuando la placa ya tenga Wi-Fi, desde el REPL:
import mip
mip.install("umqtt.simple")
mip.install("umqtt.robust")Compruébalo con from umqtt.simple import MQTTClient: si no aparece ImportError, ya puedes empezar.
2. Conéctate al Wi-Fi
El módulo network es el mismo en ESP32 y Pico W. Ponlo en wifi.py para reutilizarlo desde otros archivos:
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 wlanAmbos chips solo admiten redes de 2,4 GHz. Si el estado se queda en "conectando" para siempre, revisa la banda y la contraseña antes de sospechar de MQTT.
3. Publicar y suscribirse con 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("recibido", topic, msg)
if msg == b"led/on":
machine.Pin("LED", machine.Pin.OUT).on() # Pico W; en ESP32 usa tu pin
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) # debe definirse antes de subscribe()
client.connect()
client.publish(BASE + b"/status", b"online", retain=True, qos=1)
client.subscribe(BASE + b"/cmd", qos=1)
print("conectado como", CLIENT_ID)
last_pub = last_ping = time.ticks_ms()
while True:
client.check_msg() # no bloquea: ejecuta on_message si hay datos
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)Puntos clave:
- Topics y payloads son bytes. Las cadenas suelen funcionar, pero con bytes evitas sorpresas, y
on_messagesiempre recibe bytes. set_callback()antes desubscribe(): si no, umqtt lanza un error de aserción.check_msg()frente await_msg():wait_msg()bloquea hasta que llega algo, lo cual vale para un receptor de comandos puro, pero detiene tu bucle de sensores. Usacheck_msg()si además publicas.- El keep-alive es cosa tuya. Con
keepalive=60, el broker expulsa al cliente tras unos 90 segundos de silencio. El bucle de arriba hace ping cada 30 segundos. Consulta MQTT keep alive para las reglas exactas de tiempos. - Client ID único a partir de
machine.unique_id(), para que dos placas con el mismo archivo no se expulsen mutuamente.
Pruébalo
Abre el cliente MQTT online, conéctate a broker.connectmq.com y suscríbete a testmqtt/upy/#. Deberías ver el mensaje status retenido y un valor uptime cada 10 segundos. Publica led/on en testmqtt/upy/<tu-client-id>/cmd y el REPL mostrará recibido ....
Ver lo que publica tu placa en el navegador
4. Reconectar como es debido
El Wi-Fi se cae, los routers se reinician, los brokers se reinician. Con umqtt.simple, cada fallo de red aparece como un OSError (normalmente [Errno 104] ECONNRESET, [Errno 113] ECONNABORTED o [Errno 110] ETIMEDOUT) en la llamada que tocó el socket. Envuelve el bucle, reconstruye la conexión y vuelve a suscribirte:
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) # con una nueva clean session las suscripciones desaparecen
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) # solo termina lanzando OSError
except OSError as e:
print("conexión perdida:", e, "reintento en", delay, "s")
try:
client.disconnect()
except Exception:
pass
time.sleep(delay)
delay = min(delay * 2, 60) # backoff exponencial con topeEl backoff exponencial importa cuando tienes muchos dispositivos: si el broker se reinicia, una flota que reintenta cada segundo a la vez provoca una tormenta de reconexiones. El patrón se explica en buenas prácticas de reconexión MQTT.
Para dispositivos que nunca deben quedarse colgados, añade un watchdog de hardware: wdt = machine.WDT(timeout=30000) y llama a wdt.feed() una vez por iteración del bucle. Si el código se bloquea en una llamada de socket, la placa se reinicia sola.
¿Y umqtt.robust?
umqtt.robust.MQTTClient tiene la misma API. Cuando publish(), wait_msg() o check_msg() encuentran un OSError, llama a reconnect(), que reintenta connect(False) indefinidamente con un retardo creciente. Dos limitaciones:
- Nunca toca el Wi-Fi. Si el punto de acceso ha desaparecido, reintenta un socket que no puede funcionar.
- Se reconecta con
clean_session=Falsey no vuelve a suscribirse. Eso solo restaura tus suscripciones si el broker conservó la sesión: un broker privado normalmente lo hace, pero los endpoints de prueba compartidos comobroker.connectmq.comno guardan sesiones persistentes. Prueba el comportamiento de las sesiones persistentes contra tu propia instancia de broker.
umqtt.robust vale para un prototipo rápido. Para cualquier cosa que funcione desatendida, el bucle explícito de arriba es más fácil de razonar.
5. Advertencias sobre TLS
El puerto 1883 envía tu usuario y contraseña en texto plano. Para cualquier cosa más allá de una prueba en la mesa, conéctate al 8883 con TLS. El umqtt.simple actual acepta un ssl.SSLContext:
import ssl, ntptime
ntptime.settime() # la validez del certificado necesita un reloj real
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()Lo que suele dar problemas:
- Versiones antiguas de umqtt usaban
ssl=True, ssl_params={...}en lugar de un contexto. Si obtienes unTypeErroren el constructor, comprueba qué versión hay en la placa. - No hay almacén de CA en el dispositivo. MicroPython no incluye certificados raíz. Copia a la placa la CA raíz que firmó el certificado de tu broker (convierte PEM a DER con
openssl x509 -in ca.pem -outform der -out ca.dery súbelo conmpremote cp ca.der :). CERT_NONEes solo para depurar. Cifra el tráfico, pero acepta cualquier servidor, incluido el de un atacante.- Memoria. Un handshake TLS necesita decenas de kilobytes de heap libre. ESP32 y Pico W se las arreglan; el ESP8266 suele quedarse corto. Llama a
gc.collect()antes deconnect()y conéctate pronto, antes de que tu aplicación llene el heap. - Reloj. Sin
ntptime.settime()la placa cree que está en el año 2000 y puede rechazar un certificado válido por no ser válido todavía.
Probar una conexión TLS muestra cómo revisar la cadena de certificados del broker desde tu portátil antes de depurarla en el dispositivo.
Errores habituales
| Error | Significado | Solución |
|---|---|---|
MQTTException: 5 | CONNACK "not authorized" | Usuario incorrecto o la ACL deniega al cliente |
MQTTException: 4 | Usuario o contraseña incorrectos | Revisa las credenciales |
MQTTException: 2 | Identifier rejected | Client ID vacío, demasiado largo o vetado |
OSError: [Errno 104] ECONNRESET | El broker cerró el socket | Keep-alive perdido, client ID duplicado, TLS en un puerto sin TLS |
OSError: -2 o -202 | Falló la resolución DNS (código según el port) | Wi-Fi aún no listo o nombre de host incorrecto |
MemoryError al conectar con TLS | No hay heap suficiente | gc.collect(), conectar antes, buffers más pequeños |
Los números tras MQTTException son códigos de retorno CONNACK de MQTT 3.1.1; la herramienta de reason codes explica cada uno. Para un recorrido más a fondo, consulta errores MQTT en MicroPython.
umqtt es lo bastante pequeño como para leerlo de una sentada, y esa es su fuerza: en cuanto añades tus propios pings de keep-alive, un bucle de reconexión con backoff, la resuscripción y TLS con una CA real, es un cliente fiable para sensores que cuidan la batería. Haz el prototipo contra el endpoint compartido y luego lleva el mismo código a un broker privado cambiando el host y las credenciales.
Preguntas frecuentes
¿Uso umqtt.simple o umqtt.robust?
umqtt.robust es una subclase mínima de umqtt.simple que se reconecta sola cuando ocurre un error de socket. No reconecta el Wi-Fi ni vuelve a suscribirse por ti, así que muchos proyectos usan umqtt.simple con su propio bucle de reconexión, que da control total sobre Wi-Fi, suscripciones y backoff.
¿umqtt soporta QoS 2 o MQTT 5?
No. umqtt.simple implementa MQTT 3.1.1 solo con QoS 0 y QoS 1. Si necesitas propiedades de MQTT 5 o QoS 2 en un microcontrolador, busca otro cliente o un gateway que traduzca para el dispositivo.
¿Por qué mi cliente MQTT en MicroPython se desconecta al cabo de un minuto?
umqtt no envía pings de keep-alive por sí mismo. Si defines keepalive y luego no publicas nada durante más de 1,5 veces ese valor, el broker cierra la conexión. Llama a client.ping() periódicamente desde tu bucle principal, o usa keepalive=0 si tu broker lo permite.


