From d378a1697ac9e620e9a8f54798d5eb3918d63688 Mon Sep 17 00:00:00 2001 From: victor Date: Sat, 6 Dec 2025 13:42:15 +0100 Subject: [PATCH] Initial version --- .gitignore | 1 + RelojArduino.ino | 417 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 418 insertions(+) create mode 100644 .gitignore create mode 100644 RelojArduino.ino diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7578e1a --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.theia/ diff --git a/RelojArduino.ino b/RelojArduino.ino new file mode 100644 index 0000000..3d62616 --- /dev/null +++ b/RelojArduino.ino @@ -0,0 +1,417 @@ +#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); +} \ No newline at end of file