MicroPython umqtt en ESP32 te da MQTT en unas pocas líneas de Python, sin paso de compilación y con un prompt interactivo para depurar. Este tutorial conecta un ESP32 a broker.connectmq.com, el servidor MQTT público gratuito de ConnectMQ, con umqtt.simple, añade reconexión automática con umqtt.robust, gestiona comandos entrantes sin bloquear y explica los límites que debes conocer antes de confiar en estos clientes en producción.
Por qué MicroPython para dispositivos MQTT
C++ con el core de Arduino es más rápido y tiene más librerías, pero MicroPython gana en velocidad de iteración. Editas un archivo, lo copias a la placa y lo ejecutas en segundos. El REPL te permite probar Wi-Fi, sensores y MQTT de forma interactiva, algo muy valioso cuando intentas averiguar por qué algo no funciona.
El paquete umqtt de micropython-lib ofrece dos módulos:
umqtt.simple: un cliente MQTT 3.1.1 mínimo. Conectar, publicar, suscribirse, ping, last will. Nada ocurre de forma automática.umqtt.robust: una subclase ligera desimpleque captura errores de socket enpublish(),wait_msg()y algunas otras llamadas y se reconecta automáticamente.
Ambos son lo bastante pequeños como para caber con holgura en cualquier ESP32.
Preparar la placa
- Graba un firmware de MicroPython actual para tu ESP32 desde la página oficial de descargas, usando
esptool.py. - Instala
mpremoteen tu ordenador:pip install mpremote. - Comprueba si
umqttya está presente. Algunas compilaciones del firmware lo incluyen y otras no. Siimport umqtt.simplefalla en el REPL, instálalo con mip:
mpremote connect auto mip install umqtt.simple
mpremote connect auto mip install umqtt.robust
mip necesita que la placa esté conectada a internet. Si todavía no está conectada al Wi-Fi, ejecuta la instalación desde un script que se conecte primero, o copia los archivos del módulo manualmente con mpremote cp. La documentación de gestión de paquetes de MicroPython describe todas las opciones.
Conectarse al Wi-Fi
Pon esto en boot.py para que se ejecute en cada arranque:
import network
import time
WIFI_SSID = "YOUR_WIFI"
WIFI_PASS = "YOUR_WIFI_PASSWORD"
def connect_wifi(timeout=15):
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
if not wlan.isconnected():
wlan.connect(WIFI_SSID, WIFI_PASS)
start = time.time()
while not wlan.isconnected():
if time.time() - start > timeout:
raise RuntimeError("Wi-Fi connection timed out")
time.sleep(0.25)
print("Wi-Fi OK", wlan.ifconfig()[0])
return wlan
connect_wifi()
Publicar con umqtt.simple
El primer script se conecta al área pública de broker.connectmq.com y publica una lectura cada cinco segundos. El client ID se construye a partir del ID único del chip, de modo que nunca está vacío ni coincide con el de otra placa.
import time
import ubinascii
import machine
import esp32
from umqtt.simple import MQTTClient
BROKER = "broker.connectmq.com"
PREFIX = b"connectmq/demo/2b7e95"
CLIENT_ID = b"mpy-" + ubinascii.hexlify(machine.unique_id())
client = MQTTClient(CLIENT_ID, BROKER, port=1883, keepalive=30)
client.set_last_will(PREFIX + b"/status", b"offline", retain=True, qos=1)
client.connect()
client.publish(PREFIX + b"/status", b"online", retain=True, qos=1)
print("Connected as", CLIENT_ID)
while True:
temp_c = (esp32.raw_temperature() - 32) / 1.8 # lectura aproximada del sensor interno
payload = '{"temp":%.1f,"uptime":%d}' % (temp_c, time.ticks_ms() // 1000)
client.publish(PREFIX + b"/telemetry", payload.encode())
time.sleep(5)
En variantes de ESP32 sin raw_temperature(), sustituye esa línea por la lectura de un sensor real. Observa los mensajes desde tu ordenador:
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-2b7e95 -t 'connectmq/demo/2b7e95/#' -v
Ten en cuenta que los topics y los payloads son bytes. umqtt acepta str en muchos sitios, pero usar bytes de forma coherente evita sorpresas, sobre todo en los callbacks, que siempre reciben bytes.
Recibir comandos sin bloquear
Para recibir mensajes, registra un callback y suscríbete. Después debes llamar con regularidad a wait_msg(), que bloquea hasta que llega un mensaje, o a check_msg(), que retorna de inmediato si no hay nada pendiente. Usa check_msg() en un bucle que también haga otras tareas:
import time
import ubinascii
import machine
from umqtt.simple import MQTTClient
BROKER = "broker.connectmq.com"
PREFIX = b"connectmq/demo/2b7e95"
CLIENT_ID = b"mpy-" + ubinascii.hexlify(machine.unique_id())
led = machine.Pin(2, machine.Pin.OUT)
def on_message(topic, msg):
print("Received", topic, msg)
if topic == PREFIX + b"/led/set":
led.value(1 if msg == b"ON" else 0)
client.publish(PREFIX + b"/led/state", msg, retain=True)
client = MQTTClient(CLIENT_ID, BROKER, port=1883, keepalive=30)
client.set_callback(on_message)
client.connect()
client.subscribe(PREFIX + b"/led/set", qos=1)
last_ping = time.ticks_ms()
last_publish = time.ticks_ms()
while True:
client.check_msg() # procesa cualquier mensaje pendiente
now = time.ticks_ms()
if time.ticks_diff(now, last_publish) > 10000:
client.publish(PREFIX + b"/telemetry", b'{"alive":true}')
last_publish = now
last_ping = now
elif time.ticks_diff(now, last_ping) > 20000:
client.ping() # mantiene viva la conexión cuando está inactiva
last_ping = now
time.sleep_ms(50)
Pruébalo:
mosquitto_pub -h broker.connectmq.com -p 1883 -i pc-2b7e95-pub -t connectmq/demo/2b7e95/led/set -m ON
Hay dos detalles fáciles de pasar por alto. Primero, umqtt.simple no envía pings de keepalive por sí solo. Si configuras un keepalive y publicas con menos frecuencia, debes llamar a ping(), como hace el bucle anterior, o el broker te desconectará. Segundo, set_callback() debe llamarse antes de subscribe().
Reconexión automática con umqtt.robust
umqtt.simple lanza OSError cuando se cae la conexión, y tu script termina salvo que gestiones la excepción. umqtt.robust envuelve las llamadas principales y se reconecta con una breve espera entre intentos. El import es el único cambio:
from umqtt.robust import MQTTClient
Hay una trampa. El cliente robust reconecta el socket, pero no vuelve a suscribirse por ti, y con una sesión limpia el broker olvida tus suscripciones. Un patrón fiable es encapsular el paso de conexión y llamarlo tanto al arrancar como después de cualquier error que captures tú mismo:
def connect_and_subscribe():
client.connect(clean_session=True)
client.subscribe(PREFIX + b"/led/set", qos=1)
client.publish(PREFIX + b"/status", b"online", retain=True, qos=1)
connect_and_subscribe()
while True:
try:
client.check_msg()
# ... publicación periódica ...
except OSError as e:
print("MQTT error", e, "- reconnecting")
time.sleep(5)
try:
connect_and_subscribe()
except OSError:
pass
time.sleep_ms(50)
Para dispositivos realmente desatendidos, añade machine.WDT(timeout=30000) y aliméntalo en el bucle, de modo que un bloqueo total reinicie la placa.
Estructurar un proyecto MicroPython umqtt en ESP32
Cuando un script supera unas pocas decenas de líneas, divídelo como harías con cualquier proyecto de Python. Una estructura que funciona bien en la placa:
boot.pyconecta el Wi-Fi y nada más, para que un fallo en la aplicación nunca te deje sin acceso al REPL.config.pycontiene el host del broker, el puerto, el prefijo de topic y las credenciales. Mantenlo fuera del control de versiones y cópialo a cada placa por separado.main.pyimportaconfig, crea el cliente y ejecuta el bucle principal dentro de untry/exceptque registra la excepción y llama amachine.reset()tras una breve espera.
Dos hábitos ahorran mucho tiempo de depuración. Estructura el bucle principal de forma que pulsar Ctrl+C en el REPL lo detenga limpiamente con KeyboardInterrupt en lugar de que lo capture tu gestor de errores genérico. Y llama a gc.collect() periódicamente si construyes cadenas de payload en el bucle; el heap de MicroPython es pequeño y la fragmentación se manifiesta como MemoryError tras horas de funcionamiento, no minutos.
Publicar JSON es más sencillo con el módulo json integrado: client.publish(topic, json.dumps({"temp": 22.4})). Analizar comandos funciona igual con json.loads(msg) dentro del callback, envuelto en un try para que un mensaje mal formado no pueda bloquear el dispositivo.
Limitaciones de umqtt que debes conocer
| Limitación | Impacto | Solución |
|---|---|---|
| Solo MQTT 3.1.1 | Sin propiedades, reason codes ni funciones de suscripción compartida de MQTT 5 | Usa otro cliente si necesitas MQTT 5 |
| Solo QoS 0 y 1 | QoS 2 no está soportado | QoS 1 basta para la mayoría de telemetría y comandos |
| Sin pings de keepalive automáticos | Las conexiones inactivas se cierran | Llama a ping() periódicamente |
| Llamadas de socket bloqueantes | connect() y wait_msg() bloquean el script |
Usa check_msg(); considera un cliente basado en asyncio para dispositivos complejos |
| Sin resuscripción al reconectar | Los comandos dejan de llegar tras una reconexión | Vuelve a suscribirte en tu propia función de reconexión |
| Sin cola de mensajes offline | Los mensajes publicados durante la desconexión se pierden | Almacena tú mismo las lecturas en búfer si lo necesitas |
Para dispositivos que necesitan hacer varias cosas a la vez existen clientes MQTT comunitarios basados en asyncio que gestionan los keepalives y la reconexión en segundo plano. Para sensores y actuadores sencillos, umqtt sigue siendo la opción más ligera.
Usar TLS en el puerto 8883
Con un broker privado, cifra la conexión. Las versiones recientes de umqtt.simple aceptan un argumento ssl que recibe un SSLContext; las versiones antiguas usan en su lugar ssl=True con un diccionario ssl_params, así que comprueba la versión que has instalado.
import ssl
from umqtt.simple import MQTTClient
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="isrg-root-x1.der") # CA raíz de Let's Encrypt
client = MQTTClient(
b"YOUR_CLIENT_ID",
"broker.connectmq.com",
port=8883,
user=b"YOUR_USERNAME",
password=b"YOUR_PASSWORD",
keepalive=30,
ssl=ctx,
)
client.connect()
Copia primero el certificado ISRG Root X1 a la placa con mpremote cp. TLS necesita bastante RAM; las placas con PSRAM lo gestionan con más holgura. Usa exactamente el client ID, el usuario y la contraseña generados para tu broker, porque ConnectMQ vincula el client ID a las credenciales emitidas.
Guías relacionadas
Si prefieres trabajar en C++, la guía de proyectos MQTT con ESP32 y PlatformIO muestra una configuración de Arduino bien estructurada, y la guía de Last Will MQTT en ESP32 explica el patrón de estado usado en el primer script. El tutorial de IoT con ESP32 cubre el recorrido completo del dispositivo al dashboard y, si el protocolo es nuevo para ti, empieza por qué hace un broker MQTT.
Conecta tus dispositivos MicroPython a ConnectMQ
Todos los ejemplos funcionan con el servidor MQTT público gratuito sin necesidad de registro, pensado para pruebas y prototipos, no para datos de producción. Cuando vayas más allá de los experimentos, ConnectMQ te ofrece un broker MQTT gestionado, un broker MQTT en la nube privado con TLS en el puerto 8883 y un cliente MQTT en el navegador para comprobar lo que envía tu placa. Crea uno en el plan gratuito Serverless en app.connectmq.com, sin tarjeta de crédito.
Preguntas frecuentes
¿umqtt es compatible con MQTT 5?
No. Tanto umqtt.simple como umqtt.robust implementan MQTT 3.1.1. Los brokers compatibles con MQTT 5, como ConnectMQ, siguen aceptando conexiones 3.1.1.
¿Por qué mi cliente MQTT de MicroPython se desconecta al cabo de un minuto?
Has configurado un keepalive pero no envías nada dentro de ese intervalo. umqtt no hace ping automáticamente, así que llama a client.ping() con regularidad o publica con más frecuencia que el keepalive.
¿Cuál es la diferencia entre wait_msg() y check_msg()?
wait_msg() bloquea hasta que llega un mensaje. check_msg() procesa un mensaje pendiente si lo hay y retorna de inmediato, lo que encaja con bucles que también leen sensores.
¿Puede umqtt usar QoS 2?
No. Admite QoS 0 y 1, lo que cubre la mayoría de casos de uso en dispositivos.



