ESP32 NTP: obtener la hora de internet con zona horaria y OLED
Cómo obtener la hora NTP en el ESP32: configTzTime y zona horaria, getLocalTime, strftime, reloj en OLED, respaldo con DS3231, tarea diaria y ntptime en Pico W.
Obtener la hora con ESP32 NTP significa conectar la placa a servidores de tiempo en internet y conocer la fecha y la hora con una precisión de unas decenas de milisegundos, sin ningún módulo de reloj. Cuando el ESP32 está conectado al Wi-Fi, una sola llamada a configTzTime() obtiene la hora, aplica tu zona horaria y después la placa sigue contando por sí misma. Relojes de escritorio, riego programado, marcas de tiempo para registrar datos, una alarma por la mañana o luces que se encienden a una hora fija ya no necesitan un módulo RTC aparte.
En esta guía veremos paso a paso cómo funciona NTP, cómo configurar la zona horaria correcta (España, México, Argentina y otras), cómo escribir la fecha en cualquier formato con getLocalTime y strftime, cómo mostrar la hora en una pantalla OLED, cómo conservarla con un módulo DS3231 cuando cae el Wi-Fi o se va la luz y cómo ejecutar una tarea a una hora concreta cada día. En la última sección haremos lo mismo en la Raspberry Pi Pico W con el módulo ntptime de MicroPython.

¿Qué es NTP y cómo obtiene la hora el ESP32?
NTP (Network Time Protocol) es un protocolo que se usa desde los años 80 para sincronizar los relojes de ordenadores y dispositivos a través de internet. Los servidores se organizan en capas llamadas estratos: los servidores de estrato 1 están conectados directamente a un reloj atómico o a GPS, los de estrato 2 toman la hora de ellos, y así sucesivamente. El ESP32 pide la hora con un pequeño paquete por el puerto UDP 123; el servidor responde con una marca de tiempo que contiene los segundos desde 1900 más una fracción de segundo. El ESP32 tiene en cuenta el tiempo de ida y vuelta del paquete y ajusta su propio reloj. Con una conexión doméstica la precisión suele estar entre 10 y 50 milisegundos, más que suficiente para un proyecto de reloj.
La pila de red lwIP del ESP32 usa SNTP, una versión simplificada de NTP. Una vez recibida la hora, el ESP32 la guarda como hora del sistema y sigue contando con un temporizador derivado de su cristal; no consulta internet cada segundo. En segundo plano vuelve a preguntar al servidor a intervalos regulares (por defecto cada 3 horas en el núcleo Arduino ESP32) y corrige pequeñas derivas. La hora del sistema se guarda en UTC; la hora local que ves se calcula a partir de ella aplicando la regla de la zona horaria.
| Concepto | Valor / explicación |
|---|---|
| Protocolo y puerto | SNTP, UDP 123 |
| Marca de tiempo | Segundos desde 1900 (el ESP32 los convierte a tiempo Unix desde 1970) |
| Precisión típica | 10–50 ms con una conexión doméstica |
| Resincronización | Por defecto 3 horas en Arduino ESP32, se cambia con sntp_set_sync_interval() (mínimo 15 s) |
| Máximo de servidores | 3 (los nombres pasados a configTzTime) |
| Tráfico de internet | Petición y respuesta son paquetes NTP de 48 bytes; unos pocos kilobytes al día como mucho |
| Qué necesitas | ESP32, una red Wi-Fi de 2,4 GHz y conexión a internet |
configTime y configTzTime: configurar la zona horaria
En el núcleo Arduino ESP32 hay dos formas de iniciar NTP. configTime(gmtOffset_sec, daylightOffset_sec, servidor1, servidor2, servidor3) recibe el desfase respecto a UTC y el desfase del horario de verano en segundos. Para Turquía, por ejemplo, escribes configTime(10800, 0, "pool.ntp.org"), porque 3 horas son 10 800 segundos y Turquía sigue en UTC+3 todo el año sin horario de verano desde 2016. Este método es sencillo, pero no conoce las fechas de cambio de hora de tu región; en España el cambio se haría el día equivocado, así que allí conviene configTzTime.
La forma correcta es configTzTime(TZ, servidor1, servidor2, servidor3). Esta función recibe una cadena de zona horaria en formato POSIX que también incluye las reglas del horario de verano, y el ESP32 hace el cambio cada año por sí mismo. Para la España peninsular la cadena es "CET-1CEST,M3.5.0,M10.5.0/3": CET es la abreviatura del horario de invierno, -1 significa que hay que restar una hora a la hora local para obtener UTC, CEST es el horario de verano, M3.5.0 significa «último domingo de marzo» y M10.5.0/3 «último domingo de octubre a las 3:00». Los ejemplos de abajo usan la zona de Turquía "<+03>-3"; sustituye TZ_INFO por el valor de la tabla.
| Región | Cadena TZ (configTzTime) | Equivalente con configTime |
|---|---|---|
| Turquía (UTC+3 todo el año) | <+03>-3 | configTime(10800, 0, ...) |
| España peninsular, Francia, Italia | CET-1CEST,M3.5.0,M10.5.0/3 | No adecuado, usa configTzTime |
| Islas Canarias | WET0WEST,M3.5.0/1,M10.5.0 | No adecuado, usa configTzTime |
| México (centro, sin horario de verano) | CST6 | configTime(-21600, 0, ...) |
| Argentina (UTC-3) | <-03>3 | configTime(-10800, 0, ...) |
| UTC (para marcas de tiempo) | UTC0 | configTime(0, 0, ...) |
Qué necesitas y conexiones
Para ver la hora en el Monitor Serie basta con un ESP32 y una red Wi-Fi. Más adelante usamos una pantalla OLED SSD1306 de 0,96 pulgadas para mostrar la hora, un módulo RTC DS3231 para conservarla en los cortes de luz y un módulo de relé para el ejemplo de tarea programada. La OLED y el DS3231 comparten el mismo bus I2C, así que ambos van a los mismos dos pines. Puedes consultar los pines del ESP32 con la herramienta Pinout.
| Pin del componente | ESP32 | Raspberry Pi Pico W | Nota |
|---|---|---|---|
| OLED / DS3231 VCC | 3V3 | 3V3 (OUT) | Ambos funcionan a 3,3 V |
| OLED / DS3231 GND | GND | GND | Tierra común |
| OLED / DS3231 SDA | GPIO 21 | GP4 | Línea de datos I2C |
| OLED / DS3231 SCL | GPIO 22 | GP5 | Línea de reloj I2C |
| Módulo de relé IN | GPIO 26 | GP15 | Ejemplo de tarea programada |
| Módulo de relé VCC | 5V (VIN) | VBUS | La mayoría de bobinas de relé necesitan 5 V |
Paso a paso: tu primer código ESP32 NTP
- Asegúrate de tener instalado el paquete de placas ESP32 en el Arduino IDE y elige ESP32 Dev Module en Herramientas. Si no sabes cómo, mira el artículo Qué es el ESP32.
- No hace falta ninguna librería extra: WiFi.h y time.h vienen con el núcleo ESP32.
- Cambia WIFI_SSID y WIFI_PASS por los de tu red y TZ_INFO por tu zona horaria de la tabla.
- Sube el código y abre el Monitor Serie a 115200 baudios.
- Primero verás los puntos del Wi-Fi y, tras unos segundos, la fecha y la hora actualizándose cada segundo.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi"; // Wi-Fi adı / Wi-Fi name
5const char* WIFI_PASS = "Sifre123"; // Wi-Fi şifresi / Wi-Fi password
6const char* TZ_INFO = "<+03>-3"; // Türkiye: UTC+3, yaz saati yok / Turkey: UTC+3, no DST
7
8void setup() {
9 Serial.begin(115200);
10 WiFi.begin(WIFI_SSID, WIFI_PASS);
11 Serial.print("Wi-Fi");
12 while (WiFi.status() != WL_CONNECTED) { // bağlanana kadar bekle / wait until connected
13 delay(500);
14 Serial.print(".");
15 }
16 Serial.println(" OK");
17
18 // NTP sunucuları + saat dilimi / NTP servers + time zone
19 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
20
21 struct tm t;
22 while (!getLocalTime(&t)) { // ilk eşitlemeyi bekle (5 sn deneme) / wait for the first sync (5 s per try)
23 Serial.println("NTP bekleniyor / waiting for NTP");
24 }
25 Serial.println("Saat alindi / time received");
26}
27
28void loop() {
29 struct tm t;
30 if (getLocalTime(&t)) {
31 Serial.println(&t, "%d.%m.%Y %H:%M:%S"); // ör. 16.11.2026 14:05:09 / e.g. 16.11.2026 14:05:09
32 }
33 delay(1000);
34}
35| Línea | ¿Qué hace? |
|---|---|
| configTzTime(TZ_INFO, ...) | Inicia el cliente SNTP y fija servidores y zona horaria; vuelve enseguida, la hora llega en segundo plano |
| getLocalTime(&t) | Rellena una struct tm con la hora local; si aún no hay hora, espera hasta 5 segundos y devuelve false |
| Serial.println(&t, "...") | La clase Print del ESP32 puede imprimir una struct tm directamente con un formato de strftime |
| time(nullptr) | Devuelve los segundos desde 1970 (tiempo Unix, UTC) |
Hay un detalle importante: getLocalTime() decide si la hora es «válida» mirando el año. Al arrancar, la hora del sistema es el 1 de enero de 1970 y la función devuelve false hasta que el año es posterior a 2016. Así, el código que registra datos o ejecuta tareas antes de que llegue NTP no genera por error entradas con fecha de 1970, siempre que compruebes el valor devuelto. El segundo parámetro es el tiempo de espera en milisegundos; si no quieres bloquear loop(), escribe getLocalTime(&t, 0) para que lo intente una sola vez.
struct tm: las partes de la hora
getLocalTime() escribe la hora desglosada en una struct tm. Con estos campos comparas horas, días o meses al montar tareas programadas. Dos campos confunden a menudo a los principiantes: el mes empieza en 0 y el año se cuenta desde 1900.
| Campo | Rango | Ejemplo (lunes 16 de noviembre de 2026, 14:05:09) |
|---|---|---|
| tm_sec | 0–59 (60 en un segundo intercalar) | 9 |
| tm_min | 0–59 | 5 |
| tm_hour | 0–23 | 14 |
| tm_mday | 1–31 | 16 |
| tm_mon | 0–11 (0 = enero) | 10 |
| tm_year | Años desde 1900 | 126 (1900 + 126 = 2026) |
| tm_wday | 0–6 (0 = domingo) | 1 |
| tm_yday | 0–365 (día del año, desde 0) | 319 |
Formatear fecha y hora con strftime
strftime() escribe la hora de una struct tm en un array de caracteres según un patrón. Los códigos con signo de porcentaje se sustituyen por el valor correspondiente y el resto de caracteres se quedan igual. Reserva el array con el tamaño de la salida más larga más uno (para el cero final): «14:05:09» tiene 8 caracteres, así que el array debe tener al menos 9 bytes. Los códigos más usados:
| Código | Significado | Ejemplo de salida |
|---|---|---|
| %H / %M / %S | Hora (00–23) / minuto / segundo | 14 / 05 / 09 |
| %I y %p | Hora en formato de 12 horas y AM/PM | 02 PM |
| %d / %m / %Y | Día / mes (01–12) / año de cuatro cifras | 16 / 11 / 2026 |
| %y | Año de dos cifras | 26 |
| %A / %a | Día de la semana largo / corto (en inglés) | Monday / Mon |
| %B / %b | Mes largo / corto (en inglés) | November / Nov |
| %j | Día del año (001–366) | 320 |
| %F / %T | Atajos de %Y-%m-%d / %H:%M:%S | 2026-11-16 / 14:05:09 |
| %Z | Abreviatura de la zona horaria | +03 (o CET / CEST) |
La librería C del ESP32 solo conoce los nombres de días y meses en inglés; %A siempre escribe «Monday». Para una fecha en español guardas los nombres en tus propios arrays y usas tm_wday y tm_mon como índice. El ejemplo de abajo lo hace con nombres en turco; cámbialos por «domingo, lunes, …» y «enero, febrero, …». Como el Monitor Serie admite UTF-8, las tildes se ven bien allí. El código genera una fecha legible y el formato ISO ordenable (2026-11-16 14:05:09) para registros.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO = "<+03>-3";
7
8// tm_wday: 0 = Pazar ... 6 = Cumartesi / 0 = Sunday ... 6 = Saturday
9const char* GUNLER[] = {"Pazar", "Pazartesi", "Salı", "Çarşamba", "Perşembe", "Cuma", "Cumartesi"};
10const char* AYLAR[] = {"Ocak", "Şubat", "Mart", "Nisan", "Mayıs", "Haziran",
11 "Temmuz", "Ağustos", "Eylül", "Ekim", "Kasım", "Aralık"};
12
13void setup() {
14 Serial.begin(115200);
15 WiFi.begin(WIFI_SSID, WIFI_PASS);
16 while (WiFi.status() != WL_CONNECTED) delay(500);
17 configTzTime(TZ_INFO, "pool.ntp.org");
18}
19
20void loop() {
21 struct tm t;
22 if (!getLocalTime(&t)) return; // saat henüz yok / time not set yet
23
24 char saat[9]; // "HH:MM:SS" + '\0'
25 char iso[20]; // "YYYY-MM-DD HH:MM:SS" + '\0'
26 strftime(saat, sizeof(saat), "%H:%M:%S", &t);
27 strftime(iso, sizeof(iso), "%Y-%m-%d %H:%M:%S", &t);
28
29 // Türkçe tarih: 16 Kasım 2026 Pazartesi / Turkish date
30 Serial.printf("%d %s %d %s %s\n", t.tm_mday, AYLAR[t.tm_mon], t.tm_year + 1900, GUNLER[t.tm_wday], saat);
31 Serial.printf("Kayit / log: %s epoch: %ld\n", iso, (long)time(nullptr));
32 delay(1000);
33}
34Mostrar la hora y la fecha en una pantalla OLED
Para convertir la salida del Monitor Serie en un reloj de escritorio de verdad, la pantalla OLED SSD1306 de 0,96 pulgadas es ideal: es barata, se conecta con dos cables y muestra dígitos grandes muy nítidos. El código de abajo muestra arriba el día de la semana y la fecha, en el centro la hora en letra grande y abajo el estado del Wi-Fi. Como solo redibuja cuando cambia el segundo, y no en cada vuelta del bucle, no ocupa el bus I2C sin motivo. Instala las librerías Adafruit SSD1306 y Adafruit GFX desde el Gestor de Librerías; la pantalla en sí se explica en el artículo Pantalla OLED (SSD1306).
1#include <WiFi.h>
2#include <Wire.h>
3#include <Adafruit_GFX.h>
4#include <Adafruit_SSD1306.h>
5#include <time.h>
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO = "<+03>-3";
10// OLED yazı tipinde Türkçe harf yok / the OLED font has no Turkish letters
11const char* GUNLER[] = {"Pazar", "Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi"};
12
13Adafruit_SSD1306 display(128, 64, &Wire, -1);
14int sonSaniye = -1; // son çizilen saniye / last drawn second
15
16void yaz(const char* mesaj) { // ekrana tek satır / one line on screen
17 display.clearDisplay();
18 display.setTextSize(1);
19 display.setCursor(0, 0);
20 display.print(mesaj);
21 display.display();
22}
23
24void setup() {
25 Wire.begin(21, 22); // SDA = 21, SCL = 22
26 display.begin(SSD1306_SWITCHCAPVCC, 0x3C);
27 display.setTextColor(SSD1306_WHITE);
28 yaz("Wi-Fi baglaniyor...");
29
30 WiFi.begin(WIFI_SSID, WIFI_PASS);
31 while (WiFi.status() != WL_CONNECTED) delay(300);
32 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
33 yaz("NTP bekleniyor...");
34}
35
36void loop() {
37 struct tm t;
38 if (!getLocalTime(&t, 0)) { // 0 = beklemeden dene / try without waiting
39 delay(200);
40 return;
41 }
42 if (t.tm_sec == sonSaniye) { // saniye değişmediyse çizme / redraw only on a new second
43 delay(20);
44 return;
45 }
46 sonSaniye = t.tm_sec;
47
48 char saat[9], tarih[11];
49 strftime(saat, sizeof(saat), "%H:%M:%S", &t);
50 strftime(tarih, sizeof(tarih), "%d.%m.%Y", &t);
51
52 display.clearDisplay();
53 display.setTextSize(1);
54 display.setCursor(0, 0);
55 display.print(GUNLER[t.tm_wday]); // gün adı / day name
56 display.setCursor(68, 0);
57 display.print(tarih); // 10 karakter = 60 piksel / 10 chars = 60 px
58 display.setTextSize(2);
59 display.setCursor(16, 26);
60 display.print(saat); // 8 x 12 = 96 piksel genişlik / 96 px wide
61 display.setTextSize(1);
62 display.setCursor(0, 56);
63 display.print(WiFi.status() == WL_CONNECTED ? "Wi-Fi: var" : "Wi-Fi: yok");
64 display.display();
65}
66Fíjate en que los nombres de los días van sin caracteres especiales: la fuente por defecto de Adafruit GFX no tiene letras como á, é o ñ y en su lugar muestra símbolos sin sentido. Con tamaño de texto 2 cada carácter mide 12 píxeles de ancho; «14:05:09», de 8 caracteres, ocupa 96 píxeles, y empezando en el píxel 16 queda exactamente centrado en la pantalla de 128 píxeles. Si prefieres una LCD de caracteres 16x2, imprime la misma salida de strftime con lcd.print(); la conexión se explica en el artículo Pantalla LCD I2C.
¿Qué pasa con la hora si cae el Wi-Fi? Respaldo RTC con DS3231
La pregunta más frecuente es: si se va el Wi-Fi, ¿se va también la hora? No. Una vez que el ESP32 tiene la hora por NTP, sigue contando con su propio temporizador; perder el Wi-Fi no detiene el reloj. Como este temporizador funciona con el cristal de 40 MHz, deriva más o menos uno o dos segundos al día, y la siguiente sincronización lo corrige cuando vuelve el Wi-Fi. El verdadero problema es un corte de luz o un reinicio con el botón EN: entonces la hora del sistema vuelve a 1970 y, sin Wi-Fi, el ESP32 no tiene forma de saber la hora.
| Situación | ¿Se conserva la hora? | Qué hacer |
|---|---|---|
| El Wi-Fi cae durante un rato | Sí, el ESP32 sigue contando | Nada; NTP la corrige al volver el Wi-Fi |
| Deep sleep (sueño profundo) | Sí, el temporizador RTC sigue contando | Si duerme mucho, sincroniza con NTP en algunos despertares |
| Corte de luz o botón EN | No, el reloj vuelve a 1970 | Añade un módulo RTC con pila como el DS3231 |
| Sin internet (campo, almacén) | NTP nunca llega | Toma la hora solo de un módulo RTC |
Durante el sueño profundo la hora no la cuenta el cristal principal, sino un oscilador RC interno menos preciso. Este oscilador se ve afectado por la temperatura, así que en sueños largos el reloj puede desviarse unos segundos o más; en proyectos a pilas que duermen horas, es buena costumbre sincronizar con NTP cada pocos despertares. Los detalles están en el artículo ESP32 Deep Sleep.

La mejor forma de tener un reloj que aguante los cortes de luz es combinar ambos: al arrancar, toma la hora del módulo DS3231 de inmediato, corrígela con NTP cuando haya Wi-Fi y actualiza el DS3231 en cada sincronización NTP. En el código de abajo el DS3231 guarda UTC en lugar de la hora local; así la regla de zona horaria vive en un solo sitio (la cadena TZ) y los cambios de horario de verano nunca estropean el RTC. Para detectar la sincronización NTP se registra una función de retorno con sntp_set_time_sync_notification_cb(). Como esa función se ejecuta dentro de la tarea de red, no se hace allí ningún trabajo I2C; solo levanta una bandera y loop() se encarga de escribir en el DS3231. El código usa la librería RTClib.
1#include <WiFi.h>
2#include <Wire.h>
3#include <RTClib.h>
4#include <sys/time.h>
5#include "esp_sntp.h"
6
7const char* WIFI_SSID = "AgAdi";
8const char* WIFI_PASS = "Sifre123";
9const char* TZ_INFO = "<+03>-3";
10
11RTC_DS3231 rtc;
12volatile bool ntpGeldi = false; // NTP eşitlemesi oldu mu? / did an NTP sync happen?
13
14void ntpCallback(struct timeval* tv) { // her NTP eşitlemesinde çağrılır / called on every NTP sync
15 ntpGeldi = true; // I2C işini loop() yapsın / let loop() do the I2C work
16}
17
18void setup() {
19 Serial.begin(115200);
20 Wire.begin(21, 22);
21 setenv("TZ", TZ_INFO, 1); // NTP gelmese de saat dilimi geçerli / TZ valid even without NTP
22 tzset();
23
24 // 1) Sistem saatini önce DS3231'den kur (DS3231'de UTC saklanır) / set system time from the DS3231 (stores UTC)
25 if (rtc.begin() && !rtc.lostPower()) {
26 struct timeval tv = { (time_t)rtc.now().unixtime(), 0 };
27 settimeofday(&tv, nullptr);
28 Serial.println("Saat DS3231'den / time from DS3231");
29 }
30
31 // 2) Wi-Fi gelince NTP saati düzeltir; bağlantı sonra gelse de SNTP yeniden dener
32 // NTP corrects the clock once Wi-Fi is up; SNTP keeps retrying if the link comes later
33 sntp_set_time_sync_notification_cb(ntpCallback);
34 WiFi.begin(WIFI_SSID, WIFI_PASS);
35 configTzTime(TZ_INFO, "pool.ntp.org", "time.cloudflare.com");
36}
37
38void loop() {
39 if (ntpGeldi) {
40 ntpGeldi = false;
41 rtc.adjust(DateTime((uint32_t)time(nullptr))); // DS3231'e UTC yaz / write UTC to the DS3231
42 Serial.println("DS3231 NTP ile guncellendi / DS3231 updated from NTP");
43 }
44
45 struct tm t;
46 if (getLocalTime(&t, 0)) {
47 Serial.println(&t, "%d.%m.%Y %H:%M:%S");
48 } else {
49 Serial.println("Saat yok: NTP ve DS3231 bekleniyor / no time yet");
50 }
51 delay(1000);
52}
53Ejecutar una tarea a una hora fija cada día
El uso más habitual de la hora NTP es la tarea programada: encender la bomba de riego 10 minutos a las 07:30, las luces del jardín a las 19:00 o enviar una medida cada hora en punto. El método es sencillo: lee la hora en loop() y arranca la tarea cuando la hora y el minuto coinciden con el objetivo. La verdadera sutileza es evitar que la tarea se dispare cientos de veces dentro del mismo minuto. Para ello guardas en una variable el día en que se ejecutó (tm_yday), de modo que nunca se repite el mismo día. El código de abajo activa el relé del GPIO 26 durante 10 minutos cada mañana de lunes a viernes a las 07:30; como mide la duración con millis(), el código nunca se bloquea.
1#include <WiFi.h>
2#include <time.h>
3
4const char* WIFI_SSID = "AgAdi";
5const char* WIFI_PASS = "Sifre123";
6const char* TZ_INFO = "<+03>-3";
7
8const int ROLE_PIN = 26; // röle modülü IN / relay module IN
9const int GOREV_SAAT = 7; // her gün 07:30 / every day at 07:30
10const int GOREV_DAKIKA = 30;
11const unsigned long SURE_MS = 10UL * 60 * 1000; // 10 dakika çalış / run for 10 minutes
12
13int sonGun = -1; // görevin çalıştığı son gün (tm_yday) / last day the task ran
14bool roleAcik = false;
15unsigned long acilisMs = 0;
16
17void setup() {
18 Serial.begin(115200);
19 pinMode(ROLE_PIN, OUTPUT);
20 digitalWrite(ROLE_PIN, LOW);
21 WiFi.begin(WIFI_SSID, WIFI_PASS);
22 while (WiFi.status() != WL_CONNECTED) delay(500);
23 configTzTime(TZ_INFO, "pool.ntp.org");
24}
25
26void loop() {
27 struct tm t;
28 if (getLocalTime(&t, 0)) { // saat geçerliyse / only when the time is valid
29 bool haftaici = (t.tm_wday >= 1 && t.tm_wday <= 5); // Pzt-Cuma / Mon-Fri
30 if (haftaici && t.tm_hour == GOREV_SAAT && t.tm_min == GOREV_DAKIKA && t.tm_yday != sonGun) {
31 sonGun = t.tm_yday; // bugün bir kez / once per day
32 digitalWrite(ROLE_PIN, HIGH);
33 roleAcik = true;
34 acilisMs = millis();
35 Serial.println(&t, "%H:%M gorev basladi / task started");
36 }
37 }
38 if (roleAcik && millis() - acilisMs >= SURE_MS) { // süre dolunca kapat / switch off after the period
39 digitalWrite(ROLE_PIN, LOW);
40 roleAcik = false;
41 Serial.println("Gorev bitti / task finished");
42 }
43 delay(200);
44}
45- Comparar segundos: una condición como t.tm_sec == 0 se salta la tarea si el bucle pierde ese segundo (mientras el Wi-Fi se reconecta o durante un delay largo). Comprobar el minuto y el día es más seguro.
- Comprobar que la hora es válida: no ejecutes tareas mientras getLocalTime() devuelva false, o tomarás decisiones equivocadas basadas en 1970 justo después de arrancar.
- Tarea perdida: si la luz se va a las 07:29 y vuelve a las 07:35, la tarea no se ejecuta ese día. Si es importante, plantea la condición como «son más de las 07:30 y hoy no se ha ejecutado» y guarda sonGun en la memoria flash con Preferences.
- Varias tareas: guarda hora, minuto y duración en un array y usa una variable de último día distinta para cada tarea.
- Cargas de alta corriente: maneja la bomba o la lámpara mediante un módulo de relé o un MOSFET, nunca directamente desde un pin.
Cambiar el intervalo de sincronización y vigilar NTP
El intervalo por defecto de 3 horas sirve para la mayoría de proyectos. Para sincronizar más a menudo (por ejemplo en exteriores con grandes cambios de temperatura) o para reducir el tráfico, pasa el nuevo intervalo en milisegundos a sntp_set_sync_interval(). Llámala antes de configTzTime(), porque el nuevo intervalo se aplica en el siguiente arranque o cuando vence el intervalo actual. Por las reglas del protocolo, no puede ser menor de 15 segundos. sntp_get_sync_status() devuelve SNTP_SYNC_STATUS_COMPLETED una vez tras completar una sincronización y luego se reinicia, así puedes detectar cada nueva sincronización.
1#include <WiFi.h>
2#include <time.h>
3#include "esp_sntp.h"
4
5const char* TZ_INFO = "<+03>-3";
6
7void setup() {
8 Serial.begin(115200);
9 WiFi.begin("AgAdi", "Sifre123");
10 while (WiFi.status() != WL_CONNECTED) delay(500);
11
12 sntp_set_sync_interval(6UL * 60 * 60 * 1000); // 6 saatte bir eşitle (en az 15 sn) / sync every 6 hours (min 15 s)
13 configTzTime(TZ_INFO, "pool.ntp.org"); // ayar sntp_init() öncesi verilir / set before sntp_init()
14}
15
16void loop() {
17 if (sntp_get_sync_status() == SNTP_SYNC_STATUS_COMPLETED) {
18 Serial.println("Yeni NTP esitlemesi / new NTP sync");
19 }
20 delay(1000);
21}
22¿Qué servidor NTP usar?
La opción más común es pool.ntp.org: un conjunto de miles de servidores gestionados por voluntarios de todo el mundo que en cada petición te da la dirección de uno cercano. Los subdominios por país (es.pool.ntp.org, mx.pool.ntp.org, ar.pool.ntp.org) acercan aún más el servidor. configTzTime() admite hasta tres nombres de servidor; si el primero no responde, pasa al siguiente.
| Servidor | Característica | Nota |
|---|---|---|
| pool.ntp.org | Pool de voluntarios, servidor más cercano | Primera opción para uso general |
| es.pool.ntp.org / europe.pool.ntp.org | Pool por país o continente | Latencia algo menor |
| time.cloudflare.com | Infraestructura de una gran empresa | Buena como servidor de respaldo |
| time.google.com | Reparte los segundos intercalares (leap smear) | No lo mezcles con otros servidores |
| La IP de tu router | Algunos routers ofrecen NTP local | Útil en una red local sin internet |
NTP en la Raspberry Pi Pico W: ntptime
En la Raspberry Pi Pico W (y la Pico 2 W) el mismo trabajo lo hace el módulo ntptime de MicroPython. ntptime.settime() consulta al servidor NTP y pone el RTC interno de la Pico en UTC. MicroPython no tiene cadenas de zona horaria como el ESP32; para la hora local sumas el desfase en segundos a UTC (3 × 3600 para Turquía en el ejemplo, 3600 para España en invierno y 7200 en verano, -10800 para Argentina). ntptime solo obtiene la hora una vez y no resincroniza automáticamente; en proyectos que funcionan mucho tiempo, llamar a settime() una o dos veces al día elimina la deriva. El RTC de la Pico W también se reinicia cuando se corta la alimentación, así que el respaldo con DS3231 vale también aquí. La conexión de la Pico W al Wi-Fi se explica en el artículo Pico W Wi-Fi y servidor web.
1import network
2import ntptime
3import time
4
5SSID = "AgAdi"
6PASS = "Sifre123"
7UTC_OFFSET = 3 * 3600 # Türkiye UTC+3 / Turkey UTC+3
8# localtime(): 0 = Pazartesi / 0 = Monday
9GUNLER = ("Pazartesi", "Sali", "Carsamba", "Persembe", "Cuma", "Cumartesi", "Pazar")
10
11wlan = network.WLAN(network.STA_IF)
12wlan.active(True)
13wlan.connect(SSID, PASS)
14while not wlan.isconnected(): # bağlanana kadar bekle / wait until connected
15 time.sleep(0.5)
16
17ntptime.host = "pool.ntp.org"
18for deneme in range(5): # NTP bazen zaman aşımına uğrar / NTP may time out
19 try:
20 ntptime.settime() # dahili RTC'yi UTC'ye ayarlar / sets the internal RTC to UTC
21 break
22 except OSError:
23 time.sleep(2)
24
25def yerel_saat(): # UTC + 3 saat / UTC + 3 hours
26 return time.localtime(time.time() + UTC_OFFSET)
27
28son_gun = -1
29while True:
30 yil, ay, gun, saat, dk, sn, hgun, ygun = yerel_saat()
31 print("{:02d}.{:02d}.{} {} {:02d}:{:02d}:{:02d}".format(gun, ay, yil, GUNLER[hgun], saat, dk, sn))
32 if saat == 7 and dk == 30 and ygun != son_gun: # her gün 07:30'da bir kez / once a day at 07:30
33 son_gun = ygun
34 print("Gorev calisti / task ran")
35 time.sleep(1)
36En MicroPython, time.localtime() devuelve una tupla de ocho elementos: año, mes (1–12), día, hora, minuto, segundo, día de la semana y día del año. Tiene dos diferencias con la struct tm del ESP32: el mes empieza en 1 y el día de la semana cuenta 0 = lunes. ntptime.settime() lanza OSError cuando la red agota el tiempo de espera; por eso el código tiene un bucle de cinco intentos.
Ideas de proyectos con el reloj ESP32 NTP
- Reloj de escritorio Wi-Fi: muestra la hora, la fecha y la temperatura de la habitación en una pantalla OLED y baja el brillo por la noche.
- Reloj LED gigante: muestra la hora NTP como texto desplazable en una matriz LED MAX7219.
- Riego programado: cada mañana mide la humedad del suelo y, si hace falta, enciende una mini bomba de agua durante un tiempo fijo.
- Registro de datos con marca de tiempo: guarda las lecturas de los sensores con fecha ISO en una tarjeta SD.
- Reloj con el tiempo: añade junto a la hora el tiempo obtenido de internet; el artículo ESP32 tiempo meteorológico continúa desde aquí.
- Informe matinal: cada día a las 08:00 envía a tu móvil por Telegram la temperatura de casa y los movimientos detectados durante la noche.
Errores frecuentes y soluciones
| Síntoma | Causa probable | Solución |
|---|---|---|
| La hora sale en 1970 o nunca llega | El Wi-Fi no está conectado o el puerto NTP (UDP 123) está bloqueado | Comprueba primero WiFi.status(); en redes de colegio u oficina prueba otra red o el punto de acceso del móvil |
| La hora se desvía exactamente tu desfase | No hay zona horaria, se muestra UTC | configTzTime con la cadena de tu zona, p. ej. "CET-1CEST,M3.5.0,M10.5.0/3" |
| La hora se desvía el doble del desfase | Signo invertido en la cadena TZ ("CET+1", "UTC-3") | Escribe "CET-1CEST,..." o "<-03>3" |
| El mes sale uno menos, el año 126 | tm_mon cuenta desde 0, tm_year desde 1900 | Usa tm_mon + 1, tm_year + 1900 o strftime |
| La tarea se dispara varias veces en el mismo minuto | No hay protección contra repetición | Guarda el día en que se ejecutó (tm_yday) y compruébalo |
| El bucle se queda 5 segundos parado en cada vuelta | Sin hora válida, getLocalTime() espera 5000 ms por defecto | Usa getLocalTime(&t, 0) dentro de loop() |
| Símbolos raros en el nombre del día en la OLED | La fuente no tiene tildes ni ñ | Escribe los días en ASCII o pasa a la librería U8g2 |
ESP32 NTP es la forma más barata y cómoda de añadir una hora exacta a un proyecto: con pocas líneas de código obtiene la hora al segundo, gestiona por sí mismo la zona horaria y el horario de verano y, con un DS3231, aguanta también los cortes de luz. En la app BlueGrays encontrarás proyectos de hora real con ESP32 NTP, un reloj digital con RTC DS1307, una pantalla del tiempo en OLED y un registrador de temperatura con marca de tiempo, paso a paso con esquema y código.
Preguntas frecuentes
¿El ESP32 puede mantener la hora sin internet?
Sí, una vez que la ha recibido por NTP: el ESP32 sigue contando la hora del sistema con su propio temporizador y perder el Wi-Fi no detiene el reloj. Pero si se va la luz o la placa se reinicia, la hora vuelve a 1970. En lugares sin internet, o para un reloj que aguante cortes de luz, añade un módulo RTC con pila como el DS3231.
¿Por qué la hora NTP del ESP32 sale con una o dos horas de diferencia?
Sin zona horaria, el ESP32 muestra UTC, así que en España sale una hora menos en invierno y dos en verano. Usa configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"). Si la diferencia es el doble de tu desfase, el signo de la cadena TZ está invertido.
¿Cómo configuro el horario de verano en el ESP32?
Lo más sencillo es configTzTime con una zona POSIX que incluya las reglas de cambio. Para la España peninsular es "CET-1CEST,M3.5.0,M10.5.0/3"; el ESP32 cambia solo el último domingo de marzo y de octubre. configTime con un desfase de verano fijo no conoce esas fechas.
¿Cada cuánto se sincroniza el ESP32 con NTP?
En el núcleo Arduino ESP32, SNTP vuelve a consultar al servidor cada 3 horas por defecto y mantiene la hora con su propio temporizador entre medias. Puedes cambiar el intervalo en milisegundos con sntp_set_sync_interval(), aunque por protocolo no puede ser inferior a 15 segundos.
¿Necesito un módulo RTC con el ESP32?
No, si el dispositivo está siempre alimentado y llega al Wi-Fi; NTP da una hora más exacta. Si necesitas la hora correcta tras un corte de luz antes de que vuelva internet, o el dispositivo trabajará sin internet, añade un módulo RTC con pila como el DS3231.
¿Cómo obtengo la hora NTP en la Raspberry Pi Pico W?
En MicroPython, tras conectar al Wi-Fi, basta con import ntptime y ntptime.settime(); esto pone el RTC interno de la Pico en UTC. Para la hora local suma tu desfase en segundos, por ejemplo time.localtime(time.time() - 10800) para Argentina. ntptime no resincroniza solo, así que repite settime() una o dos veces al día.
Proyectos para hacer después de leer esto
Ver todos los proyectos
ESP32: hora real desde Internet con NTP
El ESP32 se conecta a Wi-Fi y obtiene la hora real con precisión de milisegundos desde servidores NTP (Network Time Protocol). Gracias a configTzTime(), se aplica automáticamente la zona horaria de Turquía (TRT, UTC+3) y el cambio DST se gestiona mediante la cadena POSIX TZ. La hora se muestra en el monitor serie y en una pantalla LCD I2C opcional. configTzTime() inicia el servicio SNTP; se realizan resincronizaciones periódicas mientras hay acceso a la red, y el reloj del sistema sigue avan

Reloj digital con ESP32 y RTC DS1307 (I2C + LCD)
En este proyecto, mostramos la hora y la fecha en una pantalla LCD I2C de 16x2 mediante el módulo de reloj en tiempo real (RTC) DS1307. El DS1307 es un RTC con batería de respaldo que conserva la hora incluso si el ESP32 se apaga. Ambos módulos comparten la misma línea I2C (SDA/SCL), por lo que aprendemos a controlar varios dispositivos con solo dos cables de señal. Con este proyecto conocerás el protocolo I2C, el funcionamiento de los módulos RTC y la visualización dinámica en pantallas LCD.

Conexión Wi-Fi STA con ESP32 (con pantalla LCD)
En este proyecto, conectamos el ESP32 a la red Wi-Fi doméstica en modo Station (STA) de forma segura y tolerante a fallos. Separamos el SSID y la contraseña en el archivo secrets.h para evitar que se filtren a Git, configuramos un mecanismo de reconexión automática y mostramos el nivel de señal RSSI y la dirección IP en una pantalla LCD I2C. El LED interno actúa como indicador de estado.

ESP32 HTTPClient + API del tiempo (con pantalla OLED SSD1306)
En este proyecto, utilizamos el ESP32 para realizar una llamada a la API REST de la información meteorológica (wttr.in) a través de Internet, utilizando HTTPClient.h. A diferencia de la versión anterior con pantalla LCD I2C, aquí utilizamos una pantalla OLED SSD1306 (128×64), lo que permite utilizar fuentes grandes, iconos vectoriales y una presentación visual muy rica en una sola pantalla. Para indicar el tiempo, se dibujan en tiempo de ejecución 7 iconos vectoriales diferentes (soleado, parc

ESP32 HTTPClient + API del tiempo (con pantalla LCD)
En este proyecto, utilizamos el ESP32 para realizar una llamada a la API REST de la página web de información meteorológica (wttr.in) a través de Internet, utilizando HTTPClient.h. Analizamos la respuesta JSON devuelta con ArduinoJson v7 y mostramos la información sobre la ciudad, la temperatura, la humedad, el tiempo y el viento en una pantalla LCD I2C, tanto en turco como en inglés. wttr.in es gratuito y no requiere clave API: basta con escribir el nombre de cualquier ciudad para obtener su

Modo de suspensión profunda del ESP32 — Ahorro de batería (OLED + contador en RTC RAM)
El modo «Deep Sleep» del ESP32 reduce el consumo de corriente a unos 10 µA al desactivar la CPU y la mayoría de los periféricos, lo cual es de vital importancia para los dispositivos IoT que funcionan con pilas. En este proyecto utilizamos tres fuentes de activación diferentes: el temporizador (15 s), un pin externo (EXT0 — botón GPIO 33) y el sensor táctil (T0/GPIO 4). Las variables marcadas con RTC_DATA_ATTR se conservan durante el modo Deep Sleep: el contador de arranque, el contador de act

Registrador de Temperatura ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
¡Construye un registrador de temperatura con marca de tiempo con ESP32! Lee temperatura/humedad del DHT22 cada minuto, obtén la hora real del DS1307 RTC y registra en tarjeta microSD como CSV. El OLED muestra en vivo la temperatura (fuente grande), porcentaje de humedad, marca de tiempo y contador de registros. El archivo /temp_log.csv en la tarjeta SD se abre en Excel, Google Sheets o Python pandas para análisis — perfecto para monitoreo de invernadero, seguimiento de temperatura del hog

Pantalla LCD I2C para ESP32 (16x2)
En este proyecto, mostramos texto conectando una pantalla LCD de 16x2 caracteres al ESP32 a través del protocolo I2C. Gracias al I2C, podemos controlar la pantalla con solo 2 pines de datos (SDA y SCL); así se elimina el desperdicio de pines que supone la conexión paralela clásica, que requiere más de 6 pines. El código imprime un mensaje fijo en la primera línea y un contador que aumenta cada segundo en la segunda línea. Además, al inicio ejecutamos el escáner I2C para mostrar la dirección de l
Más artículos
App BlueGrays
Monta este circuito paso a paso en tu teléfono
Esquema, código y lista de piezas en una sola pantalla. Más de 200 proyectos de Arduino, ESP32 y Pico listos para montar.
Escanea con tu teléfono
Apunta la cámara al código y se abrirá la tienda de apps de tu teléfono.
o ábrela en la tienda

