fix: mutex FreeRTOS buffer historique, découplage LittleFS/REST

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 16:43:05 +02:00
co-authored by Claude Sonnet 4.6
parent 29f26c1db4
commit 49c54f4e48
4 changed files with 49 additions and 33 deletions
+10 -5
View File
@@ -1,5 +1,7 @@
#pragma once #pragma once
#include <Arduino.h> #include <Arduino.h>
#include <freertos/FreeRTOS.h>
#include <freertos/semphr.h>
// ── Constantes matérielles ────────────────────────────────────────── // ── Constantes matérielles ──────────────────────────────────────────
#define ONE_WIRE_BUS 4 // GPIO 4 — bus OneWire DS18B20 #define ONE_WIRE_BUS 4 // GPIO 4 — bus OneWire DS18B20
@@ -57,8 +59,11 @@ struct NetworkStatus {
}; };
// ── Déclarations extern (définies dans main.cpp) ──────────────────── // ── Déclarations extern (définies dans main.cpp) ────────────────────
extern SondeConfig sondesConfig[NB_SONDES]; extern SondeConfig sondesConfig[NB_SONDES];
extern SondeEtat sondesEtat[NB_SONDES]; extern SondeEtat sondesEtat[NB_SONDES];
extern PointHistorique historique[HIST_TAILLE]; extern PointHistorique historique[HIST_TAILLE];
extern uint16_t histIdx; extern uint16_t histIdx;
extern NetworkStatus netStatus; extern NetworkStatus netStatus;
// Mutex FreeRTOS protégeant l'accès concurrent au buffer historique
// (Core 0 : callbacks AsyncWebServer vs Core 1 : loop/sensors_update)
extern SemaphoreHandle_t xHistMutex;
+9 -6
View File
@@ -4,19 +4,22 @@
#include "sensors.h" #include "sensors.h"
#include "web_server.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 },
{ "T°C Serre", "maison/jardin/serre/temperature", 60000, 0.1f }, { "T°C Serre", "maison/jardin/serre/temperature", 60000, 0.1f },
{ "T°C Sol", "maison/jardin/sol/temperature", 60000, 0.1f }, { "T°C Sol", "maison/jardin/sol/temperature", 60000, 0.1f },
}; };
SondeEtat sondesEtat[NB_SONDES] = {}; SondeEtat sondesEtat[NB_SONDES] = {};
PointHistorique historique[HIST_TAILLE] = {}; PointHistorique historique[HIST_TAILLE] = {};
uint16_t histIdx = 0; uint16_t histIdx = 0;
NetworkStatus netStatus = {}; NetworkStatus netStatus = {};
SemaphoreHandle_t xHistMutex = nullptr;
void setup() { void setup() {
Serial.begin(115200); Serial.begin(115200);
Serial.println("[BOOT] esp_jardin démarrage..."); delay(500);
Serial.println("\n[BOOT] esp_jardin v1.0 — démarrage...");
xHistMutex = xSemaphoreCreateMutex();
network_init(); network_init();
sensors_init(); sensors_init();
web_server_init(); web_server_init();
+8 -5
View File
@@ -53,12 +53,15 @@ bool sensors_update() {
} }
} }
// Enregistrement dans le buffer circulaire // Enregistrement dans le buffer circulaire (protégé contre Core 0)
historique[histIdx].timestamp = maintenant; if (xSemaphoreTake(xHistMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
for (uint8_t i = 0; i < NB_SONDES; i++) { historique[histIdx].timestamp = maintenant;
historique[histIdx].temps[i] = sondesEtat[i].tempActuelle; // NAN si erreur for (uint8_t i = 0; i < NB_SONDES; i++) {
historique[histIdx].temps[i] = sondesEtat[i].tempActuelle;
}
histIdx = (histIdx + 1) % HIST_TAILLE;
xSemaphoreGive(xHistMutex);
} }
histIdx = (histIdx + 1) % HIST_TAILLE;
return true; // nouvelle mesure disponible return true; // nouvelle mesure disponible
} }
+22 -17
View File
@@ -32,19 +32,22 @@ static String _buildJsonSondes() {
static String _buildJsonHistory() { static String _buildJsonHistory() {
JsonDocument doc; JsonDocument doc;
JsonArray arr = doc.to<JsonArray>(); JsonArray arr = doc.to<JsonArray>();
for (uint16_t i = 0; i < HIST_TAILLE; i++) { if (xSemaphoreTake(xHistMutex, pdMS_TO_TICKS(50)) == pdTRUE) {
uint16_t idx = (histIdx + i) % HIST_TAILLE; for (uint16_t i = 0; i < HIST_TAILLE; i++) {
if (historique[idx].timestamp == 0) continue; uint16_t idx = (histIdx + i) % HIST_TAILLE;
JsonObject pt = arr.add<JsonObject>(); if (historique[idx].timestamp == 0) continue;
pt["ts"] = historique[idx].timestamp; JsonObject pt = arr.add<JsonObject>();
JsonArray t = pt["t"].to<JsonArray>(); pt["ts"] = historique[idx].timestamp;
for (uint8_t j = 0; j < NB_SONDES; j++) { JsonArray t = pt["t"].to<JsonArray>();
if (isnan(historique[idx].temps[j])) { for (uint8_t j = 0; j < NB_SONDES; j++) {
t.add(nullptr); if (isnan(historique[idx].temps[j])) {
} else { t.add(nullptr);
t.add(String(historique[idx].temps[j], 1)); } else {
t.add(String(historique[idx].temps[j], 1));
}
} }
} }
xSemaphoreGive(xHistMutex);
} }
String out; String out;
serializeJson(doc, out); serializeJson(doc, out);
@@ -61,11 +64,12 @@ static void _onWsEvent(AsyncWebSocket* server, AsyncWebSocketClient* client,
} }
void web_server_init() { void web_server_init() {
if (!LittleFS.begin()) { bool fsOk = LittleFS.begin();
Serial.println("[FS] Erreur montage LittleFS !"); if (!fsOk) {
return; Serial.println("[FS] Erreur montage LittleFS — interface web indisponible, API REST active");
} else {
Serial.println("[FS] LittleFS monté");
} }
Serial.println("[FS] LittleFS monté");
_ws.onEvent(_onWsEvent); _ws.onEvent(_onWsEvent);
_server.addHandler(&_ws); _server.addHandler(&_ws);
@@ -123,8 +127,9 @@ void web_server_init() {
} }
); );
// Servir index.html depuis LittleFS if (fsOk) {
_server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html"); _server.serveStatic("/", LittleFS, "/").setDefaultFile("index.html");
}
_server.begin(); _server.begin();
Serial.println("[HTTP] Serveur web démarré sur port 80"); Serial.println("[HTTP] Serveur web démarré sur port 80");