(22-08-2026, 01:52 PM)rdsoft30 a écrit : Je pense avoir mis le doigt sur les problèmes de RAM qui diminuent quand on affiche la page d'Accueil.
quelques Gros morceau : (voir avec Inspecter du navigateur pour voir la taille transférée)
quelle est la taille des 3 derniers fichiers .csv ?
ces fichiers sont envoyés pour fabriquer la courbe historique des trois derniers mois, incluant les lignes avec des messages [quantité non contrôlé actuellement] ce qui peut finir par peser
Code :
// Vue par jour/mois Soutiré et Injecté (depuis .csv stocké en LittleFS)
// construction du json à la mano (jamais réutilisé ailleurs), traitement mois par mois, filtrage pour envoi uniquement de l'utile
void envoyerHistoriqueEnergie(WebServer &serverRef) {
// dans un monde sans reset, on a besoin d'un buffer de 31 jours pour 1 ligne type de "20260601,12345,12345,12345,12345,23.98,20260601", soit 50*31 soit ~1.6Ko par mois
String JsonMois = "";
JsonMois.reserve(2000); // reservons 2000, le mode dynamique des String fera le reste si ça n'est pas suffisant
// Vue par jour/mois Soutiré et Injecté (LittleFS)
int M0 = DateAMJ.substring(4, 6).toInt();
int an0 = DateAMJ.substring(0, 4).toInt();
String ligne;
ligne.reserve(64);
serverRef.setContentLength(CONTENT_LENGTH_UNKNOWN);
serverRef.send(200, "application/json", "");
bool somethingToSend = false;
JsonMois = "{\"EnergieJour\":[";
for (int M = -2; M <= 0; M++) {
int M1 = M0 + M;
int an1 = an0;
if (M1 < 1) {
M1 += 12;
an1--;
}
char fileName[32];
snprintf(fileName, sizeof(fileName), "/Mois_Wh_%04d%02d.csv", an1, M1);
if (LittleFS.exists(fileName)) {
File file = LittleFS.open(fileName, "r");
while (file.available()) {
ligne = file.readStringUntil('\n');
ligne.trim();
if (ligne.length() > 10 && ligne.indexOf("Date,") == -1 && ligne.substring(0, 8) == ligne.substring(ligne.length() - 8)) { // ne traite que les lignes de fin de journée
JsonMois += '\"'; // une opération par ligne pour limiter la création de String de concaténation intermédiaire
JsonMois += ligne;
JsonMois += "\",";
somethingToSend = true;
}
}
file.close();
}
if (somethingToSend && M == 0) { // mois en cours
JsonMois[JsonMois.length() - 1] = ']'; // remplace la derniere virgule par ]
JsonMois += '}'; // ferme le Json
}
if (somethingToSend) serverRef.sendContent(JsonMois);
JsonMois = ""; // on continu le mois suivant
}
serverRef.sendContent(""); // termine la transaction
}Pour les historiques sur 50h, on manipule quelques String conséquente, assemblé en 1 très gros bloc de quelques dizaine de Ko (variable selon Source, nb de sonde T° et nombre d'actions) => préférer l'envoi morceau par morceau avec réutilisation de la même variable pour composer le message (et un .reserve() au départ)
Code :
void handleAjaxHisto48h() { // Envoi Historique de 50h (600points) toutes les 5mn
String S = "";
S.reserve(5000); // bloc température d'une sonde = 600 points * 8 (-127.00,)
S = String(PuisMaxS_M) + RS + String(PuisMaxI_M) + RS + String(PuisMaxS_T) + RS + String(PuisMaxI_T) + GS;
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", "");
server.sendContent(S);
S = ""; // prepare pour l'envoi historique des puissances voie Principale (nommée 'Maison')
int iS = IdxStockPW;
for (int i = 0; i < 600; i++) {
S += String(tabPw_Maison_5mn[iS]) + ",";
iS = (1 + iS) % 600;
}
S += GS;
server.sendContent(S);
S = ""; // prepare pour l'envoi historique des puissances seconde voie (nommée 'Triac')
iS = IdxStockPW;
for (int i = 0; i < 600; i++) {
S += String(tabPw_Triac_5mn[iS]) + ",";
iS = (1 + iS) % 600;
}
S += GS;
server.sendContent(S);
S = ""; // prepare pour l'envoi historique des températures
for (int canal = 0; canal < 4; canal++) {
iS = IdxStockPW;
for (int i = 0; i < 600; i++) {
S += String(float(tabTemperature_5mn[canal][iS]) * 0.1f) + ",";
iS = (1 + iS) % 600;
}
S += String(temperature[canal]) + "|";
server.sendContent(S); // a chaque canal traité, on envoi le paquet
S = ""; // clear pour sonde T° suivante
}
S = ""; // clear pour envoi des données historique d'ouverture des Actions
for (int i = 0; i < NbActions; i++) {
if ((LesActions[i].Actif > 0) && (ITmode > 0 || i > 0)) {
iS = IdxStockPW;
if (LesActions[i].Actif > 0) {
S += GS;
for (int j = 0; j < 600; j++) {
S += String(tab_histo_ouverture[i][iS]) + RS;
iS = (1 + iS) % 600;
}
S += LesActions[i].Titre;
server.sendContent(S);
}
}
S = ""; // prépare pour traitement Action suivante
}
server.sendContent(""); // termine la transaction
}Code :
void handleAjaxData10mn() { // Envoi Historique de 10mn (300points)Energie Active Soutiré - Injecté
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/html", "");
server.sendContent(Source_data);
String S = GS;
S.reserve(2000);
int iS = IdxStock2s;
for (int i = 0; i < 300; i++) {
S += String(tabPw_Maison_2s[iS]) + ",";
S += String(tabPva_Maison_2s[iS]) + ",";
iS = (1 + iS) % 300;
}
server.sendContent(S);
S = GS;
iS = IdxStock2s;
for (int i = 0; i < 300; i++) {
S += String(tabPw_Triac_2s[iS]) + ",";
S += String(tabPva_Triac_2s[iS]) + ",";
iS = (1 + iS) % 300;
}
server.sendContent(S);
S = "";
for (int i = 0; i < NbActions; i++) {
if (LesActions[i].Actif > 0) {
iS = IdxStock2s;
S += GS + String(i) + ES;
for (int j = 0; j < 300; j++) {
S += String(tab_histo_2s_ouverture[i][iS]) + RS;
iS = (1 + iS) % 300;
}
server.sendContent(S);
}
S = "";
}
server.sendContent("");
}Code :
void handleMainJS1() {
String S = "var biSonde=";
if (nomSondeFixe.length() > 0 && ( // evite la création d'une String temporaire pour comparer avec ""
Source_data == "UxIx2" || Source_data == "ShellyEm" || Source_data == "ShellyPro" || (Source_data == "Enphase" && EnphaseSerial.toInt() != 3))) // la source est Enphase mais pas un system de conso en triphasé
{
S += "true;\r\n";
} else {
S += "false;\r\n";
}
server.setContentLength(S.length() + strlen_P(MainJS1));
server.send(200, "application/javascript", "");
server.sendContent(S);
server.sendContent_P(MainJS1); // Envoi direct depuis la Flash
server.sendContent(""); // Fin de transmission
}Code :
void handleCommunCSS() {
CacheEtClose(60);
String S = "* {box-sizing: border-box;}\n";
S += "body {font-size:150%;text-align:center;width:100%;max-width:1000px;margin:auto;padding:10px;background:linear-gradient(";
if (Couleurs == "") {
S += "#000033,#77b5fe,#000033";
} else {
S += "#" + Couleurs.substring(12, 18) + ",#" + Couleurs.substring(6, 12) + ",#" + Couleurs.substring(12, 18);
}
S += ");background-attachment:fixed;color:";
if (Couleurs == "") {
S += "#ffffff";
} else {
S += "#" + Couleurs.substring(0, 6);
}
S += ";}\n";
server.setContentLength(CONTENT_LENGTH_UNKNOWN);
server.send(200, "text/css", ""); // debut de transmission
server.sendContent(S); // la partie dynamique du CSS
server.sendContent_P(CommunCSS); // Envoi la partie statique direct depuis la Flash
server.sendContent(""); // Fin de transmission
}Code :
void lectureCookie(const char* S) {
ExtraitCookie();
if (!S || S[0] == '\0' || CleAccesRef != CleAcces ) { // Si le pointeur est invalide ou que la chaîne est vide ou que la clé d'accès n'est pas bonne
server.send_P(200, "text/html", ParaCleHtml); // demande la clé d'accès
return; // quitte
}
server.send_P(200, "text/html", S); // envoi le html de la page
}Code :
void handleParaFixe() { //Paramètres stockés en fichier
File file = LittleFS.open("/parametres.json", "r");
if (!file) {
server.send(404, "application/json", "{}");
return;
}
server.streamFile(file, "application/json"); // envoi le contenu du fichier sans le lire en mémoire
file.close();
}Code :
// recherche F1 puis F2, retourne la chaine a partir de F2 inclus / sans copie de chaine en mémoire (autre que celle retournée)
String PrefiltreJson(const String &F1, const String &F2, const String &Json) {
int p = Json.indexOf(F1);
if (p == -1) return ""; // F1 non trouvé
p = Json.indexOf(F2, p);
if (p == -1) return ""; // F2 non trouvé après F1
return Json.substring(p);
}
//retourne la chaine contenu entre F1 inclus et F2 exclus / sans copie de chaine en mémoire (autre que celle retournée)
String SubJson(const String &F1, const String &F2, const String &Json) {
int p1 = Json.indexOf(F1);
if (p1 == -1) return ""; // F1 non trouvé
int p2 = Json.indexOf(F2, p1 + F1.length());
if (p2 == -1) return ""; // F2 non trouvé
return Json.substring(p1, p2);
}
// recherche nom encadré par double quote, avance jusqu'au double dot, puis optionnelement une double quote qui encadrerai la data
// retourne le float dispo a cet endroit (ou 0.0f si la chaine ne représente pas un début de nombre)
float ValJson(const String &nom, const String &Json) {
const String lookup = "\"" + nom + "\""; // le nom encadré avant et après évite d'extraire une correspondance en fin de nom
int p = Json.indexOf(lookup);
if (p == -1) return 0.0f; // pas trouvé le nom encadré par double cote
p = Json.indexOf(':', p + lookup.length()); // Sauter le nom trouvé et chercher le ':' suivant
if (p == -1) return 0.0f; // pas trouvé le ':'
p++;
while (p < Json.length() && Json[p] <= ' ') p++; // On saute tous les caractères de contrôle (ASCII <= 32)
if (p < Json.length() && Json[p] == '\"') p++; // Si on tombe sur un guillemet ouvrant, on le saute (cas ou le nombre float serait entre "")
return Json.substring(p).toFloat(); // le fonction .toFloat accepte que la chaine commence par espace, tab, \r \n ...
}
float ValJsonSG(const String &nom, const String &Json) { // Variante pour SmartGateway
return ValJson(nom, Json);
}
long LongJson(const String &nom, String Json) { // Pour éviter des problèmes d'overflow
const String lookup = "\"" + nom + "\""; // le nom encadré avant et après évite d'extraire une correspondance en fin de nom
int p = Json.indexOf(lookup); // pas trouvé le nom encadré par double quote
if (p == -1) return 0;
p = Json.indexOf(":", p + lookup.length());
if (p == -1) return 0; // pas trouvé le : séparateur
return Json.substring(p + 1).toInt();
}
long myLongJson(const String &nom, String Json) {
return LongJson(nom, Json);
}
unsigned long ULongJson(const String &nom, String Json) {
const String lookup = "\"" + nom + "\""; // le nom encadré avant et après évite d'extraire une correspondance en fin de nom
int p = Json.indexOf(lookup); // pas trouvé le nom encadré par double quote
if (p == -1) return 0;
p = Json.indexOf(":", p + lookup.length());
if (p == -1) return 0; // pas trouvé le : séparateur
return strtoul(Json.substring(p + 1).c_str(), NULL, 10); // Utilisation de strtoul pour parser correctement un unsigned long
}
int IntJson(const String &nom, String Json) { // Pour éviter des problèmes d'overflow
return LongJson(nom, Json); // sur ESP32, Long et Int sont identique, codé sur 32 bits
}
byte ByteJson(const String &nom, String Json) { // Pour éviter des problèmes d'overflow
return byte(LongJson(nom, Json)); // on ne recupérera que la valeur entre 0 et 255 (modulo 256 si la réponse dépasse 256)
}
uint16_t UShortJson(const String &nom, String Json) { // Pour éviter des problèmes d'overflow
return uint16_t(ULongJson(nom, Json)); // on ne recupérera que la valeur entre 0 et 65535 (modulo 65536 si la réponse dépasse)
}
int16_t ShortJson(const String &nom, String Json) { // Pour éviter des problèmes d'overflow
return int16_t(LongJson(nom, Json)); // on ne recupérera que la valeur entre -32768 à 32767 (modulo 32768 si la réponse dépasse)
}
String StringJson(const String &nom, const String &Json) {
const String lookup = "\"" + nom + "\""; // le nom encadré avant et après évite d'extraire une correspondance en fin de nom
int p = Json.indexOf(lookup);
if (p != -1) p = Json.indexOf(":", p + lookup.length()); // pas trouvé le nom encadré par double quote
if (p != -1) p = Json.indexOf("\"", p + 1); // pas de :
int q = (p != -1) ? Json.indexOf("\"", p + 1) : -1; // pas trouvé le resultat commençant par double quote
if (p == -1 || q == -1) return ""; // pas trouvé le resultat encadré par double quote
return Json.substring(p + 1, q);
}
Merci André
,
Routeur V17.29 (since V2.01) / Source UxI / 5 actions
Si les réponses que je propose bénévolement sur ce forum ne vous plaisent pas, ignorez-les simplement sans me jeter la pierre ! (Ou ne posez pas de question)
,Routeur V17.29 (since V2.01) / Source UxI / 5 actions
Si les réponses que je propose bénévolement sur ce forum ne vous plaisent pas, ignorez-les simplement sans me jeter la pierre ! (Ou ne posez pas de question)

