#include <ArduinoJson.h>
#include <WiFi.h>
#include <NetworkClientSecure.h>
#include <HTTPClient.h>
#include <ESPmDNS.h>
#include "UrlEncode.h"
//#define RealGateway // sans passerelle, simulation a partir de trame dispo sur forum F1ATB
#include "trame.h"
String trame;
// zéro bricolage avec timeout dans les transctions réseau,
// zéro bricolage pour recupérer les éléments utiles du Json fournit par la passerelle
// Pas d'usage de String dont la taille évolue
// connexion avec la passerelle conservée keep-alive (httpClient se charge de rétablir la connexion si besoin)
// les variables existent en globale dans le programme RMS, les fonctions existe dans commonFx.ino
String hostname = "hostnameESP";
String TokenEnphase = "";
String EnphaseUser = "";
String EnphasePwd = "";
String EnphaseSerial = "0"; //Sert égalemnet au Shelly comme numéro de voie
String JsonToken = "";
String Session_id = "";
uint32_t previousTimeRMSMin = 0;
uint32_t previousTimeRMSMax = 0;
uint32_t previousTimeRMSMoy = 0;
uint32_t previousTimeRMS = 0;
uint32_t LastRMS_Millis = 0;
uint32_t PeriodeProgMillis = 0;
uint32_t RMSextIP = 0;
bool RMSextIPauto = false;
const float PmaxReseau = 36000.0f;
bool LissageLong = false;
float Puissance_T_moy = 0.0f;
float PuissanceS_T_inst = 0.0f;
float PuissanceI_T_inst = 0.0f;
float PuissanceI_T = 0.0f;
float PuissanceS_T = 0.0f;
float Puissance_M_moy = 0.0f;
float PuissanceI_M = 0.0f;
float PuissanceS_M = 0.0f;
float PVA_T_moy = 0.0f;
float PVAS_T_inst = 0.0f;
float PVAI_T_inst = 0.0f;
float PVAI_T = 0.0f;
float PVAS_T = 0.0f;
float PVA_M_moy = 0.0f;
float PVAI_M = 0.0f;
float PVAS_M = 0.0f;
// Variables globales réseau & énergie & puissance
float PactProd = 0.0f;
float Frequence = 0.0f; // freq
float PowerFactor_M = 0.0f; // pwrFactor
float Tension_M = 0.0f; // moyenne des phases actives
float Tension_M1 = 0.0f; // channel 0
float Tension_M2 = 0.0f; // channel 1
float Tension_M3 = 0.0f; // channel 2
float Intensite_M = 0.0f; // moyenne des phases actives
float Intensite_M1 = 0.0f; // channel 0
float Intensite_M2 = 0.0f; // channel 1
float Intensite_M3 = 0.0f; // channel 2
float PactConso_M = 0.0f;
int PuissanceS_M_inst = 0;
int PuissanceI_M_inst = 0;
int PVAS_M_inst = 0;
int PVAI_M_inst = 0;
bool Pva_valide = false;
int LastwhDlvdCum = 0;
int LastwhRcvdCum = 0;
int Energie_M_Soutiree = 0;
int Energie_M_Injectee = 0;
bool EnergieActiveValide = false;
bool PuissanceRecue = false;
int cptLEDyellow = 0;
float PfloatMax(float Pin) {
return constrain(Pin, float(-1.0f * PmaxReseau), float(PmaxReseau));
}
void filtre_puissance() { //Filtre RC
float A = 0.3; //Coef pour un lissage en multi-sinus et train de sinus sur les mesures de puissance courte
float B = 0.7;
if (!LissageLong) {
A = 1;
B = 0;
}
Puissance_T_moy = A * (PuissanceS_T_inst - PuissanceI_T_inst) + B * Puissance_T_moy;
if (Puissance_T_moy < 0) {
PuissanceI_T = -int(Puissance_T_moy); //Puissance Watt affichée en entier Triac
PuissanceS_T = 0;
} else {
PuissanceS_T = int(Puissance_T_moy);
PuissanceI_T = 0;
}
Puissance_M_moy = A * (PuissanceS_M_inst - PuissanceI_M_inst) + B * Puissance_M_moy;
if (Puissance_M_moy < 0) {
PuissanceI_M = -int(Puissance_M_moy); //Puissance Watt affichée en entier Maison
PuissanceS_M = 0;
} else {
PuissanceS_M = int(Puissance_M_moy);
PuissanceI_M = 0;
}
PVA_T_moy = A * (PVAS_T_inst - PVAI_T_inst) + B * PVA_T_moy; //Puissance VA affichée en entiers
if (PVA_T_moy < 0) {
PVAI_T = -int(PVA_T_moy);
PVAS_T = 0;
} else {
PVAS_T = int(PVA_T_moy);
PVAI_T = 0;
}
PVA_M_moy = A * (PVAS_M_inst - PVAI_M_inst) + B * PVA_M_moy;
if (PVA_M_moy < 0) {
PVAI_M = -int(PVA_M_moy);
PVAS_M = 0;
} else {
PVAS_M = int(PVA_M_moy);
PVAI_M = 0;
}
}
void EcritureEnROM() {;}
void TelnetPrintln(String m) {
Serial.println(m);
}
void StockMessage(String m) {
Serial.println(m);
}
// fin du défini ailleurs dans le routeur
#ifdef RealGateway
// obtention IP passerelle local si RMSextIPauto est vrai
// obtention Session_id et Token dans le cas firmware V7+ (EnphaseUSer, EnphasePwd et EnphaseSerial sont renseignés)
void Setup_Enphase() {
// Résolution mDNS de http://envoy.local en adresse IP // SR19
// ***************************************************
if (RMSextIPauto) {
IPAddress envoyIP;
if (!MDNS.begin(hostname)) { //Init mDNS
TelnetPrintln("Erreur : impossible d'initialiser mDNS");
return;
} else {
envoyIP = MDNS.queryHost("envoy", 2000); //avec timeout 2s
}
if (envoyIP != IPAddress(0,0,0,0)) {
StockMessage("IP Enphase : http://envoy.local -> " + envoyIP.toString());
RMSextIP = uint32_t(envoyIP[0] << 24) | uint32_t(envoyIP[1] << 16) | uint32_t(envoyIP[2] << 8) | uint32_t(envoyIP[3]);
EcritureEnROM();
} else {
StockMessage("Échec! passerelle Enphase envoy déconnectée");
return;
}
}
// Obtention Session ID
// ********************
NetworkClientSecure clientSecu; // local, sera detruit en sortant de cette fonction
HTTPClient http;
http.setFollowRedirects(HTTPC_STRICT_FOLLOW_REDIRECTS); // au cas ou un code 300 avec redirection vers une autre adresse
if (EnphaseUser != "" && EnphasePwd != "" && RMSextIP > 0) {
//Serial.printf("%lu : Call Setup_Enphase()\n", millis());
clientSecu.setInsecure();
Serial.println("Essai connexion sur enphaseenergy server / enlighten...");
http.begin(clientSecu, "https://enlighten.enphaseenergy.com/login/login.json");
http.addHeader("Content-Type", "application/x-www-form-urlencoded");
String requestBody = "user[email]=" + urlEncode(EnphaseUser) + "&user[password]=" + urlEncode(EnphasePwd);
int httpCode = http.POST(requestBody);
if (httpCode == HTTP_CODE_OK) {
String payload = http.getString(); // petit Json, on le capture en 1 fois
JsonDocument doc;
DeserializationError error = deserializeJson(doc, payload);
if (!error) {
Session_id = doc["session_id"].as<String>();
Serial.println("session_id : " + Session_id);
} else {
Serial.print("Erreur de parsing JSON : ");
Serial.println(error.c_str());
Session_id = "";
TokenEnphase = "";
return;
}
} else {
Serial.println("Echec Obtention session_id : " + String(httpCode));
Session_id = "";
TokenEnphase = "";
return;
}
http.end();
} else {
Session_id = "";
TokenEnphase = "";
Serial.println("Pas d'élements pour obtenir Token => situation bloquante !");
return;
}
// Obtention Token
// ********************
if (Session_id != "" && EnphaseSerial != "" && EnphaseUser != "") {
clientSecu.setInsecure();
Serial.println("\nEssai connexion sur enphaseenergy server / entrez...");
http.begin(clientSecu, "https://entrez.enphaseenergy.com/tokens");
http.addHeader("Content-Type", "application/json");
JsonDocument bodyDoc;
bodyDoc["session_id"] = Session_id;
bodyDoc["serial_num"] = EnphaseSerial;
bodyDoc["username"] = EnphaseUser;
String requestBody = "";
serializeJson(bodyDoc, requestBody);
int httpCode = http.POST(requestBody);
if (httpCode == HTTP_CODE_OK) {
TokenEnphase = http.getString(); // on capture en 1 fois les elements du Token
StockMessage("Token reçu (" + String(TokenEnphase.length()) + " octets)");
if (TokenEnphase.length() > 50) {
previousTimeRMSMin = 1000;
previousTimeRMSMax = 1;
previousTimeRMSMoy = 1;
previousTimeRMS = millis();
LastRMS_Millis = millis();
PeriodeProgMillis = 1000;
}
} else {
Serial.println("Echec Obtention TokenEnphase : " + String(httpCode));
TokenEnphase = "";
return;
}
http.end();
}
}
#endif // exclus la fonction Setup_Enphase RealGateway
// Seuils pour definir la/les phases actives
constexpr float VOLTAGE_PHASE_MIN = 150.0f; // V — phase active
constexpr float VOLTAGE_3PH_MIN = 600.0f; // V — seuil 3 phases (~3×230V)
String Envoy_Data = ""; // variable global si on souhaite l'afficher dans les données brutes
float PactReseau = 0.0f; // activePower — puissance active réseau (W) : sortie en global pour les tests
float PvaReseau = 0.0f; // apparentPower — puissance apparente réseau (VA) : réactivé les variables locales pour la version prod
void LectureEnphase() {
static JsonDocument filterDoc; // Définition du filtre (déclaré static, créer une seul fois)
static bool filterInitialized = false; // on a déjà créer le filtre ?
if (!filterInitialized) { // on defini le filtre une seule fois
Envoy_Data.reserve(1000); // minimum conservé du JSON obtenu par la passerelle (taille ~850 octets avec Enedis en Triphasé et prod en triphasé)
JsonArray filterArray = filterDoc.to<JsonArray>();
JsonObject f0 = filterArray.add<JsonObject>();
f0["actEnergyDlvd"] = true;
f0["actEnergyRcvd"] = true;
f0["activePower"] = true;
f0["apparentPower"] = true;
f0["pwrFactor"] = true;
f0["voltage"] = true;
f0["current"] = true;
f0["freq"] = true; // on capture la fréquence uniquement dans les entêtes
// On crée le filtre imbriqué pour le tableau "channels"
JsonArray f0_channels = f0["channels"].to<JsonArray>();
JsonObject f0_channels_item = f0_channels.add<JsonObject>();
f0_channels_item["actEnergyDlvd"] = true;
f0_channels_item["actEnergyRcvd"] = true;
f0_channels_item["activePower"] = true;
f0_channels_item["apparentPower"] = true;
f0_channels_item["pwrFactor"] = true;
f0_channels_item["voltage"] = true;
f0_channels_item["current"] = true;
filterInitialized = true;
}
#ifdef RealGateway
// Requête HTTP
static HTTPClient http; // en static pour limiter les opérations d'init en entrant et de déinit en sortant
static NetworkClientSecure secureClient; // en static, on garde le socket pour réusage au tour suivant
static bool clientInitialized = false; //
if (!clientInitialized) {
secureClient.setInsecure();
// on est sur une passerelle local, peu de chance qu'il y ait une redirection d'adresse : pas de http.setFollowRedirects(HTTPC_FORCE_FOLLOW_REDIRECTS); http.setRedirectLimit(5);
clientInitialized = true;
}
static uint32_t lastTokenUpdate = millis(); // premier passage, le token a été obtenu via setup_enphase
if ((millis() - lastTokenUpdate) > 2592000000UL) { // Tout les 30 jours on recherche un nouveau Token
if (millis() - lastTokenUpdate < 5000) delay(5000); // evite le flood sur les serveurs enphase avec un tempo mini de 5 secondes entre chaque demande de Token
lastTokenUpdate = millis(); // overflow compatible!
Setup_Enphase(); // tente d'ontenir un nouveau token
}
char IPhostEnvoy[16];
snprintf(IPhostEnvoy, sizeof(IPhostEnvoy), "%lu.%lu.%lu.%lu", (RMSextIP >> 24) & 0xFF, (RMSextIP >> 16) & 0xFF, (RMSextIP >> 8) & 0xFF, RMSextIP & 0xFF);
if (strlen(IPhostEnvoy) < 7 || TokenEnphase.length() < 50) {
Serial.printf("IP passerelle (%s) ou Token (%s) non valide\n",IPhostEnvoy, TokenEnphase.c_str());
// ici on peut mettre la puissance a traiter a 0 (pour la bonne marche du routeur !!)
return;
}
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(); // il y a un probleme on referme la comm
http.begin(secureClient, "https://" + String(IPhostEnvoy) + "/ivp/meters/readings"); // httpClient gere connexion ouverte ou réouvre si besoin
http.setReuse(true); // keep-alive, evite que httpClient referme la connexion
http.addHeader("Authorization", "Bearer " + TokenEnphase);
http.addHeader("Accept", "application/json");
httpCode = http.GET();
if (httpCode == HTTP_CODE_OK || httpCode != -2) break; // Si succès ou erreur qui n'est pas "send header failed" (-2), on sort de la boucle for
// nettoie avant de passer sur le second essai
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();
Envoy_Data = "";
secureClient.stop(); // force la déconnexion sur erreur
return;
}
NetworkClient *stream = http.getStreamPtr();
JsonDocument doc; // Parse avec filtre intégré
DeserializationError err = deserializeJson(doc, *stream, DeserializationOption::Filter(filterDoc));
while (stream->available()) stream->read(); // vide le contenu du buffer réseau
http.end();
#else // simule la reception d'une trame
JsonDocument doc;
DeserializationError err = deserializeJson(doc, trame, DeserializationOption::Filter(filterDoc));
#endif
if (err) {
Serial.print("deserializeJson failed: ");
Serial.println(err.c_str());
secureClient.stop(); // force la déconnexion sur erreur de parsing json qu'on ne maitrise pas
return;
}
JsonArray input = doc.as<JsonArray>();
// reduit le Json pour ne conserver que l'utile (Accelere le travail qui vient après, reduit l'empreinte mémoire)
while (input.size() > 2) input.remove(2); // shorten do keep only [0] and [1]
//dans le bloc [0], ne conserve que ActivePower et voltage dans l'entete et chaque channel
doc[0].remove("actEnergyDlvd");
doc[0].remove("actEnergyRcvd");
doc[0].remove("apparentPower");
doc[0].remove("pwrFactor");
doc[0].remove("current");
doc[0].remove("freq");
for (JsonObject ch : doc[0]["channels"].as<JsonArray>()) {
ch.remove("actEnergyDlvd");
ch.remove("actEnergyRcvd");
ch.remove("apparentPower");
ch.remove("pwrFactor");
ch.remove("current");
}
// elimine les informations de channel dont la tension est inférieure a 150V
for (JsonObject obj : input) {
if (obj.containsKey("channels")) {
JsonArray channels = obj["channels"].as<JsonArray>();
for (int i = channels.size() - 1; i >= 0; i--) {
float v = channels[i]["voltage"] | 0.0f;
if (v < VOLTAGE_PHASE_MIN) channels.remove(i);
}
}
}
serializeJson(doc, Envoy_Data);// facultatif, met a dispo les data strictement utile
//float PactReseau = 0.0f; // activePower — puissance active réseau (W)
//float PvaReseau = 0.0f; // apparentPower — puissance apparente réseau (VA)
float whDlvdCum = 0.0f; // actEnergyDlvd — énergie soutirée réseau cumulée (Wh)
float whRcvdCum = 0.0f; // actEnergyRcvd — énergie injectée réseau cumulée (Wh)
if (input.size() > 0) { // Section 0 : production
JsonObject src0 = input[0].as<JsonObject>();
// Calcul PactProd
float totalVoltage = src0["voltage"].as<float>();
float totalPower = src0["activePower"].as<float>();
if (totalVoltage >= VOLTAGE_3PH_MIN) { // en production triphasé, on prend la puissance dans l'entete qui combine les 3 phases
PactProd = totalPower;
} else { // en production sur 1 ou sur 2 phases, on cummul uniquement la puissance des channels avec une tension acceptable
PactProd = 0.0f;
JsonArrayConst channels = src0["channels"].as<JsonArrayConst>();
for (JsonObjectConst ch : channels) {
if (ch["voltage"].as<float>() > VOLTAGE_PHASE_MIN) PactProd += ch["activePower"].as<float>(); // normalement, il ne reste que les channels acceptable a le suite de la reduction du json
}
}
}
if (input.size() > 1) { // Section 1 : échange avec le réseau public
JsonObject src1 = input[1].as<JsonObject>();
// data commune dans l'entete
PowerFactor_M = src1["pwrFactor"].as<float>(); // valide monophasé et triphasé
Frequence = src1["freq"].as<float>(); // valide monophasé et triphasé
// Tension & intensité par phase
JsonArrayConst channels = src1["channels"].as<JsonArrayConst>();
float chwhDlvd[3] = { 0.0f, 0.0f, 0.0f };
float chwhRcvd[3] = { 0.0f, 0.0f, 0.0f };
float chPact[3] = { 0.0f, 0.0f, 0.0f };
float chPva[3] = { 0.0f, 0.0f, 0.0f };
float chVoltage[3] = { 0.0f, 0.0f, 0.0f };
float chCurrent[3] = { 0.0f, 0.0f, 0.0f };
int idx = 0;
for (JsonObjectConst ch : channels) { // pour chaque channel acceptable, on mémorise les éléments valide, les autres sont déja initialisés à 0.0f
if (idx >= 3) break; // sécurité si json mal formé
chwhDlvd[idx] = ch["actEnergyDlvd"].as<float>();
chwhRcvd[idx] = ch["actEnergyRcvd"].as<float>();
chPact[idx] = ch["activePower"].as<float>();
chPva[idx] = ch["apparentPower"].as<float>();
chVoltage[idx] = ch["voltage"].as<float>();
chCurrent[idx] = ch["current"].as<float>();
idx++;
}
// Affectation phases individuelles
Tension_M1 = chVoltage[0];
Tension_M2 = chVoltage[1];
Tension_M3 = chVoltage[2];
Intensite_M1 = chCurrent[0];
Intensite_M2 = chCurrent[1];
Intensite_M3 = chCurrent[2];
// Cummul les phases actives uniquement (V > seuil)
float sumwhR = 0.0f;
float sumwhD = 0.0f;
float sumW = 0.0f;
float sumVA = 0.0f;
float sumV = 0.0f;
float sumI = 0.0f;
int nbActif = 0;
for (int i = 0; i < 3; i++) {
if (chVoltage[i] > VOLTAGE_PHASE_MIN) {
sumwhR += chwhDlvd[i];
sumwhD += chwhRcvd[i];
sumW += chPact[i];
sumVA += chPva[i];
sumV += chVoltage[i];
sumI += chCurrent[i];
nbActif++; // comptabilise le nombre de phase active (1 en monophasé, 3 en triphasé, peu de chance d'avoir aucune ou que 2 phases reliés au réseau public)
}
}
if (nbActif == 3) { // en triphasé on prend les info de l'entête directement
whDlvdCum = src1["actEnergyDlvd"].as<float>(); // valide pour triphasé, bruité pour monophasé
whRcvdCum = src1["actEnergyRcvd"].as<float>(); // valide pour triphasé, bruité pour monophasé
PactReseau = src1["activePower"].as<float>(); // valide pour triphasé, bruité pour monophasé
PvaReseau = src1["apparentPower"].as<float>(); // valide pour triphasé, bruité pour monophasé
Tension_M = src1["voltage"].as<float>();
Intensite_M = src1["current"].as<float>();
} else if (nbActif > 0) { // en monophasé (ou biphasé?), peut importe le n° de phase utilisé par la passerelle c'est le cumul de chaque phase (et la moyenne pour la tension et l'intensité)
whDlvdCum = sumwhR;
whRcvdCum = sumwhD;
PactReseau = sumW;
PvaReseau = sumVA;
Tension_M = sumV / nbActif;
Intensite_M = sumI / nbActif;
} else { // situation étrange sans phase sous tension
whDlvdCum = 0.0f;
whRcvdCum = 0.0f;
PactReseau = 0.0f;
PvaReseau = 0.0f;
Tension_M = 0.0f;
Intensite_M = 0.0f;
}
}
PactReseau = PfloatMax(PactReseau); // ecrete entre -36000 et +36000
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);
}
Pva_valide = true;
filtre_puissance();
if (whDlvdCum != 0.0f) {
if (LastwhDlvdCum == 0)
LastwhDlvdCum = round(whDlvdCum);
long DeltaWhSoutire = round(whDlvdCum) - LastwhDlvdCum;
LastwhDlvdCum = round(whDlvdCum);
if (DeltaWhSoutire > 0) {
Energie_M_Soutiree += DeltaWhSoutire;
}
}
if (whRcvdCum != 0.0f) {
if (LastwhRcvdCum == 0)
LastwhRcvdCum = round(whRcvdCum);
long DeltaWhInjecte = round(whRcvdCum) - LastwhRcvdCum;
LastwhRcvdCum = round(whRcvdCum);
if (DeltaWhInjecte > 0) {
Energie_M_Injectee += DeltaWhInjecte;
}
}
EnergieActiveValide = true;
if (PactReseau != 0.0f || PvaReseau != 0.0f) PuissanceRecue = true; // Reset du Watchdog à chaque trame reçue de la passerelle Envoy-S metered
if (cptLEDyellow > 30) cptLEDyellow = 4;
}
void echoResult(String m) {
Serial.println(m);
Serial.println(Envoy_Data.length());
// mise en forme pour l'affichage
Envoy_Data.replace("\":[{\"", "\":[\n\t{\""); // renvoi a la ligne après channels
Envoy_Data.replace("}]},{\"", "}]},\n{\""); // renvoi a la ligne entre les 2 sections
Envoy_Data.replace("},{", "},\n\t{"); // renvoi a la ligne entre chaque item de channel
Serial.println(Envoy_Data);
Serial.printf("PowerProd : %8.3fW, activePower : %9.3fW, apparentPower : %8.3fVA, pwrFactor : %6.3f, Freq : %5.2fHz\n", PactProd, PactReseau, PvaReseau, PowerFactor_M, Frequence);
Serial.printf("Tension_M : %6.2fV, Tension_M1 : %6.2fV, Tension_M2 : %6.2fV, Tension_M3 : %6.2fV\n",Tension_M, Tension_M1, Tension_M2, Tension_M3);
Serial.printf("Courant_M : %6.2fA, Courant_M1 : %6.2fA, Courant_M2 : %6.2fA, Courant_M3 : %6.2fA\n",Intensite_M, Intensite_M1, Intensite_M2, Intensite_M3);
Serial.println();
}
void setup() {
Serial.begin(115200);
IPAddress IPpass = { 192, 168, 1, 100 };
RMSextIP = (IPpass[0] << 24) + (IPpass[1] << 16) + (IPpass[2] << 8) + IPpass[3]; // valeur représentant une IP stocké en uint32_t
#ifdef RealGateway
Setup_Enphase();
#else
trame = String(trame0); // La trame utilisé pour les simulations
#endif
}
void loop() {
LectureEnphase();
echoResult("Prod en triphasé / Enedis en triphasé");
delay(10000);
trame = String(trame1);
LectureEnphase();
echoResult("Prod en triphasé / Enedis en triphasé");
delay(10000);
trame = String(trame2);
LectureEnphase();
echoResult("Prod en monophasé sur phase 1/ Enedis en monophasé sur phase 1");
delay(10000);
trame = String(trame3);
LectureEnphase();
echoResult("Prod monophasé sur phase 1 / Enedis en triphasé");
delay(10000);
trame = String(trame4);
LectureEnphase();
echoResult("Prod sur phase 2 et 3 / Enedis en triphasé");
delay(10000);
trame = String(trame5);
LectureEnphase();
echoResult("Prod en monophasé sur phase 2 / Enedis en monophasé sur phase 1");
delay(10000);
trame = String(trame6);
LectureEnphase();
echoResult("Prod en monophasé sur phase 2 / Enedis en monophasé sur phase 3");
delay(10000);
trame = String(trame0);
}