#include #include #include #include "RTClib.h" #include "DHT.h" // --- LIBRERÍAS ADICIONALES PARA SINCRONIZACIÓN --- #include #include #include // --- 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); }