Files
ESP32Reloj/RelojArduino.ino
2025-12-06 13:42:15 +01:00

417 lines
13 KiB
C++

#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#include "RTClib.h"
#include "DHT.h"
// --- LIBRERÍAS ADICIONALES PARA SINCRONIZACIÓN ---
#include <WiFi.h>
#include <time.h>
#include <WiFiManager.h>
// --- Definiciones de hardware ---
#define SCREEN_WIDTH 128
#define SCREEN_HEIGHT 64
#define OLED_RESET -1
#define SCREEN_ADDRESS 0x3C
#define DHTPIN 4 // Pin donde conectaste el DHT11
#define DHTTYPE DHT11
// --- Constantes de tiempo ---
#define DHT_READ_INTERVAL 10 // Leer DHT cada 10 segundos
// --- Credenciales y NTP (Network Time Protocol) ---
const char* ntpServer = "pool.ntp.org";
const long gmtOffset_sec = 0; // NTP configurado a UTC (Offset 0)
const int daylightOffset_sec = 0; // Se ignora aquí
// --- Inicialización de objetos ---
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);
RTC_DS3231 rtc;
DHT dht(DHTPIN, DHTTYPE);
WiFiManager wm; // Objeto de WiFiManager
// --- Variables de estado ---
int lastSecond = -1;
float currentTempC = 0.0;
int lastDHTReadSecond = -100;
bool rtcIsPresent = true;
int lastSyncHour = -1;
bool wifiModuleOn = false;
// NUEVA VARIABLE: Para rastrear si la conexión fue automática (credenciales encontradas)
bool wasAutoConnected = false;
// =========================================
// Determina si Madrid está en horario de verano (CEST: UTC+2)
// =========================================
bool isDaylightSaving(const DateTime& dt) {
int year = dt.year();
DateTime lastSundayMarch(year, 3, 31, 0, 0, 0);
while (lastSundayMarch.dayOfTheWeek() != 0) lastSundayMarch = lastSundayMarch - TimeSpan(1, 0, 0, 0);
DateTime lastSundayOctober(year, 10, 31, 0, 0, 0);
while (lastSundayOctober.dayOfTheWeek() != 0) lastSundayOctober = lastSundayOctober - TimeSpan(1, 0, 0, 0);
DateTime startDST_UTC = lastSundayMarch + TimeSpan(0, 2, 0, 0);
DateTime endDST_UTC = lastSundayOctober + TimeSpan(0, 1, 0, 0);
return (dt >= startDST_UTC && dt < endDST_UTC);
}
// =========================================
// Obtiene la hora de Madrid (UTC + offset)
// =========================================
DateTime getMadridTime(const DateTime& rtcTime) {
bool summer = isDaylightSaving(rtcTime);
int offset = summer ? 2 : 1;
return rtcTime + TimeSpan(0, offset, 0, 0);
}
// =========================================
// Función: mostrar hora, segundos, fecha, DST y temperatura
// =========================================
void printTime(const char* timeStr, const char* secondsStr, const char* dateStr, const char* dstStr, float tempC, bool wifiIsOn) {
int16_t x1, y1;
uint16_t w, h;
display.clearDisplay();
display.setTextColor(SSD1306_WHITE);
// --- Hora grande (HH:MM) ---
display.setTextSize(3);
display.getTextBounds(timeStr, 0, 0, &x1, &y1, &w, &h);
int xHour = (SCREEN_WIDTH - (w + 6 + 6)) / 2;
int yHour = (SCREEN_HEIGHT - h) / 2 - 4;
display.setCursor(xHour, yHour);
display.print(timeStr);
// --- Segundos (SS) ---
display.setTextSize(1);
int16_t xS, yS;
uint16_t wS, hS;
display.getTextBounds(secondsStr, 0, 0, &xS, &yS, &wS, &hS);
int xSec = xHour + w + 2;
int ySec = yHour + (h - hS) / 2;
display.setCursor(xSec, ySec);
display.print(secondsStr);
// --- Fecha (DD/MM/YYYY) ---
display.setTextSize(1);
display.getTextBounds(dateStr, 0, 0, &x1, &y1, &w, &h);
int xDate = SCREEN_WIDTH - w - 1;
int yDate = SCREEN_HEIGHT - h - 1;
display.setCursor(xDate, yDate);
display.print(dateStr);
// --- DST (CET/CEST) ---
display.setTextSize(1);
display.getTextBounds(dstStr, 0, 0, &x1, &y1, &w, &h);
int xDst = SCREEN_WIDTH - w - 1;
int yDst = 1;
display.setCursor(xDst, yDst);
display.print(dstStr);
// --- Temperatura (X.XC) ---
display.setTextSize(1);
char tempStr[8];
snprintf(tempStr, sizeof(tempStr), "%.1fC", tempC);
display.getTextBounds(tempStr, 0, 0, &x1, &y1, &w, &h);
int xTemp = 1;
int yTemp = SCREEN_HEIGHT - h - 1;
display.setCursor(xTemp, yTemp);
display.print(tempStr);
// --- MENSAJE DE ESTADO WIFI (Encima de la temperatura) ---
display.setTextSize(1);
const char* wifiStr = wifiIsOn ? "WiFi: ON" : "WiFi: OFF";
display.getTextBounds(wifiStr, 0, 0, &x1, &y1, &w, &h);
int xWifi = xTemp; // Misma X que la temperatura
int yWifi = yTemp - h - 1; // Una línea encima de la temperatura
display.setCursor(xWifi, yWifi);
display.print(wifiStr);
display.display();
}
// =========================================
// Maneja la conexión WiFi y el portal cautivo
// Retorna true si está conectado, false si no.
// =========================================
bool handleWifi(bool forceConfig) {
// 1. Encender el módulo Wi-Fi
WiFi.mode(WIFI_STA);
wifiModuleOn = true;
wasAutoConnected = false; // Resetear el estado de conexión
// Mensaje de estado inicial
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
display.println("Iniciando WiFi...");
display.display();
bool connected = false;
if (forceConfig) {
// Modo de Configuración Forzada (Portal Cautivo)
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
display.println("CONFIG. FORZADA.");
display.println("Conecte a 'AutoConnectAP'");
display.println("Clave: 12345678");
display.display();
// Bloquea aquí hasta que se configure y conecte
wm.setConfigPortalBlocking(true);
wm.setDebugOutput(false);
connected = wm.startConfigPortal("AutoConnectAP", "12345678");
} else {
// Modo de Conexión Normal (Intenta usar credenciales guardadas)
wm.setConfigPortalBlocking(false);
wm.setConnectRetries(3);
wm.setConnectTimeout(10);
// autoConnect devuelve true si se conecta.
// Si no pudo conectar y no necesita portal, devolverá false.
connected = wm.autoConnect("AutoConnectAP", "12345678");
// Si se conectó y NO es una configuración forzada, asumimos que usó credenciales guardadas.
if (connected) {
wasAutoConnected = true;
}
}
// 2. Apagar Wi-Fi si no está conectado
if (WiFi.status() != WL_CONNECTED) {
WiFi.mode(WIFI_OFF);
btStop();
wifiModuleOn = false;
wasAutoConnected = false;
return false;
}
// Si llegamos aquí, está conectado.
return true;
}
// =========================================
// Sincroniza la hora con NTP.
// =========================================
void syncRtcFromNtp(bool adjustRtc) {
// 1. Conectar/Configurar WiFi. Si falla, salimos.
if (!handleWifi(false)) {
display.println("\nNO HAY CONEXION.");
display.display();
delay(3000);
return;
}
// 🚩 IMPLEMENTACIÓN DEL REQUISITO: Mensaje de éxito de la conexión SOLO si fue conexión automática
if (wasAutoConnected) {
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
display.println("Credenciales encontradas!");
display.println("Conexion WiFi OK.");
display.println("Sincronizando hora...");
display.display();
// HACEMOS UNA ESPERA DE 10 SEGUNDOS
unsigned long startWait = millis();
// Bucle de espera para mostrar el mensaje durante 10 segundos
while (millis() - startWait < 10000) {
delay(100);
}
}
// 2. Configurar y sincronizar hora NTP (UTC) con el reloj interno del ESP32
configTime(gmtOffset_sec, daylightOffset_sec, ntpServer);
struct tm timeinfo;
if (!getLocalTime(&timeinfo)) {
display.clearDisplay(); // Limpiar y mostrar error
display.setCursor(0, 0);
display.println("WiFi OK, pero ERROR NTP!");
display.display();
delay(3000);
goto end_sync;
}
// 3. Ajustar el RTC con la hora UTC obtenida
display.clearDisplay();
display.setCursor(0, 0);
display.setTextSize(1);
if (adjustRtc && rtcIsPresent) {
DateTime now(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
rtc.adjust(now);
display.println("RTC Sincronizado (UTC).");
} else {
display.println("Sistema Sincronizado.");
}
display.display();
delay(3000);
end_sync:
// 4. Apagar Wi-Fi para ahorrar energía después de la sincronización
WiFi.mode(WIFI_OFF);
btStop();
wifiModuleOn = false;
}
// =========================================
// Comprueba si el RTC está presente e intenta reanudarlo
// =========================================
void checkRtcStatusAndSync() {
bool rtcPreviouslyPresent = rtcIsPresent;
// Intentar re-inicializar el RTC
if (!rtc.begin()) {
if (rtcPreviouslyPresent) { // Si acaba de desconectarse
rtcIsPresent = false;
// Sincroniza solo el sistema para obtener la hora actual
syncRtcFromNtp(false);
}
} else {
if (!rtcPreviouslyPresent) { // Si acaba de conectarse
// Sincroniza el RTC con NTP para reanudar el reloj
syncRtcFromNtp(true);
}
rtcIsPresent = true;
}
}
// =========================================
// setup()
// =========================================
void setup() {
Wire.begin(21, 22);
display.begin(SSD1306_SWITCHCAPVCC, SCREEN_ADDRESS);
display.clearDisplay();
display.display();
dht.begin();
// 1. Inicialización y comprobación del RTC
if (!rtc.begin()) {
rtcIsPresent = false;
display.ssd1306_command(0x81);
display.ssd1306_command(0); // Valor de contraste (brillo)
display.setTextSize(1);
display.setTextColor(SSD1306_WHITE);
display.setCursor(0, 0);
display.println("RTC NO ENCONTRADO EN ARRANQUE!");
display.display();
delay(3000);
// RTC no está presente: Borramos credenciales y forzamos el portal.
display.clearDisplay();
display.setCursor(0, 0);
display.println("Borrando WiFi guardado...");
display.display();
delay(1000);
wm.resetSettings(); // Borra las credenciales de Wi-Fi guardadas
// Continúa con la configuración del WiFi Manager (Bloqueante)
handleWifi(true);
// Sincroniza el sistema (no el RTC) después de la configuración.
syncRtcFromNtp(false);
// NOTA: wasAutoConnected se pone a false en handleWifi(true), por lo que la espera de 30s no se ejecutará aquí.
} else if (rtc.lostPower()) {
// RTC presente pero perdió energía: sincroniza con la configuración guardada.
syncRtcFromNtp(true);
// NOTA: Si autoConnect tiene éxito, wasAutoConnected será true y se ejecutará la espera de 30s.
} else {
// RTC OK, apagamos el módulo Wi-Fi por defecto
WiFi.mode(WIFI_OFF);
btStop();
wifiModuleOn = false;
}
}
// =========================================
// loop()
// =========================================
void loop() {
// 1. OBTENCIÓN DE HORA (depende de si el RTC está presente o no)
DateTime nowUTC;
if (rtcIsPresent) {
nowUTC = rtc.now();
} else {
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
nowUTC = DateTime(timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
} else {
nowUTC = DateTime(2000, 1, 1, 0, 0, 0);
}
}
// 2. CONTROL HORARIO: Comprobar RTC y Sincronizar con NTP una vez cada hora (basado en la hora UTC)
if (nowUTC.hour() != lastSyncHour) {
if (lastSyncHour != -1) {
// NOTA: En la comprobación horaria (loop), wasAutoConnected no se usará para la espera.
checkRtcStatusAndSync();
}
lastSyncHour = nowUTC.hour();
}
// 3. Control para leer la temperatura solo cada N segundos (10s)
if (nowUTC.second() % DHT_READ_INTERVAL == 0 && nowUTC.second() != lastDHTReadSecond) {
float tempC = dht.readTemperature();
if (!isnan(tempC)) {
currentTempC = tempC;
}
lastDHTReadSecond = nowUTC.second();
}
// 4. Actualizar la pantalla solo si el segundo ha cambiado
if (nowUTC.second() != lastSecond) {
lastSecond = nowUTC.second();
// 5. Calcular la hora de Madrid aplicando la zona horaria/DST
DateTime nowMadrid = getMadridTime(nowUTC);
char timeStr[6]; // HH:MM
char secStr[3]; // SS
char dateStr[11]; // DD/MM/YYYY
char dstStr[5]; // CET / CEST
snprintf(timeStr, sizeof(timeStr), "%02d:%02d", nowMadrid.hour(), nowMadrid.minute());
snprintf(secStr, sizeof(secStr), "%02d", nowMadrid.second());
snprintf(dateStr, sizeof(dateStr), "%02d/%02d/%04d", nowMadrid.day(), nowMadrid.month(), nowMadrid.year());
bool summer = isDaylightSaving(nowUTC);
snprintf(dstStr, sizeof(dstStr), "%s", summer ? "CEST" : "CET");
printTime(timeStr, secStr, dateStr, dstStr, currentTempC, wifiModuleOn);
}
delay(100);
}