feat: module MQTT complet + mutex xSondesMutex thread-safety
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -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
@@ -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();
|
||||
|
||||
@@ -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
@@ -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
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user