|

Système d’irrigation via WiFi

Ce projet consiste à concevoir un système d’irrigation intelligent avec une pompe immergé dans l’eau pour arroser la plante lorsque le sol est sec et surveille en temps réel l’humidité du sol de la plante, l’humidité et la température ambiante. Les données sont affichées sur un écran LCD ainsi qu’une interface web hébergée sur un serveur local.

Matériel

  • Breadboard
  • Arduino UNO R4 WiFi
  • Afficheur LCD 16*2
  • Capteur de température et humidité(DHT22)
  • Capteur de l’humidité du sol(terre)
  • Module de Relai SRS-05VDC-SL
  • Circuit intégré TA6586
  • Pompe à eau
  • Piles AA
  • Fils jumper mâles
  • Tubes
  • Bocal en impression 3D
  • Pot
  • Plante
  • Aimants
  • Imprimante 3D

Illustration du projet

Vue de côté

Vue arrière

Vue intérieur du boitier

Schéma Bloc

Voici le schéma du projet

Caractéristiques des pièces

Circuit électronique

Description Général Du Projet

Le capteur d’humidité du sol s’assure que le seuil de la terre n’est plus bas que 41%. Si on obtient un seuil plus bas que 40%, alors la pompe va s’activer pour arroser la terre pendant 2 secondes consécutives avant de s’arrêter pour recalculer le taux d’humidité à nouveau. Si le seuil idéal n’est toujours pas atteint, la pompe va recommencer son arrosage jusqu’à ce qu’on obtient le taux d’humidité souhaité.

L’écran LCD nous permet de voir le changement de température de la pièce, le taux d’humidité de la terre et l’adresse IP de La carte Arduino. Grâce à l’adresse IP on a accès à la page web du projet. Là dessus on peut non seulement voir la température et le taux d’humidité de la terre, mais on peut aussi contrôler la pompe manuellement. Tout ça sur notre téléphone!

Description Technique Des Composants

  • Arduino UNO R4 WiFi : La carte électronique lit les capteurs, envoie les données, prend les décisions et communique avec le serveur internet. 
  • Capteur d’humidité du sol : Il mesure le niveau d’humidité du sol sous forme de valeur analogique. 
  • DHT22 : C’est un capteur qui mesure la température et l’humidité de l’air. Il permet d’avoir un suivi des conditions environnementales de la pièce. 
  • Afficheur LCD : Il affiche localement les informations des capteurs tel qu’humidité du sol, température, état de la pompe et de l’adresse IP du serveur. 
  • TA6586 CI : Puce de contrôle de moteur qui sert de module de relais. Le circuit intégrer agit comme un interrupteur commandé par Arduino qui permet d’activer la pompe en toute sécurité.  
  • Serveur local : Il gère la communication entre le téléphone et le système. Il envoie les données en temps réel. 
  • Téléphone : Il permet d’accéder à l’interface du système WiFi. Il sert a visualiser les données de la température, humidité du sol et de la pièce. 
  • Pompe à eau : Pompe l’eau depuis la source et l’envoie vers la plante lorsque le relais est activé. 
  • Source d’eau : Contenant d’eau utilisé pour circuler dans la pompe qui servira à rendre humide la terre de la plante. 
  • Plante : La plante reçois l’eau nécessaire selon les décisions prises par l’Arduino 

Conception 3D

Boitier du circuit électronique Boitier de l’écran LCD Contenant pour l’eau

Programmation

On a programmé avec Arduino, le programme se trouve sur le lien ci dessous:

#include <Adafruit_Sensor.h>
#include <WiFiS3.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <DHT.h>

// =======================
// WIFI (connexion au réseau + serveur web)
// =======================
char ssid[] = "TCOM2026";
char pass[] = "TCOM2026";
WiFiServer server(80);
int status = WL_IDLE_STATUS;

// =======================
// MATÉRIEL (broches et capteurs utilisés)
// =======================
#define DHTPIN 4
#define DHTTYPE DHT22
#define SOIL_MOISTURE_PIN A0
#define WATER_PUMP_PIN 8

// =======================
// CALIBRATION DU CAPTEUR D'HUMIDITÉ DU SOL
// =======================
const int SOIL_DRY = 700;      // valeur brute quand le sol est sec
const int SOIL_WET = 300;      // valeur brute quand le sol est très humide
const int DRY_THRESHOLD = 40;  // seuil (%) pour déclencher l’arrosage en mode AUTO

// =======================
// PARAMÈTRES DE TEMPS (en millisecondes)
// =======================
const unsigned long PUMP_ON_MS = 1000;  // durée d’activation de la pompe
const unsigned long SENSOR_MS  = 2000;  // intervalle de lecture des capteurs
const unsigned long LCD_MS     = 5000;  // intervalle de rafraîchissement de l’écran LCD

// =======================
// OBJETS DES COMPOSANTS
// =======================
LiquidCrystal_I2C lcd(0x27, 16, 2);
DHT dht(DHTPIN, DHTTYPE);

// =======================
// VARIABLES DES MESURES
// =======================
float temperature = 0.0;
float humidity = 0.0;
int soilRaw = 0;
int soilPercent = 0;
bool dhtOk = false;

// =======================
// MODES DE FONCTIONNEMENT DE LA POMPE
// =======================
enum PumpMode { AUTO, MANUAL_ON, MANUAL_OFF };
PumpMode pumpMode = AUTO;
bool pumpIsOn = false;
unsigned long pumpStartMs = 0;

// =======================
// MINUTERIES
// =======================
unsigned long lastSensorMs = 0;
unsigned long lastLcdMs = 0;
int lcdScreen = 0;

// =======================
// FONCTIONS UTILITAIRES
// =======================
int soilToPercent(int raw) {
  raw = constrain(raw, SOIL_WET, SOIL_DRY);
  return map(raw, SOIL_DRY, SOIL_WET, 0, 100);
}

const char* modeText() {
  if (pumpMode == AUTO) return "AUTO";
  if (pumpMode == MANUAL_ON) return "MANUAL ON";
  return "MANUAL OFF";
}

// =======================
// ÉCRAN LCD
// =======================
void lcdConnect() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Connexion WiFi");
  lcd.setCursor(0, 1);
  lcd.print(ssid);
}

void lcdIP() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("IP:");
  lcd.setCursor(0, 1);
  lcd.print(WiFi.localIP());
}

// =======================
// LECTURE DES CAPTEURS
// =======================
void readSensors() {
  humidity = dht.readHumidity();
  temperature = dht.readTemperature();

  soilRaw = analogRead(SOIL_MOISTURE_PIN);
  soilPercent = soilToPercent(soilRaw);

  dhtOk = !(isnan(temperature) || isnan(humidity));
}

// =======================
// GESTION DE LA POMPE
// =======================
void handlePump(unsigned long now) {
  if (pumpMode == MANUAL_ON) {
    digitalWrite(WATER_PUMP_PIN, HIGH);
    pumpIsOn = true;
    return;
  }

  if (pumpMode == MANUAL_OFF) {
    digitalWrite(WATER_PUMP_PIN, LOW);
    pumpIsOn = false;
    return;
  }

  // Mode AUTO
  if (soilPercent < DRY_THRESHOLD && !pumpIsOn) {
    digitalWrite(WATER_PUMP_PIN, HIGH);
    pumpIsOn = true;
    pumpStartMs = now;
  }

  if (pumpIsOn && (now - pumpStartMs >= PUMP_ON_MS)) {
    digitalWrite(WATER_PUMP_PIN, LOW);
    pumpIsOn = false;
  }
}

// =======================
// MISE À JOUR DU LCD
// =======================
void updateLCD(unsigned long now) {
  if (now - lastLcdMs < LCD_MS) return;
  lastLcdMs = now;

  lcd.clear();

  if (lcdScreen == 0) {
    // Écran 1 : température + humidité
    if (dhtOk) {
      lcd.setCursor(0, 0);
      lcd.print("T:");
      lcd.print(temperature, 1);
      lcd.print((char)223);
      lcd.print("C");

      lcd.setCursor(0, 1);
      lcd.print("H:");
      lcd.print(humidity, 0);
      lcd.print("%");
    } else {
      lcd.setCursor(0, 0);
      lcd.print("DHT ERREUR");
    }
  }
  else if (lcdScreen == 1) {
    // Écran 2 : humidité sol + pompe
    lcd.setCursor(0, 0);
    lcd.print("Sol:");
    lcd.print(soilPercent);
    lcd.print("%");

    lcd.setCursor(0, 1);
    lcd.print("Pompe:");
    lcd.print(pumpIsOn ? "ON" : "OFF");
  }
  else {
    // Écran 3 : mode + IP
    lcd.setCursor(0, 0);
    lcd.print("Mode:");
    lcd.print(modeText());

    lcd.setCursor(0, 1);
    lcd.print(WiFi.localIP());
  }

  lcdScreen = (lcdScreen + 1) % 3;
}

// =======================
// ENVOI JSON
// =======================
void sendJSON(WiFiClient &c) {
  c.println("HTTP/1.1 200 OK");
  c.println("Content-Type: application/json");
  c.println("Connection: close");
  c.println();

  c.print("{\"temp\":");
  c.print(dhtOk ? temperature : 0);

  c.print(",\"hum\":");
  c.print(dhtOk ? humidity : 0);

  c.print(",\"soil\":");
  c.print(soilPercent);

  c.print(",\"pump\":");
  c.print(pumpIsOn ? "true" : "false");

  c.print(",\"mode\":\"");
  c.print(modeText());
  c.print("\"}");
}

// =======================
// ENVOI PAGE WEB
// =======================
void sendPage(WiFiClient &c) {
  c.println("HTTP/1.1 200 OK");
  c.println("Content-Type: text/html; charset=utf-8");
  c.println("Connection: close");
  c.println();

  c.println(R"rawliteral(
<!doctype html>
<html lang="fr">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>PROJET OBED MARIANE</title>

<style>
:root{
  --blue:#19a7ff;
  --bg:#f3f6fb;
  --card:#ffffff;
  --text:#1f2a37;
  --muted:#6b7280;
  --shadow:0 10px 30px rgba(0,0,0,.08);
  --radius:14px;
}
*{box-sizing:border-box}
body{
  margin:0;
  font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
  background:var(--bg);
  color:var(--text);
}
.topbar{
  height:64px;
  background:var(--blue);
  color:#fff;
  display:flex;
  align-items:center;
  padding:0 18px;
  font-weight:800;
  letter-spacing:.2px;
  box-shadow:0 6px 16px rgba(25,167,255,.35);
}
.wrap{display:flex; min-height:calc(100vh - 64px);}
.sidebar{
  width:240px;
  background:#fff;
  box-shadow: 6px 0 16px rgba(0,0,0,.04);
  padding:18px 14px;
}
.brand{
  display:flex; gap:10px; align-items:center;
  padding:6px 8px 14px;
  border-bottom:1px solid #eef2f7;
  margin-bottom:12px;
}
.logo{
  width:38px;height:38px;border-radius:10px;
  background: linear-gradient(135deg, #ffb703, #fb8500);
}
.brand b{display:block; line-height:1.1}
.brand small{color:var(--muted); font-weight:700}
.nav a{
  display:flex; gap:10px; align-items:center;
  padding:10px 10px;
  border-radius:10px;
  text-decoration:none;
  color:var(--text);
  font-weight:750;
}
.nav a:hover{background:#f3f6fb}
.nav .active{background:#eaf6ff; color:#0b76c9}
.icon{width:18px;height:18px;border-radius:5px;background:#e5e7eb}
.main{flex:1; padding:18px;}
.header{
  display:flex; align-items:flex-end; justify-content:space-between;
  gap:14px; margin-bottom:14px;
}
.header h1{margin:0;font-size:28px}
.header p{margin:4px 0 0;color:var(--muted);font-weight:650;font-size:13px}
.kpis{
  display:grid;
  grid-template-columns: repeat(3, 1fr);
  gap:12px;
}
.kpi{
  background:var(--card);
  border-radius:var(--radius);
  box-shadow:var(--shadow);
  padding:12px;
  min-height:140px;
}
.kpi .label{color:var(--muted); font-weight:750; font-size:12px; margin-top:8px}
.kpi .big{font-size:18px; font-weight:900; margin-top:2px}
.donut{
  width:86px;height:86px; position:relative;
  display:grid; place-items:center; margin:auto;
}
.donut svg{transform:rotate(-90deg)}
.donut .center{
  position:absolute; text-align:center;
  font-weight:900; font-size:14px;
}
.donut .center small{
  display:block; font-weight:750; color:var(--muted); font-size:10px
}
.card{
  background:var(--card);
  border-radius:var(--radius);
  box-shadow:var(--shadow);
  padding:14px;
  margin-top:14px;
}
.card h3{margin:0 0 10px;font-size:14px;color:#374151}
.row{
  display:flex; align-items:center; justify-content:space-between;
  padding:10px 0;
  border-bottom:1px solid #eef2f7;
}
.row:last-child{border-bottom:none}
.k{color:var(--muted); font-weight:700}
.v{font-weight:900}
.controls{display:flex;gap:10px;flex-wrap:wrap;margin-top:12px}
.btn{
  flex:1; min-width:120px;
  padding:12px 12px;
  border:none;
  border-radius:12px;
  font-weight:900;
  font-size:14px;
  cursor:pointer;
  box-shadow:0 10px 22px rgba(0,0,0,.08);
}
.btn:active{transform:translateY(1px)}
.auto{background:#22c55e;color:#fff}
.on{background:#3b82f6;color:#fff}
.off{background:#ef4444;color:#fff}
.badge{
  display:inline-flex; align-items:center; gap:8px;
  padding:8px 10px;
  border-radius:999px;
  background:#fff;
  box-shadow:var(--shadow);
  font-weight:900;
}
.dot{width:10px;height:10px;border-radius:999px;background:#9ca3af}
.dot.on{background:#22c55e}
.dot.off{background:#ef4444}
@media (max-width: 920px){
  .sidebar{display:none}
  .kpis{grid-template-columns:1fr}
}
</style>
</head>

<body>
  <div class="topbar">PROJET OBED MARIANE</div>

  <div class="wrap">
    <aside class="sidebar">
      <div class="brand">
        <div class="logo"></div>
        <div>
          <b>IRRIGATION INTELLIGENT</b>
          <small>UNO R4 WiFi</small>
        </div>
      </div>
      <nav class="nav">
        <a class="active" href="#"><span class="icon"></span>Tableau</a>
      </nav>
    </aside>

    <main class="main">
      <div class="header">
        <div>
          <h1>Dashboard</h1>
          <p>Système d'irrigation intelligent</p>
        </div>

        <div class="badge">
          <span id="pumpDot" class="dot off"></span>
          Pompe: <span id="pState">OFF</span> · Mode: <span id="mState">AUTO</span>
        </div>
      </div>

      <section class="kpis">
        <div class="kpi">
          <div class="donut">
            <svg width="86" height="86" viewBox="0 0 100 100">
              <circle cx="50" cy="50" r="38" stroke="#e5e7eb" stroke-width="12" fill="none"/>
              <circle id="soilArc" cx="50" cy="50" r="38" stroke="#ef4444" stroke-width="12" fill="none"
                      stroke-linecap="round" stroke-dasharray="0 999"/>
            </svg>
            <div class="center"><span id="soilPct">--</span><small>Soil</small></div>
          </div>
          <div class="label">Humidité du sol</div>
          <div class="big"><span id="sVal">--</span> %</div>
        </div>

        <div class="kpi">
          <div class="donut">
            <svg width="86" height="86" viewBox="0 0 100 100">
              <circle cx="50" cy="50" r="38" stroke="#e5e7eb" stroke-width="12" fill="none"/>
              <circle id="humArc" cx="50" cy="50" r="38" stroke="#22c55e" stroke-width="12" fill="none"
                      stroke-linecap="round" stroke-dasharray="0 999"/>
            </svg>
            <div class="center"><span id="humPct">--</span><small>Hum</small></div>
          </div>
          <div class="label">Humidité (DHT22)</div>
          <div class="big"><span id="hVal">--</span> %</div>
        </div>

        <div class="kpi">
          <div class="donut">
            <svg width="86" height="86" viewBox="0 0 100 100">
              <circle cx="50" cy="50" r="38" stroke="#e5e7eb" stroke-width="12" fill="none"/>
              <circle id="tempArc" cx="50" cy="50" r="38" stroke="#3b82f6" stroke-width="12" fill="none"
                      stroke-linecap="round" stroke-dasharray="0 999"/>
            </svg>
            <div class="center"><span id="tempPct">--</span><small>Temp</small></div>
          </div>
          <div class="label">Température (DHT22)</div>
          <div class="big"><span id="tVal">--</span> °C</div>
        </div>
      </section>

      <div class="card">
        <h3>État du système</h3>
        <div class="row"><span class="k">Température</span><span class="v" id="t">--</span></div>
        <div class="row"><span class="k">Humidité</span><span class="v" id="h">--</span></div>
        <div class="row"><span class="k">Humidité sol</span><span class="v" id="s">--</span></div>
        <div class="row"><span class="k">Pompe</span><span class="v" id="p">--</span></div>
        <div class="row"><span class="k">Mode</span><span class="v" id="m">--</span></div>

        <div class="controls">
          <button class="btn auto" onclick="setMode('auto')">Mode AUTO</button>
          <button class="btn on" onclick="setMode('on')">Pompe ON</button>
          <button class="btn off" onclick="setMode('off')">Pompe OFF</button>
        </div>
      </div>
    </main>
  </div>

<script>
function setDonut(arcEl, pct, color){
  pct = Math.max(0, Math.min(100, pct));
  const r = 38;
  const c = 2 * Math.PI * r;
  arcEl.style.strokeDasharray = (c * pct / 100) + " " + c;
  arcEl.style.stroke = color;
}

async function refresh(){
  try{
    const r = await fetch('/data', {cache:'no-store'});
    const d = await r.json();

    document.getElementById('t').textContent = d.temp + " °C";
    document.getElementById('h').textContent = d.hum + " %";
    document.getElementById('s').textContent = d.soil + " %";
    document.getElementById('p').textContent = d.pump ? "ON" : "OFF";
    document.getElementById('m').textContent = d.mode;

    document.getElementById('pState').textContent = d.pump ? "ON" : "OFF";
    document.getElementById('mState').textContent = d.mode;
    document.getElementById('pumpDot').className = "dot " + (d.pump ? "on" : "off");

    document.getElementById('tVal').textContent = d.temp;
    document.getElementById('hVal').textContent = d.hum;
    document.getElementById('sVal').textContent = d.soil;

    const tempPctVal = Math.round((Math.min(50, Math.max(0, Number(d.temp))) / 50) * 100);
    document.getElementById('tempPct').textContent = tempPctVal + "%";
    document.getElementById('humPct').textContent = Number(d.hum) + "%";
    document.getElementById('soilPct').textContent = Number(d.soil) + "%";

    setDonut(document.getElementById('tempArc'), tempPctVal, "#3b82f6");
    setDonut(document.getElementById('humArc'), Number(d.hum), "#22c55e");

    const soilVal = Number(d.soil);
    const soilColor = (soilVal < 30) ? "#ef4444" : (soilVal < 60) ? "#f59e0b" : "#22c55e";
    setDonut(document.getElementById('soilArc'), soilVal, soilColor);
  } catch(e) {}
}

async function setMode(mode){
  try{
    await fetch('/pump?mode=' + encodeURIComponent(mode), {cache:'no-store'});
  } catch(e) {}
  refresh();
}

setInterval(refresh, 2000);
refresh();
</script>
</body>
</html>
)rawliteral");
}

// =======================
// INITIALISATION
// =======================
void setup() {
  Serial.begin(9600);
  dht.begin();

  // Entête CSV pour Excel / Data Streamer
  Serial.println("Humidity,TempC,SoilPercent,Pump,Mode");

  pinMode(WATER_PUMP_PIN, OUTPUT);
  digitalWrite(WATER_PUMP_PIN, LOW);

  lcd.init();
  lcd.backlight();

  lcdConnect();

  while (status != WL_CONNECTED) {
    status = WiFi.begin(ssid, pass);
    delay(8000);
  }

  server.begin();

  lcdIP();
  delay(2000);

  // Lecture initiale
  readSensors();
}

// =======================
// BOUCLE PRINCIPALE
// =======================
void loop() {
  unsigned long now = millis();

  // Lecture périodique des capteurs + envoi CSV pour Excel
  if (now - lastSensorMs >= SENSOR_MS) {
    lastSensorMs = now;
    readSensors();

    if (dhtOk) {
      Serial.print(humidity, 1);
      Serial.print(",");
      Serial.print(temperature, 1);
      Serial.print(",");
      Serial.print(soilPercent);
      Serial.print(",");
      Serial.print(",");
    } else {
      Serial.println("ERROR,ERROR,ERROR,ERROR,ERROR");
    }
  }

  handlePump(now);
  updateLCD(now);

  WiFiClient c = server.available();
  if (!c) return;

  String req = c.readStringUntil('\r');
  c.readStringUntil('\n');

  while (c.available()) c.read();

  if (req.indexOf("/data") >= 0) {
    sendJSON(c);
  }
  else if (req.indexOf("/pump?mode=auto") >= 0) {
    pumpMode = AUTO;
    sendJSON(c);
  }
  else if (req.indexOf("/pump?mode=on") >= 0) {
    pumpMode = MANUAL_ON;
    sendJSON(c);
  }
  else if (req.indexOf("/pump?mode=off") >= 0) {
    pumpMode = MANUAL_OFF;
    sendJSON(c);
  }
  else {
    sendPage(c);
  }

  c.stop();
}

Auteur/autrice

Publications similaires

  • |

    Compteur de Pompes

    Ce projet est pour créer un compteur de pompes intelligent à l’aide d’un microcontrôleur ESP32. Il permet d’automatiser le comptage des répétitions pendant les entraînements et il permet à l’utilisateur un retour en temps réel, visuel, sonore et numérique. Présentation générale et contexte L’idée de départ vient de rendre le comptage des « pushups » plus simple…

  • |

    Petit bolide motorisé

    Voici une petite voiture imprimée en 3D, équipée de quatre roues, d’un moteur, d’un bouton et de quatre DEL. Stylisée pour un design unique, elle est contrôlée par un Arduino Nano relié à un PCB que j’ai moi-même fabriqué. Ce projet combine impression 3D, électronique et programmation pour donner vie à un véhicule miniature fonctionnel…

  • |

    Contrôleur de ventilateur

    Ce projet consiste à contrôler un moteur à courant continu bidirectionnel (capable de tourner dans les deux sens) à l’aide d’un potentiomètre pour ajuster la vitesse et d’un bouton pour inverser le sens de rotation. Fonctionnement du projet Le potentiomètre, connecté à l’entrée analogique A0, ajuste la vitesse en générant une valeur de puissance (0…

  • |

    Voiture guidée par main

    Ce projet à pour but de développer une voiture télécommandée contrôlée par des gestes du poignet en utilisant une connexion Bluetooth Embarquez dans une aventure technologique unique où je vous dévoile chaque étape de la création d’un contrôleur gestuel pour voiture RC, conçu pour une expérience de pilotage intuitive et immersive. Imaginez piloter une voiture…

  • Robot Autonome

    Projet qui permettra aux futurs étudiants de TGE de fabriquer un robot à batteries, capable de se charger via une prise USB-C et de se déplacer de manière autonome sans causer d’accidents. Pour ce faire, des batteries lithium-ion ont été utilisées. Leur autonomie et les précautions nécessaires à prendre lors de leur charge faisaient en…