Note de ce sujet :
  • Moyenne : 4.67 (6 vote(s))
  • 1
  • 2
  • 3
  • 4
  • 5
Plus d'accès aux données de la passerelle Enphase Envoy
(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);
}


Pièces jointes Miniature(s)
   
Merci André Smile ,
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)
Répondre

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?
Répondre

(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)
Merci André Smile ,
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)
Répondre

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.

   
Répondre

(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
Merci André Smile ,
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)
Répondre

(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; }
La théorie c'est quand on sait tout et que rien ne fonctionne.
La pratique c'est quand tout fonctionne et que personne ne sait pourquoi.
Réunissez les deux, la théorie et la pratique et vous n'aurez plus rien qui fonctionne et personne ne saura pourquoi..
Un petit ? cela ne coute rien, alors pensez y.
Répondre

(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
Répondre

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!
Répondre

Bonjour,
vérifie si l'adresse IP de ton routeur n'a pas changé par hasard...
Config : 3 routeurs F1ATB en V17.25 - 2 routeurs fixes en mode Triacs + 1 routeur mobile polyvalent en mode : Triac+SSR + afficheur ESP32-S3
PV : (8*425W + Onduleur SunGrow 3KW) + (2*500w + MO Hoymiles HMS-1000W-2T)
Supervision & Domotique : F1atb + Home Assistant / Shelly & MQTT
Répondre

(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
Répondre



Atteindre :


Utilisateur(s) parcourant ce sujet :
7 visiteur(s)

Moteur MyBB, © 2002-2026 Melroy van den Berg.