#include <WiFi.h>
#include <PubSubClient.h>
#include <DHT.h>
#include <ArduinoJson.h>
// --- Configuration Wi-Fi Wokwi ---
const char* WIFI_SSID = "Wokwi-GUEST";
const char* WIFI_PASSWORD = "";
// --- Configuration ThingsBoard ---
const char* TB_SERVER = "thingsboard.cloud";
const int TB_PORT = 1883;
// /!\ REMPLACE PAR TON ACCESS TOKEN DE L'ESP32 (ne pas mettre de vrai token dans le rendu final !)
const char* TB_TOKEN ="lgSEQzFLzJFCMUAedAqR";
// --- Assignation des Pins ---
#define DHT_PIN 18
#define DHT_TYPE DHT22
#define TRIG_PIN 5
#define ECHO_PIN 19
#define RELAY_PIN 26 // Relais/LED Pompe
#define ALARM_LED_PIN 21 // LED Rouge alerte
#define BUZZER_PIN 22 // Buzzer alerte
// --- Variables d'état ---
bool pumpOn = false;
bool manualMode = false;
int alertLevel = 0; // 0=normal, 1=attention, 2=critique
// --- Dimensions de la Cuve (à adapter si nécessaire) ---
// Supposons que le capteur est placé à 100 cm du fond (vide) et que l'eau peut monter jusqu'à 10 cm du capteur (plein).
const float DISTANCE_VIDE = 100.0;
const float DISTANCE_PLEIN = 10.0;
// Dernière distance valide mesurée (évite les fausses alertes en cas de raté du capteur)
static float lastGoodDistance = DISTANCE_VIDE;
// --- Initialisation des Objets ---
DHT dht(DHT_PIN, DHT_TYPE);
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastSend = 0;
const unsigned long SEND_INTERVAL = 5000; // Publication toutes les 5 secondes
// --- Déclarations des fonctions ---
void connectWiFi();
void connectMQTT();
void callback(char* topic, byte* payload, unsigned int length);
float measureDistance();
int calculateWaterLevel(float distance);
void updateAlerts(int level);
void publishTelemetry();
void setup() {
Serial.begin(115200);
pinMode(TRIG_PIN, OUTPUT);
pinMode(ECHO_PIN, INPUT);
pinMode(RELAY_PIN, OUTPUT);
pinMode(ALARM_LED_PIN, OUTPUT);
pinMode(BUZZER_PIN, OUTPUT);
digitalWrite(RELAY_PIN, LOW);
digitalWrite(ALARM_LED_PIN, LOW);
digitalWrite(BUZZER_PIN, LOW);
dht.begin();
connectWiFi();
client.setServer(TB_SERVER, TB_PORT);
client.setCallback(callback);
}
void loop() {
if (WiFi.status() != WL_CONNECTED) {
connectWiFi();
}
if (!client.connected()) {
connectMQTT();
}
client.loop();
unsigned long now = millis();
if (now - lastSend > SEND_INTERVAL) {
publishTelemetry();
lastSend = now;
}
}
// --- Mesure de distance via HC-SR04 ---
// Retourne -1.0 si aucun écho reçu (timeout), sinon met à jour lastGoodDistance.
float measureDistance() {
digitalWrite(TRIG_PIN, LOW);
delayMicroseconds(2);
digitalWrite(TRIG_PIN, HIGH);
delayMicroseconds(10);
digitalWrite(TRIG_PIN, LOW);
long duration = pulseIn(ECHO_PIN, HIGH, 30000); // timeout de 30ms
if (duration == 0) {
return -1.0; // Pas d'écho reçu, on gardera lastGoodDistance côté appelant
}
lastGoodDistance = (duration * 0.0343) / 2.0;
return lastGoodDistance;
}
// --- Conversion distance (cm) -> Niveau (%) ---
int calculateWaterLevel(float distance) {
if (distance < 0) return 0;
if (distance >= DISTANCE_VIDE) return 0;
if (distance <= DISTANCE_PLEIN) return 100;
// Règle de trois inversée : plus la distance est petite, plus l'eau est haute.
float level = ((DISTANCE_VIDE - distance) / (DISTANCE_VIDE - DISTANCE_PLEIN)) * 100.0;
return (int)constrain(level, 0, 100);
}
// --- Gestion des alertes locales ---
void updateAlerts(int level) {
// alertLevel: 0 = normal (>=30%), 1 = attention (15% - 30%), 2 = critique (<15%)
if (level < 15) {
alertLevel = 2;
digitalWrite(ALARM_LED_PIN, HIGH);
tone(BUZZER_PIN, 1000, 200);
} else if (level < 30) {
alertLevel = 1;
digitalWrite(ALARM_LED_PIN, HIGH); // LED allumée fixe
noTone(BUZZER_PIN);
} else {
alertLevel = 0;
digitalWrite(ALARM_LED_PIN, LOW); // Éteint
noTone(BUZZER_PIN);
}
}
// --- Publication vers ThingsBoard ---
void publishTelemetry() {
float rawDistance = measureDistance();
// Si la mesure a échoué (timeout), on utilise la dernière distance valide
// au lieu de forcer 0 (ce qui déclencherait une fausse alerte critique).
float distance = (rawDistance < 0) ? lastGoodDistance : rawDistance;
int waterLevel = calculateWaterLevel(distance);
float temp = dht.readTemperature();
float hum = dht.readHumidity();
updateAlerts(waterLevel);
if (isnan(temp) || isnan(hum)) {
Serial.println("Erreur de lecture du DHT22 !");
temp = 0.0;
hum = 0.0;
}
// Création du JSON avec les clés exactes requises
StaticJsonDocument<256> doc;
doc["distanceCm"] = distance;
doc["waterLevelPct"] = waterLevel;
doc["temperature"] = temp;
doc["humidity"] = hum;
doc["pumpOn"] = pumpOn; // corrigé : "pumpOn" (P et O majuscules), clé imposée par le sujet
doc["manualMode"] = manualMode;
doc["alertLevel"] = alertLevel;
char jsonBuffer[256];
serializeJson(doc, jsonBuffer);
Serial.print("Envoi télémétrie : ");
Serial.println(jsonBuffer);
client.publish("v1/devices/me/telemetry", jsonBuffer);
}
// --- Gestion des RPC (Réception d'ordres depuis ThingsBoard/Agent) ---
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message RPC reçu sur le topic : ");
Serial.println(topic);
StaticJsonDocument<256> doc;
DeserializationError error = deserializeJson(doc, payload, length);
if (error) {
Serial.print("Erreur de parsing JSON : ");
Serial.println(error.c_str());
return;
}
// Extraction du nom de la méthode
const char* method = doc["method"];
String responseTopic = String(topic);
responseTopic.replace("request", "response");
StaticJsonDocument<128> responseDoc;
bool success = false;
if (method != NULL) {
if (strcmp(method, "setPump") == 0) {
// corrigé : le paramètre arrive sous la forme {"value": true/false},
// pas comme un booléen brut dans "params".
bool targetState = doc["params"]["value"];
pumpOn = targetState;
digitalWrite(RELAY_PIN, pumpOn ? HIGH : LOW);
success = true;
Serial.print("RPC setPump exécutée -> Pompe: ");
Serial.println(pumpOn ? "ON" : "OFF");
}
else if (strcmp(method, "setManualMode") == 0) {
// corrigé : idem, on lit params.value
manualMode = doc["params"]["value"];
success = true;
Serial.print("RPC setManualMode exécutée -> Mode Manuel: ");
Serial.println(manualMode ? "ACTIF" : "INACTIF");
}
}
responseDoc["success"] = success;
char responseBuffer[128];
serializeJson(responseDoc, responseBuffer);
client.publish(responseTopic.c_str(), responseBuffer);
// Forcer une publication immédiate de la mise à jour pour rafraîchir l'interface
publishTelemetry();
}
// --- Connexion Wi-Fi ---
void connectWiFi() {
Serial.print("Connexion au Wi-Fi ");
Serial.println(WIFI_SSID);
WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWi-Fi connecté !");
Serial.print("Adresse IP : ");
Serial.println(WiFi.localIP());
}
// --- Connexion MQTT (ThingsBoard) ---
void connectMQTT() {
while (!client.connected()) {
Serial.print("Connexion au broker MQTT ThingsBoard...");
if (client.connect("ESP32_Client", TB_TOKEN, NULL)) {
Serial.println(" Connecté !");
// S'abonner aux requêtes RPC
client.subscribe("v1/devices/me/rpc/request/+");
} else {
Serial.print("Échec, rc=");
Serial.print(client.state());
Serial.println(" Nouvelle tentative dans 5 secondes.");
delay(5000);
}
}
}