feat: serveur web HTTP + WebSocket, API REST complète (status/temperatures/history/config)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,7 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Initialise LittleFS, configure les routes REST et le WebSocket
|
||||||
|
void web_server_init();
|
||||||
|
|
||||||
|
// Pousse les données temps réel à tous les clients WebSocket connectés
|
||||||
|
void web_server_notify_clients();
|
||||||
+6
-1
@@ -2,6 +2,7 @@
|
|||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "network.h"
|
#include "network.h"
|
||||||
#include "sensors.h"
|
#include "sensors.h"
|
||||||
|
#include "web_server.h"
|
||||||
|
|
||||||
SondeConfig sondesConfig[NB_SONDES] = {
|
SondeConfig sondesConfig[NB_SONDES] = {
|
||||||
{ "T°C Ext", "maison/jardin/ext/temperature", 60000, 0.2f },
|
{ "T°C Ext", "maison/jardin/ext/temperature", 60000, 0.2f },
|
||||||
@@ -18,9 +19,13 @@ void setup() {
|
|||||||
Serial.println("[BOOT] esp_jardin démarrage...");
|
Serial.println("[BOOT] esp_jardin démarrage...");
|
||||||
network_init();
|
network_init();
|
||||||
sensors_init();
|
sensors_init();
|
||||||
|
web_server_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
void loop() {
|
void loop() {
|
||||||
network_update();
|
network_update();
|
||||||
sensors_update();
|
bool nouvelleMesure = sensors_update();
|
||||||
|
if (nouvelleMesure) {
|
||||||
|
web_server_notify_clients();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,137 @@
|
|||||||
|
#include "web_server.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include <LittleFS.h>
|
||||||
|
#include <ESPAsyncWebServer.h>
|
||||||
|
#include <ArduinoJson.h>
|
||||||
|
|
||||||
|
static AsyncWebServer _server(80);
|
||||||
|
static AsyncWebSocket _ws("/ws");
|
||||||
|
|
||||||
|
// ── Construction JSON temps réel ─────────────────────────────────────
|
||||||
|
static String _buildJsonSondes() {
|
||||||
|
JsonDocument doc;
|
||||||
|
JsonArray arr = doc["sondes"].to<JsonArray>();
|
||||||
|
for (uint8_t i = 0; i < NB_SONDES; i++) {
|
||||||
|
JsonObject s = arr.add<JsonObject>();
|
||||||
|
s["nom"] = sondesConfig[i].nom;
|
||||||
|
s["erreur"] = sondesEtat[i].erreur;
|
||||||
|
if (!sondesEtat[i].erreur) {
|
||||||
|
s["temp"] = serialized(String(sondesEtat[i].tempActuelle, 1));
|
||||||
|
} else {
|
||||||
|
s["temp"] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<JsonArray>();
|
||||||
|
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<JsonObject>();
|
||||||
|
pt["ts"] = historique[idx].timestamp;
|
||||||
|
JsonArray t = pt["t"].to<JsonArray>();
|
||||||
|
for (uint8_t j = 0; j < NB_SONDES; j++) {
|
||||||
|
if (isnan(historique[idx].temps[j])) {
|
||||||
|
t.add(nullptr);
|
||||||
|
} else {
|
||||||
|
t.add(serialized(String(historique[idx].temps[j], 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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() {
|
||||||
|
if (!LittleFS.begin()) {
|
||||||
|
Serial.println("[FS] Erreur montage LittleFS !");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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;
|
||||||
|
for (uint8_t i = 0; i < NB_SONDES; i++) {
|
||||||
|
String key = "sonde_" + String(i + 1);
|
||||||
|
if (!sondesEtat[i].erreur) {
|
||||||
|
doc[key] = serialized(String(sondesEtat[i].tempActuelle, 1));
|
||||||
|
} else {
|
||||||
|
doc[key] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<uint32_t>()) {
|
||||||
|
Serial.printf("[CONFIG] intervalleMs: %u\n", (uint32_t)doc["intervalleMs"]);
|
||||||
|
}
|
||||||
|
req->send(200, "application/json", "{\"ok\":true}");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Servir index.html depuis LittleFS
|
||||||
|
_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());
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user