#include <NetworkClientSecure.h> // to work with WiFi or Ethernet
#include <HTTPClient.h> // gere échange HTTP GET pour sortir les éléments utiles
#include <ArduinoJson.h> // traitement du JSON fournit par RTE
#define MAX_SIZE_T 80
#define HFROMRTE 7
byte Horloge = 7;
String STGEt = "0";
String RTE_Jour;
String RTE_Demain;
String LTARF = "?";
int HeureCouranteDeci = 700;
int LastHeureRTE = 600;
byte TempoRTEon = 1;
byte ModeReseau = 0;
bool HeureValide = false;
String Source = "UxI";
// *******************************************************
// * Recherche Info Tempo RTE pour les sources non Linky *
// *******************************************************
// 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
// un négatif si problème pour obtenir/exploiter la chaine
int ajustRTCDeLineDate(String lineDate, bool verbose = false) {
Serial.println(lineDate);
// le parametre String ressemble à "Date: Thu, 29 Jan 2026 16:40:27 GMT" ou "Thu, 29 Jan 2026 16:40:27 GMT"
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 -5; //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 (fini par GMT)
return 0; // tout c'est bien passé, RTC n'a pas était mise a jour
}
void Call_RTE_data() {
int Hcour = HeureCouranteDeci / 2; //Par pas de 72secondes pour faire 2 appels si un bug
int LastH = LastHeureRTE / 2;
if (Hcour == 529) {STGEt = "0";} //Pour forcer une relecture après 10h30, heure à laquelle RTE doit geler la couleur
// il faut éviter de questionner l'URL tempo light si on a déjà les infos qui nous intéressent...
bool couleur_lendemain = (STGEt == "4" || STGEt == "8" || STGEt == "C"); // true si couleur du lendemain connue
bool couleur_jour = (LTARF == "TEMPO_BLEU" || LTARF == "TEMPO_BLANC" || LTARF == "TEMPO_ROUGE"); // true si couleur du jour connu
// traitement du changement de couleur tempo à 6h00, avec ré-initialisation de la couleur du lendemain
if ((Hcour == 300) && (LTARF != "") && (STGEt != "")) // à 6h00 précises, la couleur tempo du lendemain devient la couleur du jour. Celle du lendemain n'est à priori pas encore connue
// Cela permet de continuer à fonctionner de manière nominale même si le site RTE n'est pas encore renseigné
// && (LTARF!="") pour ne pas passer dans cette boucle au reset si fait à 6h00 du matin...
// && (STGEt<>"") pour éviter de passer plusieurs fois( 3 à 4 ) dans cette boucle à cause du Hcour/2
{
if (STGEt == "4") LTARF = "TEMPO_BLEU";
else if (STGEt == "8") LTARF = "TEMPO_BLANC";
else if (STGEt == "C") LTARF = "TEMPO_ROUGE";
STGEt = "";
couleur_lendemain = false; // on ne connait plus la couleur du lendemain. Cela forcera la lecture sur le site RTE
if (LTARF != "" && TempoRTEon == 1 && ModeReseau == 0) {
//StockMessage("Tempo depuis 6h00: " + LTARF + ",demain ? ");
RTE_Jour = "NON_DEFINI";
RTE_Demain = "NON_DEFINI";
}
}
bool estHeureDeConsulter = false;
if (couleur_lendemain && couleur_jour) return; // on connait déja les couleurs du jour et celle de demain, on interroge pas RTE
if (LastHeureRTE >= 0 && LastH == Hcour) return; // On a déja obtenu des info depuis RTE et l'heure n'a pas changé, on ne traite pas la suite
if (HeureValide && Horloge != HFROMRTE) { // l'horloge n'est pas bonne, on ne peu pas comparer le jour courant
int heuresCheck[] = {306, 310, 530, 560, 600, 900, 1150};
for (int h : heuresCheck) {
if (Hcour == h) {
estHeureDeConsulter = true;
break;
}
}
} else {
estHeureDeConsulter = true; // au premier passage, on force la mise a l'heure depuis le serveur RTE
}
if (LastHeureRTE == -1 || estHeureDeConsulter) { // si l'heure actuelle fait partie des horaires à consulter
if (TempoRTEon == 1 && ModeReseau == 0) {
HTTPClient http;
NetworkClientSecure clientSecuRTE; // ressource prise sur la pile, tout sera libérer a la sortie de cette fonction
clientSecuRTE.setInsecure(); //skip https verification
http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS);
http.setRedirectLimit(5);
const char* headerKeys[] = {"Date"};
const char* url_API_RTE = "https://www.services-rte.com/cms/open_data/v1/tempoLight";
http.collectHeaders(headerKeys, 1);
http.begin(clientSecuRTE, url_API_RTE);
http.addHeader("Accept", "application/json");
Serial.println("Go");
int httpCode = http.GET();
Serial.println("Go2");
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();
clientSecuRTE.stop(); // force la déconnexion sur erreur
return; // on sort sans avoir obtenu les infos (pas de changement des données connues)
}
String dateHeader = http.header("Date"); // recupere la ligne de date contenu dans l'entete de réponse du serveur RTE
if (dateHeader.length() > 0 && Horloge == HFROMRTE) { // on a obtenu la ligne Date et on souhaite utiliser l'horloge du serveur RTE
ajustRTCDeLineDate(dateHeader, false); // met a l'heure ou ajuste l'horloge de l'ESP (si il y a plus de 2 minutes d'écart avec l'horloge RTC)
} // else la date et l'heure doivent être reglé autrement
// prépare les critéres pour l'extraction du JSON
// sans décalage d'horloge,
// la date du calendrier "cejour" associé au champ du json est valide de 6h00 a 23h59:59 : a mémoriser pour le J+1 entre 0h00 et 5h59:59
// la couleur "tarif tempo" du moment n'est pas disponible entre 0h00 et 5h59:59, il faut se rappeler de l'information obtenue préalablement
// la date du calendrier "demain" associé au champ du json est valide, si le champ existe, a partir du lendemain 6h00 jusque 23h59:59 (et jusqu'a 5h59:59 le surlendemain si on la mémorise)
char cejour[12];
time_t timestampCJ = time(NULL);
// le 02/01 a 3h00 : cejour = le 02/01 !!! la donnée json associée ne sera valide qu'a partir de 6h00
// le 02/01 a 7h00 : cejour = le 02/01 la donnée json associée est valide pour le jour donné
// le 02/01 a 23h00 : cejour = le 02/01 la donnée json associée est valide pour le jour donné
struct tm* pTimeCJ = localtime(×tampCJ);
strftime(cejour, sizeof(cejour), "%Y-%m-%d", pTimeCJ);
char demain[12];
time_t timestampDE = time(NULL) + 86400; // Decalage début période couleur RTE de 24h pour le lendemain.
// le 02/01 a 3h00 : demain = le 03/01 A cette horaire, la donnée json pour la couleur tarifaire du lendemain n'est pas disponible
// le 02/01 a 7h00 : demain = le 03/01 A cette horaire, la donnée json pour la couleur tarifaire du lendemain est disponible, valide provisoirement
// le 02/01 a 11h00 : demain = le 03/01 A cette horaire, la donnée json pour la couleur tarifaire du lendemain est disponible, valide quasi définitivement
// le 02/01 a 16h00 : demain = le 03/01 A cette horaire, la donnée json pour la couleur tarifaire du lendemain est disponible, valide définitivement
struct tm* pTimeDE = localtime(×tampDE);
strftime(demain, sizeof(demain), "%Y-%m-%d", pTimeDE);
String payloadRTE;
payloadRTE.reserve(200); // Vu 112 avec 4 parametres dans le JSON
// la réponse est 'chunked' par le serveur RTE, httpClient remet les choses correctement
payloadRTE = http.getString(); // on se permet de recuperer l'ensemble du body de RTE (petit)
JsonDocument doc;
DeserializationError err = deserializeJson(doc, payloadRTE);
http.end(); // libere les ressources http
clientSecuRTE.stop(); // libere le socket réseau
// TelnetPrint("RTEdata:"); TelnetPrintln(payloadRTE); // pour debug
if (err) {
Serial.print("Erreur deserialization: ");
Serial.println(err.c_str());
return; // sort sur erreur de decodage du contenu du JSON
}
JsonObject values = doc["values"];
const char* Cdujour = values[cejour] | "UNKNOWN";
const char* Cdemain = values[demain] | "UNKNOWN";
String colDuMoment;
String colAVenir;
String lendemain;
// Cdujour entre 6h00 et 23h59:59 est la couleur de la période en cours
// 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 lendemain de 6h à 6h le surlendemain
static String memoColorFor0h_to_6h = "UNKNOWN";
if (pTimeCJ->tm_hour >= 6) { // obligatoirement entre 6h00 et 23h59:59 soit hors période 0h00 5h59:59
colDuMoment = Cdujour;
colAVenir = Cdemain;
memoColorFor0h_to_6h = Cdujour; // sauve la couleur du jour pour l'utiliser le jour suivant entre minuit et 6h
} else {
colDuMoment = memoColorFor0h_to_6h;
colAVenir = Cdujour;
}
//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", colDuMoment.c_str());
//Serial.printf("Couleur tarifaire au prochain changement(à 6h00) : %s\n", colAVenir.c_str());
if (colDuMoment == "BLUE") LTARF = "TEMPO_BLEU";
else if (colDuMoment == "WHITE") LTARF = "TEMPO_BLANC";
else if (colDuMoment == "RED") LTARF = "TEMPO_ROUGE";
else if (colDuMoment == "UNKNOWN") LTARF = "NON_DEFINI";
else LTARF = "?";
// Pour la couleur tarifaire du lendemain, on utilise le codage Hexa du Linky comme STGE 2ieme lettre, uniquement les 2 bits haut, les 2 bit bas pour la couleur du jour reste a 00 (bit 26 et 27 du codage STGE)
if (colAVenir == "UNKNOWN") {STGEt = "0"; lendemain = "NON_DEFINI";} else
if (colAVenir == "BLUE") {STGEt = "4"; lendemain = "TEMPO_BLEU";} else
if (colAVenir == "WHITE") {STGEt = "8"; lendemain = "TEMPO_BLANC";} else
if (colAVenir == "RED") {STGEt = "C"; lendemain = "TEMPO_ROUGE";} else {STGEt = "?"; lendemain = "NON_DEFINI";}
//StockMessage(String(cejour) + " : " + LTARF + " | " + String(demain) + " : " + lendemain);
LastHeureRTE = HeureCouranteDeci; //Heure lecture Tempo RTE
} else {
if (Source != "Linky" && Source != "Ext") {
LTARF = "";
STGEt = "0";
}
}
}
}
void setup() {
// put your setup code here, to run once:
Serial.begin(115200);
Serial.println("Hello, ESP32!");
Call_RTE_data();
}
void loop() {
// put your main code here, to run repeatedly:
delay(10); // this speeds up the simulation
}