ESP32 para Automação Residencial: Projetos Práticos – 5 Projetos DIY Completos

O ESP32 é o microcontrolador definitivo para automação residencial DIY, combinando Wi-Fi, Bluetooth, processamento dual-core e preço acessível (R$ 25-40). Com mais GPIO’s que Arduino e conectividade nativa, é perfeito para criar dispositivos smart personalizados. Este guia apresenta 5 projetos práticos completos, do básico ao avançado.

Por Que ESP32 é Ideal para Smart Home

Vantagens Técnicas

Especificações Impressionantes:

  • Dual-core 240MHz: Processamento paralelo
  • Wi-Fi 802.11n: Conectividade nativa
  • Bluetooth 4.2/BLE: Comunicação de baixo consumo
  • 36 GPIO’s: Muito mais que Arduino
  • ADC 12-bit: Leituras analógicas precisas
  • DAC, PWM, I2C, SPI: Interfaces completas

Comparativo com Concorrentes:

ESP32 vs Arduino Uno + Wi-Fi Shield:
- Preço: R$ 35 vs R$ 150+
- Processamento: 240MHz vs 16MHz
- Memória: 520KB vs 2KB
- Conectividade: Nativa vs Módulo adicional
- GPIO's: 36 vs 14

Ecossistema de Desenvolvimento

Plataformas Suportadas:

  • Arduino IDE: Familiar e simples
  • PlatformIO: Profissional e poderoso
  • ESP-IDF: Framework nativo Espressif
  • MicroPython: Python para microcontroladores

Bibliotecas Abundantes:

  • WiFi, WebServer, HTTPClient nativas
  • MQTT, JSON, NTP integrados
  • Sensores (DHT, DS18B20, BME280)
  • Displays (OLED, TFT, E-Paper)
  • Protocolos (Modbus, LoRa, Zigbee)

Projeto 1: Relé Wi-Fi Universal

Visão Geral do Projeto

Funcionalidades:

  • Controle de até 8 relés via Wi-Fi
  • Interface web responsiva
  • Integração MQTT para Home Assistant
  • Controle manual com botões
  • Estados salvos na memória

Aplicações:

  • Controle de luzes residenciais
  • Acionamento de portões e bombas
  • Automação de eletrodomésticos
  • Sistema de irrigação inteligente

Lista de Materiais

Componentes Eletrônicos:

  • ESP32 DevKit: R$ 35-45
  • Módulo relé 8 canais: R$ 40-60
  • Optoacopladores: Inclusos no módulo
  • LEDs indicadores: R$ 5-10
  • Resistores 220Ω: R$ 2-5
  • Botões push: R$ 10-15 (8 unidades)
  • Jumpers: R$ 10-15
  • Protoboard: R$ 20-30

Alimentação e Proteção:

  • Fonte 5V 3A: R$ 25-40
  • Regulador 3.3V: R$ 5-10
  • Fusíveis: R$ 5-10
  • Caixa plástica: R$ 20-35

Custo Total: R$ 180-280

Esquema de Ligação

ESP32 Pinout para Relés:
GPIO 2  → Relé 1
GPIO 4  → Relé 2  
GPIO 5  → Relé 3
GPIO 18 → Relé 4
GPIO 19 → Relé 5
GPIO 21 → Relé 6
GPIO 22 → Relé 7
GPIO 23 → Relé 8

Botões (Pull-up interno):
GPIO 12 → Botão 1
GPIO 13 → Botão 2
GPIO 14 → Botão 3
GPIO 15 → Botão 4
GPIO 25 → Botão 5
GPIO 26 → Botão 6
GPIO 27 → Botão 7
GPIO 32 → Botão 8

LEDs Indicadores:
GPIO 33 → LED Status Wi-Fi
GPIO 35 → LED Status MQTT

Código Completo

#include <WiFi.h>
#include <WebServer.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <Preferences.h>

// Configurações Wi-Fi
const char* ssid = "SUA_REDE_WIFI";
const char* password = "SUA_SENHA_WIFI";

// Configurações MQTT
const char* mqtt_server = "192.168.1.100";  // IP do seu Home Assistant
const char* mqtt_user = "homeassistant";
const char* mqtt_password = "sua_senha_mqtt";

// Pinos dos relés
const int relayPins[8] = {2, 4, 5, 18, 19, 21, 22, 23};
const int buttonPins[8] = {12, 13, 14, 15, 25, 26, 27, 32};
const int wifiLED = 33;
const int mqttLED = 35;

// Estados dos relés
bool relayStates[8] = {false, false, false, false, false, false, false, false};
String relayNames[8] = {"Luz Sala", "Luz Quarto", "Ventilador", "Bomba", 
                        "Portão", "Jardim", "Garagem", "Reserva"};

// Objetos
WebServer server(80);
WiFiClient espClient;
PubSubClient mqttClient(espClient);
Preferences preferences;

// Variáveis de controle
unsigned long lastMqttReconnect = 0;
unsigned long lastButtonCheck = 0;
bool buttonStates[8] = {true, true, true, true, true, true, true, true};

void setup() {
  Serial.begin(115200);
  
  // Inicializar preferências
  preferences.begin("relay_states", false);
  
  // Configurar pinos
  setupPins();
  
  // Carregar estados salvos
  loadRelayStates();
  
  // Conectar Wi-Fi
  setupWiFi();
  
  // Configurar servidor web
  setupWebServer();
  
  // Configurar MQTT
  setupMQTT();
  
  Serial.println("Sistema iniciado!");
}

void loop() {
  // Manter conexões ativas
  server.handleClient();
  
  if (!mqttClient.connected()) {
    reconnectMQTT();
  }
  mqttClient.loop();
  
  // Verificar botões (debounce)
  if (millis() - lastButtonCheck > 50) {
    checkButtons();
    lastButtonCheck = millis();
  }
  
  // Atualizar LEDs de status
  updateStatusLEDs();
}

void setupPins() {
  // Configurar relés como saída
  for (int i = 0; i < 8; i++) {
    pinMode(relayPins[i], OUTPUT);
    digitalWrite(relayPins[i], HIGH);  // Relé desligado (lógica invertida)
  }
  
  // Configurar botões com pull-up
  for (int i = 0; i < 8; i++) {
    pinMode(buttonPins[i], INPUT_PULLUP);
  }
  
  // Configurar LEDs de status
  pinMode(wifiLED, OUTPUT);
  pinMode(mqttLED, OUTPUT);
}

void setupWiFi() {
  WiFi.begin(ssid, password);
  Serial.print("Conectando ao Wi-Fi");
  
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  
  Serial.println();
  Serial.print("Wi-Fi conectado! IP: ");
  Serial.println(WiFi.localIP());
}

void setupWebServer() {
  // Página principal
  server.on("/", HTTP_GET, handleRoot);
  
  // API para controlar relés
  server.on("/relay", HTTP_POST, handleRelayControl);
  
  // API para status
  server.on("/status", HTTP_GET, handleStatus);
  
  // Arquivos estáticos
  server.on("/style.css", HTTP_GET, handleCSS);
  
  server.begin();
  Serial.println("Servidor web iniciado na porta 80");
}

void setupMQTT() {
  mqttClient.setServer(mqtt_server, 1883);
  mqttClient.setCallback(mqttCallback);
}

void handleRoot() {
  String html = generateHTML();
  server.send(200, "text/html", html);
}

void handleRelayControl() {
  if (server.hasArg("relay") && server.hasArg("state")) {
    int relayIndex = server.arg("relay").toInt();
    bool state = server.arg("state") == "1";
    
    if (relayIndex >= 0 && relayIndex < 8) {
      setRelay(relayIndex, state, "web");
      server.send(200, "application/json", "{\"success\":true}");
    } else {
      server.send(400, "application/json", "{\"error\":\"Relé inválido\"}");
    }
  } else {
    server.send(400, "application/json", "{\"error\":\"Parâmetros faltando\"}");
  }
}

void handleStatus() {
  StaticJsonDocument<512> json;
  JsonArray relays = json.createNestedArray("relays");
  
  for (int i = 0; i < 8; i++) {
    JsonObject relay = relays.createNestedObject();
    relay["index"] = i;
    relay["name"] = relayNames[i];
    relay["state"] = relayStates[i];
  }
  
  json["wifi_rssi"] = WiFi.RSSI();
  json["mqtt_connected"] = mqttClient.connected();
  json["uptime"] = millis() / 1000;
  
  String jsonString;
  serializeJson(json, jsonString);
  server.send(200, "application/json", jsonString);
}

String generateHTML() {
  String html = R"(
<!DOCTYPE html>
<html>
<head>
    <title>Controle de Relés ESP32</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="/style.css">
</head>
<body>
    <div class="container">
        <h1>🏠 Controle de Relés</h1>
        
        <div class="status-bar">
            <span class="status-item">📶 Wi-Fi: <span id="wifi-status">)</span></span>
            <span class="status-item">🔗 MQTT: <span id="mqtt-status">)</span></span>
            <span class="status-item">⏱️ Uptime: <span id="uptime">)</span></span>
        </div>
        
        <div class="relay-grid">
)";

  for (int i = 0; i < 8; i++) {
    html += "<div class=\"relay-card\">";
    html += "<h3>" + relayNames[i] + "</h3>";
    html += "<div class=\"relay-status " + String(relayStates[i] ? "on" : "off") + "\">";
    html += relayStates[i] ? "🟢 LIGADO" : "🔴 DESLIGADO";
    html += "</div>";
    html += "<div class=\"relay-controls\">";
    html += "<button onclick=\"toggleRelay(" + String(i) + ")\" class=\"btn-toggle\">";
    html += relayStates[i] ? "DESLIGAR" : "LIGAR";
    html += "</button>";
    html += "</div>";
    html += "</div>";
  }

  html += R"(
        </div>
        
        <div class="controls">
            <button onclick="allRelays(true)" class="btn-all btn-on">🔥 LIGAR TODOS</button>
            <button onclick="allRelays(false)" class="btn-all btn-off">❄️ DESLIGAR TODOS</button>
            <button onclick="updateStatus()" class="btn-refresh">🔄 ATUALIZAR</button>
        </div>
    </div>

    <script>
        function toggleRelay(index) {
            const currentState = getRelayState(index);
            fetch('/relay', {
                method: 'POST',
                headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                body: `relay=${index}&state=${currentState ? 0 : 1}`
            })
            .then(response => response.json())
            .then(data => {
                if (data.success) {
                    setTimeout(updateStatus, 200);
                }
            });
        }
        
        function allRelays(state) {
            for (let i = 0; i < 8; i++) {
                fetch('/relay', {
                    method: 'POST',
                    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
                    body: `relay=${i}&state=${state ? 1 : 0}`
                });
            }
            setTimeout(updateStatus, 500);
        }
        
        function updateStatus() {
            fetch('/status')
                .then(response => response.json())
                .then(data => {
                    // Atualizar status dos relés
                    data.relays.forEach((relay, index) => {
                        updateRelayDisplay(index, relay.state);
                    });
                    
                    // Atualizar informações do sistema
                    document.getElementById('wifi-status').textContent = data.wifi_rssi + ' dBm';
                    document.getElementById('mqtt-status').textContent = data.mqtt_connected ? 'Conectado' : 'Desconectado';
                    document.getElementById('uptime').textContent = formatUptime(data.uptime);
                });
        }
        
        function updateRelayDisplay(index, state) {
            const cards = document.querySelectorAll('.relay-card');
            const card = cards[index];
            const statusDiv = card.querySelector('.relay-status');
            const button = card.querySelector('.btn-toggle');
            
            statusDiv.className = `relay-status ${state ? 'on' : 'off'}`;
            statusDiv.textContent = state ? '🟢 LIGADO' : '🔴 DESLIGADO';
            button.textContent = state ? 'DESLIGAR' : 'LIGAR';
        }
        
        function getRelayState(index) {
            const cards = document.querySelectorAll('.relay-card');
            const statusDiv = cards[index].querySelector('.relay-status');
            return statusDiv.classList.contains('on');
        }
        
        function formatUptime(seconds) {
            const hours = Math.floor(seconds / 3600);
            const minutes = Math.floor((seconds % 3600) / 60);
            return `${hours}h ${minutes}m`;
        }
        
        // Atualizar status a cada 5 segundos
        setInterval(updateStatus, 5000);
        updateStatus(); // Primeira execução
    </script>
</body>
</html>
  )";
  
  return html;
}

void handleCSS() {
  String css = R"(
* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
    min-height: 100vh;
    padding: 20px;
}

.container {
    max-width: 1200px;
    margin: 0 auto;
    background: white;
    border-radius: 20px;
    padding: 30px;
    box-shadow: 0 20px 40px rgba(0,0,0,0.1);
}

h1 {
    text-align: center;
    color: #333;
    margin-bottom: 30px;
    font-size: 2.5em;
}

.status-bar {
    display: flex;
    justify-content: space-around;
    background: #f8f9fa;
    padding: 15px;
    border-radius: 10px;
    margin-bottom: 30px;
    flex-wrap: wrap;
}

.status-item {
    font-weight: bold;
    color: #666;
}

.relay-grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
    gap: 20px;
    margin-bottom: 30px;
}

.relay-card {
    background: #fff;
    border: 2px solid #e9ecef;
    border-radius: 15px;
    padding: 20px;
    text-align: center;
    transition: all 0.3s ease;
}

.relay-card:hover {
    transform: translateY(-5px);
    box-shadow: 0 10px 25px rgba(0,0,0,0.1);
}

.relay-card h3 {
    color: #333;
    margin-bottom: 15px;
    font-size: 1.2em;
}

.relay-status {
    padding: 10px 20px;
    border-radius: 25px;
    margin-bottom: 15px;
    font-weight: bold;
    font-size: 1.1em;
}

.relay-status.on {
    background: #d4edda;
    color: #155724;
    border: 2px solid #c3e6cb;
}

.relay-status.off {
    background: #f8d7da;
    color: #721c24;
    border: 2px solid #f5c6cb;
}

button {
    border: none;
    padding: 12px 24px;
    border-radius: 8px;
    font-weight: bold;
    cursor: pointer;
    transition: all 0.3s ease;
    margin: 5px;
}

.btn-toggle {
    background: #007bff;
    color: white;
    font-size: 1em;
    min-width: 120px;
}

.btn-toggle:hover {
    background: #0056b3;
    transform: scale(1.05);
}

.controls {
    display: flex;
    justify-content: center;
    gap: 15px;
    flex-wrap: wrap;
}

.btn-all {
    font-size: 1.1em;
    padding: 15px 30px;
}

.btn-on {
    background: #28a745;
    color: white;
}

.btn-on:hover {
    background: #1e7e34;
}

.btn-off {
    background: #dc3545;
    color: white;
}

.btn-off:hover {
    background: #c82333;
}

.btn-refresh {
    background: #17a2b8;
    color: white;
}

.btn-refresh:hover {
    background: #138496;
}

@media (max-width: 768px) {
    .container {
        margin: 10px;
        padding: 20px;
    }
    
    h1 {
        font-size: 2em;
    }
    
    .relay-grid {
        grid-template-columns: 1fr;
    }
    
    .status-bar {
        flex-direction: column;
        gap: 10px;
    }
}
  )";
  
  server.send(200, "text/css", css);
}

void setRelay(int index, bool state, String source) {
  if (index >= 0 && index < 8) {
    relayStates[index] = state;
    digitalWrite(relayPins[index], !state);  // Lógica invertida
    
    // Salvar estado na memória
    preferences.putBool("relay_" + String(index), state);
    
    // Publicar no MQTT
    publishMQTT(index, state);
    
    Serial.println("Relé " + String(index) + " (" + relayNames[index] + ") " + 
                   (state ? "LIGADO" : "DESLIGADO") + " via " + source);
  }
}

void loadRelayStates() {
  for (int i = 0; i < 8; i++) {
    relayStates[i] = preferences.getBool("relay_" + String(i), false);
    digitalWrite(relayPins[i], !relayStates[i]);
  }
  Serial.println("Estados dos relés carregados da memória");
}

void checkButtons() {
  for (int i = 0; i < 8; i++) {
    bool currentState = digitalRead(buttonPins[i]);
    
    if (currentState != buttonStates[i] && currentState == LOW) {
      // Botão pressionado (borda de descida)
      setRelay(i, !relayStates[i], "button");
      delay(200);  // Debounce adicional
    }
    
    buttonStates[i] = currentState;
  }
}

void updateStatusLEDs() {
  // LED Wi-Fi
  digitalWrite(wifiLED, WiFi.status() == WL_CONNECTED);
  
  // LED MQTT
  digitalWrite(mqttLED, mqttClient.connected());
}

void reconnectMQTT() {
  if (millis() - lastMqttReconnect > 5000) {
    Serial.print("Tentando conectar MQTT...");
    
    String clientId = "ESP32_Relay_" + WiFi.macAddress();
    
    if (mqttClient.connect(clientId.c_str(), mqtt_user, mqtt_password)) {
      Serial.println("MQTT conectado!");
      
      // Subscrever nos tópicos de comando
      for (int i = 0; i < 8; i++) {
        String topic = "homeassistant/switch/relay" + String(i) + "/command";
        mqttClient.subscribe(topic.c_str());
      }
      
      // Publicar descoberta automática do Home Assistant
      publishDiscovery();
      
    } else {
      Serial.print("Falha na conexão MQTT. Estado: ");
      Serial.println(mqttClient.state());
    }
    
    lastMqttReconnect = millis();
  }
}

void mqttCallback(char* topic, byte* payload, unsigned int length) {
  String message;
  for (int i = 0; i < length; i++) {
    message += (char)payload[i];
  }
  
  // Extrair número do relé do tópico
  String topicStr = String(topic);
  int relayIndex = -1;
  
  for (int i = 0; i < 8; i++) {
    if (topicStr.indexOf("relay" + String(i)) > 0) {
      relayIndex = i;
      break;
    }
  }
  
  if (relayIndex >= 0) {
    bool state = (message == "ON");
    setRelay(relayIndex, state, "mqtt");
  }
}

void publishMQTT(int relayIndex, bool state) {
  if (mqttClient.connected()) {
    String topic = "homeassistant/switch/relay" + String(relayIndex) + "/state";
    String payload = state ? "ON" : "OFF";
    mqttClient.publish(topic.c_str(), payload.c_str(), true);
  }
}

void publishDiscovery() {
  for (int i = 0; i < 8; i++) {
    StaticJsonDocument<512> config;
    
    config["name"] = relayNames[i];
    config["unique_id"] = "esp32_relay_" + String(i);
    config["command_topic"] = "homeassistant/switch/relay" + String(i) + "/command";
    config["state_topic"] = "homeassistant/switch/relay" + String(i) + "/state";
    config["payload_on"] = "ON";
    config["payload_off"] = "OFF";
    config["retain"] = true;
    
    JsonObject device = config.createNestedObject("device");
    device["name"] = "ESP32 Relay Controller";
    device["model"] = "ESP32 8-Channel Relay";
    device["manufacturer"] = "DIY Smart Home";
    device["identifiers"] = WiFi.macAddress();
    
    String configTopic = "homeassistant/switch/relay" + String(i) + "/config";
    String configPayload;
    serializeJson(config, configPayload);
    
    mqttClient.publish(configTopic.c_str(), configPayload.c_str(), true);
  }
  
  Serial.println("Configuração de descoberta MQTT publicada");
}

Projeto 2: Sensor de Temperatura e Umidade Inteligente

Funcionalidades do Projeto

Recursos Principais:

  • Múltiplos sensores (DHT22, DS18B20, BME280)
  • Display OLED para visualização local
  • Histórico de dados interno
  • Alertas personalizáveis
  • Dashboard web em tempo real

Código do Sensor Inteligente

#include <WiFi.h>
#include <WebServer.h>
#include <PubSubClient.h>
#include <ArduinoJson.h>
#include <DHT.h>
#include <OneWire.h>
#include <DallasTemperature.h>
#include <Wire.h>
#include <Adafruit_SSD1306.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BME280.h>

// Configurações de rede
const char* ssid = "SUA_REDE_WIFI";
const char* password = "SUA_SENHA_WIFI";
const char* mqtt_server = "192.168.1.100";

// Pinos dos sensores
#define DHT_PIN 4
#define DHT_TYPE DHT22
#define ONE_WIRE_BUS 5
#define BUZZER_PIN 18
#define LED_PIN 2

// Display OLED
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1

// Objetos dos sensores
DHT dht(DHT_PIN, DHT_TYPE);
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
Adafruit_BME280 bme;

WebServer server(80);
WiFiClient espClient;
PubSubClient mqttClient(espClient);

// Estrutura para armazenar leituras
struct SensorReading {
  float temperature;
  float humidity;
  float pressure;
  float tempDS18B20;
  unsigned long timestamp;
};

// Array para histórico (últimas 144 leituras = 24h com 10min interval)
SensorReading readings[144];
int readingIndex = 0;
bool bufferFull = false;

// Configurações de alerta
float tempMin = 15.0;
float tempMax = 35.0;
float humidityMin = 30.0;
float humidityMax = 70.0;

unsigned long lastReading = 0;
unsigned long lastDisplay = 0;
unsigned long lastMQTT = 0;

void setup() {
  Serial.begin(115200);
  
  // Inicializar hardware
  pinMode(LED_PIN, OUTPUT);
  pinMode(BUZZER_PIN, OUTPUT);
  
  // Inicializar sensores
  dht.begin();
  sensors.begin();
  
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println("Falha ao inicializar display OLED");
  }
  
  if (!bme.begin(0x76)) {
    Serial.println("Sensor BME280 não encontrado");
  }
  
  // Configurar display
  display.clearDisplay();
  display.setTextSize(1);
  display.setTextColor(SSD1306_WHITE);
  display.display();
  
  // Conectar Wi-Fi
  connectWiFi();
  
  // Configurar servidor web
  setupWebServer();
  
  // Configurar MQTT
  mqttClient.setServer(mqtt_server, 1883);
  
  Serial.println("Sistema sensor iniciado!");
}

void loop() {
  server.handleClient();
  
  if (!mqttClient.connected()) {
    reconnectMQTT();
  }
  mqttClient.loop();
  
  // Leitura dos sensores a cada 10 segundos
  if (millis() - lastReading > 10000) {
    readSensors();
    lastReading = millis();
  }
  
  // Atualizar display a cada 2 segundos
  if (millis() - lastDisplay > 2000) {
    updateDisplay();
    lastDisplay = millis();
  }
  
  // Publicar MQTT a cada 30 segundos
  if (millis() - lastMQTT > 30000) {
    publishSensorData();
    lastMQTT = millis();
  }
}

void readSensors() {
  SensorReading reading;
  reading.timestamp = millis();
  
  // Ler DHT22
  reading.humidity = dht.readHumidity();
  reading.temperature = dht.readTemperature();
  
  // Ler DS18B20
  sensors.requestTemperatures();
  reading.tempDS18B20 = sensors.getTempCByIndex(0);
  
  // Ler BME280
  reading.pressure = bme.readPressure() / 100.0F; // hPa
  
  // Verificar se as leituras são válidas
  if (isnan(reading.humidity) || isnan(reading.temperature)) {
    Serial.println("Falha na leitura do DHT22");
    return;
  }
  
  // Armazenar no histórico
  readings[readingIndex] = reading;
  readingIndex = (reading

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

G-PD085SMG2Z
Rolar para cima