#include "web_server.h" #include "config.h" #include #include #include static AsyncWebServer _server(80); static AsyncWebSocket _ws("/ws"); // ── Construction JSON temps réel ───────────────────────────────────── static String _buildJsonSondes() { JsonDocument doc; JsonArray arr = doc["sondes"].to(); if (xSemaphoreTake(xSondesMutex, pdMS_TO_TICKS(20)) == pdTRUE) { for (uint8_t i = 0; i < NB_SONDES; i++) { JsonObject s = arr.add(); s["nom"] = sondesConfig[i].nom; s["erreur"] = sondesEtat[i].erreur; if (!sondesEtat[i].erreur) { s["temp"] = String(sondesEtat[i].tempActuelle, 1); } else { s["temp"] = nullptr; } } xSemaphoreGive(xSondesMutex); } doc["uptime"] = (millis() - netStatus.uptimeDemarrage) / 1000; doc["rssi"] = netStatus.rssi; String out; serializeJson(doc, out); return out; } // ── Construction JSON historique ───────────────────────────────────── static String _buildJsonHistory() { JsonDocument doc; JsonArray arr = doc.to(); if (xSemaphoreTake(xHistMutex, pdMS_TO_TICKS(50)) == pdTRUE) { for (uint16_t i = 0; i < HIST_TAILLE; i++) { uint16_t idx = (histIdx + i) % HIST_TAILLE; if (historique[idx].timestamp == 0) continue; JsonObject pt = arr.add(); pt["ts"] = historique[idx].timestamp; JsonArray t = pt["t"].to(); for (uint8_t j = 0; j < NB_SONDES; j++) { if (isnan(historique[idx].temps[j])) { t.add(nullptr); } else { t.add(String(historique[idx].temps[j], 1)); } } } xSemaphoreGive(xHistMutex); } String out; serializeJson(doc, out); return out; } static void _onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client, AwsEventType type, void* arg, uint8_t* data, size_t len) { if (type == WS_EVT_CONNECT) { Serial.printf("[WS] Client #%u connecté\n", client->id()); } else if (type == WS_EVT_DISCONNECT) { Serial.printf("[WS] Client #%u déconnecté\n", client->id()); } } void web_server_init() { bool fsOk = LittleFS.begin(); if (!fsOk) { Serial.println("[FS] Erreur montage LittleFS — interface web indisponible, API REST active"); } else { Serial.println("[FS] LittleFS monté"); } _ws.onEvent(_onWsEvent); _server.addHandler(&_ws); // GET /api/status _server.on("/api/status", HTTP_GET, [](AsyncWebServerRequest* req) { JsonDocument doc; doc["rssi"] = netStatus.rssi; doc["uptime"] = (millis() - netStatus.uptimeDemarrage) / 1000; doc["ramLibre"] = ESP.getFreeHeap(); doc["modeAP"] = netStatus.modeAP; doc["mqttConnecte"] = netStatus.mqttConnecte; String out; serializeJson(doc, out); req->send(200, "application/json", out); }); // GET /api/temperatures _server.on("/api/temperatures", HTTP_GET, [](AsyncWebServerRequest* req) { JsonDocument doc; if (xSemaphoreTake(xSondesMutex, pdMS_TO_TICKS(20)) == pdTRUE) { for (uint8_t i = 0; i < NB_SONDES; i++) { String key = "sonde_" + String(i + 1); if (!sondesEtat[i].erreur) { doc[key] = String(sondesEtat[i].tempActuelle, 1); } else { doc[key] = nullptr; } } xSemaphoreGive(xSondesMutex); } doc["unit"] = "C"; String out; serializeJson(doc, out); req->send(200, "application/json", out); }); // GET /api/history _server.on("/api/history", HTTP_GET, [](AsyncWebServerRequest* req) { req->send(200, "application/json", _buildJsonHistory()); }); // POST /api/config — body: {"intervalleMs":10000,"mqttBroker":"10.0.0.3","mqttPort":1883} _server.on("/api/config", HTTP_POST, [](AsyncWebServerRequest* req) {}, nullptr, [](AsyncWebServerRequest* req, uint8_t* data, size_t len, size_t, size_t) { JsonDocument doc; DeserializationError err = deserializeJson(doc, data, len); if (err) { req->send(400, "application/json", "{\"erreur\":\"JSON invalide\"}"); return; } if (doc["intervalleMs"].is()) { Serial.printf("[CONFIG] intervalleMs: %u\n", (uint32_t)doc["intervalleMs"]); } req->send(200, "application/json", "{\"ok\":true}"); } ); if (fsOk) { _server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); } _server.begin(); Serial.println("[HTTP] Serveur web démarré sur port 80"); } void web_server_notify_clients() { if (_ws.count() == 0) return; _ws.cleanupClients(); _ws.textAll(_buildJsonSondes()); }