Messages : 552
Sujets : 5
Inscription : Jun 2024
(27-11-2025, 10:17 PM)tupolev89 a écrit : 1 photo: ecodevice et esp
2 photo: l'arrivée d'eau (compteur d'eau maison, suivi d'un compteur gianola à impulsion qui envoi les données à ecodevice) suivi de la vanne motorisée.
Content que tout fonctionne toujours bien et merci pour les photos, c'est sympa de voir l'installation finale.
Messages : 171
Sujets : 13
Inscription : Jun 2024
(28-11-2025, 09:24 AM)Lolo69 a écrit : Faire un chargement OTA c est très simple dans ta page admin tu vas sur chargement OTA tu choisis le fichier BIN ( qui est le fichier compilé) et tu fais OK
Pour le BIN je pourrai te le mettre à disposition mais je préfère t apprendre à le faire pour prendre de l autonomie sur des futures mise à jour que tu voudrais faire toi même
Rassures toi faire le bin est très simple aussi
Dans Arduino tu ouvres ton projet, tu fais tes modifs comme d habitude.
A la fin au lieu de cliquer sur la flèche pour compiler / televerser tu vas dans le menu sketch/ croquis et tu fais exporter les binaires compilés »
À la fin de la compilation cela aura créé un sous sous répertoire Windows dans ton projet appelé Build….
Dans ce répertoire tu vas trouver plusieurs fichiers BIN
Il faut sélectionner celui qui s appelle NomDeTonProjet.ino.bin Merci, bien Lolo de ta réponse, avec tes explications je penses y arriver, c'est vrai ça ne paraît pas bien compliqué.
Par contre j'ai peut être découvert un petit bug, ce matin j'ai relancé ma livebox, et après le redémarrage, lesp ne se reconnecte pas au wifi, seul solution possible un reset de lesp, et ça redémarre. Alors qu'avec le routeur f1atb la reconnexion est instantanée.
A tu aussi ce problème avec la version 2,03?
Messages : 793
Sujets : 4
Inscription : Sep 2024
28-11-2025, 02:32 PM
(Modification du message : 28-11-2025, 02:50 PM par Lolo69.)
Pas essayé, mais oui c est normal y a pas de Check de perte de wifi pour le reconnecter au cas où, a voir la version 2.0x
en version 2.04 suivante , les routes sont modifiées et la reconnection wifi verifiée et relancée toutes les 10 sec
Code : //Version 2.04 28/11/25
/*
Parametrages depuis page /admin
envoi à une liste de destinataires séparés par des ;
GPIO configurable depuis admin, pilotable en temps réel
Trigger configurable pour mise à 0 de GPIO
Ajout OTA via /admin
Ajout 2ème GPIO pour recopie état de la vanne
rearrangement cosmetiques des sections de la page /admin
Rearrangement cosmetiques dans le code ( alignements commentaires indentation)
Ajout inversion logique indépendante par GPIO
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <LittleFS.h>
#include <WiFiClientSecure.h>
#include <ESP_Mail_Client.h>
#include <Update.h>
#define WIFI_CONNECT_TIMEOUT_MS 10000
#define AP_SSID "ESP32_Config"
// Eviter NULL pour ne pas appeler strlen(NULL)
#define AP_PASSWORD ""
// si tu veux un AP sécurisé par défaut, mets une chaîne >=8 caractères
// #define AP_PASSWORD "esp32admin"
const char* CONFIG_FILE = "/config.txt";
// Valeurs par défaut
String cfg_ssid = "ton SSID";
String cfg_password = "ton mot de passe wifi";
String cfg_smtp = "smtp.orange.fr";
String cfg_email = "tonemail@orange.fr";
String cfg_smtp_pwd = "ton mot de passe Mail";
String cfg_destinataires = "dest1@free.fr;dest2@orange.fr";
int cfg_smtp_port = 465;
int cfg_gpio_number = 2;
// ⭐ AJOUT — GPIO clone
int cfg_gpio_number2 = 23;
String cfg_trigger_type = "inondation";
// ⭐ AJOUT — inversion logique indépendante GPIO
bool cfg_invert_gpio1 = false; // GPIO principale
bool cfg_invert_gpio2 = false; // GPIO clone
// Réseau statique
String cfg_local_ip = "192.168.1.119";
String cfg_gateway = "192.168.1.1";
String cfg_subnet = "255.255.255.0";
String cfg_primaryDNS = "192.168.1.1";
String cfg_secondaryDNS = "8.8.4.4";
// Admin auth
String admin_user = "admin";
String admin_pwd = "admin";
// Objets globaux
WebServer server(80);
SMTPSession smtp;
// --- Helpers IP ---
IPAddress ipFromString(const String &s) {
IPAddress ip(0,0,0,0);
int parts[4] = {0,0,0,0}, idx = 0;
String tmp = s;
tmp.trim();
int start = 0;
for (int i=0; i<4; ++i) {
int pos = tmp.indexOf('.', start);
String part = (pos==-1) ? tmp.substring(start) : tmp.substring(start,pos);
part.trim();
parts[idx++] = part.toInt();
if (pos==-1) break;
start = pos+1;
}
ip = IPAddress(parts[0], parts[1], parts[2], parts[3]);
return ip;
}
// --- LittleFS ---
void saveConfig() {
File f = LittleFS.open(CONFIG_FILE, "w");
if (!f) { Serial.println("Erreur ouverture fichier config"); return; }
f.printf("ssid=%s\n", cfg_ssid.c_str());
f.printf("password=%s\n", cfg_password.c_str());
f.printf("smtp=%s\n", cfg_smtp.c_str());
f.printf("email=%s\n", cfg_email.c_str());
f.printf("smtp_pwd=%s\n", cfg_smtp_pwd.c_str());
f.printf("destinataires=%s\n", cfg_destinataires.c_str());
f.printf("smtp_port=%d\n", cfg_smtp_port);
f.printf("gpio_number=%d\n", cfg_gpio_number);
// ⭐ AJOUT — Sauvegarde GPIO2
f.printf("gpio_number2=%d\n", cfg_gpio_number2);
// ⭐ AJOUT — Sauvegarde inversion logique GPIO
f.printf("invert_gpio1=%d\n", cfg_invert_gpio1 ? 1 : 0);
f.printf("invert_gpio2=%d\n", cfg_invert_gpio2 ? 1 : 0);
f.printf("trigger_type=%s\n", cfg_trigger_type.c_str());
f.printf("local_ip=%s\n", cfg_local_ip.c_str());
f.printf("gateway=%s\n", cfg_gateway.c_str());
f.printf("subnet=%s\n", cfg_subnet.c_str());
f.printf("primaryDNS=%s\n", cfg_primaryDNS.c_str());
f.printf("secondaryDNS=%s\n", cfg_secondaryDNS.c_str());
f.printf("admin_user=%s\n", admin_user.c_str());
f.printf("admin_pwd=%s\n", admin_pwd.c_str());
f.close();
Serial.println("Config sauvegardée dans LittleFS");
}
void loadConfig() {
if (!LittleFS.exists(CONFIG_FILE)) {
Serial.println("Fichier config absent -> valeurs par défaut");
return;
}
File f = LittleFS.open(CONFIG_FILE,"r");
if (!f) {
Serial.println("Erreur ouverture fichier config");
return;
}
while(f.available()) {
String line = f.readStringUntil('\n');
line.trim();
if (line.length() == 0) continue;
int eq = line.indexOf('=');
if (eq == -1) continue;
String key = line.substring(0, eq), val = line.substring(eq+1);
key.trim();
val.trim();
if(key=="ssid") cfg_ssid=val;
else if(key=="password") cfg_password=val;
else if(key=="smtp") cfg_smtp=val;
else if(key=="email") cfg_email=val;
else if(key=="smtp_pwd") cfg_smtp_pwd=val;
else if(key=="destinataires") cfg_destinataires=val;
else if(key=="smtp_port") cfg_smtp_port=val.toInt();
else if(key=="gpio_number") cfg_gpio_number=val.toInt();
// ⭐ AJOUT — chargement GPIO2
else if(key=="gpio_number2") cfg_gpio_number2=val.toInt();
// ⭐ AJOUT — chargement inversion logique
else if(key=="invert_gpio1") cfg_invert_gpio1=(val.toInt()!=0);
else if(key=="invert_gpio2") cfg_invert_gpio2=(val.toInt()!=0);
else if(key=="trigger_type") cfg_trigger_type=val;
else if(key=="local_ip") cfg_local_ip = val;
else if(key=="gateway") cfg_gateway = val;
else if(key=="subnet") cfg_subnet = val;
else if(key=="primaryDNS") cfg_primaryDNS = val;
else if(key=="secondaryDNS") cfg_secondaryDNS = val;
else if(key=="admin_user") admin_user = val;
else if(key=="admin_pwd") admin_pwd = val;
}
f.close();
Serial.println("Config chargée depuis LittleFS");
}
// ⭐ AJOUT — helper GPIO avec inversion
void setGPIO(int gpio, bool on, bool invert) {
digitalWrite(gpio, invert ? (on ? LOW : HIGH) : (on ? HIGH : LOW));
}
// --- SMTP ---
void addAllRecipients(SMTP_Message &message, const String &liste) {
String s=liste;
int start=0, pos=0;
while((pos=s.indexOf(';',start))!=-1) {
String email=s.substring(start,pos);
email.trim();
if(email.length()>0) message.addRecipient("Destinataire",email.c_str());
start=pos+1;
}
String email=s.substring(start);
email.trim();
if(email.length()>0) message.addRecipient("Destinataire",email.c_str());
}
// --- Page root ---
void handleRoot() {
time_t now=time(nullptr);
struct tm timeinfo;
localtime_r(&now,&timeinfo);
char buffer[20];
strftime(buffer,sizeof(buffer),"%d/%m/%Y %H:%M:%S",&timeinfo);
String type="Non défini";
if(server.hasArg("type")) type=server.arg("type");
// Vérifie le trigger pour GPIO
if(type.equalsIgnoreCase(cfg_trigger_type)){
setGPIO(cfg_gpio_number, false, cfg_invert_gpio1); // GPIO principale à 0 selon logique
setGPIO(cfg_gpio_number2, false, cfg_invert_gpio2); // GPIO clone
Serial.println("Trigger activé : GPIOs mis à 0 selon logique");
}
ESP_Mail_Session session;
session.server.host_name=cfg_smtp.c_str();
session.server.port=cfg_smtp_port;
session.login.email=cfg_email.c_str();
session.login.password=cfg_smtp_pwd.c_str();
session.login.user_domain="";
SMTP_Message message;
message.sender.name="ECODEVICE";
message.sender.email=cfg_email.c_str();
addAllRecipients(message,cfg_destinataires);
message.subject="Alerte de type : "+type;
message.text.content="Bonjour ! Email envoyé depuis ESP32 le "+String(buffer);
smtp.debug(1);
String prefixPage="<html><head><meta charset='UTF-8'><meta http-equiv='Cache-Control' content='no-cache, no-store, must-revalidate'><meta name='viewport' content='width=device-width, initial-scale=1'>"
"<style>body{font-family:Arial;padding:16px;} .ok{color:green;} .ko{color:red;}</style></head><body>";
String suffixPage="</body></html>";
String page=prefixPage+suffixPage;
if(!smtp.connect(&session)){
Serial.println("Erreur de connexion SMTP !");
server.send(200,"text/html",prefixPage+"<p class='ko'>Erreur connexion SMTP</p>"+suffixPage);
return;
}
if(!MailClient.sendMail(&smtp,&message)){
page=prefixPage+"<p class='ko'>Erreur d'envoi !</p>"+suffixPage;
Serial.println("Erreur d'envoi !");
} else {
page=prefixPage+"<p class='ok'>Email envoyé le "+String(buffer)+"</p>"+suffixPage;
Serial.println("Email envoyé !");
}
smtp.closeSession();
server.send(200,"text/html",page);
}
// --- Auth & escape ---
bool checkAuth() {
if(!server.authenticate(admin_user.c_str(),admin_pwd.c_str())) {
server.requestAuthentication();
return false;
}
return true;
}
String htmlEscape(const String &s){
String r=s;
r.replace("&","&");
r.replace("<","<");
r.replace(">",">");
r.replace("\"",""");
r.replace("'","'");
return r;
}
// --- Page /admin ---
void handleAdmin() {
if(!checkAuth()) return;
int gpio_state = digitalRead(cfg_gpio_number);
int gpio_state2 = digitalRead(cfg_gpio_number2);
String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'><meta http-equiv='Cache-Control' content='no-cache, no-store, must-revalidate'><meta name='viewport' content='width=device-width, initial-scale=1'>"
"<style>body{font-family:Arial;padding:12px;} label{display:block;margin-top:8px;} input[type=text], input[type=password], textarea{width:100%;padding:8px;border-radius:6px;border:1px solid #ccc;box-sizing:border-box;} .row{display:flex;gap:8px;} .col{flex:1;} .btn{display:inline-block;padding:8px 12px;margin-top:10px;border-radius:6px;background:#2196F3;color:#fff;text-decoration:none;}</style>"
"<title>Ecodevice Gateway</title></head><body>"
"<h2>Paramètres Ecodevice Gateway</h2>"
"<h3>GPIO (principale) " + String(cfg_gpio_number) + " : <span id='gpio_state'>" + (gpio_state?"ON":"OFF") + "</span></h3>"
"<button onclick=\"toggleGPIO('on')\">Ouvrir</button> "
"<button onclick=\"toggleGPIO('off')\">Fermer</button><hr>"
"<h3>GPIO2 (secondaire) " + String(cfg_gpio_number2) + " : <span id='gpio_state2'>" + (gpio_state2?"ON":"OFF") + "</span></h3>"
"<p>(Cette sortie suit automatiquement la GPIO principale)</p><hr>"
"<form method='POST' action='/save'>"
"<h3>Paramètres globaux</h3>"
"<label>déclencheur type<input name='trigger_type' type='text' value='"+htmlEscape(cfg_trigger_type)+"'></label>"
"<label>Numéro GPIO principale<input name='gpio_number' type='text' value='"+String(cfg_gpio_number)+"'></label>"
"<label>Numéro GPIO secondaire<input name='gpio_number2' type='text' value='"+String(cfg_gpio_number2)+"'></label>"
"<label>Inverser logique GPIO principale<input type='checkbox' name='invert_gpio1' "+(cfg_invert_gpio1?"checked":"")+"></label>"
"<label>Inverser logique GPIO secondaire<input type='checkbox' name='invert_gpio2' "+(cfg_invert_gpio2?"checked":"")+"></label>"
"<h3>Paramètres Wifi SSID</h3>"
"<label>SSID WiFi<input name='ssid' type='text' value='"+htmlEscape(cfg_ssid)+"'></label>"
"<label>Mot de passe WiFi<input name='password' type='password' placeholder='(inchangé)'></label>"
"<h3>Paramètres Réseau</h3>"
"<label>Local IP<input name='local_ip' type='text' value='"+htmlEscape(cfg_local_ip)+"'></label>"
"<label>Gateway<input name='gateway' type='text' value='"+htmlEscape(cfg_gateway)+"'></label>"
"<label>Subnet<input name='subnet' type='text' value='"+htmlEscape(cfg_subnet)+"'></label>"
"<label>Primary DNS<input name='primaryDNS' type='text' value='"+htmlEscape(cfg_primaryDNS)+"'></label>"
"<label>Secondary DNS<input name='secondaryDNS' type='text' value='"+htmlEscape(cfg_secondaryDNS)+"'></label>"
"<h3>Paramètres SMTP</h3>"
"<label>SMTP host<input name='smtp' type='text' value='"+htmlEscape(cfg_smtp)+"'></label>"
"<label>SMTP port<input name='smtp_port' type='text' value='"+String(cfg_smtp_port)+"'></label>"
"<label>Adresse email (expediteur)<input name='email' type='text' value='"+htmlEscape(cfg_email)+"'></label>"
"<label>Mot de passe SMTP<input name='smtp_pwd' type='password' placeholder='(inchangé)'></label>"
"<label>Destinataires (séparés par ;) <textarea name='destinataires' rows='3'>"+htmlEscape(cfg_destinataires)+"</textarea></label>"
"<h3>Paramètres accès admin</h3>"
"<label>Admin user<input name='admin_user' type='text' value='"+htmlEscape(admin_user)+"'></label>"
"<label>Admin pwd<input name='admin_pwd' type='password' placeholder='(inchangé)'></label>"
"<div class='row'>"
" <div class='col'><button class='btn' type='submit'>Sauvegarder</button></div>"
" <div class='col'><button class='btn' type='button' onclick=\"window.location.href='/testsend'\">Tester envoi</button></div>"
"</div>"
"</form>"
"<hr><h3>Mise à jour OTA</h3>"
"<form method='POST' action='/update' enctype='multipart/form-data'>"
"<input type='file' name='firmware' accept='.bin' required>"
"<button class='btn' type='submit'>Mettre à jour</button>"
"</form>"
"<script>"
"function toggleGPIO(action){"
" fetch('/gpio?action='+action+'&json=1')"
" .then(response=>response.json())"
" .then(data=>{"
" document.getElementById('gpio_state').innerText=data.state?'ON':'OFF';"
" document.getElementById('gpio_state2').innerText=data.state?'ON':'OFF';"
" })"
" .catch(err=>console.error(err));"
"} "
"setInterval(()=>{"
" fetch('/gpio?json=1')"
" .then(r=>r.json())"
" .then(d=>{"
" document.getElementById('gpio_state').innerText=d.state?'ON':'OFF';"
" document.getElementById('gpio_state2').innerText=d.state?'ON':'OFF';"
" });"
"},1000);"
"</script>"
"</body></html>";
server.send(200,"text/html",page);
}
// --- Save ---
void handleSave() {
if(!checkAuth()) return;
if(server.hasArg("ssid")) cfg_ssid = server.arg("ssid");
if(server.hasArg("password") && server.arg("password") != "") cfg_password = server.arg("password");
if(server.hasArg("gpio_number")) {
cfg_gpio_number = server.arg("gpio_number").toInt();
pinMode(cfg_gpio_number, OUTPUT);
setGPIO(cfg_gpio_number, true, cfg_invert_gpio1);
}
if(server.hasArg("gpio_number2")) {
cfg_gpio_number2 = server.arg("gpio_number2").toInt();
pinMode(cfg_gpio_number2, OUTPUT);
setGPIO(cfg_gpio_number2, true, cfg_invert_gpio2);
}
if(server.hasArg("trigger_type")) cfg_trigger_type = server.arg("trigger_type");
if(server.hasArg("smtp")) cfg_smtp = server.arg("smtp");
if(server.hasArg("smtp_port")) cfg_smtp_port = server.arg("smtp_port").toInt();
if(server.hasArg("email")) cfg_email = server.arg("email");
if(server.hasArg("smtp_pwd") && server.arg("smtp_pwd") != "") cfg_smtp_pwd = server.arg("smtp_pwd");
if(server.hasArg("destinataires")) cfg_destinataires = server.arg("destinataires");
if(server.hasArg("local_ip")) cfg_local_ip = server.arg("local_ip");
if(server.hasArg("gateway")) cfg_gateway = server.arg("gateway");
if(server.hasArg("subnet")) cfg_subnet = server.arg("subnet");
if(server.hasArg("primaryDNS")) cfg_primaryDNS = server.arg("primaryDNS");
if(server.hasArg("secondaryDNS")) cfg_secondaryDNS = server.arg("secondaryDNS");
if(server.hasArg("admin_user")) admin_user = server.arg("admin_user");
if(server.hasArg("admin_pwd") && server.arg("admin_pwd") != "") admin_pwd = server.arg("admin_pwd");
// ⭐ AJOUT — lecture inversion logique
cfg_invert_gpio1 = server.hasArg("invert_gpio1");
cfg_invert_gpio2 = server.hasArg("invert_gpio2");
saveConfig();
server.send(200,"text/html","<html><body><p>Config sauvegardée. Redémarrage...</p></body></html>");
delay(200);
ESP.restart();
}
// --- Test send ---
void handleTestSend() {
if(!checkAuth()) return;
ESP_Mail_Session session;
session.server.host_name = cfg_smtp.c_str();
session.server.port = cfg_smtp_port;
session.login.email = cfg_email.c_str();
session.login.password = cfg_smtp_pwd.c_str();
session.login.user_domain = "";
SMTP_Message message;
message.sender.name = "ECODEVICE";
message.sender.email = cfg_email.c_str();
addAllRecipients(message, cfg_destinataires);
message.subject = "Test d'envoi depuis ESP32 (UI)";
message.text.content = "Ceci est un test envoyé depuis /admin";
smtp.debug(1);
String pageStart = "<html><body>";
String pageEnd = "</body></html>";
if(!smtp.connect(&session)) server.send(200,"text/html",pageStart+"<p style='color:red'>Erreur connexion SMTP</p>"+pageEnd);
else if(!MailClient.sendMail(&smtp,&message)) server.send(200,"text/html",pageStart+"<p style='color:red'>Erreur envoi</p>"+pageEnd);
else server.send(200,"text/html",pageStart+"<p style='color:green'>Email envoyé</p>"+pageEnd);
smtp.closeSession();
}
// --- GPIO route ---
void handleGPIO() {
if(!checkAuth()) return;
if(server.hasArg("action")) {
String action = server.arg("action");
if(action == "on") {
setGPIO(cfg_gpio_number, true, cfg_invert_gpio1);
setGPIO(cfg_gpio_number2, true, cfg_invert_gpio2);
} else if(action == "off") {
setGPIO(cfg_gpio_number, false, cfg_invert_gpio1);
setGPIO(cfg_gpio_number2, false, cfg_invert_gpio2);
}
}
int state = digitalRead(cfg_gpio_number);
if(server.hasArg("json") && server.arg("json")=="1") {
server.send(200,"application/json","{\"state\":"+String(state)+"}");
return;
}
server.send(200,"text/html","<html><body><h3>GPIO "+String(cfg_gpio_number)+" : "+(state?"ON":"OFF")+"</h3>"
"<a href='/gpio?action=on'>Ouvrir</a> | <a href='/gpio?action=off'>Fermer</a></body></html>");
}
// --- OTA upload handler ---
void handleFirmwareUpload() {
if (!checkAuth()) return;
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.setDebugOutput(true);
Serial.printf("Mise à jour OTA commencée : %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) Update.printError(Serial);
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) Update.printError(Serial);
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) Serial.printf("Mise à jour OTA terminée : %u octets\n", upload.totalSize);
else Update.printError(Serial);
Serial.setDebugOutput(false);
} else if (upload.status == UPLOAD_FILE_ABORTED) {
Update.end();
Serial.println("Mise à jour OTA annulée.");
}
}
void handleUpdatePage() {
if (!checkAuth()) return;
if (Update.hasError()) {
server.send(200, "text/html",
"<html><body><p style='color:red'>Échec de la mise à jour</p></body></html>");
} else {
server.send(200, "text/html",
"<html><body><p style='color:green'>Mise à jour réussie, redémarrage...</p></body></html>");
delay(500);
ESP.restart();
}
}
// --- AP fallback ---
void startAPMode() {
Serial.println("Demarrage AP...");
WiFi.disconnect(true);
delay(200);
WiFi.mode(WIFI_AP_STA);
delay(50);
IPAddress apIP(192,168,4,1);
IPAddress apGateway = apIP;
IPAddress apMask(255,255,255,0);
if(!WiFi.softAPConfig(apIP, apGateway, apMask)) Serial.println("softAPConfig FAILED");
else Serial.println("softAPConfig OK");
delay(50);
if(strlen(AP_PASSWORD) == 0) WiFi.softAP(AP_SSID);
else WiFi.softAP(AP_SSID, AP_PASSWORD);
delay(300);
Serial.print("AP SSID: "); Serial.println(AP_SSID);
Serial.print("AP IP (softAPIP): "); Serial.println(WiFi.softAPIP());
Serial.print("Num stations connected: "); Serial.println(WiFi.softAPgetStationNum());
}
// --- WiFi connect ---
bool attemptWiFiConnect() {
Serial.printf("Connexion a %s\n", cfg_ssid.c_str());
WiFi.mode(WIFI_STA);
WiFi.begin(cfg_ssid.c_str(), cfg_password.c_str());
unsigned long start = millis();
while(WiFi.status() != WL_CONNECTED && (millis()-start) < WIFI_CONNECT_TIMEOUT_MS) {
Serial.print(".");
delay(200);
}
Serial.println();
if(WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi connecté");
Serial.print("IP: "); Serial.println(WiFi.localIP());
return true;
}
Serial.println("WiFi non connecté -> fallback AP");
return false;
}
// ⭐ AJOUT — Reconnexion automatique WiFi
void checkWiFiReconnect() {
if (WiFi.status() != WL_CONNECTED) {
static unsigned long lastAttempt = 0;
if (millis() - lastAttempt > 10000) { // tentative toutes les 10 s
lastAttempt = millis();
Serial.println("WiFi perdu -> tentative de reconnexion...");
attemptWiFiConnect();
}
}
}
// --- Routes ---
void setupRoutes() {
server.on("/", handleAdmin); // <-- admin devient la page racine
server.on("/actions", handleRoot); // <-- ancien root va dans /actions
server.on("/save", HTTP_POST, handleSave);
server.on("/testsend", HTTP_GET, handleTestSend);
server.on("/gpio", handleGPIO);
server.on("/update", HTTP_POST, handleUpdatePage, handleFirmwareUpload);
server.begin();
Serial.println("Serveur web démarré");
}
// --- Setup ---
void setup() {
Serial.begin(115200);
delay(1000);
if(!LittleFS.begin(true)) Serial.println("LittleFS mount failed!");
else Serial.println("LittleFS OK");
loadConfig();
// Configuration des deux GPIO (GPIO2 est un clone)
pinMode(cfg_gpio_number, OUTPUT);
setGPIO(cfg_gpio_number, true, cfg_invert_gpio1);
pinMode(cfg_gpio_number2, OUTPUT);
setGPIO(cfg_gpio_number2, true, cfg_invert_gpio2);
if(cfg_local_ip.length() > 0){
IPAddress local = ipFromString(cfg_local_ip);
IPAddress gw = ipFromString(cfg_gateway);
IPAddress mask = ipFromString(cfg_subnet);
IPAddress pdns = ipFromString(cfg_primaryDNS);
IPAddress sdns = ipFromString(cfg_secondaryDNS);
if(!WiFi.config(local, gw, mask, pdns, sdns)) Serial.println("Echec config IP statique");
else Serial.println("Config IP statique appliquée");
}
bool wifiOk = attemptWiFiConnect();
if(!wifiOk) {
startAPMode();
setupRoutes();
} else {
setupRoutes();
configTzTime("CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00","fr.pool.ntp.org","time.nist.gov");
unsigned long t0 = millis();
const unsigned long NTPTIMEOUT = 5000;
while (millis() - t0 < NTPTIMEOUT && time(nullptr) < ESP_MAIL_CLIENT_VALID_TS) { delay(100);}
if (time(nullptr) >= ESP_MAIL_CLIENT_VALID_TS) Serial.println("NTP ok");
else Serial.println("NTP non synchro (timeout)");
}
}
// --- Loop ---
void loop() {
server.handleClient();
checkWiFiReconnect(); // ⭐ Ajout reconnection automatique
}
donc pour acceder à la page d'admin , directement en http://192.168.1.xx
pour les pushs ecodevice dans URL à la place de /?type=motmagique tu mettras /actions?type=motmagique ou autre mot en fonction de ton declencheur
Messages : 171
Sujets : 13
Inscription : Jun 2024
(28-11-2025, 02:32 PM)Lolo69 a écrit : Pas essayé, mais oui c est normal y a pas de Check de perte de wifi pour le reconnecter au cas où, a voir la version 2.0x
en version 2.04 suivante , les routes sont modifiées et la reconnection wifi verifiée et relancée toutes les 10 sec
Code : //Version 2.04 28/11/25
/*
Parametrages depuis page /admin
envoi à une liste de destinataires séparés par des ;
GPIO configurable depuis admin, pilotable en temps réel
Trigger configurable pour mise à 0 de GPIO
Ajout OTA via /admin
Ajout 2ème GPIO pour recopie état de la vanne
rearrangement cosmetiques des sections de la page /admin
Rearrangement cosmetiques dans le code ( alignements commentaires indentation)
Ajout inversion logique indépendante par GPIO
*/
#include <Arduino.h>
#include <WiFi.h>
#include <WebServer.h>
#include <LittleFS.h>
#include <WiFiClientSecure.h>
#include <ESP_Mail_Client.h>
#include <Update.h>
#define WIFI_CONNECT_TIMEOUT_MS 10000
#define AP_SSID "ESP32_Config"
// Eviter NULL pour ne pas appeler strlen(NULL)
#define AP_PASSWORD ""
// si tu veux un AP sécurisé par défaut, mets une chaîne >=8 caractères
// #define AP_PASSWORD "esp32admin"
const char* CONFIG_FILE = "/config.txt";
// Valeurs par défaut
String cfg_ssid = "ton SSID";
String cfg_password = "ton mot de passe wifi";
String cfg_smtp = "smtp.orange.fr";
String cfg_email = "tonemail@orange.fr";
String cfg_smtp_pwd = "ton mot de passe Mail";
String cfg_destinataires = "dest1@free.fr;dest2@orange.fr";
int cfg_smtp_port = 465;
int cfg_gpio_number = 2;
// ⭐ AJOUT — GPIO clone
int cfg_gpio_number2 = 23;
String cfg_trigger_type = "inondation";
// ⭐ AJOUT — inversion logique indépendante GPIO
bool cfg_invert_gpio1 = false; // GPIO principale
bool cfg_invert_gpio2 = false; // GPIO clone
// Réseau statique
String cfg_local_ip = "192.168.1.119";
String cfg_gateway = "192.168.1.1";
String cfg_subnet = "255.255.255.0";
String cfg_primaryDNS = "192.168.1.1";
String cfg_secondaryDNS = "8.8.4.4";
// Admin auth
String admin_user = "admin";
String admin_pwd = "admin";
// Objets globaux
WebServer server(80);
SMTPSession smtp;
// --- Helpers IP ---
IPAddress ipFromString(const String &s) {
IPAddress ip(0,0,0,0);
int parts[4] = {0,0,0,0}, idx = 0;
String tmp = s;
tmp.trim();
int start = 0;
for (int i=0; i<4; ++i) {
int pos = tmp.indexOf('.', start);
String part = (pos==-1) ? tmp.substring(start) : tmp.substring(start,pos);
part.trim();
parts[idx++] = part.toInt();
if (pos==-1) break;
start = pos+1;
}
ip = IPAddress(parts[0], parts[1], parts[2], parts[3]);
return ip;
}
// --- LittleFS ---
void saveConfig() {
File f = LittleFS.open(CONFIG_FILE, "w");
if (!f) { Serial.println("Erreur ouverture fichier config"); return; }
f.printf("ssid=%s\n", cfg_ssid.c_str());
f.printf("password=%s\n", cfg_password.c_str());
f.printf("smtp=%s\n", cfg_smtp.c_str());
f.printf("email=%s\n", cfg_email.c_str());
f.printf("smtp_pwd=%s\n", cfg_smtp_pwd.c_str());
f.printf("destinataires=%s\n", cfg_destinataires.c_str());
f.printf("smtp_port=%d\n", cfg_smtp_port);
f.printf("gpio_number=%d\n", cfg_gpio_number);
// ⭐ AJOUT — Sauvegarde GPIO2
f.printf("gpio_number2=%d\n", cfg_gpio_number2);
// ⭐ AJOUT — Sauvegarde inversion logique GPIO
f.printf("invert_gpio1=%d\n", cfg_invert_gpio1 ? 1 : 0);
f.printf("invert_gpio2=%d\n", cfg_invert_gpio2 ? 1 : 0);
f.printf("trigger_type=%s\n", cfg_trigger_type.c_str());
f.printf("local_ip=%s\n", cfg_local_ip.c_str());
f.printf("gateway=%s\n", cfg_gateway.c_str());
f.printf("subnet=%s\n", cfg_subnet.c_str());
f.printf("primaryDNS=%s\n", cfg_primaryDNS.c_str());
f.printf("secondaryDNS=%s\n", cfg_secondaryDNS.c_str());
f.printf("admin_user=%s\n", admin_user.c_str());
f.printf("admin_pwd=%s\n", admin_pwd.c_str());
f.close();
Serial.println("Config sauvegardée dans LittleFS");
}
void loadConfig() {
if (!LittleFS.exists(CONFIG_FILE)) {
Serial.println("Fichier config absent -> valeurs par défaut");
return;
}
File f = LittleFS.open(CONFIG_FILE,"r");
if (!f) {
Serial.println("Erreur ouverture fichier config");
return;
}
while(f.available()) {
String line = f.readStringUntil('\n');
line.trim();
if (line.length() == 0) continue;
int eq = line.indexOf('=');
if (eq == -1) continue;
String key = line.substring(0, eq), val = line.substring(eq+1);
key.trim();
val.trim();
if(key=="ssid") cfg_ssid=val;
else if(key=="password") cfg_password=val;
else if(key=="smtp") cfg_smtp=val;
else if(key=="email") cfg_email=val;
else if(key=="smtp_pwd") cfg_smtp_pwd=val;
else if(key=="destinataires") cfg_destinataires=val;
else if(key=="smtp_port") cfg_smtp_port=val.toInt();
else if(key=="gpio_number") cfg_gpio_number=val.toInt();
// ⭐ AJOUT — chargement GPIO2
else if(key=="gpio_number2") cfg_gpio_number2=val.toInt();
// ⭐ AJOUT — chargement inversion logique
else if(key=="invert_gpio1") cfg_invert_gpio1=(val.toInt()!=0);
else if(key=="invert_gpio2") cfg_invert_gpio2=(val.toInt()!=0);
else if(key=="trigger_type") cfg_trigger_type=val;
else if(key=="local_ip") cfg_local_ip = val;
else if(key=="gateway") cfg_gateway = val;
else if(key=="subnet") cfg_subnet = val;
else if(key=="primaryDNS") cfg_primaryDNS = val;
else if(key=="secondaryDNS") cfg_secondaryDNS = val;
else if(key=="admin_user") admin_user = val;
else if(key=="admin_pwd") admin_pwd = val;
}
f.close();
Serial.println("Config chargée depuis LittleFS");
}
// ⭐ AJOUT — helper GPIO avec inversion
void setGPIO(int gpio, bool on, bool invert) {
digitalWrite(gpio, invert ? (on ? LOW : HIGH) : (on ? HIGH : LOW));
}
// --- SMTP ---
void addAllRecipients(SMTP_Message &message, const String &liste) {
String s=liste;
int start=0, pos=0;
while((pos=s.indexOf(';',start))!=-1) {
String email=s.substring(start,pos);
email.trim();
if(email.length()>0) message.addRecipient("Destinataire",email.c_str());
start=pos+1;
}
String email=s.substring(start);
email.trim();
if(email.length()>0) message.addRecipient("Destinataire",email.c_str());
}
// --- Page root ---
void handleRoot() {
time_t now=time(nullptr);
struct tm timeinfo;
localtime_r(&now,&timeinfo);
char buffer[20];
strftime(buffer,sizeof(buffer),"%d/%m/%Y %H:%M:%S",&timeinfo);
String type="Non défini";
if(server.hasArg("type")) type=server.arg("type");
// Vérifie le trigger pour GPIO
if(type.equalsIgnoreCase(cfg_trigger_type)){
setGPIO(cfg_gpio_number, false, cfg_invert_gpio1); // GPIO principale à 0 selon logique
setGPIO(cfg_gpio_number2, false, cfg_invert_gpio2); // GPIO clone
Serial.println("Trigger activé : GPIOs mis à 0 selon logique");
}
ESP_Mail_Session session;
session.server.host_name=cfg_smtp.c_str();
session.server.port=cfg_smtp_port;
session.login.email=cfg_email.c_str();
session.login.password=cfg_smtp_pwd.c_str();
session.login.user_domain="";
SMTP_Message message;
message.sender.name="ECODEVICE";
message.sender.email=cfg_email.c_str();
addAllRecipients(message,cfg_destinataires);
message.subject="Alerte de type : "+type;
message.text.content="Bonjour ! Email envoyé depuis ESP32 le "+String(buffer);
smtp.debug(1);
String prefixPage="<html><head><meta charset='UTF-8'><meta http-equiv='Cache-Control' content='no-cache, no-store, must-revalidate'><meta name='viewport' content='width=device-width, initial-scale=1'>"
"<style>body{font-family:Arial;padding:16px;} .ok{color:green;} .ko{color:red;}</style></head><body>";
String suffixPage="</body></html>";
String page=prefixPage+suffixPage;
if(!smtp.connect(&session)){
Serial.println("Erreur de connexion SMTP !");
server.send(200,"text/html",prefixPage+"<p class='ko'>Erreur connexion SMTP</p>"+suffixPage);
return;
}
if(!MailClient.sendMail(&smtp,&message)){
page=prefixPage+"<p class='ko'>Erreur d'envoi !</p>"+suffixPage;
Serial.println("Erreur d'envoi !");
} else {
page=prefixPage+"<p class='ok'>Email envoyé le "+String(buffer)+"</p>"+suffixPage;
Serial.println("Email envoyé !");
}
smtp.closeSession();
server.send(200,"text/html",page);
}
// --- Auth & escape ---
bool checkAuth() {
if(!server.authenticate(admin_user.c_str(),admin_pwd.c_str())) {
server.requestAuthentication();
return false;
}
return true;
}
String htmlEscape(const String &s){
String r=s;
r.replace("&","&");
r.replace("<","<");
r.replace(">",">");
r.replace("\"",""");
r.replace("'","'");
return r;
}
// --- Page /admin ---
void handleAdmin() {
if(!checkAuth()) return;
int gpio_state = digitalRead(cfg_gpio_number);
int gpio_state2 = digitalRead(cfg_gpio_number2);
String page = "<!DOCTYPE html><html><head><meta charset='UTF-8'><meta http-equiv='Cache-Control' content='no-cache, no-store, must-revalidate'><meta name='viewport' content='width=device-width, initial-scale=1'>"
"<style>body{font-family:Arial;padding:12px;} label{display:block;margin-top:8px;} input[type=text], input[type=password], textarea{width:100%;padding:8px;border-radius:6px;border:1px solid #ccc;box-sizing:border-box;} .row{display:flex;gap:8px;} .col{flex:1;} .btn{display:inline-block;padding:8px 12px;margin-top:10px;border-radius:6px;background:#2196F3;color:#fff;text-decoration:none;}</style>"
"<title>Ecodevice Gateway</title></head><body>"
"<h2>Paramètres Ecodevice Gateway</h2>"
"<h3>GPIO (principale) " + String(cfg_gpio_number) + " : <span id='gpio_state'>" + (gpio_state?"ON":"OFF") + "</span></h3>"
"<button onclick=\"toggleGPIO('on')\">Ouvrir</button> "
"<button onclick=\"toggleGPIO('off')\">Fermer</button><hr>"
"<h3>GPIO2 (secondaire) " + String(cfg_gpio_number2) + " : <span id='gpio_state2'>" + (gpio_state2?"ON":"OFF") + "</span></h3>"
"<p>(Cette sortie suit automatiquement la GPIO principale)</p><hr>"
"<form method='POST' action='/save'>"
"<h3>Paramètres globaux</h3>"
"<label>déclencheur type<input name='trigger_type' type='text' value='"+htmlEscape(cfg_trigger_type)+"'></label>"
"<label>Numéro GPIO principale<input name='gpio_number' type='text' value='"+String(cfg_gpio_number)+"'></label>"
"<label>Numéro GPIO secondaire<input name='gpio_number2' type='text' value='"+String(cfg_gpio_number2)+"'></label>"
"<label>Inverser logique GPIO principale<input type='checkbox' name='invert_gpio1' "+(cfg_invert_gpio1?"checked":"")+"></label>"
"<label>Inverser logique GPIO secondaire<input type='checkbox' name='invert_gpio2' "+(cfg_invert_gpio2?"checked":"")+"></label>"
"<h3>Paramètres Wifi SSID</h3>"
"<label>SSID WiFi<input name='ssid' type='text' value='"+htmlEscape(cfg_ssid)+"'></label>"
"<label>Mot de passe WiFi<input name='password' type='password' placeholder='(inchangé)'></label>"
"<h3>Paramètres Réseau</h3>"
"<label>Local IP<input name='local_ip' type='text' value='"+htmlEscape(cfg_local_ip)+"'></label>"
"<label>Gateway<input name='gateway' type='text' value='"+htmlEscape(cfg_gateway)+"'></label>"
"<label>Subnet<input name='subnet' type='text' value='"+htmlEscape(cfg_subnet)+"'></label>"
"<label>Primary DNS<input name='primaryDNS' type='text' value='"+htmlEscape(cfg_primaryDNS)+"'></label>"
"<label>Secondary DNS<input name='secondaryDNS' type='text' value='"+htmlEscape(cfg_secondaryDNS)+"'></label>"
"<h3>Paramètres SMTP</h3>"
"<label>SMTP host<input name='smtp' type='text' value='"+htmlEscape(cfg_smtp)+"'></label>"
"<label>SMTP port<input name='smtp_port' type='text' value='"+String(cfg_smtp_port)+"'></label>"
"<label>Adresse email (expediteur)<input name='email' type='text' value='"+htmlEscape(cfg_email)+"'></label>"
"<label>Mot de passe SMTP<input name='smtp_pwd' type='password' placeholder='(inchangé)'></label>"
"<label>Destinataires (séparés par ;) <textarea name='destinataires' rows='3'>"+htmlEscape(cfg_destinataires)+"</textarea></label>"
"<h3>Paramètres accès admin</h3>"
"<label>Admin user<input name='admin_user' type='text' value='"+htmlEscape(admin_user)+"'></label>"
"<label>Admin pwd<input name='admin_pwd' type='password' placeholder='(inchangé)'></label>"
"<div class='row'>"
" <div class='col'><button class='btn' type='submit'>Sauvegarder</button></div>"
" <div class='col'><button class='btn' type='button' onclick=\"window.location.href='/testsend'\">Tester envoi</button></div>"
"</div>"
"</form>"
"<hr><h3>Mise à jour OTA</h3>"
"<form method='POST' action='/update' enctype='multipart/form-data'>"
"<input type='file' name='firmware' accept='.bin' required>"
"<button class='btn' type='submit'>Mettre à jour</button>"
"</form>"
"<script>"
"function toggleGPIO(action){"
" fetch('/gpio?action='+action+'&json=1')"
" .then(response=>response.json())"
" .then(data=>{"
" document.getElementById('gpio_state').innerText=data.state?'ON':'OFF';"
" document.getElementById('gpio_state2').innerText=data.state?'ON':'OFF';"
" })"
" .catch(err=>console.error(err));"
"} "
"setInterval(()=>{"
" fetch('/gpio?json=1')"
" .then(r=>r.json())"
" .then(d=>{"
" document.getElementById('gpio_state').innerText=d.state?'ON':'OFF';"
" document.getElementById('gpio_state2').innerText=d.state?'ON':'OFF';"
" });"
"},1000);"
"</script>"
"</body></html>";
server.send(200,"text/html",page);
}
// --- Save ---
void handleSave() {
if(!checkAuth()) return;
if(server.hasArg("ssid")) cfg_ssid = server.arg("ssid");
if(server.hasArg("password") && server.arg("password") != "") cfg_password = server.arg("password");
if(server.hasArg("gpio_number")) {
cfg_gpio_number = server.arg("gpio_number").toInt();
pinMode(cfg_gpio_number, OUTPUT);
setGPIO(cfg_gpio_number, true, cfg_invert_gpio1);
}
if(server.hasArg("gpio_number2")) {
cfg_gpio_number2 = server.arg("gpio_number2").toInt();
pinMode(cfg_gpio_number2, OUTPUT);
setGPIO(cfg_gpio_number2, true, cfg_invert_gpio2);
}
if(server.hasArg("trigger_type")) cfg_trigger_type = server.arg("trigger_type");
if(server.hasArg("smtp")) cfg_smtp = server.arg("smtp");
if(server.hasArg("smtp_port")) cfg_smtp_port = server.arg("smtp_port").toInt();
if(server.hasArg("email")) cfg_email = server.arg("email");
if(server.hasArg("smtp_pwd") && server.arg("smtp_pwd") != "") cfg_smtp_pwd = server.arg("smtp_pwd");
if(server.hasArg("destinataires")) cfg_destinataires = server.arg("destinataires");
if(server.hasArg("local_ip")) cfg_local_ip = server.arg("local_ip");
if(server.hasArg("gateway")) cfg_gateway = server.arg("gateway");
if(server.hasArg("subnet")) cfg_subnet = server.arg("subnet");
if(server.hasArg("primaryDNS")) cfg_primaryDNS = server.arg("primaryDNS");
if(server.hasArg("secondaryDNS")) cfg_secondaryDNS = server.arg("secondaryDNS");
if(server.hasArg("admin_user")) admin_user = server.arg("admin_user");
if(server.hasArg("admin_pwd") && server.arg("admin_pwd") != "") admin_pwd = server.arg("admin_pwd");
// ⭐ AJOUT — lecture inversion logique
cfg_invert_gpio1 = server.hasArg("invert_gpio1");
cfg_invert_gpio2 = server.hasArg("invert_gpio2");
saveConfig();
server.send(200,"text/html","<html><body><p>Config sauvegardée. Redémarrage...</p></body></html>");
delay(200);
ESP.restart();
}
// --- Test send ---
void handleTestSend() {
if(!checkAuth()) return;
ESP_Mail_Session session;
session.server.host_name = cfg_smtp.c_str();
session.server.port = cfg_smtp_port;
session.login.email = cfg_email.c_str();
session.login.password = cfg_smtp_pwd.c_str();
session.login.user_domain = "";
SMTP_Message message;
message.sender.name = "ECODEVICE";
message.sender.email = cfg_email.c_str();
addAllRecipients(message, cfg_destinataires);
message.subject = "Test d'envoi depuis ESP32 (UI)";
message.text.content = "Ceci est un test envoyé depuis /admin";
smtp.debug(1);
String pageStart = "<html><body>";
String pageEnd = "</body></html>";
if(!smtp.connect(&session)) server.send(200,"text/html",pageStart+"<p style='color:red'>Erreur connexion SMTP</p>"+pageEnd);
else if(!MailClient.sendMail(&smtp,&message)) server.send(200,"text/html",pageStart+"<p style='color:red'>Erreur envoi</p>"+pageEnd);
else server.send(200,"text/html",pageStart+"<p style='color:green'>Email envoyé</p>"+pageEnd);
smtp.closeSession();
}
// --- GPIO route ---
void handleGPIO() {
if(!checkAuth()) return;
if(server.hasArg("action")) {
String action = server.arg("action");
if(action == "on") {
setGPIO(cfg_gpio_number, true, cfg_invert_gpio1);
setGPIO(cfg_gpio_number2, true, cfg_invert_gpio2);
} else if(action == "off") {
setGPIO(cfg_gpio_number, false, cfg_invert_gpio1);
setGPIO(cfg_gpio_number2, false, cfg_invert_gpio2);
}
}
int state = digitalRead(cfg_gpio_number);
if(server.hasArg("json") && server.arg("json")=="1") {
server.send(200,"application/json","{\"state\":"+String(state)+"}");
return;
}
server.send(200,"text/html","<html><body><h3>GPIO "+String(cfg_gpio_number)+" : "+(state?"ON":"OFF")+"</h3>"
"<a href='/gpio?action=on'>Ouvrir</a> | <a href='/gpio?action=off'>Fermer</a></body></html>");
}
// --- OTA upload handler ---
void handleFirmwareUpload() {
if (!checkAuth()) return;
HTTPUpload& upload = server.upload();
if (upload.status == UPLOAD_FILE_START) {
Serial.setDebugOutput(true);
Serial.printf("Mise à jour OTA commencée : %s\n", upload.filename.c_str());
if (!Update.begin(UPDATE_SIZE_UNKNOWN)) Update.printError(Serial);
} else if (upload.status == UPLOAD_FILE_WRITE) {
if (Update.write(upload.buf, upload.currentSize) != upload.currentSize) Update.printError(Serial);
} else if (upload.status == UPLOAD_FILE_END) {
if (Update.end(true)) Serial.printf("Mise à jour OTA terminée : %u octets\n", upload.totalSize);
else Update.printError(Serial);
Serial.setDebugOutput(false);
} else if (upload.status == UPLOAD_FILE_ABORTED) {
Update.end();
Serial.println("Mise à jour OTA annulée.");
}
}
void handleUpdatePage() {
if (!checkAuth()) return;
if (Update.hasError()) {
server.send(200, "text/html",
"<html><body><p style='color:red'>Échec de la mise à jour</p></body></html>");
} else {
server.send(200, "text/html",
"<html><body><p style='color:green'>Mise à jour réussie, redémarrage...</p></body></html>");
delay(500);
ESP.restart();
}
}
// --- AP fallback ---
void startAPMode() {
Serial.println("Demarrage AP...");
WiFi.disconnect(true);
delay(200);
WiFi.mode(WIFI_AP_STA);
delay(50);
IPAddress apIP(192,168,4,1);
IPAddress apGateway = apIP;
IPAddress apMask(255,255,255,0);
if(!WiFi.softAPConfig(apIP, apGateway, apMask)) Serial.println("softAPConfig FAILED");
else Serial.println("softAPConfig OK");
delay(50);
if(strlen(AP_PASSWORD) == 0) WiFi.softAP(AP_SSID);
else WiFi.softAP(AP_SSID, AP_PASSWORD);
delay(300);
Serial.print("AP SSID: "); Serial.println(AP_SSID);
Serial.print("AP IP (softAPIP): "); Serial.println(WiFi.softAPIP());
Serial.print("Num stations connected: "); Serial.println(WiFi.softAPgetStationNum());
}
// --- WiFi connect ---
bool attemptWiFiConnect() {
Serial.printf("Connexion a %s\n", cfg_ssid.c_str());
WiFi.mode(WIFI_STA);
WiFi.begin(cfg_ssid.c_str(), cfg_password.c_str());
unsigned long start = millis();
while(WiFi.status() != WL_CONNECTED && (millis()-start) < WIFI_CONNECT_TIMEOUT_MS) {
Serial.print(".");
delay(200);
}
Serial.println();
if(WiFi.status() == WL_CONNECTED) {
Serial.println("WiFi connecté");
Serial.print("IP: "); Serial.println(WiFi.localIP());
return true;
}
Serial.println("WiFi non connecté -> fallback AP");
return false;
}
// ⭐ AJOUT — Reconnexion automatique WiFi
void checkWiFiReconnect() {
if (WiFi.status() != WL_CONNECTED) {
static unsigned long lastAttempt = 0;
if (millis() - lastAttempt > 10000) { // tentative toutes les 10 s
lastAttempt = millis();
Serial.println("WiFi perdu -> tentative de reconnexion...");
attemptWiFiConnect();
}
}
}
// --- Routes ---
void setupRoutes() {
server.on("/", handleAdmin); // <-- admin devient la page racine
server.on("/actions", handleRoot); // <-- ancien root va dans /actions
server.on("/save", HTTP_POST, handleSave);
server.on("/testsend", HTTP_GET, handleTestSend);
server.on("/gpio", handleGPIO);
server.on("/update", HTTP_POST, handleUpdatePage, handleFirmwareUpload);
server.begin();
Serial.println("Serveur web démarré");
}
// --- Setup ---
void setup() {
Serial.begin(115200);
delay(1000);
if(!LittleFS.begin(true)) Serial.println("LittleFS mount failed!");
else Serial.println("LittleFS OK");
loadConfig();
// Configuration des deux GPIO (GPIO2 est un clone)
pinMode(cfg_gpio_number, OUTPUT);
setGPIO(cfg_gpio_number, true, cfg_invert_gpio1);
pinMode(cfg_gpio_number2, OUTPUT);
setGPIO(cfg_gpio_number2, true, cfg_invert_gpio2);
if(cfg_local_ip.length() > 0){
IPAddress local = ipFromString(cfg_local_ip);
IPAddress gw = ipFromString(cfg_gateway);
IPAddress mask = ipFromString(cfg_subnet);
IPAddress pdns = ipFromString(cfg_primaryDNS);
IPAddress sdns = ipFromString(cfg_secondaryDNS);
if(!WiFi.config(local, gw, mask, pdns, sdns)) Serial.println("Echec config IP statique");
else Serial.println("Config IP statique appliquée");
}
bool wifiOk = attemptWiFiConnect();
if(!wifiOk) {
startAPMode();
setupRoutes();
} else {
setupRoutes();
configTzTime("CET-1CEST-2,M3.5.0/02:00:00,M10.5.0/03:00:00","fr.pool.ntp.org","time.nist.gov");
unsigned long t0 = millis();
const unsigned long NTPTIMEOUT = 5000;
while (millis() - t0 < NTPTIMEOUT && time(nullptr) < ESP_MAIL_CLIENT_VALID_TS) { delay(100);}
if (time(nullptr) >= ESP_MAIL_CLIENT_VALID_TS) Serial.println("NTP ok");
else Serial.println("NTP non synchro (timeout)");
}
}
// --- Loop ---
void loop() {
server.handleClient();
checkWiFiReconnect(); // ⭐ Ajout reconnection automatique
}
donc pour acceder à la page d'admin , directement en http://192.168.1.xx
pour les pushs ecodevice dans URL à la place de /?type=motmagique tu mettras /actions?type=motmagique ou autre mot en fonction de ton declencheur Vraiment merci de ta réponse rapide, j'essaie dans la fin d'après-midi, la version 2,04 et je vais essayer de l'envoyer par OTA, merci, je te tiens informé,
?
Messages : 171
Sujets : 13
Inscription : Jun 2024
28-11-2025, 09:51 PM
(Modification du message : 28-11-2025, 10:06 PM par tupolev89.)
bonsoir lolo , version 2.04 installée depuis ota tout fonctionne bien parfait, juste eu a modifier les pushs comme dans le mode d'emploi (/actions) à rajouter.
parfait. encore merci de ton aide pour finaliser ce projet.
Bien cordialement
Tupolev
|