#include <WiFi.h>
#include <NetworkClientSecure.h> // to work with WiFi or Ethernet
#include <HTTPClient.h>
#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(" GMT") >= 0) {
// Date: Thu, 29 Jan 2026 16:40:27 GMT
lineDate.replace("Date :", ""); // supprime Date : si présent au début de la ligne
lineDate = lineDate.substring(5, 29); // supprime le jour de la semaine 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}; // 3 lettres pour le mois + le 0 de fin
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 < 2026) return -6; //date avant 2026 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
}
void jsonRTE(bool keepAlive = true) {
static HTTPClient http;
static NetworkClientSecure secureClient; // en static, on garde le socket pour réusage au tour suivant
static bool clientInitialized = false; //
if (!clientInitialized) {
secureClient.setInsecure();
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
http.setRedirectLimit(5);
clientInitialized = true;
}
int httpCode = 0;
for (int attempt = 0; attempt < 2; attempt++) { // 2 tentatives, au cas ou le serveur distant aurait fermé la comm.
if (secureClient.connected() && !secureClient.available()) secureClient.stop();
//if (secureClient.connected()) Serial.println("+++"); else Serial.println("---");
const char* headerKeys[] = {"Date"};
http.collectHeaders(headerKeys, 1);
http.begin(secureClient, "https://www.services-rte.com/cms/open_data/v1/tempoLight"); // httpClient gere connexion ouverte ou réouvre si besoin
http.setReuse(true); // keep-alive, evite que httpClient referme la connexion lors de l'appel du http.end()
http.addHeader("Accept", "application/json");
httpCode = http.GET();
// Si succès ou erreur qui n'est pas "send header failed" (-2), on sort
if (httpCode == HTTP_CODE_OK || httpCode != -2) break;
// Si on arrive ici, c'est qu'on a eu -2 : on nettoie et on reboucle
//Serial.println("Erreur -2 détectée, tentative de reconnexion...");
http.end();
secureClient.stop();
delay(100); // Petite pause avant de réessayer
}
if (httpCode != HTTP_CODE_OK) {
Serial.printf("Code HTTP != 200 [%d]\n", httpCode);
if (httpCode < 0) Serial.printf("Erreur réseau (Client) : %s\n", http.errorToString(httpCode).c_str()); // erreur bas niveau
http.end();
secureClient.stop(); // force la déconnexion sur erreur
return;
}
String dateHeader = http.header("Date");
if (dateHeader.length() > 0) {
//Serial.println("Date recue du serveur : " + dateHeader);
} else {
Serial.println("L'en-tête Date est absente.");
dateHeader = "Wed, 01 Jul 2026 18:10:03 GMT"; // pour le test
}
//Serial.println(http.header("Connection")); // le serveur est Ok pour le keep-alive
ajustRTCDeLineDate(dateHeader, false); // met a l'heure l'horloge de l'ESP
// prépare filtre du JSON
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); // au format AAAA-MM-JJ
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); // au format AAAA-MM-JJ
String payload;
payload.reserve(200); // Vu 112
// la réponse est 'chunked' par le serveur RTE, httpClient remet les choses correctement
payload = http.getString(); // on se permet de recuperer l'ensemble du body de RTE (petit)
//Serial.println(payload);
JsonDocument doc;
DeserializationError err = deserializeJson(doc, payload);
if (err) {
Serial.print("Erreur deserialization: ");
Serial.println(err.c_str());
http.end();
secureClient.stop(); // force la déconnexion sur erreur
return;
}
http.end();
if (!keepAlive) secureClient.stop();
JsonObject values = doc["values"];
const char* Cdujour = values[cejour] | "UNKNOWN";
const char* Cdemain = values[demain] | "UNKNOWN";
// Cdujour entre 6h00 et 23h59:59 est la couleur de la période en cour
// Cdujour entre 0h00 et 5h59:59 est la couleur du tarif qui sera appliqué a partir de 6h00
// Cdemain n'est plus disponible a partir de 0h00 jusqu'a nouvelle disponibilité provisoire vers 6h12 et confirmation vers 10h30
// Cdemain aprés l'obtention vers 6h12 est la couleur pour le landemain de 6h a 6h le surlandemain
String CPeriodeEnCour = "UNKNOWN";
String CPeriodeAVenir = "UNKNOWN";
static String memoColorFor0h_to_6h = "UNKNOWN";
struct tm timeinfo;
if (getLocalTime(&timeinfo)) {
if (timeinfo.tm_hour >= 6) { // obligatoirement entre 6h00 et 23h59:59 soit hors période 0h00 5h59:59
CPeriodeEnCour = Cdujour;
CPeriodeAVenir = Cdemain;
memoColorFor0h_to_6h = Cdujour; // sauve la couleur du jour pour l'utiliser le jour suivant entre minuit et 6h
} else {
CPeriodeEnCour = memoColorFor0h_to_6h;
CPeriodeAVenir = Cdujour;
}
} else { // L'heure de l'ESP n'est pas valide
CPeriodeEnCour = "UNKNOWN"; // sans horloge, on ne peux pas savoir les couleurs!
CPeriodeAVenir = "UNKNOWN";
}
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);
Serial.printf("Couleur tarifaire active en ce moment : %s\n", CPeriodeEnCour.c_str());
Serial.printf("Couleur tarifaire au prochain changement(à 6h00) : %s\n", CPeriodeAVenir.c_str());
}
uint32_t start;
int qt = 0;
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!");
Serial.println("******************");
for (int i = 0; i < 4; i++) {
start = millis();
jsonRTE(false); // le premier appel va mettre a l'heure l'ESP
Serial.printf("Durée sans keep-Alive : %dms\n", millis() - start);
}
Serial.println("Pass to loop\n");
}
void loop() {
start = millis();
jsonRTE();
Serial.printf("Durée avec keep-Alive : %dms\n", millis() - start);
qt++;
if (qt > 10) {
Serial.println("Pause");
delay(5000); // temporisation 'longue pour voir si coupure du keep-alive
qt = 0;
}
}