Browse Source

Ajout gestion reconnexion automatique

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

2
.gitignore vendored

@ -23,4 +23,4 @@ Thumbs.db @@ -23,4 +23,4 @@ Thumbs.db
# Build artifacts
*.bin
*.elf
*.map
*.map

12
README.md

@ -258,6 +258,18 @@ Procédure recommandée (sans secret en clair dans les fichiers): @@ -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`
3. En mode Station, trouvez l'IP dans les logs du moniteur série
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

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 = @@ -12,7 +12,27 @@ lib_deps =
ESP32Async/AsyncTCP
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 =
-DARDUINO_USB_CDC_ON_BOOT=1
-DBOARD_HAS_PSRAM
upload_speed = 921600
; Configuration ArduinoOTA pour firmware et filesystem
;upload_protocol = espota
;upload_port = 192.168.0.xx
;upload_flags =
; --auth=password

80
src/main.cpp

@ -23,6 +23,26 @@ String wifiEapUsername = ""; @@ -23,6 +23,26 @@ String wifiEapUsername = "";
String wifiEapPassword = "";
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() {
logMessage(LOG_INFO, "Création de la configuration par défaut");
@ -132,7 +152,7 @@ bool waitForWiFiConnection() { @@ -132,7 +152,7 @@ bool waitForWiFiConnection() {
if (WiFi.status() == WL_CONNECTED) {
logMessage(LOG_INFO, "WiFi connecté");
logMessage(LOG_INFO, "Adresse IP: " + WiFi.localIP().toString());
logNetworkInfo();
return true;
}
@ -146,19 +166,11 @@ bool connectWPA2PEAP() { @@ -146,19 +166,11 @@ bool connectWPA2PEAP() {
}
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)
WiFi.begin(
wifiSSID.c_str(),
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());
#if HAS_LEGACY_WPA2_ENTERPRISE
WiFi.disconnect(true, true);
delay(100);
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());
@ -169,6 +181,19 @@ bool connectWPA2PEAP() { @@ -169,6 +181,19 @@ bool connectWPA2PEAP() {
#endif
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();
#else
logMessage(LOG_ERROR, "WPA2-PEAP non supporté par cette version du framework");
@ -185,6 +210,9 @@ bool connectWPA2PSK() { @@ -185,6 +210,9 @@ bool connectWPA2PSK() {
void setupWiFi() {
if (wifiSSID.length() > 0) {
WiFi.mode(WIFI_STA);
WiFi.setSleep(false);
WiFi.setAutoReconnect(true);
WiFi.persistent(false);
WiFi.setHostname(hostname.c_str());
bool connected = false;
@ -195,6 +223,7 @@ void setupWiFi() { @@ -195,6 +223,7 @@ void setupWiFi() {
}
if (connected) {
startFallbackAP();
return;
}
@ -271,9 +300,34 @@ void setup() { @@ -271,9 +300,34 @@ void setup() {
}
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() {
ArduinoOTA.handle();
checkWiFiConnection();
// Nettoyer les sessions expirées toutes les minutes
unsigned long now = millis();

46
src/server.cpp

@ -8,6 +8,23 @@ WebServerManager::WebServerManager() { @@ -8,6 +8,23 @@ WebServerManager::WebServerManager() {
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) {
if (request->hasHeader("Cookie")) {
String cookie = request->header("Cookie");
@ -54,16 +71,25 @@ void WebServerManager::sendJsonSuccess(AsyncWebServerRequest* request, const Str @@ -54,16 +71,25 @@ void WebServerManager::sendJsonSuccess(AsyncWebServerRequest* request, const Str
void WebServerManager::setupRoutes() {
// Route de login
server->on("/login", HTTP_POST, [this](AsyncWebServerRequest* request) {
logRequest(request, "login");
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
server->on("/logout", HTTP_GET, [this](AsyncWebServerRequest* request) {
logRequest(request, "logout");
this->handleLogout(request);
});
// API - Configuration
server->on("/api/config", HTTP_GET, [this](AsyncWebServerRequest* request) {
logRequest(request, "get_config");
if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié");
return;
@ -72,6 +98,7 @@ void WebServerManager::setupRoutes() { @@ -72,6 +98,7 @@ void WebServerManager::setupRoutes() {
});
server->on("/api/config", HTTP_POST, [this](AsyncWebServerRequest* request) {
logRequest(request, "set_config");
if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié");
return;
@ -81,6 +108,7 @@ void WebServerManager::setupRoutes() { @@ -81,6 +108,7 @@ void WebServerManager::setupRoutes() {
// API - Changement de mot de passe
server->on("/api/password", HTTP_POST, [this](AsyncWebServerRequest* request) {
logRequest(request, "change_password");
if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié");
return;
@ -90,6 +118,7 @@ void WebServerManager::setupRoutes() { @@ -90,6 +118,7 @@ void WebServerManager::setupRoutes() {
// API - Statistiques
server->on("/api/stats", HTTP_GET, [this](AsyncWebServerRequest* request) {
logRequest(request, "get_stats");
if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié");
return;
@ -100,6 +129,7 @@ void WebServerManager::setupRoutes() { @@ -100,6 +129,7 @@ void WebServerManager::setupRoutes() {
// OTA Update
server->on("/api/update", HTTP_POST,
[this](AsyncWebServerRequest* request) {
logRequest(request, "ota_update");
if (!isAuthenticated(request)) {
sendJsonError(request, 401, "Non authentifié");
return;
@ -120,7 +150,8 @@ void WebServerManager::setupRoutes() { @@ -120,7 +150,8 @@ void WebServerManager::setupRoutes() {
);
// 404
server->onNotFound([](AsyncWebServerRequest* request) {
server->onNotFound([this](AsyncWebServerRequest* request) {
logRequest(request, "not_found");
request->send(404, "text/plain", "Page non trouvée");
});
@ -128,6 +159,19 @@ void WebServerManager::setupRoutes() { @@ -128,6 +159,19 @@ void WebServerManager::setupRoutes() {
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) {
if (!request->hasParam("username", true) || !request->hasParam("password", true)) {
sendJsonError(request, 400, "Paramètres manquants");

2
src/server.h

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

Loading…
Cancel
Save