feat: module MQTT complet + mutex xSondesMutex thread-safety

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-23 16:49:31 +02:00
parent 4854faa027
commit cd8232bafb
5 changed files with 45 additions and 26 deletions
+2
View File
@@ -67,3 +67,5 @@ 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;
// Mutex FreeRTOS protégeant l'accès concurrent à sondesEtat[]
extern SemaphoreHandle_t xSondesMutex;
+3 -1
View File
@@ -15,12 +15,14 @@ PointHistorique historique[HIST_TAILLE] = {};
uint16_t histIdx = 0;
NetworkStatus netStatus = {};
SemaphoreHandle_t xHistMutex = nullptr;
SemaphoreHandle_t xSondesMutex = nullptr;
void setup() {
Serial.begin(115200);
delay(500);
Serial.println("\n[BOOT] esp_jardin v1.0 — démarrage...");
xHistMutex = xSemaphoreCreateMutex();
xHistMutex = xSemaphoreCreateMutex();
xSondesMutex = xSemaphoreCreateMutex();
network_init();
sensors_init();
web_server_init();
+5 -5
View File
@@ -48,8 +48,9 @@ void mqtt_update() {
_mqtt.loop();
// Publication par sonde avec filtre deadband
if (xSemaphoreTake(xSondesMutex, pdMS_TO_TICKS(10)) != pdTRUE) return;
for (uint8_t i = 0; i < NB_SONDES; i++) {
// Ne jamais publier une valeur d'erreur (127 °C, 85 °C, NAN)
if (sondesEtat[i].erreur) continue;
float delta = fabsf(sondesEtat[i].tempActuelle - sondesEtat[i].dernierPublie);
@@ -57,12 +58,9 @@ void mqtt_update() {
bool deadbandOk = delta >= sondesConfig[i].deadband;
bool intervalOk = ecart >= sondesConfig[i].intervalleMs;
// Publication si variation hors deadband OU intervalle max écoulé
// Note : au premier cycle, dernierPubliMs == 0 → ecart très grand → publication immédiate
if (deadbandOk || intervalOk) {
char payload[8];
char payload[10];
snprintf(payload, sizeof(payload), "%.1f", sondesEtat[i].tempActuelle);
// retain=true : le broker mémorise la dernière valeur pour les nouveaux abonnés
bool ok = _mqtt.publish(sondesConfig[i].topic, payload, true);
if (ok) {
sondesEtat[i].dernierPublie = sondesEtat[i].tempActuelle;
@@ -71,4 +69,6 @@ void mqtt_update() {
}
}
}
xSemaphoreGive(xSondesMutex);
}
+15 -6
View File
@@ -38,21 +38,30 @@ bool sensors_update() {
_derniereMesureMs = maintenant;
// Lecture et validation de chaque sonde
float tempLues[NB_SONDES];
bool erreurLues[NB_SONDES];
for (uint8_t i = 0; i < NB_SONDES; i++) {
float t = _sensors.getTempCByIndex(i);
// Rejeter les valeurs d'erreur DS18B20
if (t == DEVICE_DISCONNECTED_C || t == 85.0f) {
sondesEtat[i].erreur = true;
sondesEtat[i].tempActuelle = NAN;
erreurLues[i] = true;
tempLues[i] = NAN;
Serial.printf("[SONDE %u] ERREUR — valeur rejetée: %.1f\n", i, t);
} else {
sondesEtat[i].erreur = false;
sondesEtat[i].tempActuelle = t;
erreurLues[i] = false;
tempLues[i] = t;
Serial.printf("[SONDE %u] %s = %.1f°C\n", i, sondesConfig[i].nom, t);
}
}
// Écriture atomique dans sondesEtat (protégé Core 0 AsyncWebServer)
if (xSemaphoreTake(xSondesMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
for (uint8_t i = 0; i < NB_SONDES; i++) {
sondesEtat[i].erreur = erreurLues[i];
sondesEtat[i].tempActuelle = tempLues[i];
}
xSemaphoreGive(xSondesMutex);
}
// Enregistrement dans le buffer circulaire (protégé contre Core 0)
if (xSemaphoreTake(xHistMutex, pdMS_TO_TICKS(10)) == pdTRUE) {
historique[histIdx].timestamp = maintenant;
+20 -14
View File
@@ -11,15 +11,18 @@ static AsyncWebSocket _ws("/ws");
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"] = String(sondesEtat[i].tempActuelle, 1);
} else {
s["temp"] = nullptr;
if (xSemaphoreTake(xSondesMutex, pdMS_TO_TICKS(20)) == pdTRUE) {
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"] = String(sondesEtat[i].tempActuelle, 1);
} else {
s["temp"] = nullptr;
}
}
xSemaphoreGive(xSondesMutex);
}
doc["uptime"] = (millis() - netStatus.uptimeDemarrage) / 1000;
doc["rssi"] = netStatus.rssi;
@@ -90,13 +93,16 @@ void web_server_init() {
// 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] = String(sondesEtat[i].tempActuelle, 1);
} else {
doc[key] = nullptr;
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;