O MicroPython traz um cliente MQTT minúsculo chamado umqtt que roda num ESP32, num ESP8266 ou num Raspberry Pi Pico W com poucos kilobytes de RAM. Ele vem em dois sabores: umqtt.simple, um cliente síncrono mínimo, e umqtt.robust, que acrescenta reconexão automática por cima. Este tutorial instala os dois, conecta via Wi-Fi, publica e assina, mantém a conexão viva, se recupera de quedas e explica o que observar com TLS.
O código mira o MicroPython 1.22 ou mais novo e o umqtt.simple atual da micropython-lib. Roda sem mudanças em placas ESP32 e no Pico W; só os números de pino mudam se você ler sensores.
umqtt em resumo
| umqtt.simple | umqtt.robust | |
|---|---|---|
| Versão do MQTT | 3.1.1 | 3.1.1 |
| QoS | 0 e 1 (publish e subscribe) | 0 e 1 |
| Reconexão automática | Não | Sim, em erros de socket |
| Reconexão do Wi-Fi | Não | Não |
| Reassinatura após reconectar | Não | Não (depende de uma sessão persistente) |
| Pings de keep-alive | ping() manual | ping() manual |
| TLS | Sim, via contexto ssl | Igual ao simple |
Os dois são síncronos: wait_msg() bloqueia até chegar uma mensagem, check_msg() retorna na hora. Não existe thread em segundo plano, então o seu loop principal é responsável por tudo.
1. Instale o umqtt
Alguns builds de firmware já incluem o umqtt (o port do ESP8266 geralmente inclui). No ESP32 e no Pico W, instale a partir da micropython-lib com o mpremote no seu computador:
pip install mpremote
mpremote mip install umqtt.simple
mpremote mip install umqtt.robustOu, quando a placa já estiver no Wi-Fi, pelo REPL:
import mip
mip.install("umqtt.simple")
mip.install("umqtt.robust")Confira com from umqtt.simple import MQTTClient: sem ImportError, está tudo pronto.
2. Conecte ao Wi-Fi
O módulo network é o mesmo no ESP32 e no Pico W. Coloque isto em wifi.py para reutilizar em outros arquivos:
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 wlanOs dois chips só suportam redes de 2,4 GHz. Se o status ficar em "conectando" para sempre, verifique a banda e a senha antes de desconfiar do MQTT.
3. Publicar e assinar com 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("recebido", topic, msg)
if msg == b"led/on":
machine.Pin("LED", machine.Pin.OUT).on() # Pico W; no ESP32 use o seu pino
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) # precisa vir 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() # não bloqueia: roda on_message se houver dados
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)Pontos principais:
- Topics e payloads são bytes. Strings costumam funcionar, mas bytes evitam surpresas, e
on_messagesempre recebe bytes. set_callback()antes desubscribe(): caso contrário, o umqtt lança um erro de asserção.check_msg()xwait_msg():wait_msg()bloqueia até algo chegar, o que serve para um receptor de comandos puro, mas trava o seu loop de sensores. Usecheck_msg()quando também publica.- Keep-alive é com você. Com
keepalive=60, o broker derruba o cliente depois de cerca de 90 segundos de silêncio. O loop acima faz ping a cada 30 segundos. Veja MQTT keep alive para as regras exatas de tempo. - Client ID único a partir de
machine.unique_id(), para que duas placas rodando o mesmo arquivo não derrubem uma à outra.
Teste
Abra o cliente MQTT online, conecte em broker.connectmq.com e assine testmqtt/upy/#. Você deve ver a mensagem status retida e um valor de uptime a cada 10 segundos. Publique led/on em testmqtt/upy/<seu-client-id>/cmd e o REPL imprime recebido ....
Veja a placa publicando no navegador
4. Reconectando do jeito certo
O Wi-Fi cai, roteadores reiniciam, brokers reiniciam. Com o umqtt.simple, toda falha de rede aparece como um OSError (tipicamente [Errno 104] ECONNRESET, [Errno 113] ECONNABORTED ou [Errno 110] ETIMEDOUT) na chamada que tocou o socket. Envolva o loop, refaça a conexão e assine de novo:
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) # numa nova clean session as assinaturas somem
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) # só termina lançando OSError
except OSError as e:
print("conexão perdida:", e, "nova tentativa em", delay, "s")
try:
client.disconnect()
except Exception:
pass
time.sleep(delay)
delay = min(delay * 2, 60) # backoff exponencial com tetoO backoff exponencial importa quando você tem muitos dispositivos: se o broker reinicia, uma frota tentando a cada segundo, todos ao mesmo tempo, cria uma tempestade de reconexões. O padrão é explicado em boas práticas de reconexão MQTT.
Para dispositivos que nunca podem travar, adicione um watchdog de hardware: wdt = machine.WDT(timeout=30000) e chame wdt.feed() uma vez por iteração do loop. Se o código bloquear numa chamada de socket, a placa se reinicia sozinha.
E o umqtt.robust?
umqtt.robust.MQTTClient tem a mesma API. Quando publish(), wait_msg() ou check_msg() encontram um OSError, ele chama reconnect(), que tenta connect(False) para sempre com um atraso crescente. Duas limitações:
- Ele nunca mexe no Wi-Fi. Se o ponto de acesso sumiu, ele insiste num socket que não tem como funcionar.
- Ele reconecta com
clean_session=Falsee não refaz as assinaturas. Isso só restaura as assinaturas se o broker guardou a sessão: um broker privado normalmente guarda, mas endpoints de teste compartilhados comobroker.connectmq.comnão mantêm sessões persistentes. Teste o comportamento de sessões persistentes na sua própria instância de broker.
O umqtt.robust serve para um protótipo rápido. Para qualquer coisa que rode sem supervisão, o loop explícito acima é mais fácil de entender.
5. Cuidados com TLS
A porta 1883 envia usuário e senha em texto puro. Para qualquer coisa além de um teste na bancada, conecte na 8883 com TLS. O umqtt.simple atual recebe um ssl.SSLContext:
import ssl, ntptime
ntptime.settime() # a validade do certificado exige um relógio 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()O que costuma pegar as pessoas:
- Versões antigas do umqtt usavam
ssl=True, ssl_params={...}em vez de um contexto. Se aparecerTypeErrorno construtor, confira qual versão está na placa. - Nenhum repositório de CAs no dispositivo. O MicroPython não traz certificados raiz. Copie para a placa a CA raiz que assinou o certificado do seu broker (converta PEM para DER com
openssl x509 -in ca.pem -outform der -out ca.dere envie commpremote cp ca.der :). CERT_NONEé só para depuração. Criptografa o tráfego, mas aceita qualquer servidor, inclusive o de um atacante.- Memória. Um handshake TLS precisa de dezenas de kilobytes de heap livre. ESP32 e Pico W dão conta; o ESP8266 costuma ficar apertado. Chame
gc.collect()antes deconnect()e conecte cedo, antes que a aplicação encha o heap. - Relógio. Sem
ntptime.settime()a placa acha que está no ano 2000 e pode rejeitar um certificado válido como ainda não válido.
Testar uma conexão TLS mostra como verificar a cadeia de certificados do broker pelo notebook antes de depurar no dispositivo.
Erros comuns
| Erro | Significado | Correção |
|---|---|---|
MQTTException: 5 | CONNACK "not authorized" | Usuário errado ou ACL nega o cliente |
MQTTException: 4 | Usuário ou senha inválidos | Verifique as credenciais |
MQTTException: 2 | Identifier rejected | Client ID vazio, longo demais ou bloqueado |
OSError: [Errno 104] ECONNRESET | O broker fechou o socket | Keep-alive perdido, client ID duplicado, TLS numa porta sem TLS |
OSError: -2 ou -202 | Falha na resolução DNS (código depende do port) | Wi-Fi ainda não pronto ou hostname errado |
MemoryError ao conectar com TLS | Heap insuficiente | gc.collect(), conectar mais cedo, buffers menores |
Os números depois de MQTTException são códigos de retorno CONNACK do MQTT 3.1.1; a ferramenta de reason codes explica cada um. Para um passo a passo mais profundo, veja erros MQTT no MicroPython.
O umqtt é pequeno o bastante para ser lido de uma vez, e essa é a sua força: assim que você adiciona os próprios pings de keep-alive, um loop de reconexão com backoff, a reassinatura e TLS com uma CA de verdade, ele vira um cliente confiável para sensores que poupam bateria. Faça o protótipo no endpoint compartilhado e depois leve o mesmo código para um broker privado trocando o host e as credenciais.
Perguntas frequentes
Devo usar umqtt.simple ou umqtt.robust?
umqtt.robust é uma subclasse fina do umqtt.simple que reconecta automaticamente quando ocorre um erro de socket. Ela não reconecta o Wi-Fi nem refaz as assinaturas por você, por isso muitos projetos usam umqtt.simple com um loop de reconexão próprio, que dá controle total sobre Wi-Fi, assinaturas e backoff.
O umqtt suporta QoS 2 ou MQTT 5?
Não. O umqtt.simple implementa MQTT 3.1.1 apenas com QoS 0 e QoS 1. Se você precisa de propriedades do MQTT 5 ou QoS 2 num microcontrolador, procure outro cliente ou um gateway que faça a tradução para o dispositivo.
Por que meu cliente MQTT em MicroPython desconecta depois de cerca de um minuto?
O umqtt não envia pings de keep-alive sozinho. Se você define keepalive e depois não publica nada por mais de 1,5 vez esse valor, o broker fecha a conexão. Chame client.ping() periodicamente no loop principal, ou use keepalive=0 se o seu broker permitir.


