Pular para o conteúdo

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):

ConstanteValorSignificado
network.STAT_IDLE1000parado, sem tentar conectar
network.STAT_CONNECTING1001conectando
network.STAT_GOT_IP1010conectado e com IP
network.STAT_NO_AP_FOUND201a rede não foi encontrada
network.STAT_WRONG_PASSWORD202senha errada
network.STAT_CONNECT_FAIL203falhou por outro motivo
Diagrama dos estados da conexão Wi-Fi da ESP32: depois de connect, o status vai para STAT_CONNECTING (1001) e então para STAT_GOT_IP (1010) com o IP do roteador, ou para STAT_WRONG_PASSWORD (202) com a senha errada, ou para STAT_NO_AP_FOUND (201) se a rede não existe. disconnect leva a STAT_IDLE (1000).
Os estados do 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.

FastTeacherPara professores

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
Conhecer o FastTeacher

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.test respondem 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 getaddrinfo da placa devolve OSError: -202 e 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.

Diagrama do MQTT: a ESP32 publica no tópico lab/contador e assina lab/led; no centro, o broker; à direita, um celular que assina lab/contador e publica liga no tópico lab/led. Setas mostram as mensagens passando sempre pelo broker.
No MQTT ninguém fala direto com ninguém: todos publicam e assinam tópicos num broker, que entrega cada mensagem a quem assinou.

Tópicos e curingas

Tópicos são hierárquicos, separados por /. Ao assinar, dá para usar curingas:

  • + substitui um nível: lab/+/temperatura recebe lab/sala1/temperatura e lab/sala2/temperatura;
  • # substitui todos os níveis seguintes: lab/# recebe tudo que começa com lab/.

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.

Captura de tela do PyPlaca com a aba MQTT da bancada aberta: dispositivos conectados ao broker simulado, tópicos com a última mensagem e o formulário para publicar como outro dispositivo; à esquerda, o código com umqtt.simple.
A aba MQTT do simulador: o broker roda no navegador, mostra quem está conectado e deixa você publicar como se fosse outro dispositivo.

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:

  1. conectar no Wi-Fi com limite de tempo e mensagem clara se falhar;
  2. conectar no broker e assinar os tópicos de comando;
  3. no laço: check_msg(), ler os sensores no seu ritmo, publicar no seu ritmo, pausa curta;
  4. tratar OSError na 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.

FastMindAssistente de IA local

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
Baixar grátis para Windows

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:

  1. 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;
  2. o nome do servidor resolve? Um OSError: -202 antes 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;
  3. o servidor respondeu? Olhe o status_code do HTTP antes de chamar json(). Um 404 tem corpo, mas não o que você esperava;
  4. no MQTT, a conexão aceitou? O connect() levanta erro se o broker recusar (usuário e senha errados, por exemplo);
  5. 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;
  6. o callback recebe bytes: comparar msg == 'liga' (texto) com b'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

Lições

Fichas de componentes

FastMindAssistente de IA local

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
Baixar grátis para Windows

Plano Essencial grátis para sempre

FastTeacherPara professores

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
Conhecer o FastTeacher

Versão Community grátis · Windows

FastMindAssistente de IA local

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
Baixar grátis para Windows

Plano Essencial grátis para sempre