O MicroPython umqtt no ESP32 oferece MQTT em poucas linhas de Python, sem etapa de compilação e com um prompt interativo para depuração. Este tutorial conecta um ESP32 ao servidor MQTT público broker.connectmq.com com umqtt.simple, adiciona reconexão automática com umqtt.robust, trata comandos recebidos sem bloquear e explica os limites que você precisa conhecer antes de confiar nesses clientes em produção.
Por que MicroPython para dispositivos MQTT
C++ com o core Arduino é mais rápido e tem mais bibliotecas, mas o MicroPython ganha em velocidade de iteração. Você edita um arquivo, copia para a placa e executa em segundos. O REPL permite testar Wi-Fi, sensores e MQTT de forma interativa, o que é inestimável quando você está tentando descobrir por que algo não funciona.
O pacote umqtt da micropython-lib fornece dois módulos:
umqtt.simple: um cliente MQTT 3.1.1 mínimo. Conectar, publicar, assinar, ping, last will. Nada acontece automaticamente.umqtt.robust: uma subclasse fina desimpleque captura erros de socket empublish(),wait_msg()e algumas outras chamadas e reconecta automaticamente.
Ambos são pequenos o suficiente para caber com folga em qualquer ESP32.
Preparando a placa
- Grave um firmware MicroPython atual para o seu ESP32 a partir da página oficial de downloads, usando
esptool.py. - Instale o
mpremoteno seu computador:pip install mpremote. - Verifique se o
umqttjá está presente. Alguns builds de firmware o incluem, outros não. Seimport umqtt.simplefalhar no REPL, instale-o com o mip:
mpremote connect auto mip install umqtt.simple
mpremote connect auto mip install umqtt.robust
O mip precisa que a placa esteja online. Se ela ainda não estiver conectada ao Wi-Fi, execute a instalação a partir de um script que conecte primeiro, ou copie os arquivos do módulo manualmente com mpremote cp. A documentação de gerenciamento de pacotes do MicroPython descreve todas as opções.
Conectando ao Wi-Fi
Coloque isto em boot.py para que seja executado a cada inicialização:
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()
Publicando com umqtt.simple
O primeiro script se conecta ao servidor MQTT público em broker.connectmq.com e publica uma leitura a cada cinco segundos. O client ID é montado a partir do ID único do chip, para que nunca fique vazio nem colida com outra 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 # leitura aproximada do sensor interno
payload = '{"temp":%.1f,"uptime":%d}' % (temp_c, time.ticks_ms() // 1000)
client.publish(PREFIX + b"/telemetry", payload.encode())
time.sleep(5)
Em variantes do ESP32 sem raw_temperature(), substitua essa linha pela leitura de um sensor real. Acompanhe as mensagens pelo seu computador:
mosquitto_sub -h broker.connectmq.com -p 1883 -i pc-2b7e95 -t 'connectmq/demo/2b7e95/#' -v
Observe que topics e payloads são bytes. O umqtt aceita str em muitos lugares, mas usar bytes de forma consistente evita surpresas, principalmente nos callbacks, que sempre recebem bytes.
Recebendo comandos sem bloquear
Para receber mensagens, registre um callback e faça a assinatura. Depois, você precisa chamar regularmente wait_msg(), que bloqueia até chegar uma mensagem, ou check_msg(), que retorna imediatamente se não houver nada esperando. Use check_msg() em um loop que também faz outras tarefas:
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() # trata qualquer mensagem pendente
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() # mantém a conexão viva quando ociosa
last_ping = now
time.sleep_ms(50)
Teste:
mosquitto_pub -h broker.connectmq.com -p 1883 -i pc-2b7e95-pub -t connectmq/demo/2b7e95/led/set -m ON
Dois detalhes passam despercebidos com facilidade. Primeiro, o umqtt.simple não envia pings de keepalive por conta própria. Se você define um keepalive e publica com menos frequência que isso, precisa chamar ping(), como faz o loop acima, ou o broker vai derrubar a conexão. Segundo, set_callback() deve ser chamado antes de subscribe().
Reconexão automática com umqtt.robust
O umqtt.simple lança OSError quando a conexão cai, e seu script morre se você não tratar o erro. O umqtt.robust envolve as chamadas principais e reconecta com um pequeno intervalo entre as tentativas. A única mudança é o import:
from umqtt.robust import MQTTClient
Há uma pegadinha. O cliente robust reconecta o socket, mas não refaz as assinaturas para você, e com uma clean session o broker esquece suas assinaturas. Um padrão confiável é encapsular a etapa de conexão e chamá-la tanto na inicialização quanto após qualquer erro que você mesmo capture:
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()
# ... publicação 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 autônomos, como um sensor em uma fazenda distante do escritório, adicione machine.WDT(timeout=30000) e alimente-o no loop, para que um travamento total reinicie a placa.
Estruturando um projeto MicroPython umqtt no ESP32
Quando um script passa de algumas dezenas de linhas, divida-o como faria em qualquer projeto Python. Uma organização que funciona bem na placa:
boot.pyconecta o Wi-Fi e nada mais, para que um bug na aplicação nunca impeça seu acesso ao REPL.config.pyguarda host do broker, porta, prefixo de topic e credenciais. Mantenha-o fora do controle de versão e copie-o para cada placa separadamente.main.pyimportaconfig, cria o cliente e executa o loop principal dentro de umtry/exceptque registra a exceção e chamamachine.reset()após um breve intervalo.
Dois hábitos economizam muito tempo de depuração. Estruture o loop principal de modo que pressionar Ctrl+C no REPL o interrompa de forma limpa com KeyboardInterrupt, em vez de ser capturado pelo seu tratador genérico de erros. E chame gc.collect() periodicamente se você monta strings de payload no loop; o heap do MicroPython é pequeno e a fragmentação aparece como MemoryError depois de horas de execução, não de minutos.
Publicar JSON é mais fácil com o módulo nativo json: client.publish(topic, json.dumps({"temp": 22.4})). A interpretação de comandos funciona do mesmo jeito com json.loads(msg) dentro do callback, envolto em um try para que uma mensagem malformada não derrube o dispositivo.
Limitações do umqtt que você precisa conhecer
| Limitação | Impacto | Alternativa |
|---|---|---|
| Apenas MQTT 3.1.1 | Sem propriedades MQTT 5, reason codes ou recursos de assinatura compartilhada | Use outro cliente se precisar de MQTT 5 |
| Apenas QoS 0 e 1 | QoS 2 não é suportado | QoS 1 basta para a maioria das telemetrias e comandos |
| Sem pings de keepalive automáticos | Conexões ociosas são derrubadas | Chame ping() periodicamente |
| Chamadas de socket bloqueantes | connect() e wait_msg() bloqueiam o script |
Use check_msg(); considere um cliente baseado em asyncio para dispositivos complexos |
| Sem reassinatura na reconexão | Os comandos deixam de chegar após uma reconexão | Refaça as assinaturas na sua própria função de reconexão |
| Sem fila de mensagens offline | Mensagens publicadas enquanto desconectado são perdidas | Armazene as leituras em buffer por conta própria, se necessário |
Para dispositivos que precisam fazer várias coisas ao mesmo tempo, existem clientes MQTT da comunidade baseados em asyncio que cuidam de keepalives e reconexão em segundo plano. Para sensores e atuadores simples, o umqtt continua sendo a opção mais leve.
Usando TLS na porta 8883
Em um broker privado, criptografe a conexão. Versões recentes do umqtt.simple aceitam um argumento ssl que recebe um SSLContext; versões mais antigas usam ssl=True com um dicionário ssl_params, então confira a versão que você instalou.
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 raiz do 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()
Copie antes o certificado ISRG Root X1 para a placa com mpremote cp. O TLS exige uma boa quantidade de RAM; placas com PSRAM lidam com ele com mais folga. Use exatamente o client ID, o usuário e a senha gerados para o seu broker, porque o ConnectMQ vincula o client ID às credenciais emitidas.
Guias relacionados
Se você prefere trabalhar em C++, o guia de projeto MQTT com ESP32 e PlatformIO mostra uma configuração Arduino bem estruturada, e o guia de Last Will MQTT no ESP32 explica o padrão de status usado no primeiro script. O tutorial de IoT com ESP32 cobre o caminho completo do dispositivo ao dashboard e, se você é novo no protocolo, comece por o que faz um broker MQTT.
Conecte seus dispositivos MicroPython ao ConnectMQ
Todos os exemplos aqui funcionam com o servidor MQTT público (gratuito), sem cadastro, mas ele serve para testes e protótipos, não para dados de produção. Quando for além dos experimentos, o ConnectMQ oferece um broker MQTT gerenciado privado, com TLS na porta 8883 e um cliente MQTT no navegador para verificar o que sua placa envia; o guia de broker MQTT na nuvem compara as opções de hospedagem. Crie o seu no plano gratuito Serverless em app.connectmq.com, sem cartão de crédito.
Perguntas frequentes
O umqtt suporta MQTT 5?
Não. Tanto o umqtt.simple quanto o umqtt.robust implementam MQTT 3.1.1. Brokers que suportam MQTT 5, como o ConnectMQ, continuam aceitando conexões 3.1.1.
Por que meu cliente MQTT em MicroPython desconecta depois de um minuto?
Você definiu um keepalive, mas não está enviando nada dentro desse intervalo. O umqtt não faz ping automaticamente, então chame client.ping() regularmente ou publique com mais frequência que o keepalive.
Qual é a diferença entre wait_msg() e check_msg()?
wait_msg() bloqueia até chegar uma mensagem. check_msg() processa uma mensagem pendente, se houver, e retorna imediatamente, o que é ideal para loops que também leem sensores.
O umqtt pode usar QoS 2?
Não. Ele suporta QoS 0 e 1, o que cobre a maioria dos casos de uso em dispositivos.



