#include <DHT.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <ESP32Servo.h>
#include <WiFi.h>
#include <PubSubClient.h>
// --- PINS ---
#define DHT_PIN 4
#define CO2_PIN 34
#define LDR_PIN 35
#define PIR_PIN 27
#define SERVO_PIN 18
#define LED_RED 25
#define LED_GREEN 26
#define DHT_TYPE DHT22
// --- WiFi spécial Wokwi ---
const char* ssid = "Wokwi-GUEST";
const char* password = ""; // Laissez vide
// --- MQTT ---
const char* mqtt_server = "broker.hivemq.com";
WiFiClient espClient;
PubSubClient mqttClient(espClient);
// --- Objets ---
DHT dht(DHT_PIN, DHT_TYPE);
LiquidCrystal_I2C lcd(0x27, 16, 2);
Servo windowServo;
// --- Historique CO2 ---
float co2History[10];
int histIndex = 0;
bool histFull = false;
// ─────────────────────────────────────────
// RECONNEXION WIFI (appelée si déconnecté)
// ─────────────────────────────────────────
void reconnectWiFi() {
if (WiFi.status() == WL_CONNECTED) return; // déjà connecté → rien à faire
Serial.println("WiFi perdu, reconnexion...");
WiFi.disconnect();
delay(500);
WiFi.begin(ssid, password);
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 20) {
delay(500);
attempts++;
Serial.print(".");
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi reconnecte!");
} else {
Serial.println("\nWiFi echec reconnexion");
}
}
// ─────────────────────────────────────────
// RECONNEXION MQTT (appelée si déconnecté)
// ─────────────────────────────────────────
void reconnectMQTT() {
if (mqttClient.connected()) return; // déjà connecté → rien à faire
if (WiFi.status() != WL_CONNECTED) return; // pas de WiFi → impossible
Serial.println("MQTT perdu, reconnexion...");
int attempts = 0;
while (!mqttClient.connected() && attempts < 5) {
// Génère un ID unique pour éviter les conflits
String clientId = "SmartClass_" + String(random(0xffff), HEX);
if (mqttClient.connect(clientId.c_str())) {
Serial.println("MQTT reconnecte!");
} else {
Serial.print("MQTT echec code=");
Serial.println(mqttClient.state());
delay(1000);
attempts++;
}
}
}
// ─────────────────────────────────────────
// SCORE COGNITIF
// ─────────────────────────────────────────
int computeScore(float temp, float co2_ppm, float lux, bool presence) {
int score = 100;
if (temp < 18 || temp > 26) score -= 30;
else if (temp < 20 || temp > 24) score -= 15;
if (co2_ppm > 2000) score -= 40;
else if (co2_ppm > 1500) score -= 25;
else if (co2_ppm > 1000) score -= 10;
if (lux < 100) score -= 20;
else if (lux < 200) score -= 10;
if (!presence) score = max(score, 60);
return max(0, min(100, score));
}
// ─────────────────────────────────────────
// PRÉDICTION CO2
// ─────────────────────────────────────────
bool predictCO2Rise() {
if (!histFull && histIndex < 3) return false;
int count = histFull ? 10 : histIndex;
float recent = co2History[(histIndex - 1 + 10) % 10];
float older = co2History[(histIndex - min(count, 5) + 10) % 10];
return (recent - older) > 100;
}
// ─────────────────────────────────────────
// SETUP
// ─────────────────────────────────────────
void setup() {
Serial.begin(115200);
dht.begin();
lcd.init();
lcd.backlight();
windowServo.attach(SERVO_PIN);
windowServo.write(0);
pinMode(PIR_PIN, INPUT);
pinMode(LED_RED, OUTPUT);
pinMode(LED_GREEN, OUTPUT);
// Message démarrage LCD
lcd.setCursor(0, 0);
lcd.print("Smart Classroom");
lcd.setCursor(0, 1);
lcd.print("Connexion...");
// Connexion WiFi initiale
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
Serial.print("Connexion WiFi");
int attempts = 0;
while (WiFi.status() != WL_CONNECTED && attempts < 30) {
delay(500);
Serial.print(".");
attempts++;
}
if (WiFi.status() == WL_CONNECTED) {
Serial.println("\nWiFi OK: " + WiFi.localIP().toString());
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("WiFi OK!");
lcd.setCursor(0, 1);
lcd.print(WiFi.localIP());
delay(2000);
}
// Connexion MQTT initiale
mqttClient.setServer(mqtt_server, 1883);
reconnectMQTT();
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Smart Classroom");
lcd.setCursor(0, 1);
lcd.print("Pret !");
delay(1000);
}
// ─────────────────────────────────────────
// LOOP
// ─────────────────────────────────────────
void loop() {
// ── Vérification et reconnexion automatique ──
reconnectWiFi();
reconnectMQTT();
mqttClient.loop();
// ── Lecture capteurs ──
float temp = dht.readTemperature();
float hum = dht.readHumidity();
if (isnan(temp)) temp = 22.0;
if (isnan(hum)) hum = 50.0;
float co2_ppm = map(analogRead(CO2_PIN), 0, 4095, 400, 3000);
float lux = map(analogRead(LDR_PIN), 0, 4095, 0, 1000);
bool presence = digitalRead(PIR_PIN);
// ── Historique CO2 ──
co2History[histIndex] = co2_ppm;
histIndex = (histIndex + 1) % 10;
if (histIndex == 0) histFull = true;
// ── Score et prédiction ──
int score = computeScore(temp, co2_ppm, lux, presence);
bool risingCO2 = predictCO2Rise();
bool openWindow = (co2_ppm > 1000) || risingCO2;
bool alert = (score < 50);
// ── Actionneurs ──
windowServo.write(openWindow ? 90 : 0);
digitalWrite(LED_GREEN, score >= 70 ? HIGH : LOW);
digitalWrite(LED_RED, alert ? HIGH : LOW);
// ── LCD ──
lcd.clear();
lcd.setCursor(0, 0);
lcd.print("Score:");
lcd.print(score);
lcd.print(risingCO2 ? "% PRED!" : "% OK ");
lcd.setCursor(0, 1);
lcd.print("T:");
lcd.print((int)temp);
lcd.print("C CO2:");
lcd.print((int)co2_ppm);
// ── Serial Monitor ──
Serial.print("WiFi:");
Serial.print(WiFi.status() == WL_CONNECTED ? "OK" : "KO");
Serial.print(" MQTT:");
Serial.print(mqttClient.connected() ? "OK" : "KO");
Serial.print(" Score="); Serial.print(score);
Serial.print(" CO2="); Serial.print(co2_ppm);
Serial.print(" Temp="); Serial.println(temp);
// ── Publication MQTT ──
if (mqttClient.connected()) {
char buf[10];
sprintf(buf, "%d", score);
mqttClient.publish("smartclass/score", buf);
sprintf(buf, "%.1f", temp);
mqttClient.publish("smartclass/temperature", buf);
sprintf(buf, "%.0f", co2_ppm);
mqttClient.publish("smartclass/co2", buf);
sprintf(buf, "%.0f", lux);
mqttClient.publish("smartclass/luminosite", buf);
mqttClient.publish("smartclass/fenetre", openWindow ? "1" : "0");
mqttClient.publish("smartclass/prediction", risingCO2 ? "1" : "0");
}
delay(2000);
}