"""
===============================================================================
AULA: Sistema IoT com ESP32 - Controle Remoto via MQTT
===============================================================================
📚 O QUE VOCÊ VAI APRENDER NESTE PROJETO:
✅ Como conectar ESP32 ao WiFi
✅ Como usar o protocolo MQTT (comunicação IoT)
✅ Como publicar dados de sensores na nuvem
✅ Como receber comandos remotos (controlar LEDs pelo celular/computador)
✅ Como lidar com desconexões e reconexões automáticas
✅ Boas práticas de programação em MicroPython
📊 ARQUITETURA DO PROJETO:
[ESP32 + DHT22] ──────> [MQTT Broker] <────── [n8n / App / Dashboard]
(sensor) publicar | receber (controle remoto)
|
[RabbitMQ Server]
XX.XX.XX.XXX <- Seu IP AQUI
🔄 FLUXO DE DADOS:
1. ESP32 lê temperatura/umidade do sensor DHT22
2. ESP32 PUBLICA dados no tópico "auto/sensor"
3. RabbitMQ recebe e armazena os dados
4. Aplicações (n8n, dashboard) CONSOMEM os dados
5. Usuário envia comando (exemplo: "ligar LED")
6. Aplicação PUBLICA no tópico "ativar/pino1"
7. ESP32 RECEBE o comando
8. ESP32 executa ação (liga o LED)
===============================================================================
"""
# ==================== IMPORTAÇÕES ====================
# Importar bibliotecas necessárias para o projeto
import network # Biblioteca para conexão WiFi
import time # Biblioteca para delays e timestamps
import WLANNetwork # Módulo customizado para gerenciar WiFi (você criou isso!)
import utils # Módulo customizado para debug (você criou isso!)
from machine import Pin, I2C # Controlar pinos GPIO (LEDs, botões, etc)
import ssd1306
import dht # Biblioteca para sensores DHT11/DHT22
import ujson # JSON otimizado para MicroPython (mais leve que json)
# 🔧 IMPORTANTE: Usamos umqtt.robust ao invés de umqtt.simple!
#
# umqtt.simple = básico, você precisa tratar erros manualmente
# umqtt.robust = profissional, reconecta automaticamente quando dá erro
#
# Para produção, SEMPRE use robust!
from umqtt.robust import MQTTClient
i2c = I2C(
0,
scl=Pin(22),
sda=Pin(21)
)
temperatura = 0.0
umidade = 0
led1_status = False
led2_status = False
oled = ssd1306.SSD1306_I2C(128, 64, i2c)
# ==================== CONFIGURAÇÕES DO PROJETO ====================
# 📝 Centralizamos todas as configurações aqui para facilitar mudanças
# --- Configurações do Broker MQTT (Servidor RabbitMQ) ---
MQTT_BROKER = "72.61.52.197" # IP do servidor (pode ser domínio também)
MQTT_PORT = 1883 # Porta padrão MQTT (não criptografado)
# Porta 8883 seria para MQTT com SSL/TLS
MQTT_USER = "guest" # Usuário do RabbitMQ
MQTT_PASSWORD = "testeadm" # Senha do RabbitMQ
MQTT_CLIENT_ID = "esp32_robust" # ID único deste dispositivo
# ⚠️ Cada ESP32 deve ter um ID diferente!
MQTT_KEEPALIVE = 60 # Keepalive em segundos
# Envia "ping" a cada 60s para manter conexão viva
# --- Tópicos MQTT ---
# 📌 Tópicos são como "canais" de comunicação
# Funciona tipo "canais do Slack" ou "grupos do WhatsApp"
MQTT_TOPIC_PUBLISH = "auto/sensor" # ESP32 PUBLICA dados aqui
MQTT_TOPIC_LED1 = "ativar/pino1" # ESP32 ESCUTA comandos para LED 1
MQTT_TOPIC_LED2 = "ativar/pino2" # ESP32 ESCUTA comandos para LED 2
# 💡 CONVENÇÃO DE NOMENCLATURA:
# - use lowercase (minúsculas)
# - use / para hierarquia (ex: casa/sala/temperatura)
# - evite espaços e caracteres especiais
# --- Configurações de WiFi ---
WIFI_SSID = "Wokwi-GUEST" # Nome da rede WiFi
WIFI_PASSWORD = "" # Senha (vazio = rede aberta)
# ⚠️ No ESP32 real, coloque sua rede WiFi aqui!
# --- Configurações de Hardware (Pinos GPIO) ---
DHT_PIN = 15 # Pino onde o sensor DHT22 está conectado
LED1_PIN = 2 # LED onboard (interno da placa)
LED2_PIN = 23 # LED externo (você conecta na protoboard)
# 💡 DICA: No ESP32, nem todos os pinos podem ser usados!
# Pinos seguros: 2, 4, 5, 12-19, 21-23, 25-27, 32-33
# Evite: 0, 6-11 (usados pela flash interna)
# --- Configurações de Tempo ---
PUBLISH_INTERVAL = 5 # Publicar dados a cada 5 segundos
# ⚠️ Não coloque muito rápido (< 1s) para não sobrecarregar!
# --- Configurações de Debug ---
DEBUG_MODE = True # True = mostra mensagens debug
MODULE_MESSAGE = "\n\r<< MAIN >> : " # Prefixo das mensagens debug
myUtils = utils.Utils(DEBUG_MODE, MODULE_MESSAGE) # Objeto utilitário para debug
# ==================== CONFIGURAÇÃO DO HARDWARE ====================
# 🔧 Inicializar sensores e atuadores (LEDs)
# --- Sensor DHT22 ---
# DHT22 mede temperatura (-40 a 80°C) e umidade (0-100%)
# Mais preciso que DHT11
sensor = dht.DHT22(Pin(DHT_PIN))
# 📝 NOTA: Para DHT11, use: sensor = dht.DHT11(Pin(DHT_PIN))
# --- LEDs ---
# Pin.OUT = pino configurado como SAÍDA (para controlar LEDs, relés, motores)
# Pin.IN = pino configurado como ENTRADA (para ler botões, sensores digitais)
led1 = Pin(LED1_PIN, Pin.OUT)
led2 = Pin(LED2_PIN, Pin.OUT)
# Inicializar LEDs desligados
# 🔒 BOA PRÁTICA: Sempre inicialize pinos em estado conhecido
led1.off() # ou led1.value(0)
led2.off() # ou led2.value(0)
def atualizar_display():
oled.fill(0)
oled.text(f"Temp: {temperatura:.1f} C", 0, 0)
oled.text(f"Umid: {umidade} %", 0, 16)
oled.text(f"LED 1: {'ON' if led1_status else 'OFF'}", 0, 32)
oled.text(f"LED 2: {'ON' if led2_status else 'OFF'}", 0, 48)
oled.show()
# ==================== CALLBACK MQTT ====================
# 📨 Esta função é chamada AUTOMATICAMENTE quando chega mensagem MQTT
def mqtt_callback(topic, msg):
global led1_status, led2_status
"""
Callback = função que é "chamada de volta" quando algo acontece
Parâmetros:
topic (bytes): Tópico MQTT da mensagem (ex: b'ativar/pino1')
msg (bytes): Conteúdo da mensagem (ex: b'ligar')
⚠️ IMPORTANTE: topic e msg vêm como bytes (b'texto')
Precisamos decodificar para string normal!
"""
try:
# --- Decodificar bytes para string ---
topic_str = topic.decode('utf-8') # b'ativar/pino1' → 'ativar/pino1'
msg_str = msg.decode('utf-8') # b'ligar' → 'ligar'
# .strip() remove espaços em branco nas pontas
# " ligar " → "ligar"
msg_str = msg_str.strip()
# --- Log da mensagem recebida ---
print(f"\n📨 MQTT RX: {topic_str} = '{msg_str}'")
# RX = Received (recebido)
# TX = Transmitted (transmitido) - você verá isso no código de publicação
# --- Processar comando para LED 1 ---
if topic_str == MQTT_TOPIC_LED1:
# Aceita vários formatos: '1', 'on', 'ligar', 'true'
# .lower() converte para minúscula ('LIGAR' → 'ligar')
if msg_str.lower() in ['1', 'on', 'ligar', 'true']:
led1.on() # Liga o LED
led1_status = True
print(" ✅ LED 1 ON")
else:
# Qualquer outra mensagem desliga
led1.off()
led1_status = False
print(" ⭕ LED 1 OFF")
# --- Processar comando para LED 2 ---
elif topic_str == MQTT_TOPIC_LED2:
if msg_str.lower() in ['1', 'on', 'ligar', 'true']:
led2.on()
led2_status = True
print(" ✅ LED 2 ON")
else:
led2.off()
led2_status = False
print(" ⭕ LED 2 OFF")
# 💡 DICA: Você pode adicionar mais tópicos aqui!
# elif topic_str == "ativar/rele":
# rele.value(1 if msg_str == 'ligar' else 0)
except Exception as e:
# Se der erro, imprime mas não trava o programa
print(f"❌ Callback error: {e}")
# 🔒 BOA PRÁTICA: NUNCA deixe exceções sem tratamento!
# ==================== FUNÇÕES DE CONEXÃO ====================
def connect_wifi():
"""
Conecta o ESP32 ao WiFi
🌐 WiFi é essencial para IoT!
Sem WiFi, não tem internet, não tem MQTT!
"""
myUtils.DEBUG_message("Conectando WiFi...")
# WLANNetworkDriver é um módulo customizado que você criou
# Ele encapsula a lógica de conexão WiFi
myWLANNetwork = WLANNetwork.WLANNetworkDriver(WIFI_SSID, WIFI_PASSWORD)
myWLANNetwork.WLANNetwork_Connect()
myUtils.DEBUG_message("✅ WiFi OK")
# 💡 CURIOSIDADE: Por baixo dos panos, isso faz algo como:
# wlan = network.WLAN(network.STA_IF)
# wlan.active(True)
# wlan.connect(SSID, PASSWORD)
# while not wlan.isconnected():
# time.sleep(1)
def connect_mqtt():
"""
Conecta ao broker MQTT (RabbitMQ)
📡 MQTT = Message Queuing Telemetry Transport
Protocolo leve para IoT, criado pela IBM
Funciona com modelo Publish/Subscribe:
- PUBLISH = publicar mensagens em um tópico
- SUBSCRIBE = receber mensagens de um tópico
"""
try:
myUtils.DEBUG_message(f"MQTT: {MQTT_BROKER}:{MQTT_PORT}")
# --- Criar cliente MQTT ---
# 🔧 umqtt.robust = reconecta automaticamente se cair
client = MQTTClient(
client_id=MQTT_CLIENT_ID, # ID único deste ESP32
server=MQTT_BROKER, # IP ou domínio do servidor
port=MQTT_PORT, # Porta (1883 = MQTT sem SSL)
user=MQTT_USER, # Usuário
password=MQTT_PASSWORD, # Senha
keepalive=MQTT_KEEPALIVE # Keepalive em segundos
)
# --- Configurar callback ---
# Define qual função chamar quando chegar mensagem
# É tipo um "ouvinte" ou "listener"
client.set_callback(mqtt_callback)
# --- Conectar ao broker ---
# Aqui acontece o "aperto de mão" (handshake) com o servidor
client.connect()
# Se der erro, cai no except lá embaixo
# --- Subscribe nos tópicos ---
# "Se inscrever" = começar a receber mensagens desses tópicos
client.subscribe(MQTT_TOPIC_LED1)
client.subscribe(MQTT_TOPIC_LED2)
# 💡 ANALOGIA: É tipo se inscrever em um canal do YouTube
# Agora você recebe notificação quando alguém publicar nesses tópicos!
# --- Logs de sucesso ---
myUtils.DEBUG_message("✅ MQTT OK")
myUtils.DEBUG_message(f" SUB: {MQTT_TOPIC_LED1}")
myUtils.DEBUG_message(f" SUB: {MQTT_TOPIC_LED2}")
# SUB = Subscribed (inscrito)
return client # Retorna o objeto cliente para usar no loop
except Exception as e:
# Se der erro (servidor offline, senha errada, etc), mostra erro
myUtils.DEBUG_message(f"❌ MQTT Error: {e}")
raise # Re-lança a exceção para o código que chamou tratar
# ==================== INICIALIZAÇÃO DO SISTEMA ====================
# 🚀 Este código roda UMA VEZ quando o ESP32 liga
myUtils.DEBUG_message("=" * 40)
myUtils.DEBUG_message("🌡️ ESP32 MQTT Station v2.0")
myUtils.DEBUG_message("=" * 40)
# Passo 1: Conectar WiFi
connect_wifi()
# ⏸️ Só continua quando WiFi conectar (ou der erro e travar)
# Passo 2: Conectar MQTT
client = connect_mqtt()
# Agora temos um objeto 'client' para publicar e receber mensagens
myUtils.DEBUG_message("🚀 Running...")
# ==================== VARIÁVEIS DE CONTROLE ====================
# 📊 Variáveis que controlam o loop principal
last_publish = 0 # Timestamp da última publicação
# Usado para controlar intervalo de publicação
msg_count = 0 # Contador de mensagens publicadas
# Útil para debug e estatísticas
# ==================== LOOP PRINCIPAL ====================
# 🔄 Este código roda PARA SEMPRE (até desligar o ESP32)
while True: # Loop infinito
# PARTE NOVA DO DISPLAY
#temperatura += 0.1
#if temperatura > 30:
# temperatura = 25.0
#umidade += 1
#if umidade > 80:
# umidade = 60
atualizar_display()
try:
# ==================== TAREFA 1: VERIFICAR MENSAGENS ====================
# Verifica se chegou alguma mensagem MQTT
# Se chegou, chama o callback automaticamente
client.check_msg()
# 💡 IMPORTANTE: check_msg() NÃO bloqueia!
# Ela verifica rapidamente e continua
# Se tiver mensagem, chama mqtt_callback()
# 🔧 Com umqtt.robust, se der erro aqui, ele reconecta sozinho!
# ==================== TAREFA 2: PUBLICAR DADOS DO SENSOR ====================
# Publica dados a cada PUBLISH_INTERVAL segundos
# Verificar se já passou tempo suficiente desde última publicação
if time.time() - last_publish >= PUBLISH_INTERVAL:
# --- Ler sensor DHT22 ---
sensor.measure() # Faz a leitura (demora ~250ms)
temperatura = sensor.temperature()
umidade = sensor.humidity()
# 📝 NOTA: SEMPRE chame measure() antes de pegar valores!
# Se não chamar, vai pegar valores velhos
# --- Montar mensagem JSON ---
# JSON = formato universal de dados
# {chave: valor, chave: valor}
message = ujson.dumps({
"temp": sensor.temperature(), # Temperatura em °C
"humidity": sensor.humidity(), # Umidade em %
"timestamp": int(time.time()), # Unix timestamp (segundos desde 1970)
"led1": led1.value(), # Estado do LED 1 (0 ou 1)
"led2": led2.value(), # Estado do LED 2 (0 ou 1)
"count": msg_count # Número da mensagem
})
# ujson.dumps() converte dicionário Python para string JSON
# {"temp": 25.5} → '{"temp":25.5}'
# 💡 EXEMPLO DE MENSAGEM:a
# '{"temp":25.5,"humidity":60.0,"timestamp":12345,"led1":0,"led2":1,"count":42}'
# --- Publicar no MQTT ---
print(f"\n📤 TX [{msg_count}]: {message}")
# TX = Transmitted (transmitido)
client.publish(MQTT_TOPIC_PUBLISH, message)
# Envia a mensagem para o tópico "auto/sensor"
# Qualquer app inscrito nesse tópico vai receber!
# 🔧 Com robust, se der erro aqui, reconecta e tenta de novo!
# --- Atualizar contadores ---
last_publish = time.time() # Guarda quando foi a última publicação
msg_count += 1 # Incrementa contador
# 💡 CURIOSIDADE: Isso garante que publique exatamente a cada 5s
# Mesmo se o loop demorar um pouco, compensa no próximo ciclo
# ==================== TAREFA 3: DELAY ====================
# Pausa por 1 segundo antes de repetir o loop
time.sleep(1)
# ⚠️ IMPORTANTE: sleep() libera a CPU!
# Sem sleep(), o ESP32 esquenta e gasta bateria à toa
# 🔧 Por que 1 segundo?
# - Verifica mensagens a cada 1s (responsivo!)
# - Publica dados a cada 5s (configurado em PUBLISH_INTERVAL)
# - Não sobrecarrega CPU nem rede
# ==================== TRATAMENTO DE ERROS ====================
except KeyboardInterrupt:
# Se apertar Ctrl+C no terminal, para a execução do programa gracefully
myUtils.DEBUG_message("\n👋 Bye")
# --- Desconectar do MQTT ---
try:
client.disconnect()
except:
pass # Se já estava desconectado, ignora erro
# --- Desligar LEDs ---
led1.off()
led2.off()
# --- Sair do loop ---
break # Sai do while True
# 🔒 BOA PRÁTICA: Sempre limpe recursos antes de sair!
except Exception as e:
# Qualquer outro erro que acontecer
print(f"❌ Error: {e}")
# 🔧 umqtt.robust JÁ reconecta automaticamente!
# Então só precisamos esperar um pouco e continuar
time.sleep(5) # Pausa 5s antes de tentar de novo
# 💡 NOTA: Com umqpt.simple, você teria que fazer:
# try:
# client.disconnect()
# except:
# pass
# time.sleep(5)
# client = connect_mqtt() # Reconectar manualmente
# Com robust, ele faz isso AUTOMATICAMENTE! 🎉
# ==================== FIM DO PROGRAMA ====================
"""
🎓 RESUMO DO QUE VOCÊ APRENDEU:
1. 📡 Conexão WiFi em MicroPython
2. 📨 Protocolo MQTT (publish/subscribe)
3. 🌡️ Leitura de sensor DHT22
4. 💡 Controle de GPIOs (LEDs)
5. 🔄 Loop principal não-bloqueante
6. 🐛 Tratamento de erros e exceções
7. 🔧 umqtt.robust para reconexão automática
8. 📊 JSON para estruturar dados
9. ⏰ Controle de tempo com timestamps
10. 🔒 Boas práticas de programação
💡 DESAFIOS PARA PRATICAR:
1. Adicione um terceiro LED
2. Adicione um sensor de temperatura diferente (BMP280, DS18B20)
3. Publique dados em outro tópico
4. Crie um tópico para controlar a frequência de publicação
5. Adicione um botão físico para ligar/desligar LEDs
6. Implemente controle por PWM (LED com brilho variável)
7. Adicione um display OLED para mostrar valores localmente
8. Implemente modo "automático" (ex: ligar LED se temp > 30°C)
9. Adicione autenticação mais robusta (troque guest/guest)
10. Implemente OTA (atualização de firmware via WiFi)
🔗 PRÓXIMOS PASSOS:
- Integrar com n8n para automação
- Criar dashboard web para visualizar dados
- Implementar alertas (WhatsApp, Email, Telegram)
📚 DOCUMENTAÇÃO ÚTIL:
- MicroPython: https://docs.micropython.org
- MQTT Protocol: https://mqtt.org
- RabbitMQ: https://www.rabbitmq.com/mqtt.html
- ESP32 Pinout: https://randomnerdtutorials.com/esp32-pinout-reference-gpios/
===============================================================================
🎉 PARABÉNS! VOCÊ COMPLETOU O PROJETO! 🎉
===============================================================================
"""LED PARA SIMULAR
LAMPADA