diff --git a/include/config.h b/include/config.h index a2e8c10..2567d89 100644 --- a/include/config.h +++ b/include/config.h @@ -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; diff --git a/src/main.cpp b/src/main.cpp index 4a9fb62..a04d749 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -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(); diff --git a/src/mqtt_manager.cpp b/src/mqtt_manager.cpp index 569bb24..ea166a2 100644 --- a/src/mqtt_manager.cpp +++ b/src/mqtt_manager.cpp @@ -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); } diff --git a/src/sensors.cpp b/src/sensors.cpp index 293118c..230eabc 100644 --- a/src/sensors.cpp +++ b/src/sensors.cpp @@ -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; diff --git a/src/web_server.cpp b/src/web_server.cpp index fa7c55c..d6292b1 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -11,15 +11,18 @@ static AsyncWebSocket _ws("/ws"); static String _buildJsonSondes() { JsonDocument doc; JsonArray arr = doc["sondes"].to(); - 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; + 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; @@ -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;