Internet Clock 1

From
Jump to: navigation, search

Internet Clock[edit]

retour


Internet Clock with ESP32 & OLED 128×64 Display
Base sur le travail de Techlogics.net (Blog) basé en Inde (GMT+5:30)

https://techlogics.net/create-an-internet-clock-with-esp32-oled-128x64-display-live-date-time-tutorial/

GMT Offsets
https://techlogics.net/how-to-use-gmt-offsets-in-your-arduino-projects-for-accurate-time-synchronization/

Internet Clock - Code 1 -[edit]

Grove Base for XIAO 1.PNG XIAO-ESP32S3 1.PNG OLED Display 128x64 recto.jpg ou OLED-Display-0-96-SSD1315.png

Code 1
Grove Base for XIAO
XIAO-ESP32S3
OLED 128x64 (SSD1308), Port I2c
ou
OLED 128x64 (SSD1315), Port I2c

// Initialize OLED display 0.96 inches (SSD1308) (Seeed-Studio) 128×64
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // 
#include <U8g2lib.h>
#include <Wire.h>
#include <WiFi.h>
#include <NTPClient.h>
#include <WiFiUdp.h>
#include <time.h>
 
// Replace with your WiFi credentials
const char* ssid     = "Your_wifi_SSID";   
const char* password = "Wifi_password";  

// Initialize OLED display (128x128 SH1107)
// U8G2_SH1107_SEEED_128X128_1_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);

 
// Initialize OLED display (128x64 SH1106)
// U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

// Initialize OLED display (128x64 SSD1306) Ideaspark ESP32 0,96 OLED Board
U8G2_SSD1306_128X64_NONAME_F_SW_I2C 
u8g2(U8G2_R0, /* clock=*/ 22, /* data=*/ 21, /* reset=*/ U8X8_PIN_NONE);
 
// NTP Client Setup
WiFiUDP ntpUDP;
// NTPClient timeClient(ntpUDP, "pool.ntp.org", 19800, 3600000); // Timezone offset (19800 seconds = UTC +5:30) Colombo
NTPClient timeClient(ntpUDP, "pool.ntp.org", 3600, 3600000); // Timezone offset (3600 seconds = UTC +1:00) Zurich
 
void setup() {
  // Start serial communication for debugging
  Serial.begin(115200);
   
  // Connect to Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");
 
  // Initialize the OLED
  u8g2.begin();
   
  // Start the NTP client
  timeClient.begin();
  //timeClient.setTimeOffset(19800); // Indian Standard Time colombo (UTC +5:30)
  timeClient.setTimeOffset(3600); // Europe Standard Time Zurich (UTC +1:00)
}
 
void loop() {
  timeClient.update(); // Update time from NTP server
 
  // Get current time in seconds
  unsigned long currentEpoch = timeClient.getEpochTime();
   
  // Convert epoch time (unsigned long) to time_t (signed long)
  time_t currentTime = (time_t)currentEpoch;
 
  // Convert time to time structure
  struct tm* timeInfo;
  timeInfo = localtime(&currentTime);  // Convert to time structure
   
  // Extract time components
  int hours = timeInfo->tm_hour;
  int minutes = timeInfo->tm_min;
  int seconds = timeInfo->tm_sec;
 
  // Extract date components
  int day = timeInfo->tm_mday;
  int month = timeInfo->tm_mon + 1;  // tm_mon is zero-based, so add 1
  int year = timeInfo->tm_year + 1900;  // tm_year is years since 1900, so add 1900
 
  // Extract day of the week (0 = Sunday, 1 = Monday, etc.)
  String daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  String dayOfWeek = daysOfWeek[timeInfo->tm_wday];
 
  // Clear the display
  u8g2.clearBuffer();
 
  // Set font size for the clock
  u8g2.setFont(u8g2_font_ncenB18_tr);  // Larger font (18px)
 
  // Format the time
  String timeStr = String(hours) + ":" + (minutes < 10 ? "0" + String(minutes) : String(minutes)) + ":" + (seconds < 10 ? "0" + String(seconds) : String(seconds));
 
  // Calculate the width of the time text to center it
  int textWidth = u8g2.getStrWidth(timeStr.c_str());
 
  // Set the cursor position to center the time text
  int x = (128 - textWidth) / 2;  // Center horizontally on 128x64 screen
  int y = 24;  // Set Y position to the middle of the screen for time
 
  // Draw the time at the center of the screen
  u8g2.setCursor(x, y);
  u8g2.print(timeStr);
 
  // Set font size for the date
  u8g2.setFont(u8g2_font_ncenB10_tr);  // Smaller font (10px) for the date
 
  // Format the date as DD/MM/YYYY with leading zeroes if necessary
  String dayStr = (day < 10) ? "0" + String(day) : String(day);
  String monthStr = (month < 10) ? "0" + String(month) : String(month);
  String dateStr = dayStr + "/" + monthStr + "/" + String(year);
 
  // Calculate the width of the date text to center it
  int dateTextWidth = u8g2.getStrWidth(dateStr.c_str());
 
  // Set the cursor position to center the date text
  int dateX = (128 - dateTextWidth) / 2;  // Center horizontally
  int dateY = 40;  // Move the date 2px up from the previous position
 
  // Draw the date below the time
  u8g2.setCursor(dateX, dateY);
  u8g2.print(dateStr);
 
  // Set font size for the day of the week
  u8g2.setFont(u8g2_font_ncenB10_tr);  // Smaller font (10px) for the day of the week
 
  // Calculate the width of the day of the week text to center it
  int dayOfWeekWidth = u8g2.getStrWidth(dayOfWeek.c_str());
 
  // Set the cursor position to center the day of the week text
  int dayOfWeekX = (128 - dayOfWeekWidth) / 2;  // Center horizontally
  int dayOfWeekY = 56;  // Set Y position below the date text
 
  // Draw the day of the week below the date
  u8g2.setCursor(dayOfWeekX, dayOfWeekY);
  u8g2.print(dayOfWeek);
 
  // Send the buffer to the display
  u8g2.sendBuffer();
 
  delay(1000);  // Update every second
}

Internet Clock - Code 2 - 128x64[edit]

Code adapté pour tenir compte du daylight

Grove Base for XIAO 1.PNG XIAO-ESP32S3 1.PNG OLED Display 128x64 recto.jpg ou OLED-Display-0-96-SSD1315.png

Code 2
Grove Base for XIAO
XIAO-ESP32S3
OLED 128x64 (SSD1308), Port I2c
ou
OLED 128x64 (SSD1315), Port I2c

// Initialize OLED display 0.96 inches (SSD1308) (Seeed-Studio) 128×64
U8G2_SH1106_128X64_NONAME_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE); // 
#include <U8g2lib.h>
#include <Wire.h>
#include <WiFi.h>
#include <time.h>

// WiFi credentials
const char* ssid     = "Your_wifi_SSID";
const char* password = "Wifi_password";

// OLED display (SSD1306 128x64)
U8G2_SSD1306_128X64_NONAME_F_SW_I2C 
u8g2(U8G2_R0, /* clock=*/ 22, /* data=*/ 21, /* reset=*/ U8X8_PIN_NONE);

void setup() {
  Serial.begin(115200);

  // Connexion Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  // Initialisation OLED
  u8g2.begin();

  // Configuration NTP + fuseau horaire Europe/Zurich avec DST
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
  tzset();
}

void loop() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time");
    return;
  }

  // Extraction des composants
  int hours = timeinfo.tm_hour;
  int minutes = timeinfo.tm_min;
  int seconds = timeinfo.tm_sec;
  int day = timeinfo.tm_mday;
  int month = timeinfo.tm_mon + 1;
  int year = timeinfo.tm_year + 1900;

  String daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  String dayOfWeek = daysOfWeek[timeinfo.tm_wday];

  // Effacer l'écran
  u8g2.clearBuffer();

  // Heure
  u8g2.setFont(u8g2_font_ncenB18_tr);
  String timeStr = String(hours) + ":" +
                   (minutes < 10 ? "0" + String(minutes) : String(minutes)) + ":" +
                   (seconds < 10 ? "0" + String(seconds) : String(seconds));
  int textWidth = u8g2.getStrWidth(timeStr.c_str());
  u8g2.setCursor((128 - textWidth) / 2, 24);
  u8g2.print(timeStr);

  // Date
  u8g2.setFont(u8g2_font_ncenB10_tr);
  String dateStr = (day < 10 ? "0" + String(day) : String(day)) + "/" +
                   (month < 10 ? "0" + String(month) : String(month)) + "/" +
                   String(year);
  int dateTextWidth = u8g2.getStrWidth(dateStr.c_str());
  u8g2.setCursor((128 - dateTextWidth) / 2, 40);
  u8g2.print(dateStr);

  // Jour de la semaine
  int dayOfWeekWidth = u8g2.getStrWidth(dayOfWeek.c_str());
  u8g2.setCursor((128 - dayOfWeekWidth) / 2, 56);
  u8g2.print(dayOfWeek);

  u8g2.sendBuffer();

  delay(1000);
}

Internet Clock - Code 2 - 128x128[edit]

// Initialize OLED constructeur **adapté au SH1107 128x128**
U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);
#include <U8g2lib.h>
#include <Wire.h>
#include <WiFi.h>
#include <time.h>

// WiFi credentials
const char* ssid     = "Your_wifi_SSID";
const char* password = "Wifi_password";

// Initialize OLED constructeur **adapté au SH1107 128x128**
U8G2_SH1107_SEEED_128X128_F_HW_I2C u8g2(U8G2_R0, /* reset=*/ U8X8_PIN_NONE);

void setup() {
  Serial.begin(115200);

  // Connexion Wi-Fi
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    delay(1000);
    Serial.println("Connecting to WiFi...");
  }
  Serial.println("Connected to WiFi");

  // Initialisation OLED
  u8g2.begin();

  // Configuration NTP + fuseau horaire Europe/Zurich avec DST
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  setenv("TZ", "CET-1CEST,M3.5.0,M10.5.0/3", 1);
  tzset();
}

void loop() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Failed to obtain time");
    return;
  }

  // Extraction des composants
  int hours = timeinfo.tm_hour;
  int minutes = timeinfo.tm_min;
  int seconds = timeinfo.tm_sec;
  int day = timeinfo.tm_mday;
  int month = timeinfo.tm_mon + 1;
  int year = timeinfo.tm_year + 1900;

  String daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  String dayOfWeek = daysOfWeek[timeinfo.tm_wday];

  // Effacer l'écran
  u8g2.clearBuffer();

  // Heure
  u8g2.setFont(u8g2_font_ncenB18_tr);
  String timeStr = String(hours) + ":" +
                   (minutes < 10 ? "0" + String(minutes) : String(minutes)) + ":" +
                   (seconds < 10 ? "0" + String(seconds) : String(seconds));
  int textWidth = u8g2.getStrWidth(timeStr.c_str());
  u8g2.setCursor((128 - textWidth) / 2, 50);
  u8g2.print(timeStr);

  // Date
  u8g2.setFont(u8g2_font_ncenB10_tr);
  String dateStr = (day < 10 ? "0" + String(day) : String(day)) + "/" +
                   (month < 10 ? "0" + String(month) : String(month)) + "/" +
                   String(year);
  int dateTextWidth = u8g2.getStrWidth(dateStr.c_str());
  u8g2.setCursor((128 - dateTextWidth) / 2, 80);
  u8g2.print(dateStr);

  // Jour de la semaine
  int dayOfWeekWidth = u8g2.getStrWidth(dayOfWeek.c_str());
  u8g2.setCursor((128 - dayOfWeekWidth) / 2, 100);
  u8g2.print(dayOfWeek);

  u8g2.sendBuffer();

  delay(1000);
}

Internet Clock - Code 3 -[edit]

Oled 128X64 avec une version daylight & une page html de configuration

#include <U8g2lib.h>
#include <Wire.h>
#include <WiFi.h>
#include <time.h>
#include <ESPAsyncWebServer.h>
#include <Preferences.h>

// === OLED ===
U8G2_SSD1306_128X64_NONAME_F_SW_I2C 
u8g2(U8G2_R0, /* clock=*/ 22, /* data=*/ 21, /* reset=*/ U8X8_PIN_NONE);

// === Stockage paramètres ===
Preferences prefs;

// === Variables config ===
String wifiSSID;
String wifiPASS;
String timezone;
int alarmHour;
int alarmMinute;

// === Serveur Web ===
AsyncWebServer server(80);

// === Page HTML ===
const char* htmlPage PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>ESP32 Config</title></head>
<body>
<h1>Configuration ESP32</h1>
<form action="/save" method="GET">
  <label>WiFi SSID:</label><br>
  <input type="text" name="ssid" value="%SSID%"><br>
  <label>WiFi Password:</label><br>
  <input type="password" name="pass" value="%PASS%"><br><br>

  <label>Fuseau horaire:</label><br>
  <select name="tz">
    <option value="CET-1CEST,M3.5.0,M10.5.0/3" %TZ1%>Europe/Zurich</option>
    <option value="UTC0" %TZ2%>UTC</option>
  </select><br><br>

  <label>Heure alarme:</label><br>
  <input type="number" name="ah" min="0" max="23" value="%AH%"> :
  <input type="number" name="am" min="0" max="59" value="%AM%"><br><br>

  <input type="submit" value="Enregistrer">
</form>
</body>
</html>
)rawliteral";

// Remplacement des balises %VAR%
String processor(const String& var) {
  if (var == "SSID") return wifiSSID;
  if (var == "PASS") return wifiPASS;
  if (var == "AH") return String(alarmHour);
  if (var == "AM") return String(alarmMinute);
  if (var == "TZ1") return (timezone == "CET-1CEST,M3.5.0,M10.5.0/3") ? "selected" : "";
  if (var == "TZ2") return (timezone == "UTC0") ? "selected" : "";
  return String();
}

void setup() {
  Serial.begin(115200);

  // Charger paramètres
  prefs.begin("config", false);
  wifiSSID = prefs.getString("ssid", "Your_wifi_SSID");
  wifiPASS = prefs.getString("pass", "Wifi_password");
  timezone = prefs.getString("tz", "CET-1CEST,M3.5.0,M10.5.0/3");
  alarmHour = prefs.getInt("ah", 7);
  alarmMinute = prefs.getInt("am", 0);

  // Connexion Wi-Fi
  WiFi.begin(wifiSSID.c_str(), wifiPASS.c_str());
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi non trouvé, démarrage en AP...");
    WiFi.softAP("ESP32_Config", "12345678");
  } else {
    Serial.println("Connecté au WiFi");
  }

  // Serveur Web
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", htmlPage, processor);
  });

  server.on("/save", HTTP_GET, [](AsyncWebServerRequest *request){
    if (request->hasParam("ssid")) wifiSSID = request->getParam("ssid")->value();
    if (request->hasParam("pass")) wifiPASS = request->getParam("pass")->value();
    if (request->hasParam("tz")) timezone = request->getParam("tz")->value();
    if (request->hasParam("ah")) alarmHour = request->getParam("ah")->value().toInt();
    if (request->hasParam("am")) alarmMinute = request->getParam("am")->value().toInt();

    prefs.putString("ssid", wifiSSID);
    prefs.putString("pass", wifiPASS);
    prefs.putString("tz", timezone);
    prefs.putInt("ah", alarmHour);
    prefs.putInt("am", alarmMinute);

    request->send(200, "text/html", "<h1>Paramètres enregistrés. Redémarrage...</h1>");
    delay(2000);
    ESP.restart();
  });

  server.begin();

  // Initialisation OLED
  u8g2.begin();

  // Config NTP avec fuseau horaire choisi
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  setenv("TZ", timezone.c_str(), 1);
  tzset();
}

void loop() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Impossible d'obtenir l'heure");
    return;
  }

  // Extraction
  int hours = timeinfo.tm_hour;
  int minutes = timeinfo.tm_min;
  int seconds = timeinfo.tm_sec;
  int day = timeinfo.tm_mday;
  int month = timeinfo.tm_mon + 1;
  int year = timeinfo.tm_year + 1900;
  String daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  String dayOfWeek = daysOfWeek[timeinfo.tm_wday];

  // Affichage OLED
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_ncenB18_tr);
  String timeStr = String(hours) + ":" +
                   (minutes < 10 ? "0" + String(minutes) : String(minutes)) + ":" +
                   (seconds < 10 ? "0" + String(seconds) : String(seconds));
  int textWidth = u8g2.getStrWidth(timeStr.c_str());
  u8g2.setCursor((128 - textWidth) / 2, 24);
  u8g2.print(timeStr);

  u8g2.setFont(u8g2_font_ncenB10_tr);
  String dateStr = (day < 10 ? "0" + String(day) : String(day)) + "/" +
                   (month < 10 ? "0" + String(month) : String(month)) + "/" +
                   String(year);
  int dateTextWidth = u8g2.getStrWidth(dateStr.c_str());
  u8g2.setCursor((128 - dateTextWidth) / 2, 40);
  u8g2.print(dateStr);

  int dayOfWeekWidth = u8g2.getStrWidth(dayOfWeek.c_str());
  u8g2.setCursor((128 - dayOfWeekWidth) / 2, 56);
  u8g2.print(dayOfWeek);

  u8g2.sendBuffer();

  // Exemple : déclenchement alarme
  if (hours == alarmHour && minutes == alarmMinute && seconds == 0) {
    Serial.println("ALARM!");
    // Ici tu peux activer un buzzer ou LED
  }

  delay(1000);
}

Internet Clock - Code 4 -[edit]

Basé sur le code 3, la même version daylight & une page html de configuration mais pour un écran Oled 128 x 128

#include <U8g2lib.h>
#include <Wire.h>
#include <WiFi.h>
#include <time.h>
#include <ESPAsyncWebServer.h>
#include <Preferences.h>

// === OLED 128x128 SH1107 ===
U8G2_SH1107_SEEED_128X128_1_SW_I2C 
u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);

// === Stockage paramètres ===
Preferences prefs;

// === Variables config ===
String wifiSSID;
String wifiPASS;
String timezone;
int alarmHour;
int alarmMinute;

// === Serveur Web ===
AsyncWebServer server(80);

// === Page HTML ===
const char* htmlPage PROGMEM = R"rawliteral(
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>ESP32 Config</title></head>
<body>
<h1>Configuration ESP32</h1>
<form action="/save" method="GET">
  <label>WiFi SSID:</label><br>
  <input type="text" name="ssid" value="%SSID%"><br>
  <label>WiFi Password:</label><br>
  <input type="password" name="pass" value="%PASS%"><br><br>

  <label>Fuseau horaire:</label><br>
  <select name="tz">
    <option value="CET-1CEST,M3.5.0,M10.5.0/3" %TZ1%>Europe/Zurich</option>
    <option value="UTC0" %TZ2%>UTC</option>
  </select><br><br>

  <label>Heure alarme:</label><br>
  <input type="number" name="ah" min="0" max="23" value="%AH%"> :
  <input type="number" name="am" min="0" max="59" value="%AM%"><br><br>

  <input type="submit" value="Enregistrer">
</form>
</body>
</html>
)rawliteral";

// Remplacement des balises %VAR%
String processor(const String& var) {
  if (var == "SSID") return wifiSSID;
  if (var == "PASS") return wifiPASS;
  if (var == "AH") return String(alarmHour);
  if (var == "AM") return String(alarmMinute);
  if (var == "TZ1") return (timezone == "CET-1CEST,M3.5.0,M10.5.0/3") ? "selected" : "";
  if (var == "TZ2") return (timezone == "UTC0") ? "selected" : "";
  return String();
}

void setup() {
  Serial.begin(115200);

  // Charger paramètres
  prefs.begin("config", false);
  wifiSSID = prefs.getString("ssid", "Your_wifi_SSID");
  wifiPASS = prefs.getString("pass", "Wifi_password");
  timezone = prefs.getString("tz", "CET-1CEST,M3.5.0,M10.5.0/3");
  alarmHour = prefs.getInt("ah", 7);
  alarmMinute = prefs.getInt("am", 0);

  // Connexion Wi-Fi
  WiFi.begin(wifiSSID.c_str(), wifiPASS.c_str());
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi non trouvé, démarrage en AP...");
    WiFi.softAP("ESP32_Config", "12345678");
  } else {
    Serial.println("Connecté au WiFi");
  }

  // Serveur Web
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", htmlPage, processor);
  });

  server.on("/save", HTTP_GET, [](AsyncWebServerRequest *request){
    if (request->hasParam("ssid")) wifiSSID = request->getParam("ssid")->value();
    if (request->hasParam("pass")) wifiPASS = request->getParam("pass")->value();
    if (request->hasParam("tz")) timezone = request->getParam("tz")->value();
    if (request->hasParam("ah")) alarmHour = request->getParam("ah")->value().toInt();
    if (request->hasParam("am")) alarmMinute = request->getParam("am")->value().toInt();

    prefs.putString("ssid", wifiSSID);
    prefs.putString("pass", wifiPASS);
    prefs.putString("tz", timezone);
    prefs.putInt("ah", alarmHour);
    prefs.putInt("am", alarmMinute);

    request->send(200, "text/html", "<h1>Paramètres enregistrés. Redémarrage...</h1>");
    delay(2000);
    ESP.restart();
  });

  server.begin();

  // Initialisation OLED
  u8g2.begin();

  // Config NTP avec fuseau horaire choisi
  configTime(0, 0, "pool.ntp.org", "time.nist.gov");
  setenv("TZ", timezone.c_str(), 1);
  tzset();
}

void loop() {
  struct tm timeinfo;
  if (!getLocalTime(&timeinfo)) {
    Serial.println("Impossible d'obtenir l'heure");
    return;
  }

  // Extraction
  int hours = timeinfo.tm_hour;
  int minutes = timeinfo.tm_min;
  int seconds = timeinfo.tm_sec;
  int day = timeinfo.tm_mday;
  int month = timeinfo.tm_mon + 1;
  int year = timeinfo.tm_year + 1900;
  String daysOfWeek[] = {"Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"};
  String dayOfWeek = daysOfWeek[timeinfo.tm_wday];

  // Affichage OLED 128x128
  u8g2.clearBuffer();

  // Heure (plus grande police)
  u8g2.setFont(u8g2_font_fub30_tr); // Police large pour 128x128
  String timeStr = String(hours) + ":" +
                   (minutes < 10 ? "0" + String(minutes) : String(minutes));
  int textWidth = u8g2.getStrWidth(timeStr.c_str());
  u8g2.setCursor((128 - textWidth) / 2, 50);
  u8g2.print(timeStr);

  // Secondes plus petites à côté
  u8g2.setFont(u8g2_font_fub14_tr);
  String secStr = (seconds < 10 ? "0" + String(seconds) : String(seconds));
  u8g2.setCursor((128 - textWidth) / 2 + textWidth + 4, 50);
  u8g2.print(secStr);

  // Date
  u8g2.setFont(u8g2_font_ncenB14_tr);
  String dateStr = (day < 10 ? "0" + String(day) : String(day)) + "/" +
                   (month < 10 ? "0" + String(month) : String(month)) + "/" +
                   String(year);
  int dateTextWidth = u8g2.getStrWidth(dateStr.c_str());
  u8g2.setCursor((128 - dateTextWidth) / 2, 80);
  u8g2.print(dateStr);

  // Jour de la semaine
  int dayOfWeekWidth = u8g2.getStrWidth(dayOfWeek.c_str());
  u8g2.setCursor((128 - dayOfWeekWidth) / 2, 100);
  u8g2.print(dayOfWeek);

  u8g2.sendBuffer();

  // Déclenchement alarme
  if (hours == alarmHour && minutes == alarmMinute && seconds == 0) {
    Serial.println("ALARM!");
    // Ici tu peux activer un buzzer ou LED
  }

  delay(1000);
}

Internet Clock - Code 5 -[edit]

avec 128x128