@ -55,6 +55,11 @@ float solarProduction = 0.0; // Production solaire en Watts
@@ -55,6 +55,11 @@ float solarProduction = 0.0; // Production solaire en Watts
float powerConsumption = 0.0 ; // Consommation électrique en Watts
int heaterPower = 0 ; // Puissance de chauffe du chauffe-eau en % (0-100)
float waterTemperature = 0.0 ; // Température de l'eau du chauffe-eau en °C
// Flag pour l'état de la connexion Enphase
bool enphaseConnectionError = false ;
// Configuration GPIO
# define HEATER_GPIO 12 // GPIO12 pour contrôle chauffe-eau
// Configuration GPIO
# define HEATER_GPIO 12 // GPIO12 pour contrôle chauffe-eau
@ -63,6 +68,9 @@ float waterTemperature = 0.0; // Température de l'eau du chauffe-eau en °C
@@ -63,6 +68,9 @@ float waterTemperature = 0.0; // Température de l'eau du chauffe-eau en °C
bool otaRebootPending = false ;
unsigned long otaRebootTime = 0 ;
// Variable pour le mode JOUR - mémoriser si la température max a été atteinte aujourd'hui
bool maxTempReachedToday = false ;
// Variables pour la gestion PWM avec timer (période 1s)
const unsigned long PWM_PERIOD = 1000 ; // Période de 1 seconde en ms
volatile unsigned long pwmCycleCount = 0 ; // Compteur de cycles PWM
@ -409,50 +417,38 @@ void IRAM_ATTR onTimerISR() {
@@ -409,50 +417,38 @@ void IRAM_ATTR onTimerISR() {
void fetchEnphaseData ( ) {
if ( enphaseGatewayIP . length ( ) = = 0 | | enphaseToken . length ( ) = = 0 ) {
Serial . println ( " Configuration Enphase manquante " ) ;
enphaseConnectionError = true ;
return ;
}
WiFiClientSecure client ;
client . setInsecure ( ) ; // Ignorer la vérification du certificat SSL
HTTPClient http ;
String url = " https:// " + enphaseGatewayIP + " /ivp/meters/reports/consumption " ;
http . begin ( client , url ) ;
http . addHeader ( " Accept " , " application/json " ) ;
http . addHeader ( " Authorization " , " Bearer " + enphaseToken ) ;
http . setFollowRedirects ( HTTPC_STRICT_FOLLOW_REDIRECTS ) ; // Suivre les redirections
int httpCode = http . GET ( ) ;
if ( httpCode = = HTTP_CODE_OK ) {
String payload = http . getString ( ) ;
JsonDocument doc ;
DeserializationError error = deserializeJson ( doc , payload ) ;
if ( ! error & & doc . is < JsonArray > ( ) & & doc . size ( ) > = 2 ) {
// Selon le code Python:
// json[0]['cumulative']['currW'] = total (consommation totale)
// json[1]['cumulative']['currW'] = net (réseau Enedis)
// panneaux = total - net (production solaire)
float total = doc [ 0 ] [ " cumulative " ] [ " currW " ] . as < float > ( ) ;
float net = doc [ 1 ] [ " cumulative " ] [ " currW " ] . as < float > ( ) ;
solarProduction = round ( total - net ) ; // Production des panneaux arrondie
powerConsumption = round ( total ) ; // Consommation totale arrondie
Serial . printf ( " Enphase - Total: %.1fW, Panneaux: %.1fW, Net: %.1fW \n " ,
total , solarProduction , net ) ;
enphaseConnectionError = false ;
} else {
Serial . println ( " Erreur parsing JSON Enphase " ) ;
enphaseConnectionError = true ;
}
} else {
Serial . printf ( " Erreur HTTP Enphase: %d \n " , httpCode ) ;
enphaseConnectionError = true ;
}
http . end ( ) ;
}
@ -482,6 +478,23 @@ void calculateHeaterPower() {
@@ -482,6 +478,23 @@ void calculateHeaterPower() {
isNightPeriod = ( currentHour > = nightStartHour | | currentHour < nightEndHour ) ;
}
// Vérifier si on est dans la période jour (entre lever et coucher du soleil)
bool isDayPeriod = false ;
if ( sunriseTime ! = " --:-- " & & sunsetTime ! = " --:-- " ) {
// Parser les heures de lever et coucher
int sunriseHour = sunriseTime . substring ( 0 , 2 ) . toInt ( ) ;
int sunriseMinute = sunriseTime . substring ( 3 , 5 ) . toInt ( ) ;
int sunsetHour = sunsetTime . substring ( 0 , 2 ) . toInt ( ) ;
int sunsetMinute = sunsetTime . substring ( 3 , 5 ) . toInt ( ) ;
// Convertir en minutes depuis minuit pour faciliter la comparaison
int currentTimeInMinutes = currentHour * 60 + currentMinute ;
int sunriseTimeInMinutes = sunriseHour * 60 + sunriseMinute ;
int sunsetTimeInMinutes = sunsetHour * 60 + sunsetMinute ;
isDayPeriod = ( currentTimeInMinutes > = sunriseTimeInMinutes & & currentTimeInMinutes < = sunsetTimeInMinutes ) ;
}
// Calculer l'excédent solaire et ajuster le pourcentage de chauffe
float solarExcess = solarProduction - powerConsumption ;
int calculatedHeaterPowerJour , calculatedHeaterPowerSoleil = 0 ;
@ -511,10 +524,19 @@ void calculateHeaterPower() {
@@ -511,10 +524,19 @@ void calculateHeaterPower() {
}
} else if ( modeSOLEIL | | modeJOUR ) {
if ( modeJOUR ) {
// Mode JOUR: calculer selon la formule
if ( modeJOUR & & isDayPeriod ) {
// Mode JOUR: calculer selon la formule (uniquement pendant la journée)
// calculatedHeaterPowerJour = 100 * CoeffJour * (max_water_temp - water_temperature) / (secondes_restantes)
// Vérifier si la température max a déjà été atteinte aujourd'hui
if ( waterTemperature > = maxWaterTemp ) {
maxTempReachedToday = true ;
}
if ( maxTempReachedToday ) {
// La température max a été atteinte aujourd'hui, ne pas chauffer
calculatedHeaterPowerJour = 0 ;
} else {
// Calculer le temps actuel en secondes depuis minuit
int currentTimeInSeconds = currentHour * 3600 + currentMinute * 60 + currentSecond ;
@ -547,6 +569,7 @@ void calculateHeaterPower() {
@@ -547,6 +569,7 @@ void calculateHeaterPower() {
// waterTemperature, maxWaterTemp);
}
}
}
if ( modeSOLEIL ) {
// Mode SOLEIL: ajuster selon l'excédent solaire
@ -774,12 +797,17 @@ void setup() {
@@ -774,12 +797,17 @@ void setup() {
// API POST /api/settings
server . on ( " /api/settings " , HTTP_POST ,
[ ] ( AsyncWebServerRequest * request ) {
Serial . println ( " [SETTINGS] POST /api/settings appelé " ) ;
// Handler appelé quand la requête est complète
if ( ! checkAuthentication ( request ) ) {
Serial . println ( " [SETTINGS] Authentification échouée " ) ;
request - > send ( 401 , " text/plain " , " Non autorisé " ) ;
return ;
}
Serial . println ( " [SETTINGS] Authentification OK " ) ;
// Récupérer le body depuis _tempObject
if ( request - > _tempObject = = NULL ) {
Serial . println ( " [SETTINGS] Erreur: Pas de body reçu " ) ;
@ -852,16 +880,22 @@ void setup() {
@@ -852,16 +880,22 @@ void setup() {
if ( requestDoc [ " wifi " ] [ " ssid " ] & & requestDoc [ " wifi " ] [ " ssid " ] . as < String > ( ) . length ( ) > 0 ) {
String newSSID = requestDoc [ " wifi " ] [ " ssid " ] . as < String > ( ) ;
if ( newSSID ! = wifiSSID ) {
configDoc [ " wifi " ] [ " ssid " ] = newSSID ;
wifiSSID = newSSID ;
wifiChanged = true ;
Serial . println ( " [SETTINGS] SSID modifié, redémarrage nécessaire " ) ;
}
}
if ( requestDoc [ " wifi " ] [ " password " ] & & requestDoc [ " wifi " ] [ " password " ] . as < String > ( ) . length ( ) > 0 ) {
String newWifiPass = requestDoc [ " wifi " ] [ " password " ] . as < String > ( ) ;
if ( newWifiPass ! = wifiPassword ) {
configDoc [ " wifi " ] [ " password " ] = newWifiPass ;
wifiPassword = newWifiPass ;
wifiChanged = true ;
Serial . println ( " [SETTINGS] Mot de passe WiFi modifié, redémarrage nécessaire " ) ;
}
}
if ( requestDoc [ " wifi " ] [ " ap_password " ] & & requestDoc [ " wifi " ] [ " ap_password " ] . as < String > ( ) . length ( ) > 0 ) {
@ -988,6 +1022,7 @@ void setup() {
@@ -988,6 +1022,7 @@ void setup() {
JsonDocument responseDoc ;
responseDoc [ " success " ] = true ;
responseDoc [ " restart " ] = wifiChanged ;
if ( wifiChanged ) {
responseDoc [ " message " ] = " Paramètres sauvegardés. Redémarrage pour appliquer les changements WiFi... " ;
@ -1013,12 +1048,21 @@ void setup() {
@@ -1013,12 +1048,21 @@ void setup() {
if ( index = = 0 ) {
// Premier chunk: créer le buffer
Serial . printf ( " [SETTINGS] Réception body: %d bytes total \n " , total ) ;
if ( total > 8192 ) {
Serial . println ( " [SETTINGS] ERREUR: Body trop grand! " ) ;
return ;
}
request - > _tempObject = malloc ( total + 1 ) ;
if ( request - > _tempObject = = NULL ) {
Serial . println ( " [SETTINGS] ERREUR: Échec allocation mémoire! " ) ;
return ;
}
}
if ( request - > _tempObject ! = NULL ) {
// Copier ce chunk
memcpy ( ( uint8_t * ) request - > _tempObject + index , data , len ) ;
Serial . printf ( " [SETTINGS] Chunk reçu: index=%d len=%d total=%d \n " , index , len , total ) ;
if ( index + len = = total ) {
// Dernier chunk: terminer la chaîne
@ -1043,7 +1087,7 @@ void setup() {
@@ -1043,7 +1087,7 @@ void setup() {
doc [ " sunrise_time " ] = sunriseTime ;
doc [ " sunset_time " ] = sunsetTime ;
doc [ " timestamp " ] = millis ( ) ;
doc [ " enphase_connection_error " ] = enphaseConnectionError ;
String response ;
serializeJson ( doc , response ) ;
request - > send ( 200 , " application/json " , response ) ;
@ -1412,5 +1456,8 @@ void loop() {
@@ -1412,5 +1456,8 @@ void loop() {
if ( timeinfo . tm_mday ! = lastDay ) {
lastDay = timeinfo . tm_mday ;
calculateSunriseSunset ( ) ;
// Réinitialiser le flag de température max atteinte pour le nouveau jour
maxTempReachedToday = false ;
Serial . println ( " [JOUR] Nouveau jour - réinitialisation du flag température max " ) ;
}
}