ESP32 NTP : l'heure d'internet avec fuseau horaire, OLED et RTC
Obtenir l'heure NTP sur l'ESP32 : configTzTime et fuseau horaire, getLocalTime, strftime, horloge OLED, secours DS3231, tâche quotidienne et ntptime sur Pico W.
Récupérer l'heure avec ESP32 NTP, c'est relier la carte aux serveurs de temps d'internet pour connaître la date et l'heure à quelques dizaines de millisecondes près, sans aucun module horloge. Une fois l'ESP32 connecté au Wi-Fi, un seul appel à configTzTime() récupère l'heure, applique ton fuseau horaire, puis la carte continue de compter toute seule. Horloges de bureau, arrosage programmé, horodatage des données, réveil du matin ou éclairage qui s'allume à heure fixe : plus besoin d'un module RTC séparé.
Dans ce guide, on voit pas à pas comment fonctionne NTP, comment régler le bon fuseau horaire (France, Belgique, Suisse, Québec), comment écrire la date dans n'importe quel format avec getLocalTime et strftime, comment afficher l'heure sur un écran OLED, comment la conserver avec un module DS3231 quand le Wi-Fi ou le courant coupe, et comment lancer une tâche à heure fixe chaque jour. Dans la dernière partie, on fait la même chose sur le Raspberry Pi Pico W avec le module MicroPython ntptime.

Qu'est-ce que NTP et comment l'ESP32 obtient-il l'heure ?
NTP (Network Time Protocol) est un protocole utilisé depuis les années 1980 pour synchroniser les horloges des ordinateurs et des appareils via internet. Les serveurs sont organisés en couches appelées strates : les serveurs de strate 1 sont reliés directement à une horloge atomique ou au GPS, ceux de strate 2 prennent leur heure chez eux, et ainsi de suite. L'ESP32 demande l'heure avec un petit paquet sur le port UDP 123 ; le serveur répond avec un horodatage contenant les secondes écoulées depuis 1900 et une fraction de seconde. L'ESP32 tient compte du temps d'aller-retour du paquet et règle sa propre horloge. Sur une connexion domestique, la précision obtenue se situe en général entre 10 et 50 millisecondes, largement suffisant pour une horloge.
La pile réseau lwIP de l'ESP32 utilise SNTP, une version simplifiée de NTP. Une fois l'heure reçue, l'ESP32 la garde comme heure système et continue de compter avec un compteur dérivé de son quartz ; il ne va pas sur internet à chaque seconde. En arrière-plan, il interroge de nouveau le serveur à intervalles réguliers (par défaut toutes les 3 heures dans le cœur Arduino ESP32) et corrige les petites dérives. L'heure système est stockée en UTC ; l'heure locale que tu vois est calculée en lui appliquant la règle du fuseau horaire.
| Notion | Valeur / explication |
|---|---|
| Protocole et port | SNTP, UDP 123 |
| Horodatage | Secondes depuis 1900 (l'ESP32 les convertit en temps Unix depuis 1970) |
| Précision typique | 10–50 ms sur une connexion domestique |
| Resynchronisation | 3 heures par défaut dans Arduino ESP32, modifiable avec sntp_set_sync_interval() (minimum 15 s) |
| Nombre maximal de serveurs | 3 (les noms passés à configTzTime) |
| Trafic internet | Requête et réponse sont des paquets NTP de 48 octets ; quelques kilo-octets par jour au plus |
| Ce qu'il faut | Un ESP32, un réseau Wi-Fi 2,4 GHz et une connexion internet |
configTime et configTzTime : régler le fuseau horaire
Le cœur Arduino ESP32 propose deux façons de démarrer NTP. configTime(gmtOffset_sec, daylightOffset_sec, serveur1, serveur2, serveur3) prend le décalage par rapport à UTC et le décalage d'heure d'été en secondes. Pour la Turquie, par exemple, on écrit configTime(10800, 0, "pool.ntp.org"), car 3 heures font 10 800 secondes et la Turquie reste à UTC+3 toute l'année sans heure d'été depuis 2016. Cette méthode est simple, mais elle ne connaît pas les dates de changement d'heure de ta région ; en France, le passage à l'heure d'été tomberait le mauvais jour, il vaut donc mieux utiliser configTzTime.
La bonne méthode est configTzTime(TZ, serveur1, serveur2, serveur3). Cette fonction prend une chaîne de fuseau horaire au format POSIX qui contient aussi les règles d'heure d'été, et l'ESP32 fait le changement tout seul chaque année. Pour la France, la Belgique et la Suisse, la chaîne est "CET-1CEST,M3.5.0,M10.5.0/3" : CET est l'abréviation de l'heure d'hiver, -1 signifie qu'il faut retirer une heure à l'heure locale pour obtenir UTC, CEST est l'heure d'été, M3.5.0 veut dire « dernier dimanche de mars » et M10.5.0/3 « dernier dimanche d'octobre à 3 h ». Les exemples ci-dessous utilisent le fuseau turc "<+03>-3" ; remplace simplement TZ_INFO par la valeur du tableau.
| Région | Chaîne TZ (configTzTime) | Équivalent avec configTime |
|---|---|---|
| Turquie (UTC+3 toute l'année) | <+03>-3 | configTime(10800, 0, ...) |
| France, Belgique, Suisse | CET-1CEST,M3.5.0,M10.5.0/3 | Pas adapté, utilise configTzTime |
| Royaume-Uni, Portugal | GMT0BST,M3.5.0/1,M10.5.0 | Pas adapté, utilise configTzTime |
| Québec (Montréal) | EST5EDT,M3.2.0,M11.1.0 | Pas adapté, utilise configTzTime |
| La Réunion (UTC+4) | <+04>-4 | configTime(14400, 0, ...) |
| UTC (pour l'horodatage) | UTC0 | configTime(0, 0, ...) |
Matériel et câblage
Pour voir l'heure dans le moniteur série, un ESP32 et un réseau Wi-Fi suffisent. Plus loin dans le guide, on utilise un écran OLED SSD1306 de 0,96 pouce pour afficher l'heure, un module RTC DS3231 pour la conserver pendant les coupures de courant et un module relais pour l'exemple de tâche programmée. L'OLED et le DS3231 partagent le même bus I2C, donc ils se branchent sur les deux mêmes broches. Tu peux consulter le brochage de l'ESP32 avec l'outil Pinout.
| Broche du composant | ESP32 | Raspberry Pi Pico W | Remarque |
|---|---|---|---|
| OLED / DS3231 VCC | 3V3 | 3V3 (OUT) | Les deux fonctionnent en 3,3 V |
| OLED / DS3231 GND | GND | GND | Masse commune |
| OLED / DS3231 SDA | GPIO 21 | GP4 | Ligne de données I2C |
| OLED / DS3231 SCL | GPIO 22 | GP5 | Ligne d'horloge I2C |
| Module relais IN | GPIO 26 | GP15 | Exemple de tâche programmée |
| Module relais VCC | 5V (VIN) | VBUS | La plupart des bobines de relais demandent 5 V |
Pas à pas : ton premier code ESP32 NTP
- Vérifie que le paquet de cartes ESP32 est installé dans l'Arduino IDE et choisis ESP32 Dev Module dans Outils. Si tu ne sais pas comment faire, lis l'article Qu'est-ce que l'ESP32.
- Aucune bibliothèque supplémentaire n'est nécessaire : WiFi.h et time.h sont fournis avec le cœur ESP32.
- Remplace WIFI_SSID et WIFI_PASS par ceux de ton réseau, et TZ_INFO par "CET-1CEST,M3.5.0,M10.5.0/3".
- Téléverse le code et ouvre le moniteur série à 115200 bauds.
- Tu dois d'abord voir les points du Wi-Fi, puis après quelques secondes la date et l'heure mises à jour chaque seconde.
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| Ligne | Que fait-elle ? |
|---|---|
| configTzTime(TZ_INFO, ...) | Démarre le client SNTP, règle serveurs et fuseau ; rend la main tout de suite, l'heure arrive en arrière-plan |
| getLocalTime(&t) | Remplit une struct tm avec l'heure locale ; sans heure valide, attend jusqu'à 5 secondes puis renvoie false |
| Serial.println(&t, "...") | La classe Print de l'ESP32 peut afficher une struct tm directement avec un format strftime |
| time(nullptr) | Renvoie les secondes depuis 1970 (temps Unix, UTC) |
Un détail important : getLocalTime() décide si l'heure est « valide » en regardant l'année. Au démarrage, l'heure système est le 1er janvier 1970 et la fonction renvoie false tant que l'année n'est pas postérieure à 2016. Ainsi, un code qui enregistre des données ou lance des tâches avant l'arrivée de NTP ne produit pas par erreur des entrées datées de 1970, à condition de tester la valeur renvoyée. Le second paramètre est le délai d'attente en millisecondes ; pour ne pas bloquer loop(), écris getLocalTime(&t, 0) afin qu'elle n'essaie qu'une fois.
struct tm : les morceaux de l'heure
getLocalTime() écrit l'heure découpée dans une struct tm. Avec ces champs, tu compares heures, jours ou mois pour construire des tâches programmées. Deux champs piègent souvent les débutants : le mois commence à 0 et l'année se compte à partir de 1900.
| Champ | Plage | Exemple (lundi 16 novembre 2026, 14:05:09) |
|---|---|---|
| tm_sec | 0–59 (60 lors d'une seconde intercalaire) | 9 |
| tm_min | 0–59 | 5 |
| tm_hour | 0–23 | 14 |
| tm_mday | 1–31 | 16 |
| tm_mon | 0–11 (0 = janvier) | 10 |
| tm_year | Années depuis 1900 | 126 (1900 + 126 = 2026) |
| tm_wday | 0–6 (0 = dimanche) | 1 |
| tm_yday | 0–365 (jour de l'année, à partir de 0) | 319 |
Formater la date et l'heure avec strftime
strftime() écrit l'heure d'une struct tm dans un tableau de caractères selon un modèle. Les codes précédés d'un signe pourcentage sont remplacés par la valeur correspondante, les autres caractères restent tels quels. Dimensionne le tableau à la sortie la plus longue plus un (pour le zéro final) : « 14:05:09 » fait 8 caractères, le tableau doit donc faire au moins 9 octets. Les codes les plus utilisés :
| Code | Signification | Exemple de sortie |
|---|---|---|
| %H / %M / %S | Heure (00–23) / minute / seconde | 14 / 05 / 09 |
| %I et %p | Heure sur 12 heures et AM/PM | 02 PM |
| %d / %m / %Y | Jour / mois (01–12) / année sur quatre chiffres | 16 / 11 / 2026 |
| %y | Année sur deux chiffres | 26 |
| %A / %a | Jour de la semaine long / court (en anglais) | Monday / Mon |
| %B / %b | Mois long / court (en anglais) | November / Nov |
| %j | Jour de l'année (001–366) | 320 |
| %F / %T | Raccourcis de %Y-%m-%d / %H:%M:%S | 2026-11-16 / 14:05:09 |
| %Z | Abréviation du fuseau | +03 (ou CET / CEST) |
La bibliothèque C de l'ESP32 ne connaît que les noms anglais des jours et des mois ; %A écrit toujours « Monday ». Pour une date en français, tu ranges les noms dans tes propres tableaux et tu utilises tm_wday et tm_mon comme indices. L'exemple ci-dessous le fait avec des noms turcs ; remplace-les par « dimanche, lundi, … » et « janvier, février, … ». Comme le moniteur série gère l'UTF-8, les accents s'y affichent correctement. Le code produit une date lisible et le format ISO triable (2026-11-16 14:05:09) pour l'enregistrement.
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}
34Afficher l'heure et la date sur un écran OLED
Pour transformer la sortie du moniteur série en vraie horloge de bureau, l'écran OLED SSD1306 de 0,96 pouce est idéal : bon marché, branché avec deux fils, il affiche de grands chiffres bien nets. Le code ci-dessous affiche en haut le jour et la date, au centre l'heure en grand et en bas l'état du Wi-Fi. Comme il ne redessine que lorsque la seconde change, et pas à chaque tour de boucle, il n'occupe pas le bus I2C pour rien. Installe les bibliothèques Adafruit SSD1306 et Adafruit GFX depuis le gestionnaire de bibliothèques ; l'écran lui-même est présenté dans l'article Écran 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}
66Remarque que les noms des jours sont écrits sans accents : la police par défaut d'Adafruit GFX n'a pas de caractères comme é, è ou ç et affiche à la place des symboles sans signification. En taille de texte 2, chaque caractère fait 12 pixels de large ; « 14:05:09 » et ses 8 caractères occupent 96 pixels, et en partant du pixel 16 ils sont parfaitement centrés sur l'écran de 128 pixels. Si tu préfères un écran LCD 16x2, affiche les mêmes sorties strftime avec lcd.print() ; le câblage est expliqué dans l'article Écran LCD I2C.
Que devient l'heure quand le Wi-Fi coupe ? Secours RTC avec DS3231
La question la plus fréquente : si le Wi-Fi tombe, l'heure tombe-t-elle aussi ? Non. Une fois que l'ESP32 a reçu l'heure par NTP, il continue de compter avec son propre compteur ; la perte du Wi-Fi n'arrête pas l'horloge. Comme ce compteur tourne sur le quartz de 40 MHz, il dérive d'environ une ou deux secondes par jour, et la synchronisation suivante corrige l'écart au retour du Wi-Fi. Le vrai problème, c'est une coupure de courant ou un redémarrage par le bouton EN : l'heure système revient alors à 1970 et, sans Wi-Fi, l'ESP32 n'a aucun moyen de connaître l'heure.
| Situation | L'heure est-elle conservée ? | Que faire ? |
|---|---|---|
| Le Wi-Fi coupe un moment | Oui, l'ESP32 continue de compter | Rien ; NTP corrige au retour du Wi-Fi |
| Deep sleep (sommeil profond) | Oui, le compteur RTC continue | Pour de longs sommeils, resynchronise avec NTP à certains réveils |
| Coupure de courant ou bouton EN | Non, l'horloge revient à 1970 | Ajoute un module RTC sur pile comme le DS3231 |
| Pas d'internet du tout (champ, entrepôt) | NTP n'arrive jamais | Prends l'heure uniquement d'un module RTC |
Pendant le sommeil profond, l'heure n'est pas comptée par le quartz principal mais par un oscillateur RC interne moins précis. Cet oscillateur est sensible à la température, si bien que lors de longs sommeils l'horloge peut dériver de quelques secondes, voire plus ; pour les projets sur batterie qui dorment des heures, prends l'habitude de resynchroniser avec NTP tous les quelques réveils. Les détails sont dans l'article ESP32 Deep Sleep.

La meilleure façon d'avoir une horloge qui résiste aux coupures est de combiner les deux : au démarrage, prendre tout de suite l'heure du module DS3231, la corriger par NTP dès que le Wi-Fi est là, et mettre à jour le DS3231 à chaque synchronisation NTP. Dans le code ci-dessous, le DS3231 stocke l'heure UTC plutôt que l'heure locale ; ainsi, la règle de fuseau reste à un seul endroit (la chaîne TZ) et les changements d'heure n'abîment jamais le RTC. Pour détecter la synchronisation NTP, on enregistre une fonction de rappel avec sntp_set_time_sync_notification_cb(). Comme ce rappel s'exécute dans la tâche réseau, aucun travail I2C n'y est fait ; il lève seulement un drapeau et c'est loop() qui écrit dans le DS3231. Le code utilise la bibliothèque 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}
53Lancer une tâche à heure fixe chaque jour
L'usage le plus courant de l'heure NTP, c'est la tâche programmée : faire tourner la pompe d'arrosage 10 minutes à 07:30, allumer l'éclairage du jardin à 19:00, envoyer une mesure à chaque heure pile. La méthode est simple : lire l'heure dans loop() et lancer la tâche quand l'heure et la minute correspondent à la cible. La vraie subtilité consiste à empêcher la tâche de se déclencher des centaines de fois dans la même minute. Pour cela, tu mémorises dans une variable le jour où elle a tourné (tm_yday), ainsi elle ne se relance jamais le même jour. Le code ci-dessous active le relais du GPIO 26 pendant 10 minutes chaque matin de semaine à 07:30 ; comme il mesure la durée avec millis(), il ne bloque jamais.
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- Tester les secondes : une condition comme t.tm_sec == 0 saute la tâche si la boucle rate cette seconde (pendant une reconnexion Wi-Fi ou un long delay). Tester la minute et le jour est plus sûr.
- Vérifier que l'heure est valide : ne lance pas de tâche tant que getLocalTime() renvoie false, sinon tu prends de mauvaises décisions basées sur 1970 juste après le démarrage.
- Tâche manquée : si le courant coupe à 07:29 et revient à 07:35, la tâche ne tourne pas ce jour-là. Si c'est important, écris la condition « il est plus de 07:30 et elle n'a pas encore tourné aujourd'hui » et enregistre sonGun en flash avec Preferences.
- Plusieurs tâches : range heure, minute et durée dans un tableau et utilise une variable de dernier jour séparée pour chaque tâche.
- Charges à fort courant : pilote une pompe ou une lampe via un module relais ou un MOSFET, jamais directement depuis une broche.
Modifier l'intervalle de synchronisation et surveiller NTP
L'intervalle par défaut de 3 heures convient à la plupart des projets. Pour synchroniser plus souvent (par exemple en extérieur avec de fortes variations de température) ou réduire le trafic, passe le nouvel intervalle en millisecondes à sntp_set_sync_interval(). Appelle-la avant configTzTime(), car le nouvel intervalle s'applique au démarrage suivant ou à l'expiration de l'intervalle en cours. D'après les règles du protocole, il ne peut pas être inférieur à 15 secondes. sntp_get_sync_status() renvoie SNTP_SYNC_STATUS_COMPLETED une fois après chaque synchronisation terminée, puis se remet à zéro ; tu peux ainsi repérer chaque nouvelle synchronisation.
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}
22Quel serveur NTP choisir ?
Le choix le plus courant est pool.ntp.org : un ensemble de milliers de serveurs gérés par des bénévoles du monde entier, qui te donne à chaque requête l'adresse d'un serveur proche. Les sous-domaines par pays (fr.pool.ntp.org, be.pool.ntp.org, ch.pool.ntp.org, ca.pool.ntp.org) rapprochent encore le serveur. configTzTime() accepte jusqu'à trois noms de serveur ; si le premier ne répond pas, il passe au suivant.
| Serveur | Particularité | Remarque |
|---|---|---|
| pool.ntp.org | Pool de bénévoles, serveur le plus proche | Premier choix pour un usage général |
| fr.pool.ntp.org / europe.pool.ntp.org | Pool par pays ou continent | Latence un peu plus faible |
| time.cloudflare.com | Infrastructure d'une grande entreprise | Bon serveur de secours |
| time.google.com | Étale les secondes intercalaires (leap smear) | Ne pas le mélanger avec d'autres serveurs |
| Adresse IP de ta box | Certaines box proposent un NTP local | Utile sur un réseau local sans internet |
NTP sur le Raspberry Pi Pico W : ntptime
Sur le Raspberry Pi Pico W (et le Pico 2 W), c'est le module MicroPython ntptime qui fait le travail. ntptime.settime() interroge le serveur NTP et règle le RTC interne du Pico sur l'heure UTC. MicroPython n'a pas de chaîne de fuseau comme l'ESP32 ; pour l'heure locale, tu ajoutes ton décalage en secondes à UTC (3 × 3600 pour la Turquie dans l'exemple, 3600 pour la France en hiver et 7200 en été). ntptime ne récupère l'heure qu'une fois et ne resynchronise pas automatiquement ; pour les projets qui tournent longtemps, appeler settime() une ou deux fois par jour supprime la dérive. Le RTC du Pico W est lui aussi remis à zéro à la coupure de courant, donc le secours DS3231 s'applique ici aussi. La connexion du Pico W au Wi-Fi est détaillée dans l'article Pico W Wi-Fi et serveur 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() renvoie un tuple de huit éléments : année, mois (1–12), jour, heure, minute, seconde, jour de la semaine et jour de l'année. Il diffère de la struct tm de l'ESP32 sur deux points : le mois commence à 1 et le jour de la semaine compte 0 = lundi. ntptime.settime() lève une OSError quand le réseau dépasse le délai ; c'est pour cela que le code comporte une boucle de cinq tentatives.
Idées de projets avec l'horloge ESP32 NTP
- Horloge de bureau Wi-Fi : affiche l'heure, la date et la température de la pièce sur un écran OLED et baisse la luminosité la nuit.
- Horloge LED géante : affiche l'heure NTP en texte défilant sur une matrice LED MAX7219.
- Arrosage programmé : chaque matin, mesure l'humidité du sol et, si besoin, fais tourner une mini pompe à eau pendant une durée fixe.
- Enregistrement horodaté : écris les mesures des capteurs avec une date ISO sur une carte SD.
- Horloge météo : ajoute à côté de l'heure la météo récupérée sur internet ; l'article ESP32 météo prend la suite.
- Rapport du matin : chaque jour à 08:00, envoie sur ton téléphone via Telegram la température de la maison et les mouvements détectés pendant la nuit.
Erreurs fréquentes et solutions
| Symptôme | Cause probable | Solution |
|---|---|---|
| L'heure affiche 1970 ou n'arrive jamais | Wi-Fi non connecté ou port NTP (UDP 123) bloqué | Vérifie d'abord WiFi.status() ; sur un réseau d'école ou d'entreprise, essaie un autre réseau ou le partage de connexion du téléphone |
| L'heure est décalée d'exactement 1 ou 2 heures | Pas de fuseau défini, l'heure UTC est affichée | configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", ...) |
| L'heure est décalée du double du décalage | Signe inversé dans la chaîne TZ ("CET+1") | Écris "CET-1CEST,..." ou "<+03>-3" |
| Le mois a un de moins, l'année vaut 126 | tm_mon compte à partir de 0, tm_year à partir de 1900 | Utilise tm_mon + 1, tm_year + 1900 ou strftime |
| La tâche se déclenche plusieurs fois dans la même minute | Pas de protection contre la répétition | Mémorise le jour d'exécution (tm_yday) et teste-le |
| La boucle reste bloquée 5 secondes à chaque tour | Sans heure valide, getLocalTime() attend 5000 ms par défaut | Utilise getLocalTime(&t, 0) dans loop() |
| Symboles bizarres dans le nom du jour sur l'OLED | La police n'a pas d'accents | Écris les jours en ASCII ou passe à la bibliothèque U8g2 |
ESP32 NTP est le moyen le moins cher et le plus simple d'ajouter une heure exacte à un projet : en quelques lignes de code, il récupère l'heure à la seconde près, gère tout seul le fuseau et l'heure d'été et, avec un DS3231 en plus, résiste aussi aux coupures de courant. Dans l'application BlueGrays, tu trouveras des projets d'heure réelle par ESP32 NTP, d'horloge numérique avec RTC DS1307, d'affichage météo sur OLED et d'enregistreur de température horodaté, pas à pas avec schéma et code.
Questions fréquentes
L'ESP32 peut-il garder l'heure sans internet ?
Oui, une fois qu'il l'a reçue par NTP : l'ESP32 continue de compter l'heure système avec son propre compteur, et la perte du Wi-Fi n'arrête pas l'horloge. En revanche, si le courant coupe ou si la carte redémarre, l'heure revient à 1970. Pour un lieu sans internet ou une horloge qui résiste aux coupures, ajoute un module RTC sur pile comme le DS3231.
Pourquoi l'heure NTP de mon ESP32 est-elle décalée d'une ou deux heures ?
Sans fuseau horaire, l'ESP32 affiche l'heure UTC, soit une heure de moins qu'en France en hiver et deux en été. Utilise configTzTime("CET-1CEST,M3.5.0,M10.5.0/3", "pool.ntp.org"). Si le décalage vaut le double, le signe de ta chaîne TZ est inversé.
Comment gérer l'heure d'été sur l'ESP32 ?
Le plus simple est configTzTime avec un fuseau POSIX qui contient les règles de changement. Pour la France, la Belgique et la Suisse, c'est "CET-1CEST,M3.5.0,M10.5.0/3" ; l'ESP32 change alors tout seul le dernier dimanche de mars et d'octobre. configTime avec un décalage d'été fixe ne connaît pas ces dates.
À quelle fréquence l'ESP32 se synchronise-t-il avec NTP ?
Dans le cœur Arduino ESP32, SNTP interroge de nouveau le serveur toutes les 3 heures par défaut et garde l'heure avec son propre compteur entre deux. Tu peux changer l'intervalle en millisecondes avec sntp_set_sync_interval(), mais le protocole impose au moins 15 secondes.
Faut-il un module RTC avec l'ESP32 ?
Pas si l'appareil est toujours alimenté et accède au Wi-Fi ; NTP donne une heure plus précise. Si tu as besoin de la bonne heure après une coupure avant le retour d'internet, ou si l'appareil fonctionne sans internet, ajoute un module RTC sur pile comme le DS3231.
Comment obtenir l'heure NTP sur le Raspberry Pi Pico W ?
En MicroPython, après la connexion Wi-Fi, il suffit de import ntptime puis ntptime.settime() ; cela règle le RTC interne du Pico sur UTC. Pour l'heure française, utilise time.localtime(time.time() + 3600) en hiver et + 7200 en été. ntptime ne resynchronise pas tout seul, donc relance settime() une ou deux fois par jour.
Projets à réaliser après cet article
Voir tous les projets
ESP32 : heure réelle depuis Internet via NTP
L'ESP32 se connecte au Wi-Fi et récupère l'heure réelle avec une précision de la milliseconde depuis des serveurs NTP (Network Time Protocol). Grâce à configTzTime(), le fuseau horaire de la Turquie (TRT, UTC+3) est appliqué automatiquement et le passage DST est géré via la chaîne POSIX TZ. L'heure est affichée sur le moniteur série et sur un écran LCD I2C en option. configTzTime() démarre le service SNTP ; des resynchronisations périodiques ont lieu tant que l'accès au réseau est disponible

Horloge numérique avec ESP32 et RTC DS1307 (I2C + LCD)
Dans ce projet, nous affichons l'heure et la date sur un écran LCD 16x2 I2C à l'aide du module d'horloge en temps réel (RTC) DS1307. Le DS1307 est une RTC avec batterie de secours qui conserve l'heure même si l'ESP32 s'éteint. Les deux modules partagent la même ligne I2C (SDA/SCL), ce qui nous permet d'apprendre à contrôler plusieurs appareils avec seulement deux fils de signal. Vous decouvrirez ainsi le protocole I2C et le fonctionnement des modules RTC en pratique.

Connexion Wi-Fi STA ESP32 (avec écran LCD)
Dans ce projet, nous connectons l'ESP32 au réseau Wi-Fi domestique en mode Station (STA) de manière sécurisée et tolérante aux pannes. Nous isolons SSID/mot de passe dans un fichier secrets.h pour empêcher leur fuite sur Git, mettons en place une reconnexion automatique, et affichons le niveau RSSI et l'adresse IP sur un écran LCD I2C. La LED intégrée sert d'indicateur d'état.

ESP32 HTTPClient + API météo (avec écran OLED SSD1306)
Dans ce projet, nous effectuons un appel à l'API REST de la météo (wttr.in) via Internet à l'aide de l'ESP32 et de la bibliothèque HTTPClient.h. Contrairement à la version précédente avec écran LCD I2C, nous utilisons ici un écran OLED SSD1306 (128×64) — ce qui permet d’afficher une police de grande taille, des icônes vectorielles et une présentation visuelle riche sur un seul écran. Sept icônes vectorielles différentes représentant les conditions météorologiques (ensoleillé, partiellement nua

ESP32 HTTPClient + API météo (avec écran LCD)
Dans ce projet, nous effectuons un appel REST à l'API météo (wttr.in) via Internet à l'aide de l'ESP32 et de la bibliothèque HTTPClient.h. À l’aide d’ArduinoJson v7, nous analysons la réponse JSON renvoyée et affichons les informations relatives à la ville, à la température, à l’humidité, à la météo et au vent sur un écran LCD I2C, en turc et en anglais. wttr.in est gratuit et ne nécessite pas de clé API : il suffit de saisir le nom d’une ville pour obtenir les conditions météo en temps réel d

Mode veille profonde de l'ESP32 — Économie de batterie (OLED + compteur RAM RTC)
Le mode « Deep Sleep » de l'ESP32 réduit la consommation de courant à environ 10 µA en désactivant le processeur et la plupart des périphériques, ce qui revêt une importance cruciale pour les appareils IoT fonctionnant sur batterie. Dans ce projet, nous utilisons trois sources de réveil différentes : une minuterie (timer, 15 s), une broche externe (EXT0 — bouton GPIO 33) et un capteur tactile (T0/GPIO 4). Les variables marquées avec RTC_DATA_ATTR sont conservées pendant le mode Deep Sleep — le

Enregistreur de Température ESP32 (DHT22 + DS1307 RTC + microSD + OLED)
Construis un enregistreur de température horodaté avec ESP32 ! Lis la température/humidité du DHT22 chaque minute, obtiens l'heure réelle du DS1307 RTC et enregistre sur carte microSD au format CSV. L'OLED affiche en direct la température (grande police), le pourcentage d'humidité, l'horodatage et le compteur. Le fichier /temp_log.csv sur la carte SD s'ouvre dans Excel, Google Sheets ou Python pandas pour analyse — parfait pour surveillance de serre, suivi de température à domicile, c

Écran LCD I2C ESP32 (16x2)
Dans ce projet, nous affichons du texte en connectant un écran LCD 16x2 à l'ESP32 via le protocole I2C. Grâce à l'I2C, nous pouvons contrôler l'écran avec seulement 2 broches de données (SDA et SCL) ; cela évite le gaspillage de 6 broches ou plus inhérent à la connexion parallèle classique. Le code affiche un message fixe sur la première ligne et un compteur qui augmente chaque seconde sur la deuxième ligne. De plus, au démarrage, nous lançons un scanner I2C pour afficher l'adresse de l'écran su
Autres articles
Application BlueGrays
Réalise ce montage pas à pas sur ton téléphone
Schéma, code et liste des composants sur un seul écran. Plus de 200 projets Arduino, ESP32 et Pico à réaliser.
Scanne avec ton téléphone
Pointe l’appareil photo vers le code : la boutique d’applications de ton téléphone s’ouvre.
ou ouvre la boutique

