#include <WiFi.h>
#include <NetworkClientSecure.h> // to work with WiFi or Ethernet
#include <ArduinoJson.h>
// attend une chaine texte Date: conforme RFC 7231 (§7.1.1.2, §7.1.1.1 Prefererd format) obtenu de l'entete d'un serveur
// si la chaine est correct, avec une date postérieur a 2025, et qu'il y a une écart de plus de 2mn avec la l'heure en RTC, on ajuste RTC
// retourne :
// 1 si mise a jour de la RTC
// 0 si tout va bien sans modif RTC
// -3, -4, -5, -6 si problème pour exploiter la chaine
int ajustRTCDeLineDate(String lineDate, bool verbose = false) {
if (lineDate.indexOf("Date: ") == 0 && lineDate.indexOf(" GMT") >= 0) {
// Date: Thu, 29 Jan 2026 16:40:27 GMT
lineDate = lineDate.substring(lineDate.indexOf("Date: ") + 11, lineDate.indexOf("Date: ") + 35); // 20 Dec 2025 14:39:38 GMT
time_t tSecCurrentTime; // nombre de seconde écoulé depuis le 1er janvier 1970, d'apres le serveur
char monthStr[4] = {0,0,0,0};
struct tm tmServ; // charge une structure tm avec la date et l'heure du fuseau GMT avec les infos du serveur
if (sscanf(lineDate.c_str(), "%d %s %d %d:%d:%d", &tmServ.tm_mday, &monthStr, &tmServ.tm_year, &tmServ.tm_hour , &tmServ.tm_min, &tmServ.tm_sec) != 6) return -4; // chaine mal formatée
if (tmServ.tm_year < 2025) return -5; //date avant 2025 depuis le serveur ?
tmServ.tm_year -= 1900;
const char* months = "JanFebMarAprMayJunJulAugSepOctNovDec";
char* m = strstr(months, monthStr);
if (!m) return -6; // pas de correspondance mois dans la date obtenue de l'entete
tmServ.tm_mon = (m - months) / 3;
//Serial.println(&tmServ, "%d/%m/%Y %H:%M:%S Greenwich Mean Time\n"); // date/heure GMT tel que recupéré (heure hiver en angleterre)
// a quelques secondes près c'est une datage temporelle de l'instant
char* oldTZ = getenv("TZ"); // on a un timestamp GMT → utiliser mktime en mode Universal Time Coordinated
char backupTZ[64] = {0};
if (oldTZ) strncpy(backupTZ, oldTZ, sizeof(backupTZ)-1);
//setenv("TZ", "GMT", 1);// On force temporairement TZ=GMT
setenv("TZ", "UTC", 1);// On force temporairement TZ=UTC
tzset();
tSecCurrentTime = mktime(&tmServ);
if (oldTZ) setenv("TZ", backupTZ, 1); else unsetenv("TZ");
tzset();
struct tm timeRTC;
getLocalTime(&timeRTC);
if (abs(tSecCurrentTime - mktime(&timeRTC)) > 120) { // si besoin ajuste l'horloge RTC de l'ESP avec ce qu'on a obtenu du serveur
struct timeval tv = { .tv_sec = tSecCurrentTime };
settimeofday(&tv, NULL);
if (verbose) {
Serial.print("Date/Heure en RTC avant actualisation : "); Serial.println(&timeRTC, "%d/%m/%Y %H:%M:%S");
Serial.print("Date/Heure extrait de "); Serial.println(lineDate);
getLocalTime(&timeRTC);
Serial.print("actualisation Date/Heure RTC de l'ESP : "); Serial.println(&timeRTC, "%d/%m/%Y %H:%M:%S\n");
}
return 1; // tout c'est bien passé, RTC a était mise a jour
}
} else return -3; // La chaine reçu n'est pas conforme a l'attendu
return 0; // tout c'est bien passé, RTC n'a pas était mise a jour
}
// tente d'obtenir une date/heure depuis le serveur en parametre de la fonction
// retourne :
// 1 si actualisation de la RTC
// 0 si pas de changement d'heure en RTC
// -1 si probleme pour contacté le serveur
// -2 si timeout reception données depuis le serveur
// -3 le serveur ne donne pas Date: dans son entete
// autre = probleme avec la chaine obtenu du serveur
int AjustRTCDeServeur(String serv, bool verbose = false) {
NetworkClientSecure client; // Use clientSecu class to create TCP connections
client.setInsecure(); // skip verification
if (!client.connect(serv.c_str(), 443)) {
if (verbose) Serial.println("Fail to connect " + serv);
return -1; //Connection failed to server
} else {
String command = "GET / HTTP/1.0\r\nHost: " + serv + "\r\nConnection: close\r\n\r\n"; // HTTP/1.0 pour éviter transfer chunked
client.print(command.c_str());
uint32_t timeout = millis();
while (client.available() == 0) { // attend la reception du premier octet
if (millis() - timeout > 5000) { // abandone si rien après 5 secondes
client.stop();
if (verbose) Serial.println("Problem avec \n" + command);
return -2; // pas de retour du serveur
}
delay(1); // laisse un peu d'air au systeme pour faire autre chose
}
String lineDate = "";
timeout = millis();
while (client.connected() && (millis() - timeout < 5000)) { // Lecture des données brutes distantes
String line = client.readStringUntil('\n');
if (line.indexOf("Date: ") == 0 && line.indexOf(" GMT") >= 0) { // Date selon RFC7231
lineDate = line;
break; // on a ce qui nous interresse, abandonne le reste
}
delay(1); // laisse un peu d'air au systeme pour faire autre chose
}
client.stop();
if (lineDate == "") return -3;
if (verbose) Serial.println("Heure obtenue de " + serv + " : " + lineDate);
return ajustRTCDeLineDate(lineDate, verbose);
}
}
void jsonRTE() {
NetworkClientSecure clientSecu;
clientSecu.setInsecure(); //skip verification
if (!clientSecu.connect("www.services-rte.com", 443, 3000)) {
Serial.println("Fail to contact RTE");
return;
}
char host[] = "www.services-rte.com";
// en HTTP/1.0 pour eviter taille paquets chunked
clientSecu.printf("GET https://%s/cms/open_data/v1/tempoLight HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n", host, host);
clientSecu.setTimeout(5000); // 5s pour établir la connexion et recevoir l'entête
if (clientSecu.find("Date: ")) { // collecte la date et l'heure GMT dans l'entete : Date: Sat, 02 May 2026 07:36:12 GMT
String line = "Date: " + clientSecu.readStringUntil('\n'); // potentielement line termine par \r\n donc le \r est inclus dans line
Serial.println(line);
if (line.length() >= 35 && line.substring(31, 35) == " GMT") { // Sat, 02 May 2026 07:36:12 GMT
ajustRTCDeLineDate(line, true);
}
}
if (!clientSecu.find("\r\n\r\n")) {
clientSecu.stop();
Serial.println("Pb reception entête");
return; // saute l'entete ou sort de la fonction si probleme de reception
}
// {"values":{"2026-05-03-fallback":"false","2026-05-02":"BLUE","2026-05-02-fallback":"false","2026-05-03":"BLUE"}}
JsonDocument doc;
DeserializationError error = deserializeJson(doc, clientSecu);
if (error) {
Serial.printf("Erreur JSON: %s\n", error.c_str());
clientSecu.stop();
return;
}
char cejour[12];
time_t timestamp = time(NULL) - 21600; //Decallage début période couleur RTE de 6h.
struct tm* pTime = localtime(×tamp);
strftime(cejour, sizeof(cejour), "%Y-%m-%d", pTime);
//Serial.println(cejour);
char demain[12];
time_t timestamp2 = time(NULL) + 64800; //Decallage début période couleur RTE de 18h pour le lendemainh.
struct tm* pTime2 = localtime(×tamp2);
strftime(demain, sizeof(demain), "%Y-%m-%d", pTime2);
//Serial.println(demain);
JsonObject values = doc["values"];
const char* Cdujour = values[cejour];
const char* Cdemain = values[demain];
clientSecu.stop();
Serial.printf("Couleur ce jour (du %s 6h00 au %s 6h00): %s\n", cejour, demain, Cdujour);
Serial.printf("Couleur demain (le %s a partir de 6h00): %s\n", demain, Cdemain);
}
void setup() {
setenv("TZ", "CET-1CEST,M3.5.0/2,M10.5.0/3", 1);
tzset();
Serial.begin(115200);
Serial.print("Connecting to WiFi");
WiFi.begin("Wokwi-GUEST", "", 6);
while (WiFi.status() != WL_CONNECTED) {
delay(250);
Serial.print(".");
}
Serial.println(" Connected!");
struct tm timeinfo;
getLocalTime(&timeinfo);
if (timeinfo.tm_year < 125) {
Serial.print("Horloge RTC non cohérente ! Date/Heure connue : ");
Serial.println(&timeinfo, "%d/%m/%Y %H:%M:%S\n");
// AjustRTCDeServeur(WiFi.gatewayIP().toString()); // ne fonctionne pas sur wokwi, pas de service http sur 10.0.0.1
AjustRTCDeServeur("8.8.8.8", false); // va chercher l'heure chez Google
}
Serial.println("******************");
jsonRTE();
}
void loop() {
// put your main code here, to run repeatedly:
delay(10); // this speeds up the simulation
}