Luz e ventilador que desligam sozinhos quando a sala esvazia
Um ESP32 com dois relés liga e desliga uma luz e um ventilador pelo celular, por horário ou quando alguém entra. Um sensor de presença apaga a luz depois de 10 minutos sem ninguém. Com IA, você manda "desliga tudo da sala" em texto ou por voz.
Como funciona
Três jeitos de ligar, um jeito de desligar
O ESP32 cria uma página com botões que abre no celular, na mesma rede Wi-Fi. Cada relé é uma chave controlada pelo ESP32: fecha o contato e a fita de LED ou o ventilador liga. O sensor PIR percebe o calor de uma pessoa se mexendo. Enquanto houver movimento, a luz fica acesa; depois de 10 minutos parados, ela apaga. Às 22 h, tudo desliga, mesmo que alguém tenha esquecido.
Peças
Lista de compras
| Peça | Para que serve | Preço aprox. |
|---|---|---|
| ESP32 DevKit V1 (30 pinos) | O cérebro, com Wi-Fi | R$ 28–40 |
| Módulo relé 2 canais 5 V com optoacoplador | Liga e desliga as duas cargas. Compre um que aceite sinal de 3,3 V | R$ 8–14 |
| Sensor de presença PIR HC-SR501 | Percebe pessoa se mexendo, até uns 5 m | R$ 7–12 |
| Fonte 12 V 1 A com plugue P4 | Alimenta a fita e o ventilador | R$ 12–20 |
| Fita de LED 12 V (50 cm) | A "luz da sala" do projeto | R$ 6–12 |
| Cooler 12 V (8 cm, de computador) | O "ventilador" do projeto | R$ 6–10 |
| Protoboard, jumpers, borne P4 | Ligações sem solda | R$ 8–12 |
| Total | R$ 75–120 |
Preços aproximados de loja online brasileira em outubro de 2026, a conferir antes de comprar. Não inclui caixa de acabamento.
Ligação
O circuito, pino por pino
| Componente | Pino do componente | Liga no ESP32 |
|---|---|---|
| Relé 2 canais | VCC · GND · IN1 · IN2 | VIN (5 V do USB) · GND · GPIO 26 · GPIO 25 |
| PIR HC-SR501 | VCC · OUT · GND | VIN · GPIO 27 · GND (a saída já é 3,3 V, liga direto) |
| Fita e cooler | + e − | Nada no ESP32: só nos contatos COM/NO dos relés e na fonte 12 V |
Passo a passo
Do pacote ao primeiro clique
- Prepare a Arduino IDE. Instale a Arduino IDE 2 e, em Gerenciador de Placas, instale "esp32" (Espressif). Este projeto não precisa de biblioteca extra:
WiFi,WebServere o relógio já vêm com a placa. - Teste a placa sozinha. Escolha "ESP32 Dev Module" e a porta, e grave o exemplo Blink. Se não gravar, segure o botão BOOT quando aparecer "Connecting...".
- Ligue o relé, sem as cargas. Siga a tabela. Grave o código e aperte os botões no painel: o LED de cada canal do relé acende e você ouve o clique.
- Ligue o PIR. Gire o trimpot de tempo (Tx) todo para a esquerda, o mínimo, e deixe o jumper em H. Espere 1 minuto depois de ligar: é o tempo de o sensor estabilizar.
- Ligue as cargas de 12 V. Fonte desligada da tomada. Faça a ligação do desenho de baixo, confira o + e o − e só então ligue a fonte.
- Abra o painel. O Monitor Serial (115200) mostra o IP (ex.: 192.168.0.50). Digite no navegador do celular, na mesma rede Wi-Fi.
- Teste o horário. Troque
HORA_DESLIGApara a hora atual + 1 minuto, grave e veja tudo desligar sozinho. Depois volte para 22.
Código
Arduino IDE para ESP32
Troque o nome e a senha da rede e grave. No painel, cada canal tem um botão liga e um desliga. O canal 0 quer dizer "todos".
// Controle da casa — ESP32, Arduino IDE
// Sem bibliotecas extras.
#include <WiFi.h>
#include <WebServer.h>
#include <time.h>
const char* REDE = "NOME_DA_REDE";
const char* SENHA = "SENHA_DA_REDE";
// GPIO 26: relé canal 1 (luz, fita LED)
// GPIO 25: relé canal 2 (ventilador)
const int RELE[2] = {26, 25};
const char* NOME[2] = {"Luz", "Ventilador"};
// GPIO 27: saída (OUT) do sensor PIR
const int PINO_PIR = 27;
// A maioria dos relés liga com LOW.
// Se o seu ligar com HIGH, troque os dois.
const int LIGA = LOW, DESLIGA = HIGH;
// apaga a luz após 10 min sem movimento
const unsigned long SEM_GENTE_MS =
10UL * 60 * 1000;
// hora em que tudo desliga
const int HORA_DESLIGA = 22;
WebServer servidor(80);
bool ligado[2] = {false, false};
unsigned long ultimoMovimento = 0;
int diaDesligou = -1;
void aciona(int c, bool liga) {
ligado[c] = liga;
digitalWrite(RELE[c], liga ? LIGA : DESLIGA);
}
// lê ?c= e confere: 0 = todos, 1 ou 2
int canal() {
int c = servidor.arg("c").toInt();
if (c < 0 || c > 2) return -1;
return c;
}
void comando(bool liga) {
int c = canal();
if (c < 0) {
servidor.send(400, "text/plain", "canal?");
return;
}
for (int i = 0; i < 2; i++) {
if (c == 0 || c == i + 1) aciona(i, liga);
}
// volta para o painel
servidor.sendHeader("Location", "/");
servidor.send(303);
}
void rotaLiga() { comando(true); }
void rotaDesliga() { comando(false); }
// /dados: JSON para o painel e para a IA
void dados() {
String j = "{\"luz\":";
j += ligado[0] ? "true" : "false";
j += ",\"ventilador\":";
j += ligado[1] ? "true" : "false";
unsigned long s =
(millis() - ultimoMovimento) / 1000;
j += ",\"sem_movimento_s\":" + String(s);
j += "}";
servidor.send(200, "application/json", j);
}
// /: página com botões para o celular
void painel() {
String h = "<meta name=viewport "
"content='width=device-width'>"
"<h2>Sala</h2>";
for (int i = 0; i < 2; i++) {
String n = String(i + 1);
h += "<p>" + String(NOME[i]) + ": ";
h += ligado[i] ? "ligado" : "desligado";
h += "<br><a href=/liga?c=" + n;
h += ">Liga</a> | <a href=/desliga?c=";
h += n + ">Desliga</a></p>";
}
h += "<p><a href=/desliga?c=0>";
h += "Desligar tudo</a></p>";
servidor.send(200, "text/html", h);
}
void setup() {
Serial.begin(115200);
for (int i = 0; i < 2; i++) {
// relé desligado antes de virar saída
digitalWrite(RELE[i], DESLIGA);
pinMode(RELE[i], OUTPUT);
}
pinMode(PINO_PIR, INPUT);
WiFi.begin(REDE, SENHA);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.print("\nPainel em http://");
Serial.println(WiFi.localIP());
// relógio pela internet, fuso -3 h
configTime(-3 * 3600, 0, "pool.ntp.org");
servidor.on("/", painel);
servidor.on("/liga", rotaLiga);
servidor.on("/desliga", rotaDesliga);
servidor.on("/dados", dados);
servidor.begin();
}
void loop() {
servidor.handleClient();
// presença: movimento acende a luz
if (digitalRead(PINO_PIR) == HIGH) {
ultimoMovimento = millis();
if (!ligado[0]) aciona(0, true);
}
// 10 min sem ninguém: apaga a luz
unsigned long parado =
millis() - ultimoMovimento;
if (ligado[0] && parado > SEM_GENTE_MS) {
aciona(0, false);
}
// horário: às 22 h desliga tudo, 1 vez/dia
struct tm t;
if (getLocalTime(&t, 10) &&
t.tm_hour == HORA_DESLIGA &&
t.tm_mday != diaDesligou) {
aciona(0, false);
aciona(1, false);
diaDesligou = t.tm_mday;
}
}
A camada de IA
O que a IA faz neste projeto
Comando em texto
Você escreve "desliga tudo da sala" ou "liga só o ventilador". A IA traduz a frase para um comando curto que o ESP32 entende.
Comando por voz
Use o ditado do teclado do celular ou do computador: a fala vira texto e o resto é igual ao comando em texto.
Lista fechada de ações
A IA só sugere. O script confere se a resposta é "liga" ou "desliga" e se o canal existe. Qualquer outra coisa é recusada.
Script em Python, rodando no computador na mesma rede. Na versão 1, você mesmo cola o pedido no chat da IA e cola a resposta de volta. Funciona sem chave nenhuma.
# comando.py — frase em português vira
# comando para o ESP32
import json, urllib.request
# troque pelo IP do seu ESP32
ESP = "http://192.168.0.50"
CANAIS = {0: "todos", 1: "luz da sala",
2: "ventilador"}
ACOES = {"liga", "desliga"}
def perguntar_ia(prompt):
# Versão 1: cole o prompt no chat da IA
# e cole aqui a resposta.
# Versão 2: troque pela biblioteca do
# serviço que você usa, com a SUA chave
# numa variável de ambiente (nunca no
# código), ou por um modelo local no PC.
print(prompt)
return input("Resposta da IA: ")
frase = input("Comando: ")
prompt = (
"Você controla uma sala. Canais: "
+ json.dumps(CANAIS, ensure_ascii=False)
+ '. Responda SÓ um JSON assim: '
'{"canal": 1, "acao": "desliga"}. '
"acao é liga ou desliga. Comando: "
+ frase
)
resposta = perguntar_ia(prompt)
# confere ANTES de acionar
try:
cmd = json.loads(resposta)
c, a = int(cmd["canal"]), cmd["acao"]
except Exception:
c, a = -1, ""
if a in ACOES and c in CANAIS:
urllib.request.urlopen(f"{ESP}/{a}?c={c}")
print("Feito:", a, CANAIS[c])
else:
print("Comando recusado:", resposta)
Teste e melhore
Confira antes de mostrar para alguém
- Ao ligar a placa, nenhum relé clica sozinho.
- Cada botão do painel liga e desliga o canal certo; "Desligar tudo" apaga os dois.
- Passando a mão na frente do PIR, a luz acende; parado por 10 min, apaga.
- Com
HORA_DESLIGAna hora atual, tudo desliga e não volta a desligar no mesmo dia. - O script de IA recusa uma resposta inventada, como canal 5 ou "pisca".
| Erro comum | Causa provável | Como resolver |
|---|---|---|
| Relé fica ligado direto | Relé liga com HIGH, não com LOW | Troque LIGA e DESLIGA no código |
| Relé não clica, só o LED acende fraco | Módulo não aceita sinal de 3,3 V | Use um módulo que aceite 3,3 V, ou com jumper de nível alto/baixo |
| Luz acende sozinha toda hora | PIR aquecendo, sensível demais ou mirando janela e ar-condicionado | Espere 1 min ao ligar, baixe o trimpot de sensibilidade (Sx) e mude a mira |
| Desligar às 22 h não funciona | Sem internet, o relógio não acerta | Confira o Wi-Fi; imprima a hora no Monitor Serial com Serial.println(&t, "%H:%M") |
| Cooler não gira | + e − invertidos ou fio no NC em vez do NO | Confira as cores e use o borne NO de cada relé |
Caderno de testes. Cada vez que mudar alguma coisa, anote numa linha. Depois de uma semana na sala, você sabe quantas horas a luz ficaria acesa à toa, e isso vira argumento de venda.
| Data | Versão | O que mudou | Resultado |
|---|---|---|---|
| 20/10 | v1 | Botões, PIR com 10 min, desliga às 22 h | Luz acendeu às 23 h quando o gato passou |
| 21/10 | v2 | Presença só vale entre 6 h e 22 h | Noite toda apagada |
| … | v3 | Contar quantos minutos cada canal ficou ligado no dia | Anote aqui |
Segurança
Tomada não entra no kit
Ficha de negócio
Como vender o controle
| Quem compra | Escola com sala que fica com luz e ventilador ligados depois da aula, pequeno comércio que esquece a vitrine acesa, vizinho que quer apagar a luz da garagem pelo celular |
| Custo do kit | R$ 70–120 (lista acima, a conferir) |
| Preço sugerido | R$ 200–350 o controle montado, testado, em caixa (sugestão) |
| Mão de obra | 1 a 2 horas para configurar o Wi-Fi, os horários e ensinar o painel. A ligação na rede de 127/220 V é do eletricista do cliente |
| Assinatura opcional | R$ 10–25 por mês: ajuste de horários, relatório de horas ligadas e comando por texto (sugestão) |
| Argumento de economia | Mostre a conta: potência (W) × horas ligadas à toa por dia × 30 ÷ 1000 = kWh no mês; × tarifa da conta de luz do cliente (R$/kWh). Ex.: 400 W × 2 h × 30 = 24 kWh por mês, vezes a tarifa |
Para ir além
Próximos passos
Para entender como o modelo transforma uma frase em comando, comece pelo curso IA do zero. Para usar um agente de IA que ajuda a escrever e corrigir o código, siga para o OSWork. Todos os cursos são abertos e gratuitos no INEMA.CLUB.