/*
========================================================
🌕 LUMINÁRIA INTELIGENTE (ESP-01 + RTC DS3231)
========================================================
Projeto:
- Controle automático de brilho por horários
- Até 10 cronogramas
- Portal Cativo Wi-Fi automático
- Economia extrema de bateria
- Configuração via celular
- EEPROM permanente
- RTC DS3231 para horário preciso
Hardware:
- ESP-01 (ESP8266)
- RTC DS3231
- TP4056
- Bateria 18650
- Divisor resistivo 10k + 20k
========================================================
*/
#include <Wire.h>
#include <RTClib.h>
#include <ESP8266WiFi.h>
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <EEPROM.h>
/*
========================================================
PINAGEM
========================================================
*/
// LED azul interno do ESP-01 (GPIO1/TX)
// OBS:
// - lógica invertida
// - LOW = acende
// - HIGH = apaga
#define LED_INTERNO 1
// I2C do RTC DS3231
#define PINO_SDA 0
#define PINO_SCL 2
// GPIO3/RX
// Detecta presença dos 5V do carregador
// através do divisor resistivo
#define PINO_DETECTOR 3
/*
========================================================
OBJETOS GLOBAIS
========================================================
*/
// Porta DNS usada pelo Portal Cativo
const byte PORTA_DNS = 53;
// Servidor DNS
DNSServer dnsServer;
// Servidor WEB
ESP8266WebServer server(80);
// RTC DS3231
RTC_DS3231 rtc;
/*
========================================================
ESTRUTURA DOS HORÁRIOS
========================================================
Cada slot ocupa poucos bytes
para economizar RAM e EEPROM
*/
struct SlotHorario {
// 0 = desativado
// 1 = ativado
byte ativo;
// Horário inicial
byte horaInicio;
byte minInicio;
// Horário final
byte horaFim;
byte minFim;
// Brilho 0~100%
byte brilho;
};
// Vetor com 10 horários
SlotHorario cronograma[10];
/*
========================================================
ESTRUTURA DO WI-FI
========================================================
*/
struct ConfigWifi {
// Nome da rede
char ssid[32];
// Senha da rede
char senha[64];
};
ConfigWifi dadosWifi;
/*
========================================================
VARIÁVEIS DE CONTROLE
========================================================
*/
// Indica se o portal Wi-Fi está ativo
bool modoWifiAtivo = false;
// Controle robusto do detector
bool carregadorDetectado = false;
// Timestamp do momento em que
// o carregador foi conectado
unsigned long tempoInicioPlugado = 0;
// Tempo necessário plugado
// para ativar o portal Wi-Fi
const unsigned long TEMPO_ATIVAR_WIFI = 10000;
/*
========================================================
FUNÇÃO PWM DO LED
========================================================
Corrige a lógica invertida do LED
do ESP-01
0% -> HIGH
100% -> LOW
*/
void definirBrilho(int porcentagem) {
// Totalmente apagado
if (porcentagem <= 0) {
pinMode(LED_INTERNO, OUTPUT);
digitalWrite(LED_INTERNO, HIGH);
}
// Totalmente aceso
else if (porcentagem >= 100) {
pinMode(LED_INTERNO, OUTPUT);
digitalWrite(LED_INTERNO, LOW);
}
// PWM intermediário
else {
// PWM invertido
int valorPWM = map(
porcentagem,
0,
100,
1023,
0
);
analogWrite(LED_INTERNO, valorPWM);
}
}
/*
========================================================
SALVAR DADOS NA EEPROM
========================================================
*/
void salvarDadosNaEEPROM() {
// Salva cronograma
EEPROM.put(0, cronograma);
// Salva Wi-Fi logo após
EEPROM.put(sizeof(cronograma), dadosWifi);
// Commit obrigatório
EEPROM.commit();
}
/*
========================================================
CARREGAR CONFIGURAÇÕES
========================================================
*/
void carregarConfiguracoes() {
// Lê cronograma
EEPROM.get(0, cronograma);
// Lê Wi-Fi
EEPROM.get(sizeof(cronograma), dadosWifi);
/*
========================================
Verifica se EEPROM está vazia/corrompida
========================================
*/
if (cronograma[0].ativo > 1) {
/*
========================================
CONFIGURAÇÃO PADRÃO DE FÁBRICA
========================================
*/
cronograma[0] = {1, 18, 0, 22, 0, 100};
cronograma[1] = {1, 22, 0, 23, 59, 50};
cronograma[2] = {1, 23, 59, 4, 0, 25};
cronograma[3] = {1, 4, 0, 6, 0, 50};
cronograma[4] = {1, 6, 0, 8, 0, 100};
cronograma[5] = {1, 8, 0, 18, 0, 0};
// Limpa slots restantes
for(int i = 6; i < 10; i++) {
cronograma[i] = {0,0,0,0,0,0};
}
// Limpa Wi-Fi
memset(dadosWifi.ssid, 0, sizeof(dadosWifi.ssid));
memset(dadosWifi.senha, 0, sizeof(dadosWifi.senha));
// Salva padrão
salvarDadosNaEEPROM();
}
}
/*
========================================================
PÁGINA WEB
========================================================
*/
void tratarPaginaWeb() {
// Hora atual
DateTime agora = rtc.now();
// HTML da página
String html = "";
html += "<html>";
html += "<head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
html += "<title>Luminaria Inteligente</title>";
/*
========================================
CSS
========================================
*/
html += "<style>";
html += "body{font-family:sans-serif;background:#1a1a2e;color:#fff;text-align:center;padding:10px;}";
html += "table{width:100%;max-width:500px;margin:15px auto;border-collapse:collapse;}";
html += "th,td{padding:6px;border:1px solid #444;}";
html += "input[type='number']{width:45px;background:#161625;color:#fff;border:1px solid #666;padding:4px;text-align:center;}";
html += "input[type='text'],input[type='password']{width:80%;max-width:250px;padding:8px;margin:5px;background:#161625;color:#fff;border:1px solid #666;}";
html += "button{padding:12px 24px;background:#e94560;border:none;color:#fff;font-size:16px;border-radius:4px;cursor:pointer;margin:15px;width:80%;max-width:250px;}";
html += "</style>";
html += "</head>";
html += "<body>";
/*
========================================
TÍTULO
========================================
*/
html += "<h2>Painel da Luminaria Inteligente</h2>";
html += "<p>Hora atual: ";
html += String(agora.hour());
html += ":";
html += String(agora.minute());
html += "</p>";
/*
========================================
FORMULÁRIO
========================================
*/
html += "<form action='/salvar' method='POST'>";
/*
========================================
WI-FI
========================================
*/
html += "<h3>1. Wi-Fi da Casa</h3>";
html += "<input type='text' name='wifi_ssid' placeholder='Nome do Wi-Fi' value='";
html += String(dadosWifi.ssid);
html += "'><br>";
html += "<input type='password' name='wifi_pass' placeholder='Senha do Wi-Fi' value='";
html += String(dadosWifi.senha);
html += "'><br>";
/*
========================================
AJUSTE DO RTC
========================================
*/
html += "<h3>2. Ajustar Hora</h3>";
html += "<input type='text' name='set_time' placeholder='2026-05-26 21:30'><br>";
/*
========================================
TABELA
========================================
*/
html += "<h3>3. Cronograma</h3>";
html += "<table>";
html += "<tr>";
html += "<th>Ativo</th>";
html += "<th>Inicio</th>";
html += "<th>Fim</th>";
html += "<th>Brilho</th>";
html += "</tr>";
for(int i = 0; i < 10; i++) {
String chk = (cronograma[i].ativo == 1)
? "checked"
: "";
html += "<tr>";
// Ativo
html += "<td>";
html += "<input type='checkbox' name='atv";
html += String(i);
html += "' value='1' ";
html += chk;
html += ">";
html += "</td>";
// Hora inicial
html += "<td>";
html += "<input type='number' name='hi";
html += String(i);
html += "' min='0' max='23' value='";
html += String(cronograma[i].horaInicio);
html += "'>";
html += ":";
html += "<input type='number' name='mi";
html += String(i);
html += "' min='0' max='59' value='";
html += String(cronograma[i].minInicio);
html += "'>";
html += "</td>";
// Hora final
html += "<td>";
html += "<input type='number' name='hf";
html += String(i);
html += "' min='0' max='23' value='";
html += String(cronograma[i].horaFim);
html += "'>";
html += ":";
html += "<input type='number' name='mf";
html += String(i);
html += "' min='0' max='59' value='";
html += String(cronograma[i].minFim);
html += "'>";
html += "</td>";
// Brilho
html += "<td>";
html += "<input type='number' name='br";
html += String(i);
html += "' min='0' max='100' value='";
html += String(cronograma[i].brilho);
html += "'>%";
html += "</td>";
html += "</tr>";
yield();
}
html += "</table>";
html += "<button type='submit'>Salvar Configuracoes</button>";
html += "</form>";
html += "</body></html>";
// Envia página
server.send(200, "text/html", html);
}
/*
========================================================
SALVAR CONFIGURAÇÕES
========================================================
*/
void tratarSalvar() {
/*
========================================
WI-FI
========================================
*/
if (server.hasArg("wifi_ssid")) {
String(server.arg("wifi_ssid"))
.toCharArray(dadosWifi.ssid, 32);
String(server.arg("wifi_pass"))
.toCharArray(dadosWifi.senha, 64);
}
/*
========================================
AJUSTE DE DATA/HORA
========================================
*/
if (
server.hasArg("set_time") &&
server.arg("set_time").length() >= 16
) {
String dados = server.arg("set_time");
int ano = dados.substring(0,4).toInt();
int mes = dados.substring(5,7).toInt();
int dia = dados.substring(8,10).toInt();
int hora = dados.substring(11,13).toInt();
int minuto = dados.substring(14,16).toInt();
rtc.adjust(
DateTime(
ano,
mes,
dia,
hora,
minuto,
0
)
);
}
/*
========================================
TABELA DE HORÁRIOS
========================================
*/
for(int i = 0; i < 10; i++) {
cronograma[i].ativo =
server.hasArg("atv" + String(i))
? 1
: 0;
cronograma[i].horaInicio =
server.arg("hi" + String(i)).toInt();
cronograma[i].minInicio =
server.arg("mi" + String(i)).toInt();
cronograma[i].horaFim =
server.arg("hf" + String(i)).toInt();
cronograma[i].minFim =
server.arg("mf" + String(i)).toInt();
cronograma[i].brilho =
server.arg("br" + String(i)).toInt();
}
/*
========================================
SALVA EEPROM
========================================
*/
salvarDadosNaEEPROM();
/*
========================================
RESPOSTA WEB
========================================
*/
server.send(
200,
"text/html",
"<html><body style='background:#1a1a2e;color:#fff;text-align:center;'><h3>Configuracoes salvas!</h3></body></html>"
);
delay(2000);
/*
========================================
CONECTA NO WI-FI DA CASA
========================================
*/
if (strlen(dadosWifi.ssid) > 0) {
WiFi.mode(WIFI_STA);
WiFi.begin(
dadosWifi.ssid,
dadosWifi.senha
);
}
/*
========================================
DESLIGA PORTAL
========================================
*/
modoWifiAtivo = false;
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
WiFi.forceSleepBegin();
delay(1);
}
/*
========================================================
ATIVA PORTAL CAPTIVO
========================================================
*/
void ligarPortalConfiguracao() {
modoWifiAtivo = true;
/*
========================================
MODO ACCESS POINT
========================================
*/
WiFi.mode(WIFI_AP);
// Rede sem senha
WiFi.softAP("Luminaria Inteligente");
/*
========================================
DNS CAPTIVO
========================================
*/
dnsServer.start(
PORTA_DNS,
"*",
WiFi.softAPIP()
);
/*
========================================
ROTAS WEB
========================================
*/
server.on("/", tratarPaginaWeb);
server.on("/salvar", tratarSalvar);
/*
========================================
REDIRECIONAMENTO AUTOMÁTICO
========================================
*/
server.onNotFound([]() {
server.sendHeader(
"Location",
String("http://") +
WiFi.softAPIP().toString(),
true
);
server.send(
302,
"text/plain",
""
);
});
server.begin();
/*
========================================
ANIMAÇÃO LED
========================================
*/
for(int i=0; i<5; i++) {
definirBrilho(100);
delay(80);
definirBrilho(0);
delay(80);
}
}
/*
========================================================
SETUP
========================================================
*/
void setup() {
/*
========================================
EEPROM
========================================
*/
EEPROM.begin(1024);
carregarConfiguracoes();
/*
========================================
GPIO
========================================
*/
pinMode(PINO_DETECTOR, INPUT);
/*
========================================
I2C
========================================
*/
Wire.begin(
PINO_SDA,
PINO_SCL
);
/*
========================================
RTC
========================================
*/
if (!rtc.begin()) {
// Erro crítico
while(true) {
definirBrilho(100);
delay(100);
definirBrilho(0);
delay(100);
}
}
/*
========================================
DESLIGA WI-FI
========================================
*/
WiFi.mode(WIFI_OFF);
WiFi.forceSleepBegin();
delay(1);
}
/*
========================================================
LOOP PRINCIPAL
========================================================
*/
void loop() {
/*
========================================
MODO PORTAL Wi-Fi
========================================
*/
if (modoWifiAtivo) {
dnsServer.processNextRequest();
server.handleClient();
yield();
/*
====================================
SE DESPLUGAR O CARREGADOR
====================================
*/
if (digitalRead(PINO_DETECTOR) == LOW) {
modoWifiAtivo = false;
server.stop();
WiFi.softAPdisconnect(true);
WiFi.mode(WIFI_OFF);
WiFi.forceSleepBegin();
}
return;
}
/*
========================================
DETECÇÃO DO CARREGADOR
========================================
*/
if (digitalRead(PINO_DETECTOR) == HIGH) {
// Primeira detecção
if (!carregadorDetectado) {
carregadorDetectado = true;
tempoInicioPlugado = millis();
}
// Tempo conectado
unsigned long tempoDecorrido =
millis() - tempoInicioPlugado;
// Ativa portal
if (tempoDecorrido >= TEMPO_ATIVAR_WIFI) {
ligarPortalConfiguracao();
return;
}
} else {
// Reset
carregadorDetectado = false;
}
/*
========================================
CONTROLE DE BRILHO
========================================
*/
DateTime agora = rtc.now();
int minutosAtuais =
(agora.hour() * 60) +
agora.minute();
int brilhoTarget = 0;
/*
========================================
PROCURA REGRA ATIVA
========================================
*/
for(int i = 0; i < 10; i++) {
if (cronograma[i].ativo == 1) {
int minInicioRegra =
(cronograma[i].horaInicio * 60) +
cronograma[i].minInicio;
int minFimRegra =
(cronograma[i].horaFim * 60) +
cronograma[i].minFim;
/*
====================================
HORÁRIO NORMAL
====================================
*/
if (minInicioRegra <= minFimRegra) {
if (
minutosAtuais >= minInicioRegra &&
minutosAtuais < minFimRegra
) {
brilhoTarget =
cronograma[i].brilho;
break;
}
}
/*
====================================
CRUZANDO MEIA-NOITE
====================================
*/
else {
if (
minutosAtuais >= minInicioRegra ||
minutosAtuais < minFimRegra
) {
brilhoTarget =
cronograma[i].brilho;
break;
}
}
}
}
/*
========================================
APLICA BRILHO
========================================
*/
definirBrilho(brilhoTarget);
/*
========================================
PEQUENA PAUSA
========================================
*/
delay(1000);
}