#include <WiFi.h>
#include <WebServer.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include "RTClib.h"
// ==== CONFIGURACIÓN DE HARDWARE ====
RTC_DS3231 rtc;
LiquidCrystal_I2C lcd(0x27, 16, 2);
#define buzzerPin 23 // Buzzer pasivo en lugar del relé
#define botonModo 5 // Botón modo vacaciones
// ==== CONFIGURACIÓN WI-FI ====
const char* ssid = "FIONA";
const char* password = "95444824";
IPAddress local_IP(192, 168, 100, 133);
IPAddress gateway(192, 168, 100, 1);
IPAddress subnet(255, 255, 255, 0);
WebServer server(80);
// ==== PÁGINA WEB ====
const char MAIN_page[] PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<title>Control de Timbre ESP32</title>
<style>
body { font-family: Arial; text-align: center; margin-top: 40px; }
button { padding: 15px; margin: 10px; font-size: 18px; border-radius: 10px; cursor: pointer; }
.on { background: green; color: white; }
.off { background: red; color: white; }
</style></head>
<body>
<h1> Control de Timbre (Buzzer)</h1>
<button class="on" onclick="fetch('/buzzer/on')">Encender</button>
<button class="off" onclick="fetch('/buzzer/off')">Apagar</button>
<p id="estado"></p>
<script>
document.querySelectorAll("button").forEach(btn => {
btn.addEventListener("click", () => {
fetch(btn.getAttribute("onclick").replace("fetch('","").replace("')",""))
.then(r => r.text())
.then(t => document.getElementById("estado").innerText = "Respuesta: " + t);
});
});
</script>
</body></html>
)rawliteral";
// ==== HORARIOS PROGRAMADOS ====
struct Horario {
int hora;
int minuto;
const char* evento;
};
Horario horarios[] = {
{7, 30, "Entrada"},
{9, 20, "Recreo"},
{11, 35, "Salida"},
{13, 0, "Entrada"},
{15, 20, "Recreo"},
{17, 0, "Salida"},
{17, 45, "Entrada"},
{19, 45, "Recreo"},
{22, 0, "Salida"}
};
const int totalHorarios = sizeof(horarios) / sizeof(horarios[0]);
// ==== VARIABLES ====
bool yaSono = false;
bool buzzerActivo = false;
unsigned long tiempoInicioBuzzer = 0;
const unsigned long duracionBuzzer = 3000;
bool modoVacaciones = false;
bool ultimoEstadoBoton = HIGH;
unsigned long tiempoInicioEvento = 0;
bool mostrandoEvento = false;
// ==== FUNCIONES SERVIDOR ====
void handleRoot() {
server.send(200, "text/html", MAIN_page);
}
void handleBuzzerOn() {
tone(buzzerPin, 1000);
buzzerActivo = true;
server.send(200, "text/plain", "Buzzer encendido");
}
void handleBuzzerOff() {
noTone(buzzerPin);
buzzerActivo = false;
server.send(200, "text/plain", "Buzzer apagado");
}
// ==== SETUP ====
void setup() {
Serial.begin(115200);
Wire.begin();
rtc.begin();
lcd.init();
lcd.backlight();
pinMode(botonModo, INPUT_PULLUP);
if (rtc.lostPower()) {
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
// WiFi
if (!WiFi.config(local_IP, gateway, subnet)) {
Serial.println(" Error IP fija");
}
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\n WiFi conectado");
Serial.print("🌐 IP: ");
Serial.println(WiFi.localIP());
// Servidor
server.on("/", handleRoot);
server.on("/buzzer/on", handleBuzzerOn);
server.on("/buzzer/off", handleBuzzerOff);
server.begin();
Serial.println("Servidor HTTP iniciado");
lcd.setCursor(0, 0);
lcd.print("Timbre escolar");
lcd.setCursor(0, 1);
lcd.print("Estado: Activo");
delay(1500);
lcd.clear();
}
// ==== LOOP ====
void loop() {
server.handleClient();
DateTime now = rtc.now();
int diaSemana = now.dayOfTheWeek();
int mes = now.month();
// ==== BOTÓN MODO VACACIONES ====
bool estadoBoton = digitalRead(botonModo);
if (estadoBoton == LOW && ultimoEstadoBoton == HIGH) {
modoVacaciones = !modoVacaciones;
lcd.clear();
lcd.setCursor(0, 0);
if (modoVacaciones) {
lcd.print("Modo vacaciones");
} else {
lcd.print("Timbre escolar");
}
delay(800);
lcd.clear();
}
ultimoEstadoBoton = estadoBoton;
// ==== MODO VACACIONES ====
if (modoVacaciones) {
noTone(buzzerPin);
buzzerActivo = false;
mostrarHoraFecha(now);
delay(200);
return;
}
// ==== CONTROL DE TIMBRES ====
bool diaHabil = (diaSemana >= 1 && diaSemana <= 5);
bool mesValido = (mes >= 2 && mes <= 12);
if (diaHabil && mesValido && !buzzerActivo) {
for (int i = 0; i < totalHorarios; i++) {
if (now.hour() == horarios[i].hora && now.minute() == horarios[i].minuto && now.second() == 0 && !yaSono) {
tone(buzzerPin, 1000);
tiempoInicioBuzzer = millis();
buzzerActivo = true;
yaSono = true;
lcd.clear();
lcd.setCursor(0, 0);
lcd.print(horarios[i].evento);
lcd.setCursor(0, 1);
lcd.print("Hora ");
if (now.hour() < 10) lcd.print("0");
lcd.print(now.hour());
lcd.print(":");
if (now.minute() < 10) lcd.print("0");
lcd.print(now.minute());
mostrandoEvento = true;
tiempoInicioEvento = millis();
break;
}
}
}
// ==== FINALIZAR SONIDO ====
if (buzzerActivo && millis() - tiempoInicioBuzzer >= duracionBuzzer) {
noTone(buzzerPin);
buzzerActivo = false;
}
// ==== VOLVER A MOSTRAR HORA ====
if (mostrandoEvento && millis() - tiempoInicioEvento >= 3000) {
lcd.clear();
mostrandoEvento = false;
}
// ==== MOSTRAR HORA Y FECHA ====
if (!mostrandoEvento) {
mostrarHoraFecha(now);
}
if (now.second() > 0) {
yaSono = false;
}
delay(200);
}
// ==== FUNCIÓN PARA MOSTRAR HORA Y FECHA ====
void mostrarHoraFecha(DateTime now) {
lcd.setCursor(0, 0);
lcd.print("Hora: ");
if (now.hour() < 10) lcd.print("0");
lcd.print(now.hour());
lcd.print(":");
if (now.minute() < 10) lcd.print("0");
lcd.print(now.minute());
lcd.setCursor(0, 1);
lcd.print("Fecha: ");
if (now.day() < 10) lcd.print("0");
lcd.print(now.day());
lcd.print("/");
if (now.month() < 10) lcd.print("0");
lcd.print(now.month());
}