IoT
Wi-Fi, HTTP e MQTT na ESP32 com MicroPython
Wi-Fi com network.WLAN, HTTP com requests e MQTT com umqtt.simple na ESP32: estados da conexão, erros da placa e como testar tudo no simulador.
Por Prof. Rodrigo Faustino · Atualizado em · 12 min de leitura
Até aqui, a ESP32 conversou com o mundo pelos pinos: LEDs, botões, sensores e displays. O que a diferencia de muitas placas de microcontrolador, porém, é o rádio Wi-Fi embutido no chip. Com ele, a placa busca a previsão do tempo na internet, envia leituras de sensores para um painel, recebe comandos do celular. É a base do que se chama Internet das Coisas (IoT).
Este artigo percorre as três camadas desse caminho no MicroPython: o Wi-Fi com o módulo network, as requisições HTTP com o módulo requests e as mensagens MQTT com o umqtt.simple. Em cada uma, os comportamentos da placa que pegam iniciantes — a conexão que não é instantânea, os códigos de erro, o ADC2 que para de funcionar — e como o PyPlaca simula tudo isso com segurança, sem a placa precisar sair do navegador.
O Wi-Fi como uma interface de rede
No MicroPython, o Wi-Fi é a classe WLAN do módulo network. A ESP32 tem duas interfaces Wi-Fi:
- STA (station, estação): a placa se conecta a um roteador, como um celular. É o modo de quase todos os projetos;
- AP (access point, ponto de acesso): a placa cria a própria rede, e outros aparelhos se conectam a ela.
network.WLAN(network.STA_IF) devolve a interface de estação — e sempre o mesmo objeto, por mais vezes que seja chamado. Antes de qualquer coisa, a interface precisa ser ativada com active(True), que liga o rádio. Só então connect() funciona; chamado antes, ele dá um OSError com o motivo em texto (como Wifi Not Started).
import network
import time
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('laboratorio', 'esp32lab')
inicio = time.ticks_ms()
while not wlan.isconnected():
if time.ticks_diff(time.ticks_ms(), inicio) > 10000:
print('não conectou; status =', wlan.status())
break
time.sleep_ms(100)
else:
print('conectado! IP:', wlan.ifconfig()[0])No PyPlaca, a placa simulada "enxerga" uma rede chamada laboratorio, com a senha esp32lab. O botão Wi-Fi da bancada mostra o estado da conexão e deixa o professor trocar o nome e a senha dessa rede. Abra o exemplo e rode: depois de um instante, o Serial mostra o IP que o "roteador" do laboratório deu para a placa.
A conexão não é instantânea
O ponto mais importante do exemplo é o laço de espera. O connect() volta na hora: ele só pede ao sistema do rádio que comece a conectar. A conexão de verdade — encontrar a rede, negociar a senha, receber um IP por DHCP — acontece depois, em segundo plano. Um programa que chama connect() e logo em seguida tenta usar a rede falha.
Por isso todo programa de Wi-Fi espera o isconnected() ficar verdadeiro. E espera com limite de tempo: se a senha estiver errada ou a rede não existir, o laço sem limite ficaria preso para sempre. O exemplo usa ticks_diff, a mesma técnica do artigo Programar sem travar, e o else do while só roda se o laço terminar sem break.
No simulador, a conexão leva cerca de 1,5 s de tempo virtual. É um valor de referência escolhido para a simulação: o tempo real depende do roteador e ainda vai ser medido numa placa do laboratório.
Os estados da conexão
wlan.status() diz em que pé está a conexão, com as constantes do MicroPython (os números são os do ESP-IDF, o sistema da Espressif por baixo do MicroPython):
| Constante | Valor | Significado |
|---|---|---|
network.STAT_IDLE | 1000 | parado, sem tentar conectar |
network.STAT_CONNECTING | 1001 | conectando |
network.STAT_GOT_IP | 1010 | conectado e com IP |
network.STAT_NO_AP_FOUND | 201 | a rede não foi encontrada |
network.STAT_WRONG_PASSWORD | 202 | senha errada |
network.STAT_CONNECT_FAIL | 203 | falhou por outro motivo |
wlan.status() no MicroPython da ESP32: o connect() volta na hora, e a conexão acontece depois — por isso o programa espera o STAT_GOT_IP.Dois detalhes do MicroPython real que o simulador reproduz: o motivo de uma falha não é zerado só por chamar connect() de novo; e depois de disconnect() o status volta a STAT_IDLE. Troque a senha do exemplo por uma errada e rode de novo para ver o 202.
Procurando redes
wlan.scan() lista as redes ao alcance. Cada item é uma tupla com o nome (em bytes), o endereço do roteador, o canal, a força do sinal (RSSI, em dBm — quanto mais perto de zero, melhor), o tipo de segurança e se a rede é oculta. A lista vem do sinal mais forte para o mais fraco, e o scan() bloqueia o programa enquanto procura.
import network
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
for ssid, bssid, canal, rssi, seguranca, oculta in wlan.scan():
print(ssid.decode(), 'canal', canal, 'sinal', rssi, 'dBm')O ADC2 deixa de funcionar
Um efeito colateral do Wi-Fi pega muita gente: com o rádio ligado, os pinos do ADC2 (GPIOs 0, 2, 4, 12 a 15 e 25 a 27) não conseguem mais fazer leituras analógicas, porque o sistema do Wi-Fi segura esse conversor para si. A leitura dá OSError: [Errno 116] ETIMEDOUT, e o simulador mostra o aviso adc2-wifi. A regra, explicada no artigo sobre o ADC da ESP32, é simples: projetos com Wi-Fi leem sensores analógicos no ADC1, os GPIOs 32 a 39.
Da ementa ao semestre planejado
- Plano de ensino e calendário montados sozinhos
- Diário de aulas pronto para colar no SIGA
- Slides e listas de exercícios com gabarito
Versão Community grátis · Windows
HTTP com requests
Com a placa na rede, o caminho mais comum para buscar dados é o HTTP, o mesmo protocolo das páginas da web. O firmware do MicroPython para a ESP32 traz o módulo requests (também importável como urequests), da biblioteca oficial micropython-lib. O simulador roda o mesmo arquivo original, sem adaptações, sobre uma pilha de rede simulada.
import network
import time
import requests
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('laboratorio', 'esp32lab')
while not wlan.isconnected():
time.sleep_ms(100)
r = requests.get('http://api.laboratorio.test/clima')
print('status:', r.status_code)
dados = r.json()
print('temperatura:', dados['temperatura'], '°C')
print('umidade:', dados['umidade'], '%')
r.close()A resposta tem status_code (200 é "deu certo"), text (o corpo como texto), content (como bytes) e json(), que transforma o corpo em dicionário quando o servidor manda JSON. O close() no fim devolve a conexão: a ESP32 tem pouca memória e poucos sockets, e um programa que faz muitos pedidos sem fechar acaba ficando sem.
Para enviar dados, o requests.post() aceita um dicionário no argumento json=:
r = requests.post('http://api.laboratorio.test/leituras', json={'temperatura': 23.4})
print(r.status_code) # 201: criado
r.close()O que é "a internet" no simulador
O código do aluno não pode sair buscando qualquer endereço da internet a partir do navegador — seria um risco para o aluno e para a plataforma. Por isso a rede do simulador funciona assim:
- respostas prontas (mocks): endereços como
api.laboratorio.testrespondem com dados fixos, sem sair do navegador. Cada pedido aparece numa linha curta no Serial, com o método, o endereço e o status; - lista permitida: só uns poucos domínios aprovados pela plataforma são buscados de verdade, e só quando a atividade permite;
- todo o resto: o nome não é encontrado, como numa rede sem internet. O
getaddrinfoda placa devolveOSError: -202e o simulador mostra o aviso rede-fora-da-lista.
O mesmo -202 aparece se o programa tentar usar um nome antes de o Wi-Fi conectar — é a placa real dizendo "não consegui resolver esse nome". Se o servidor demora mais que o tempo-limite do socket, o erro é OSError: [Errno 116] ETIMEDOUT.
HTTP e programas que não travam
Um requests.get() bloqueia o programa até a resposta chegar. Num projeto que também pisca LEDs e lê botões, isso significa que tudo para durante o pedido. Duas práticas ajudam: fazer pedidos raramente (a cada minuto, não a cada volta do laço), controlados por ticks_diff, e preferir o MQTT quando a placa precisa trocar mensagens com frequência.
MQTT: mensagens por tópicos
O HTTP funciona no modelo "pergunta e resposta": a placa pergunta, o servidor responde. Para IoT, isso tem um problema — o servidor não consegue avisar a placa de nada; ela teria de perguntar o tempo todo. O MQTT resolve isso com outro modelo, o de publicar e assinar:
- existe um servidor central, o broker;
- cada dispositivo se conecta ao broker e mantém a conexão aberta;
- quem quer enviar algo publica uma mensagem num tópico, um nome como
lab/sala1/temperatura; - quem quer receber assina o tópico, e o broker entrega a mensagem a todos os assinantes.
Ninguém fala direto com ninguém. A placa que publica a temperatura não sabe se quem lê é um celular, um painel ou dez outros dispositivos — e não precisa saber.
Tópicos e curingas
Tópicos são hierárquicos, separados por /. Ao assinar, dá para usar curingas:
+substitui um nível:lab/+/temperaturarecebelab/sala1/temperaturaelab/sala2/temperatura;#substitui todos os níveis seguintes:lab/#recebe tudo que começa comlab/.
Ao publicar, curingas não são permitidos. Uma mensagem pode ser retida (retain=True): o broker guarda a última mensagem do tópico e a entrega a quem assinar depois — útil para "o estado atual do LED", que um painel recém-aberto precisa saber.
umqtt.simple na prática
O firmware da ESP32 traz o cliente umqtt.simple, também da micropython-lib, e o simulador usa o original. O exemplo abaixo conecta no Wi-Fi, assina o tópico lab/led para receber comandos e publica um contador em lab/contador a cada 2 segundos — sem travar, com check_msg() e ticks_diff:
import network
import time
from machine import Pin
from umqtt.simple import MQTTClient
wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect('laboratorio', 'esp32lab')
while not wlan.isconnected():
time.sleep_ms(100)
led = Pin(2, Pin.OUT)
def chegou(topico, msg):
print('chegou em', topico, ':', msg)
if msg == b'liga':
led.on()
elif msg == b'desliga':
led.off()
cliente = MQTTClient('esp32-aluno', 'broker.laboratorio.test')
cliente.set_callback(chegou)
cliente.connect()
cliente.subscribe(b'lab/led')
ultimo = time.ticks_ms()
n = 0
while n < 15:
cliente.check_msg() # olha se chegou mensagem, sem esperar
if time.ticks_diff(time.ticks_ms(), ultimo) >= 2000:
ultimo = time.ticks_add(ultimo, 2000)
n += 1
cliente.publish(b'lab/contador', str(n).encode())
time.sleep_ms(50)
cliente.disconnect()Os passos são sempre os mesmos: criar o MQTTClient com um identificador único e o endereço do broker, set_callback() com a função que recebe as mensagens, connect(), subscribe() e, no laço, check_msg(). O callback recebe o tópico e a mensagem como bytes — por isso a comparação é com b'liga', e não com 'liga'.
check_msg() é a versão que não bloqueia: se não chegou nada, volta na hora. Existe também wait_msg(), que fica parado até chegar uma mensagem — prático num programa que só espera comandos, desastroso num que também precisa fazer outras coisas.
O broker do simulador
No PyPlaca, o broker roda no próprio navegador e fala MQTT 3.1.1 de verdade, byte a byte: conexão, publicação com QoS 0, 1 e 2, assinaturas com curingas, mensagens retidas, sessão persistente e "última vontade". O endereço broker.laboratorio.test aponta para ele, e os brokers públicos que os tutoriais usam — test.mosquitto.org, broker.hivemq.com, broker.emqx.io e mqtt.eclipseprojects.io — também: nenhum broker da internet é contatado. Assim, um programa copiado de um tutorial roda igual, e nenhuma mensagem de aluno vai parar num servidor público.
A aba MQTT da bancada mostra os dispositivos conectados, o que cada um assinou, os tópicos com a última mensagem e as mensagens recentes. Ela também tem o formulário Publicar como outro dispositivo: rode o exemplo, publique liga no tópico lab/led e veja o LED azul da placa acender.

QoS, Keep Alive e conexões que caem
Três conceitos do MQTT que aparecem logo nos primeiros projetos:
- QoS (qualidade de serviço): 0 é "manda e esquece"; 1 garante que a mensagem chega pelo menos uma vez (o broker confirma); 2 garante exatamente uma vez, com mais idas e vindas. Para leituras de sensor, QoS 0 basta;
- Keep Alive: o cliente promete mandar algum pacote a cada tantos segundos. Se passar uma vez e meia esse tempo sem notícias, o broker considera a conexão morta e a derruba;
- última vontade (last will): uma mensagem que o cliente deixa combinada ao conectar e que o broker publica se ele cair sem se despedir — o jeito clássico de um painel mostrar "placa offline".
Quando a conexão cai, as leituras do socket começam a falhar. O umqtt.simple não tenta reconectar; para isso existe o umqtt.robust, também no firmware, que reconecta sozinho.
Juntando sensores, rede e tempo
Um projeto de IoT completo combina tudo o que a série mostrou: ler um sensor no ADC1 ou pelo DHT22, organizar as tarefas com ticks_diff para nada travar, publicar as leituras por MQTT de tempos em tempos e reagir a comandos que chegam pelo check_msg(). Um esqueleto típico:
- conectar no Wi-Fi com limite de tempo e mensagem clara se falhar;
- conectar no broker e assinar os tópicos de comando;
- no laço:
check_msg(), ler os sensores no seu ritmo, publicar no seu ritmo, pausa curta; - tratar
OSErrorna rede (Wi-Fi caiu, broker fora do ar) sem derrubar o resto do programa.
Antes de levar para a placa real, troque o broker do laboratório pelo endereço do broker da sua escola ou do seu projeto — o resto do código continua igual. E lembre que, na placa de verdade, a rede é de verdade: senhas não vão para repositórios públicos, e tópicos com nomes únicos evitam que a sua placa receba as mensagens da placa do colega.
A IA que lembra da sua vida e mora no seu computador
- Memória de longo prazo com a fonte de cada resposta
- Funciona offline: seus dados ficam no seu PC
- Lê PDFs, slides, fotos e gravações de reunião
Plano Essencial grátis para sempre
Diagnosticando problemas de rede
Quando um programa de IoT não funciona, vale seguir as camadas de baixo para cima, como na bancada:
- o Wi-Fi conectou? Imprima
wlan.status(): 201 é rede não encontrada, 202 é senha errada. No simulador, o botão Wi-Fi da bancada mostra o estado e o IP; - o nome do servidor resolve? Um
OSError: -202antes de qualquer resposta indica que o nome não foi encontrado — Wi-Fi ainda desconectado, nome digitado errado ou, no simulador, um endereço fora da lista da atividade; - o servidor respondeu? Olhe o
status_codedo HTTP antes de chamarjson(). Um 404 tem corpo, mas não o que você esperava; - no MQTT, a conexão aceitou? O
connect()levanta erro se o broker recusar (usuário e senha errados, por exemplo); - as mensagens chegam? Confira se o tópico assinado é exatamente o publicado — maiúsculas, barras e espaços contam — e se o laço chama
check_msg()com frequência; - o callback recebe bytes: comparar
msg == 'liga'(texto) comb'liga'(bytes) é falso, e o comando parece ignorado.
No simulador, cada pedido HTTP aparece no Serial e na janela da rede simulada, e a aba MQTT mostra quem está conectado e o que foi publicado. É o equivalente a ter um analisador de rede ao lado da bancada.
Para continuar
Este é o último artigo da série, e as três últimas trilhas da plataforma são a continuação direta dele. A trilha Wi-Fi e HTTP refaz a primeira metade deste texto com lições e desafios corrigidos automaticamente: conectar com limite de tempo, entender o status(), buscar e interpretar JSON, enviar leituras com POST e não ficar esperando para sempre por um servidor lento. A trilha MQTT pratica publicar e assinar, tópicos e curingas, receber comandos sem travar com check_msg(), Keep Alive e última vontade. E o projeto final junta tudo numa estação meteorológica: DHT22, OLED, configuração por HTTP e leituras no MQTT.
Para escolher os pinos certos quando o projeto sair do simulador e for para a bancada, consulte a ficha da ESP32 DevKit V1 e o mapa de pinos — e lembre do ADC1 para os sensores analógicos.
Para praticar no PyPlaca
Trilhas
- Trilha 8: Wi-Fi e HTTP
- Trilha 9: MQTT: publicar e assinar
- Trilha 10: Projeto final: estação meteorológica IoT
Lições
- Wi-Fi: a placa entra na rede
- HTTP: a placa faz um pedido, o servidor responde
- JSON: os dados que o programa entende
- MQTT: publicar e assinar
- check_msg(): receber sem travar
- O ADC: de tensão para número