RE: Plus d'accès aux données de la passerelle Enphase Envoy - michy - 22-08-2026
(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);
}
RE: Plus d'accès aux données de la passerelle Enphase Envoy - FrancoisD - 22-08-2026
Merci Michy,
si j'ai bien compris, Enphase a modifié l'API dans la passerelle, et le fichier "Source_EnphaseEnvoy.ino" a été modifié pour chercher les infos.
La 17.29 me renvoie des infos, donc même un ancien firmware, le routeur en lui-même fonctionne, mais donne des valeurs toujours positives.
Il semble que lorsque les panneaux ne donnent rien, les données soient cohérentes.
Mais je pense avoir trouver dans le fichier un bug.
PvaReseau = PfloatMax(PvaReseau);
if (PactReseau < 0) {
PVAS_M_inst = 0;
PVAI_M_inst = int(PvaReseau); // ce ne devrait pas être PVAI_M_inst = int(-PvaReseau);
} else {
PVAI_M_inst = 0;
PVAS_M_inst = int(PvaReseau);
Pouvez-vous me le confirmer?
RE: Plus d'accès aux données de la passerelle Enphase Envoy - michy - 22-08-2026
(22-08-2026, 05:35 PM)FrancoisD a écrit : Merci Michy,
si j'ai bien compris, Enphase a modifié supprimé l'API dans la passerelle, et le fichier "Source_EnphaseEnvoy.ino" a été modifié pour chercher les infos en utilisant une autre API.
La 17.29 me renvoie des infos, donc même un ancien firmware, le routeur en lui-même fonctionne, mais donne des valeurs toujours positives.
Il semble que lorsque les panneaux ne donnent rien, les données soient cohérentes., si les panneaux ne produisent pas, la passerelle "voie" la même chose que Linky,
avec la pince à la mauvaise place, Linky voie de l'injection vers le réseau, la passerelle mesure le flux de puissance selon la position de la pince, l'APP Enphase interprète les données disponibles de manière différente du routeur
Mais je pense avoir trouver dans le fichier un bug. => Non
PvaReseau = PfloatMax(PvaReseau);
if (PactReseau < 0) {
PVAS_M_inst = 0;
PVAI_M_inst = int(PvaReseau); // ce ne devrait pas être PVAI_M_inst = int(-PvaReseau);
} else {
PVAI_M_inst = 0;
PVAS_M_inst = int(PvaReseau);
Pouvez-vous me le confirmer?
Ancien ou nouveau Firmware enphase, l'API "/ivp/meters/readings" donne la même chose
poster ce que retourne "/ivp/meters/reports/consumption" sur votre installation
***
Pas de bug là :
PvaReseau est toujours positif, c'est PactReseau qui peut être négatif (ce qui permet de déterminer le sens soutirage ou injection)
on peut éventuellement compacter le code mais le résultat restera identique
Code : PactReseau = PfloatMax(PactReseau);
PvaReseau = PfloatMax(PvaReseau);
if (PactReseau < 0) {
PuissanceS_M_inst = 0;
PuissanceI_M_inst = int(-PactReseau);
PVAS_M_inst = 0;
PVAI_M_inst = int(PvaReseau);
} else {
PuissanceI_M_inst = 0;
PuissanceS_M_inst = int(PactReseau);
PVAI_M_inst = 0;
PVAS_M_inst = int(PvaReseau);
}
Confirmer ce que dit votre Linky (une flèche vers la gauche apparait quand il y a de l'injection) et simultanément ce qu'annonce le routeur et l'APP enphase
Confirmer où est positionnée votre pince de mesure "échange avec Enedis" (avec une photo si possible)
RE: Plus d'accès aux données de la passerelle Enphase Envoy - FrancoisD - 23-08-2026
Bonjour Michy,
Voila quelques photos lorsque l'installation est en surproduction.
ainsi que la boucle.
Elle est installée sur la phase qui alimente le panneau électrique de la maison, mais les panneaux sont branchés en aval, sur le 500mA.
RE: Plus d'accès aux données de la passerelle Enphase Envoy - michy - 23-08-2026
(23-08-2026, 12:33 PM)FrancoisD a écrit : Elle est installée sur la phase qui alimente le panneau électrique de la maison, mais les panneaux sont branchés en aval, sur le 500mA.
Bonjour,
Confirmation : vous exportez vu par le Linky et vous consommez vu par le routeur
changez (ou faites changer) la position de la pince, (actuellement, elle est positionnée comme en mauve sur le croquis ci dessous)
il faut impérativement la mettre à la bonne position comme prévu dans la documentation Enphase
elle doit voir ce qui est échangé avec le réseau incluant la production des panneaux PV
RE: Plus d'accès aux données de la passerelle Enphase Envoy - fazer31 - 23-08-2026
(23-08-2026, 12:33 PM)FrancoisD a écrit : Bonjour Michy,
Voila quelques photos lorsque l'installation est en surproduction.
ainsi que la boucle.
Elle est installée sur la phase qui alimente le panneau électrique de la maison, mais les panneaux sont branchés en aval, sur le 500mA.
Bonjour,
Il y a aussi un point critique, la norme électrique et énedis précise bien que le raccordement des conducteurs dans les cages à bornes de sortie sont prévues pour recevoir un seul câble par borne (généralement du 16 mm² ou du 10 mm² pour alimenter le tableau électrique principal), le disjoncteur de branchement 500 mA (situé en amont de la maison) n'est pas un tableau de répartition.
Il est strictement interdit et non conforme de coincer deux câbles de sections différentes (par exemple le 16 mm² de la maison et un autre câble de 6 ou 10 mm² des panneaux) sous la même vis du disjoncteur 500 mA. La pression de serrage sera inégale, ce qui créera un faux contact, un point d'échauffement anormal, et un risque d'incendie majeur (en plus d'un refus systématique du Consuel ou d'énedis).
@+
#s3gt_translate_tooltip_mini { display: none !important; }
RE: Plus d'accès aux données de la passerelle Enphase Envoy - Pliz72 - 24-08-2026
(23-08-2026, 12:33 PM)FrancoisD a écrit : Bonjour Michy,
Voila quelques photos lorsque l'installation est en surproduction.
ainsi que la boucle.
Elle est installée sur la phase qui alimente le panneau électrique de la maison, mais les panneaux sont branchés en aval, sur le 500mA.
Est il possible d'avoir une photo plus reculé des raccordements aval ? Si il a 2 câbles sur la phase, la pince doit prendre les 2 câbles
RE: Plus d'accès aux données de la passerelle Enphase Envoy - Gus - 24-08-2026
bonjour
j'ai un problème bizarre, ça fonctionnait bien avec la dernière version mais j'ai eu une coupure de courant à la maison et ça ne fonctionne plus.
J'avais un scipt python pour vérifier le bon fonctionnement et qui fonctionnait également et qui ne fonctionne plus maintenant donc on dirait qu'il y a un changement de comportement côté routeur enphase après le redémarrage. J'ai cherché avec mon amis Claude Copilot, j'ai réussi à me connecter maintenant avec mon script python, en enlevant le SNI du TLS il arrive à se connecter. Mais ça ne marche toujours pas mon routeur... quelqu'un a déjà eu un comportement "bizarre" comme ça? une idée?
merci beaucoup!
RE: Plus d'accès aux données de la passerelle Enphase Envoy - Sgb31 - 24-08-2026
Bonjour,
vérifie si l'adresse IP de ton routeur n'a pas changé par hasard...
RE: Plus d'accès aux données de la passerelle Enphase Envoy - Gus - 24-08-2026
(24-08-2026, 05:06 PM)Sgb31 a écrit : Bonjour,
vérifie si l'adresse IP de ton routeur n'a pas changé par hasard...
L'ip n'a pas changé, c'est comme si le routeur avait changé de comportement... vraiment étrange
|