Browse Source

Ajout gestion reconnexion automatique

Support-PEAP
scayac 4 weeks ago
parent
commit
83b8f43b9e
  1. 12
      README.md
  2. BIN
      data/css/styles.css.gz
  3. BIN
      data/index.html.gz
  4. BIN
      data/js/api.js.gz
  5. BIN
      data/js/app.js.gz
  6. BIN
      data/js/crypto-js.min.js.gz
  7. BIN
      data/login.html.gz
  8. BIN
      data/settings.html.gz
  9. BIN
      data/update.html.gz
  10. 24
      platformio.ini
  11. 80
      src/main.cpp
  12. 46
      src/server.cpp
  13. 2
      src/server.h

12
README.md

@ -258,6 +258,18 @@ Procédure recommandée (sans secret en clair dans les fichiers):
2. En mode AP, l'adresse est toujours `192.168.4.1` 2. En mode AP, l'adresse est toujours `192.168.4.1`
3. En mode Station, trouvez l'IP dans les logs du moniteur série 3. En mode Station, trouvez l'IP dans les logs du moniteur série
4. Désactivez le cache du navigateur (Ctrl+F5) 4. Désactivez le cache du navigateur (Ctrl+F5)
5. En réseau entreprise (PEAP), un filtrage « client isolation » peut bloquer ping/HTTP entre clients WiFi
6. Utilisez l'AP de secours `ESP32-Webapp-setup` (ou `<hostname>-setup`) avec l'IP `192.168.4.1`
### Diagnostic rapide réseau (2 minutes)
1. Ouvrez le moniteur série et vérifiez `Serveur web démarré sur le port 80`
2. Depuis votre PC, testez l'endpoint de santé:
- en STA: `curl -v http://<ip_sta>/api/health`
- en AP de secours: `curl -v http://192.168.4.1/api/health`
3. Vérifiez les logs série:
- si vous voyez `HTTP GET /api/health depuis ...`, le trafic arrive bien
- si rien n'apparaît, le réseau filtre la communication vers l'ESP32
### Erreur lors de la mise à jour OTA ### Erreur lors de la mise à jour OTA

BIN
data/css/styles.css.gz

Binary file not shown.

BIN
data/index.html.gz

Binary file not shown.

BIN
data/js/api.js.gz

Binary file not shown.

BIN
data/js/app.js.gz

Binary file not shown.

BIN
data/js/crypto-js.min.js.gz

Binary file not shown.

BIN
data/login.html.gz

Binary file not shown.

BIN
data/settings.html.gz

Binary file not shown.

BIN
data/update.html.gz

Binary file not shown.

24
platformio.ini

@ -12,7 +12,27 @@ lib_deps =
ESP32Async/AsyncTCP ESP32Async/AsyncTCP
bblanchon/ArduinoJson@^7.0.0 bblanchon/ArduinoJson@^7.0.0
#build_flags =
# -DBOARD_HAS_PSRAM
upload_speed = 921600
[env:esp32-s3-supermini]
platform = espressif32
board = esp32-s3-devkitc-1
framework = arduino
board_upload.flash_size = 4MB
board_build.filesystem = littlefs
board_build.partitions = default.csv
monitor_speed = 115200
lib_deps =
ESP32Async/ESPAsyncWebServer
ESP32Async/AsyncTCP
bblanchon/ArduinoJson@^7.0.0
build_flags = build_flags =
-DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM -DBOARD_HAS_PSRAM
; Configuration ArduinoOTA pour firmware et filesystem
upload_speed = 921600 ;upload_protocol = espota
;upload_port = 192.168.0.xx
;upload_flags =
; --auth=password

80
src/main.cpp

@ -23,6 +23,26 @@ String wifiEapUsername = "";
String wifiEapPassword = ""; String wifiEapPassword = "";
String apPassword = "webapp123"; String apPassword = "webapp123";
void logNetworkInfo() {
logMessage(LOG_INFO, "Adresse IP: " + WiFi.localIP().toString());
logMessage(LOG_INFO, "Passerelle: " + WiFi.gatewayIP().toString());
logMessage(LOG_INFO, "Masque: " + WiFi.subnetMask().toString());
logMessage(LOG_INFO, "DNS 1: " + WiFi.dnsIP(0).toString());
logMessage(LOG_INFO, "DNS 2: " + WiFi.dnsIP(1).toString());
logMessage(LOG_INFO, "MAC STA: " + WiFi.macAddress());
}
void startFallbackAP() {
String fallbackSsid = hostname + "-setup";
WiFi.mode(WIFI_AP_STA);
if (WiFi.softAP(fallbackSsid.c_str(), apPassword.c_str())) {
logMessage(LOG_INFO, "AP de secours actif: " + fallbackSsid);
logMessage(LOG_INFO, "IP AP de secours: " + WiFi.softAPIP().toString());
} else {
logMessage(LOG_WARN, "Échec du démarrage de l'AP de secours");
}
}
void createDefaultConfig() { void createDefaultConfig() {
logMessage(LOG_INFO, "Création de la configuration par défaut"); logMessage(LOG_INFO, "Création de la configuration par défaut");
@ -132,7 +152,7 @@ bool waitForWiFiConnection() {
if (WiFi.status() == WL_CONNECTED) { if (WiFi.status() == WL_CONNECTED) {
logMessage(LOG_INFO, "WiFi connecté"); logMessage(LOG_INFO, "WiFi connecté");
logMessage(LOG_INFO, "Adresse IP: " + WiFi.localIP().toString()); logNetworkInfo();
return true; return true;
} }
@ -146,19 +166,11 @@ bool connectWPA2PEAP() {
} }
logMessage(LOG_INFO, "Connexion WPA2-PEAP au WiFi: " + wifiSSID); logMessage(LOG_INFO, "Connexion WPA2-PEAP au WiFi: " + wifiSSID);
logMessage(LOG_INFO, "Profil PEAP: sans certificat, domaine vide, MSCHAPv2 interne");
#if defined(WPA2_AUTH_PEAP) #if HAS_LEGACY_WPA2_ENTERPRISE
WiFi.begin( WiFi.disconnect(true, true);
wifiSSID.c_str(), delay(100);
WPA2_AUTH_PEAP,
wifiEapUsername.c_str(),
wifiEapUsername.c_str(),
wifiEapPassword.c_str(),
nullptr
);
return waitForWiFiConnection();
#elif HAS_LEGACY_WPA2_ENTERPRISE
WiFi.begin(wifiSSID.c_str());
esp_wifi_sta_wpa2_ent_set_identity((uint8_t*)wifiEapUsername.c_str(), wifiEapUsername.length()); esp_wifi_sta_wpa2_ent_set_identity((uint8_t*)wifiEapUsername.c_str(), wifiEapUsername.length());
esp_wifi_sta_wpa2_ent_set_username((uint8_t*)wifiEapUsername.c_str(), wifiEapUsername.length()); esp_wifi_sta_wpa2_ent_set_username((uint8_t*)wifiEapUsername.c_str(), wifiEapUsername.length());
@ -169,6 +181,19 @@ bool connectWPA2PEAP() {
#endif #endif
esp_wifi_sta_wpa2_ent_enable(); esp_wifi_sta_wpa2_ent_enable();
WiFi.begin(wifiSSID.c_str());
return waitForWiFiConnection();
#elif defined(WPA2_AUTH_PEAP)
WiFi.disconnect(true, true);
delay(100);
WiFi.begin(
wifiSSID.c_str(),
WPA2_AUTH_PEAP,
wifiEapUsername.c_str(),
wifiEapUsername.c_str(),
wifiEapPassword.c_str(),
nullptr
);
return waitForWiFiConnection(); return waitForWiFiConnection();
#else #else
logMessage(LOG_ERROR, "WPA2-PEAP non supporté par cette version du framework"); logMessage(LOG_ERROR, "WPA2-PEAP non supporté par cette version du framework");
@ -185,6 +210,9 @@ bool connectWPA2PSK() {
void setupWiFi() { void setupWiFi() {
if (wifiSSID.length() > 0) { if (wifiSSID.length() > 0) {
WiFi.mode(WIFI_STA); WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.setAutoReconnect(true);
WiFi.persistent(false);
WiFi.setHostname(hostname.c_str()); WiFi.setHostname(hostname.c_str());
bool connected = false; bool connected = false;
@ -195,6 +223,7 @@ void setupWiFi() {
} }
if (connected) { if (connected) {
startFallbackAP();
return; return;
} }
@ -271,10 +300,35 @@ void setup() {
} }
unsigned long lastCleanup = 0; unsigned long lastCleanup = 0;
static unsigned long lastReconnectAttempt = 0;
static const unsigned long RECONNECT_INTERVAL = 30000;
void checkWiFiConnection() {
if (wifiSSID.isEmpty()) return;
if (WiFi.status() == WL_CONNECTED) return;
unsigned long now = millis();
if (now - lastReconnectAttempt < RECONNECT_INTERVAL) return;
lastReconnectAttempt = now;
logMessage(LOG_WARN, "WiFi déconnecté, tentative de reconnexion...");
WiFi.mode(WIFI_STA);
bool connected = (wifiSecurity == "wpa2_peap") ? connectWPA2PEAP() : connectWPA2PSK();
if (connected) {
logMessage(LOG_INFO, "WiFi reconnecté");
startFallbackAP();
} else {
logMessage(LOG_WARN, "Échec de reconnexion WiFi");
}
}
void loop() { void loop() {
ArduinoOTA.handle(); ArduinoOTA.handle();
checkWiFiConnection();
// Nettoyer les sessions expirées toutes les minutes // Nettoyer les sessions expirées toutes les minutes
unsigned long now = millis(); unsigned long now = millis();
if (now - lastCleanup > 60000) { if (now - lastCleanup > 60000) {

46
src/server.cpp

@ -8,6 +8,23 @@ WebServerManager::WebServerManager() {
server = new AsyncWebServer(80); server = new AsyncWebServer(80);
} }
void WebServerManager::logRequest(AsyncWebServerRequest* request, const String& routeTag) {
String method = "OTHER";
if (request->method() == HTTP_GET) method = "GET";
else if (request->method() == HTTP_POST) method = "POST";
else if (request->method() == HTTP_PUT) method = "PUT";
else if (request->method() == HTTP_DELETE) method = "DELETE";
else if (request->method() == HTTP_OPTIONS) method = "OPTIONS";
else if (request->method() == HTTP_PATCH) method = "PATCH";
String remoteIp = "unknown";
if (request->client()) {
remoteIp = request->client()->remoteIP().toString();
}
logMessage(LOG_INFO, "HTTP " + method + " " + request->url() + " depuis " + remoteIp + " (" + routeTag + ")");
}
String WebServerManager::getSessionIdFromRequest(AsyncWebServerRequest* request) { String WebServerManager::getSessionIdFromRequest(AsyncWebServerRequest* request) {
if (request->hasHeader("Cookie")) { if (request->hasHeader("Cookie")) {
String cookie = request->header("Cookie"); String cookie = request->header("Cookie");
@ -54,16 +71,25 @@ void WebServerManager::sendJsonSuccess(AsyncWebServerRequest* request, const Str
void WebServerManager::setupRoutes() { void WebServerManager::setupRoutes() {
// Route de login // Route de login
server->on("/login", HTTP_POST, [this](AsyncWebServerRequest* request) { server->on("/login", HTTP_POST, [this](AsyncWebServerRequest* request) {
logRequest(request, "login");
this->handleLogin(request); this->handleLogin(request);
}); });
// Endpoint de diagnostic réseau (sans authentification)
server->on("/api/health", HTTP_GET, [this](AsyncWebServerRequest* request) {
logRequest(request, "health");
this->handleHealth(request);
});
// Route de logout // Route de logout
server->on("/logout", HTTP_GET, [this](AsyncWebServerRequest* request) { server->on("/logout", HTTP_GET, [this](AsyncWebServerRequest* request) {
logRequest(request, "logout");
this->handleLogout(request); this->handleLogout(request);
}); });
// API - Configuration // API - Configuration
server->on("/api/config", HTTP_GET, [this](AsyncWebServerRequest* request) { server->on("/api/config", HTTP_GET, [this](AsyncWebServerRequest* request) {
logRequest(request, "get_config");
if (!isAuthenticated(request)) { if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié"); sendJsonError(request, 401, "Non authentifié");
return; return;
@ -72,6 +98,7 @@ void WebServerManager::setupRoutes() {
}); });
server->on("/api/config", HTTP_POST, [this](AsyncWebServerRequest* request) { server->on("/api/config", HTTP_POST, [this](AsyncWebServerRequest* request) {
logRequest(request, "set_config");
if (!isAuthenticated(request)) { if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié"); sendJsonError(request, 401, "Non authentifié");
return; return;
@ -81,6 +108,7 @@ void WebServerManager::setupRoutes() {
// API - Changement de mot de passe // API - Changement de mot de passe
server->on("/api/password", HTTP_POST, [this](AsyncWebServerRequest* request) { server->on("/api/password", HTTP_POST, [this](AsyncWebServerRequest* request) {
logRequest(request, "change_password");
if (!isAuthenticated(request)) { if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié"); sendJsonError(request, 401, "Non authentifié");
return; return;
@ -90,6 +118,7 @@ void WebServerManager::setupRoutes() {
// API - Statistiques // API - Statistiques
server->on("/api/stats", HTTP_GET, [this](AsyncWebServerRequest* request) { server->on("/api/stats", HTTP_GET, [this](AsyncWebServerRequest* request) {
logRequest(request, "get_stats");
if (!isAuthenticated(request)) { if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié"); sendJsonError(request, 401, "Non authentifié");
return; return;
@ -100,6 +129,7 @@ void WebServerManager::setupRoutes() {
// OTA Update // OTA Update
server->on("/api/update", HTTP_POST, server->on("/api/update", HTTP_POST,
[this](AsyncWebServerRequest* request) { [this](AsyncWebServerRequest* request) {
logRequest(request, "ota_update");
if (!isAuthenticated(request)) { if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié"); sendJsonError(request, 401, "Non authentifié");
return; return;
@ -120,7 +150,8 @@ void WebServerManager::setupRoutes() {
); );
// 404 // 404
server->onNotFound([](AsyncWebServerRequest* request) { server->onNotFound([this](AsyncWebServerRequest* request) {
logRequest(request, "not_found");
request->send(404, "text/plain", "Page non trouvée"); request->send(404, "text/plain", "Page non trouvée");
}); });
@ -128,6 +159,19 @@ void WebServerManager::setupRoutes() {
server->serveStatic("/", LittleFS, "/").setDefaultFile("login.html"); server->serveStatic("/", LittleFS, "/").setDefaultFile("login.html");
} }
void WebServerManager::handleHealth(AsyncWebServerRequest* request) {
JsonDocument doc;
doc["ok"] = true;
doc["ip_sta"] = WiFi.localIP().toString();
doc["ip_ap"] = WiFi.softAPIP().toString();
doc["wifi_status"] = WiFi.status();
doc["ssid"] = WiFi.SSID();
String response;
serializeJson(doc, response);
request->send(200, "application/json", response);
}
void WebServerManager::handleLogin(AsyncWebServerRequest* request) { void WebServerManager::handleLogin(AsyncWebServerRequest* request) {
if (!request->hasParam("username", true) || !request->hasParam("password", true)) { if (!request->hasParam("username", true) || !request->hasParam("password", true)) {
sendJsonError(request, 400, "Paramètres manquants"); sendJsonError(request, 400, "Paramètres manquants");

2
src/server.h

@ -13,6 +13,7 @@ private:
AsyncWebServer* server; AsyncWebServer* server;
String getSessionIdFromRequest(AsyncWebServerRequest* request); String getSessionIdFromRequest(AsyncWebServerRequest* request);
void logRequest(AsyncWebServerRequest* request, const String& routeTag);
bool isAuthenticated(AsyncWebServerRequest* request); bool isAuthenticated(AsyncWebServerRequest* request);
void sendJsonError(AsyncWebServerRequest* request, int code, const String& message); void sendJsonError(AsyncWebServerRequest* request, int code, const String& message);
void sendJsonSuccess(AsyncWebServerRequest* request, const String& message); void sendJsonSuccess(AsyncWebServerRequest* request, const String& message);
@ -28,6 +29,7 @@ public:
void handleGetConfig(AsyncWebServerRequest* request); void handleGetConfig(AsyncWebServerRequest* request);
void handleSetConfig(AsyncWebServerRequest* request); void handleSetConfig(AsyncWebServerRequest* request);
void handleChangePassword(AsyncWebServerRequest* request); void handleChangePassword(AsyncWebServerRequest* request);
void handleHealth(AsyncWebServerRequest* request);
void handleGetStats(AsyncWebServerRequest* request); void handleGetStats(AsyncWebServerRequest* request);
void handleOTAUpdate(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final); void handleOTAUpdate(AsyncWebServerRequest* request, String filename, size_t index, uint8_t* data, size_t len, bool final);
}; };

Loading…
Cancel
Save