🌿 GreenTech Agricole

Déploiement d'une Serre Intelligente Pilotée par Raspberry Pi — TFE 2025–2026

6+
Composants matériels
3
Capteurs intégrés
100%
Cloud Firebase
2
Fichiers Python

🎯 Objectif du projet

Automatiser la gestion d'une serre agricole grâce à des capteurs connectés, un Raspberry Pi, Firebase et une interface graphique Tkinter. Le système surveille température, humidité, sol et pluie, et contrôle automatiquement pompe et ventilateur.

📌 Description

Ce projet consiste à déployer une serre intelligente pilotée par Raspberry Pi. Des capteurs mesurent en temps réel la température, l'humidité de l'air, l'humidité du sol et la détection de pluie. Les données sont envoyées sur Firebase et affichées dans une interface Tkinter.


🔄 Flux de données


Capteurs  →  Raspberry Pi (code_systeme.py)  →  Firebase Realtime DB  →  Interface Tkinter (code_tkinter.py)
+ Stockage local MySQL · Affichage LCD local · Alertes buzzer/LED


📁 Architecture des fichiers


📁 serre_venv/
  ├── 🐍 code_systeme.py     — Programme principal Raspberry Pi
  ├── 🐍 code_tkinter.py     — Interface Tkinter (dashboard)
  └── ⚙️ config.json         — Configuration Firebase & paramètres

🔧 Composants utilisés

  • Raspberry Pi 4 — Unité centrale de traitement
  • DHT22 — Capteur température & humidité air
  • ADS1115 + Capteur sol — Humidité du sol (analogique)
  • Capteur de pluie — Détection pluie
  • Pompe à eau — Arrosage automatique via relais
  • Ventilateur — Régulation température via relais
  • LCD I2C 16x2 — Affichage local
  • Buzzer + LED rouge — Alertes sonores et visuelles

☁️ Firebase Realtime Database

Les données capteurs sont envoyées toutes les 2 secondes sur Firebase. L'interface Tkinter lit ces données en temps réel et les affiche avec des indicateurs visuels. Les commandes (pompe, ventilateur, mode auto) sont aussi gérées via Firebase.

🗄️ Base de données MySQL locale

Un historique local est conservé dans MySQL. Les tables capteurs et plantes stockent les relevés et les profils de plantes configurés par l'utilisateur.

⬇️ Télécharger le rapport PDF (2025-2026)

📋 Table des matières

  • Introduction
  • 1. Analyse des besoins
    • 1.2 Description du problème
    • 1.3 Public cible / utilisateur
    • 1.4 Cahier des charges & matériels utilisés
  • 2. Conception du projet (architecture, topologie, schéma mécanique, maquette)
  • 3. Déploiement et mise en ligne – karakusravza.be
  • 4. Explication du code principal (code_systeme.py)
  • 5. Base de données (Firebase Auth, Realtime Database)
  • 6. Architecture des fichiers
  • 7. Problèmes rencontrés & solutions
  • 8. Réalisation
  • Bibliographie

📝 Introduction

Ce projet de fin d'études porte sur le déploiement d'une serre intelligente pilotée par Raspberry Pi. L'objectif est d'automatiser la gestion d'une serre agricole grâce à des capteurs, des actionneurs et une connexion cloud.


J'ai choisi le Raspberry Pi car j'ai déjà quelques connaissances sur cet outil : j'ai suivi un an de cours de projet sur le Raspberry Pi et je me suis dit que cela pourrait être une bonne opportunité de me perfectionner et d'apprendre davantage sur cet appareil.

⚠️ Problèmes rencontrés & Solutions

  • Courts-circuits : Détectés lors du câblage, corrigés en revérifiant chaque connexion.
  • Mise en veille du Raspberry Pi : Désactivée via les paramètres système pour éviter les interruptions.
  • Blocage sur Raspberry Pi : Travail continué sur Wokwi (simulateur en ligne) pour avancer le code sans le matériel.

🔔 Système d'alertes

  • Notification visuelle : Une LED rouge s'allume et l'écran affiche un message si quelque chose ne va pas.
  • Alertes sonores : Un buzzer sonne si la température dépasse un seuil, si le réservoir est vide, ou si un capteur ne fonctionne pas.
📎 Code source : github.com/Anzai0/tfe-R  ·  🌐 Site : karakusravza.be
🐍 code_systeme.py
🖥️ code_tkinter.py
code_systeme.py
# -*- coding: utf-8 -*-
# code principal de la serre connectee
# gere les capteurs, le lcd, le buzzer et firebase

import time
import board
import busio
import digitalio
import adafruit_dht
import adafruit_ads1x15.ads1115 as ADS
from adafruit_ads1x15.analog_in import AnalogIn
from RPLCD.i2c import CharLCD
from gpiozero import TonalBuzzer
from gpiozero.tones import Tone
import pyrebase
import os
import json

os.environ['GPIOZERO_PIN_FACTORY'] = 'rpigpio'

# infos pour se connecter a firebase
firebase_config = {
    "apiKey": "AIzaSyA8zkZrgGnxJzDTxCr3-gZ96DuGAn5B-OE",
    "authDomain": "serre-connecte-60616.firebaseapp.com",
    "projectId": "serre-connecte-60616",
    "storageBucket": "serre-connecte-60616.firebasestorage.app",
    "messagingSenderId": "217991792040",
    "appId": "1:217991792040:web:ae6fd4a5e0953418aec99d",
    "databaseURL": "https://serre-connecte-60616-default-rtdb.europe-west1.firebasedatabase.app"
}

firebase = pyrebase.initialize_app(firebase_config)
auth_fb  = firebase.auth()
db       = firebase.database()

CONFIG_FILE = "config.json"

# je charge la config sauvegardee si elle existe
def charger_config():
    if os.path.exists(CONFIG_FILE):
        with open(CONFIG_FILE, "r") as f:
            return json.load(f)
    return None

# je sauvegarde l'email et le mot de passe pour pas les retaper
def sauvegarder_config(email, password):
    with open(CONFIG_FILE, "w") as f:
        json.dump({"email": email, "password": password}, f)

# connexion a firebase
def connect_firebase():
    config = charger_config()
    if config:
        print("=== Utilisateur connecte : " + config['email'] + " ===")
    else:
        print("=== Premiere connexion ===")
        email    = input("Email    : ")
        password = input("Password : ")
        sauvegarder_config(email, password)
        config = {"email": email, "password": password}
    try:
        user = auth_fb.sign_in_with_email_and_password(config["email"], config["password"])
        uid  = user["localId"]
        print("[Firebase] Connecte : " + config['email'])
        return uid
    except Exception as e:
        print("[Firebase] Erreur: " + str(e))
        os.remove(CONFIG_FILE)
        return connect_firebase()

# je remet les commandes a zero quand on arrete le programme
def reset_commandes_firebase(uid):
    try:
        db.child("commandes").child(uid).set({
            "pompe": False,
            "ventilateur": False,
            "auto": False
        })
        print("[Firebase] Commandes remises a zero")
    except Exception as e:
        print("[Firebase] Erreur reset: " + str(e))

# initialisation des composants
dht        = adafruit_dht.DHT22(board.D4)
i2c        = busio.I2C(board.SCL, board.SDA)
ads        = ADS.ADS1115(i2c)
canal_sol  = AnalogIn(ads, ADS.P0)
canal_pluie= AnalogIn(ads, ADS.P1)
lcd        = CharLCD('PCF8574', 0x27, cols=16, rows=2)
buzzer     = TonalBuzzer(18)
pompe      = digitalio.DigitalInOut(board.D17)
pompe.direction = digitalio.Direction.OUTPUT
ventilateur = digitalio.DigitalInOut(board.D27)
ventilateur.direction = digitalio.Direction.OUTPUT

uid              = connect_firebase()
mode_auto        = False
plante_active    = None
cmd_pompe_manuelle   = False
cmd_ventilo_manuelle = False
ecran            = 0

# je lis les commandes firebase dans un thread
import threading

def ecouter_commandes():
    global mode_auto, plante_active, cmd_pompe_manuelle, cmd_ventilo_manuelle
    while True:
        try:
            data = db.child("commandes").child(uid).get().val()
            if data:
                mode_auto            = data.get("auto", False)
                cmd_pompe_manuelle   = data.get("pompe", False)
                cmd_ventilo_manuelle = data.get("ventilateur", False)
                plante_active        = data.get("plante_active", None)
        except:
            pass
        time.sleep(1)

threading.Thread(target=ecouter_commandes, daemon=True).start()

def envoyer_donnees_firebase(uid, temp, hum, sol, pluie):
    try:
        db.child("capteurs").child(uid).set({
            "temperature"  : temp,
            "humidite_air" : hum,
            "humidite_sol" : sol,
            "pluie"        : pluie
        })
    except Exception as e:
        print("[Firebase] Erreur envoi: " + str(e))

# boucle principale
try:
    while True:
        try:
            temp = dht.temperature
            hum  = dht.humidity
        except:
            temp = None
            hum  = None

        val_sol        = canal_sol.value
        pourcentage_sol= round(max(0, min(100, (1 - val_sol / 26000) * 100)), 1)
        val_pluie      = canal_pluie.value
        pluie          = val_pluie < 10000

        # logique automatique selon les seuils de la plante
        if mode_auto and plante_active and isinstance(plante_active, dict):
            temp_max = plante_active.get("temp_max", 30)
            eau_min  = plante_active.get("eau_min",  30)

            if temp and temp > temp_max:
                ventilateur.value = True
                etat_ventilo = "ON"
            else:
                ventilateur.value = False
                etat_ventilo = "OFF"

            if pourcentage_sol < eau_min:
                pompe.value  = True
                etat_pompe   = "ON"
            else:
                pompe.value  = False
                etat_pompe   = "OFF"
        else:
            if cmd_pompe_manuelle:
                pompe.value = True;  etat_pompe   = "ON"
            else:
                pompe.value = False; etat_pompe   = "OFF"

            if cmd_ventilo_manuelle:
                ventilateur.value = True;  etat_ventilo = "ON"
            else:
                ventilateur.value = False; etat_ventilo = "OFF"

        if uid and temp and hum:
            envoyer_donnees_firebase(uid, temp, hum, pourcentage_sol, pluie)

        # affichage LCD rotatif
        lcd.clear()
        if ecran == 0:
            lcd.write_string("Temp: " + (str(temp) + "C" if temp else "--"))
            lcd.cursor_pos = (1, 0)
            lcd.write_string("Hum : " + (str(hum) + "%"  if hum  else "--"))
        elif ecran == 1:
            lcd.write_string("Sol : " + str(pourcentage_sol) + "%")
            lcd.cursor_pos = (1, 0)
            lcd.write_string("Pluie: " + ("OUI" if pluie else "NON"))
        else:
            lcd.write_string("Pompe: "  + etat_pompe)
            lcd.cursor_pos = (1, 0)
            lcd.write_string("Ventil: " + etat_ventilo)

        ecran = (ecran + 1) % 3
        time.sleep(2)

finally:
    pompe.value = False
    pompe.deinit()
    ventilateur.value = False
    ventilateur.deinit()
    buzzer.stop()
    dht.exit()
    lcd.clear()
    if uid:
        reset_commandes_firebase(uid)
    print("Systeme arrete")
code_tkinter.py
# -*- coding: utf-8 -*-
# interface graphique de la serre connectee
# permet de voir les capteurs et controler la pompe et le ventilateur

import tkinter as tk
from tkinter import messagebox
import threading
import time
import pymysql
pymysql.install_as_MySQLdb()
import pyrebase

# connexion firebase
firebase_config = {
    "apiKey": "AIzaSyA8zkZrgGnxJzDTxCr3-gZ96DuGAn5B-OE",
    "authDomain": "serre-connecte-60616.firebaseapp.com",
    "projectId": "serre-connecte-60616",
    "storageBucket": "serre-connecte-60616.firebasestorage.app",
    "messagingSenderId": "217991792040",
    "appId": "1:217991792040:web:ae6fd4a5e0953418aec99d",
    "databaseURL": "https://serre-connecte-60616-default-rtdb.europe-west1.firebasedatabase.app"
}

firebase = pyrebase.initialize_app(firebase_config)
auth     = firebase.auth()
db       = firebase.database()

# couleurs de l'interface
FOND   = "#0a0f1e"
FOND2  = "#0d1529"
CARTE  = "#111d35"
CARTE2 = "#162040"
CARTE3 = "#1a2650"
TEXTE  = "#e8f4f8"
GRIS   = "#4a6fa5"
GRIS2  = "#1e2d50"
VERT   = "#00ff88"
VERT2  = "#00cc6a"
ROUGE  = "#ff4757"
ORANGE = "#ffa502"
BLEU   = "#1e90ff"
ACCENT = "#00d4aa"
ACCENT2= "#009977"
TITRE  = "#00d4aa"
CONSOLE= "#050d1a"

# connexion a la base de donnees mysql
def get_db():
    return pymysql.connect(
        host="localhost", user="ravza",
        password="ravza", database="serre", charset="utf8mb4"
    )

# je cree les tables si elles existent pas encore
def init_db():
    try:
        conn = get_db()
        cur  = conn.cursor()
        cur.execute("""
            CREATE TABLE IF NOT EXISTS capteurs (
                id            INT AUTO_INCREMENT PRIMARY KEY,
                uid           VARCHAR(255),
                temperature   FLOAT,
                humidite_air  FLOAT,
                humidite_sol  FLOAT,
                pluie         BOOLEAN,
                timestamp     DATETIME DEFAULT CURRENT_TIMESTAMP
            )""")
        cur.execute("""
            CREATE TABLE IF NOT EXISTS plantes (
                id          INT AUTO_INCREMENT PRIMARY KEY,
                uid         VARCHAR(255),
                nom         VARCHAR(100),
                temp_max    FLOAT,
                humidite_min FLOAT,
                eau_min     FLOAT
            )""")
        conn.commit()
        cur.close()
        conn.close()
    except Exception as e:
        print("[DB] Erreur init: " + str(e))

# style pour les boutons
def style_bouton(bouton, bg=ACCENT, fg=FOND, taille=10):
    bouton.configure(
        bg=bg, fg=fg,
        font=("Courier", taille, "bold"),
        relief="flat", bd=0, cursor="hand2",
        activebackground=ACCENT2, activeforeground=FOND
    )

# style pour les champs de texte
def style_entry(entry):
    entry.configure(
        bg=GRIS2, fg=TEXTE,
        font=("Courier", 10),
        relief="flat", bd=0,
        insertbackground=ACCENT, selectbackground=ACCENT
    )

# ---- page de connexion ----
class PageConnexion(tk.Frame):
    def __init__(self, parent, quand_connecte):
        super().__init__(parent, bg=FOND)
        self.quand_connecte = quand_connecte
        self.mode = "connexion"
        self.construire()

    def construire(self):
        wrap = tk.Frame(self, bg=FOND)
        wrap.place(relx=0.5, rely=0.5, anchor="center")

        tk.Label(wrap, text="◈ SERRE CONNECTEE",
                 font=("Courier", 22, "bold"),
                 bg=FOND, fg=ACCENT).pack(pady=(0, 4))
        tk.Label(wrap, text="Systeme de monitoring IoT",
                 font=("Courier", 10),
                 bg=FOND, fg=GRIS).pack()
        tk.Frame(wrap, bg=ACCENT, height=2).pack(fill="x", pady=(16, 20))

        card = tk.Frame(wrap, bg=CARTE, padx=30, pady=30)
        card.pack(ipadx=10, ipady=10)

        self.lbl_titre = tk.Label(card, text="[ SE CONNECTER ]",
                                    font=("Courier", 13, "bold"),
                                    bg=CARTE, fg=ACCENT)
        self.lbl_titre.pack(pady=(0, 20))

        tk.Label(card, text="EMAIL", font=("Courier", 8),
                 bg=CARTE, fg=GRIS).pack(anchor="w")
        self.e_email = tk.Entry(card, width=28)
        style_entry(self.e_email)
        self.e_email.pack(pady=(2, 12),
                         ipady=6)

        tk.Label(card, text="MOT DE PASSE", font=("Courier", 8),
                 bg=CARTE, fg=GRIS).pack(anchor="w")
        self.e_mdp = tk.Entry(card, width=28, show="*")
        style_entry(self.e_mdp)
        self.e_mdp.pack(pady=(2, 16), ipady=6)

        self.btn_ok = tk.Button(card, text="[ CONNEXION ]",
                                  command=self._action, width=26)
        style_bouton(self.btn_ok)
        self.btn_ok.pack(pady=(0, 10), ipady=6)

        self.lbl_msg = tk.Label(card, text="",
                                   font=("Courier", 8),
                                   bg=CARTE, fg=ROUGE)
        self.lbl_msg.pack()

    def _action(self):
        email = self.e_email.get().strip()
        mdp   = self.e_mdp.get().strip()
        if not email or not mdp:
            self.lbl_msg.configure(text="[!] Remplissez tous les champs")
            return
        self.btn_ok.configure(text="...", state="disabled")
        self.lbl_msg.configure(text="")
        threading.Thread(target=self._connexion,
                          args=(email, mdp), daemon=True).start()

    def _connexion(self, email, mdp):
        try:
            user = auth.sign_in_with_email_and_password(email, mdp)
            uid  = user["localId"]
            self.after(0, lambda: self.quand_connecte(uid, email))
        except:
            self.after(0, lambda: self.lbl_msg.configure(
                text="[!] Email ou mot de passe incorrect"))
            self.after(0, lambda: self.btn_ok.configure(
                text="[ CONNEXION ]", state="normal"))

# ---- application principale ----
class App(tk.Frame):
    def __init__(self, root):
        super().__init__(root, bg=FOND)
        self.pack(fill="both", expand=True)
        self.root      = root
        self.uid       = None
        self.mode_auto = False
        self.running   = False
        self._afficher_connexion()

    def _afficher_connexion(self):
        for w in self.winfo_children():
            w.destroy()
        page = PageConnexion(self, self._apres_connexion)
        page.pack(fill="both", expand=True)

    def _apres_connexion(self, uid, email):
        self.uid   = uid
        self.email = email
        self._construire_dashboard()
        self.running = True
        threading.Thread(target=self._boucle_donnees,
                          daemon=True).start()

    def _construire_dashboard(self):
        for w in self.winfo_children():
            w.destroy()

        # ---- barre du haut ----
        topbar = tk.Frame(self, bg=CARTE2, height=50)
        topbar.pack(fill="x")
        topbar.pack_propagate(False)
        tk.Label(topbar, text="◈ SERRE CONNECTEE",
                 font=("Courier", 13, "bold"),
                 bg=CARTE2, fg=ACCENT).pack(side="left", padx=16)
        self.lbl_user = tk.Label(topbar, text="● " + self.email,
                                   font=("Courier", 8),
                                   bg=CARTE2, fg=VERT)
        self.lbl_user.pack(side="right", padx=16)

        # ---- contenu principal ----
        main = tk.Frame(self, bg=FOND)
        main.pack(fill="both", expand=True, padx=16, pady=16)

        # colonne gauche : capteurs + controles
        col_g = tk.Frame(main, bg=FOND)
        col_g.pack(side="left", fill="both", expand=True)

        # carte capteurs
        c_cap = tk.Frame(col_g, bg=CARTE, bd=0)
        c_cap.pack(fill="x", pady=(0, 10))
        tk.Label(c_cap, text="[ CAPTEURS EN DIRECT ]",
                 font=("Courier", 10, "bold"),
                 bg=CARTE, fg=ACCENT).pack(anchor="w", padx=12, pady=(10,6))

        self.lbls = {}
        capteurs  = [
            ("🌡", "temperature",  "Température"),
            ("💧", "humidite_air", "Humidité air"),
            ("🌱", "humidite_sol", "Humidité sol"),
            ("🌧", "pluie",        "Pluie"),
        ]
        for ico, cle, nom in capteurs:
            row = tk.Frame(c_cap, bg=CARTE2)
            row.pack(fill="x", padx=8, pady=3)
            tk.Label(row, text=ico + " " + nom,
                     font=("Courier", 9),
                     bg=CARTE2, fg=TEXTE, width=18,
                     anchor="w").pack(side="left", padx=8, pady=6)
            lbl = tk.Label(row, text="---",
                            font=("Courier", 11, "bold"),
                            bg=CARTE2, fg=VERT)
            lbl.pack(side="right", padx=12)
            self.lbls[cle] = lbl
        tk.Frame(c_cap, bg=CARTE, height=8).pack(fill="x")

        # carte controles
        c_ctrl = tk.Frame(col_g, bg=CARTE)
        c_ctrl.pack(fill="x", pady=(0, 10))
        tk.Label(c_ctrl, text="[ CONTROLES ]",
                 font=("Courier", 10, "bold"),
                 bg=CARTE, fg=ACCENT).pack(anchor="w", padx=12, pady=(10,6))

        # mode auto
        row_auto = tk.Frame(c_ctrl, bg=CARTE2)
        row_auto.pack(fill="x", padx=8, pady=4)
        tk.Label(row_auto, text="⚙ Mode automatique",
                 font=("Courier", 9),
                 bg=CARTE2, fg=TEXTE).pack(side="left", padx=8, pady=8)
        self.lbl_auto = tk.Label(row_auto, text="[OFF]",
                                   font=("Courier", 9, "bold"),
                                   bg=CARTE2, fg=ROUGE)
        self.lbl_auto.pack(side="right", padx=8)
        self.btn_auto = tk.Button(row_auto, text="[ACTIVER AUTO]",
                                   command=self._toggle_auto, width=16)
        style_bouton(self.btn_auto)
        self.btn_auto.pack(side="right", padx=8, pady=4)

        # pompe
        row_p = tk.Frame(c_ctrl, bg=CARTE2)
        row_p.pack(fill="x", padx=8, pady=4)
        tk.Label(row_p, text="💧 Pompe à eau",
                 font=("Courier", 9),
                 bg=CARTE2, fg=TEXTE).pack(side="left", padx=8, pady=8)
        self.lbl_pompe = tk.Label(row_p, text="[OFF]",
                                    font=("Courier", 9, "bold"),
                                    bg=CARTE2, fg=ROUGE)
        self.lbl_pompe.pack(side="right", padx=8)
        self.btn_pompe = tk.Button(row_p, text="[ACTIVER]",
                                    command=lambda: self._toggle("pompe"),
                                    width=14)
        style_bouton(self.btn_pompe)
        self.btn_pompe.pack(side="right", padx=8, pady=4)

        # ventilateur
        row_v = tk.Frame(c_ctrl, bg=CARTE2)
        row_v.pack(fill="x", padx=8, pady=4)
        tk.Label(row_v, text="🌀 Ventilateur",
                 font=("Courier", 9),
                 bg=CARTE2, fg=TEXTE).pack(side="left", padx=8, pady=8)
        self.lbl_ventilo = tk.Label(row_v, text="[OFF]",
                                      font=("Courier", 9, "bold"),
                                      bg=CARTE2, fg=ROUGE)
        self.lbl_ventilo.pack(side="right", padx=8)
        self.btn_ventilo = tk.Button(row_v, text="[ACTIVER]",
                                      command=lambda: self._toggle("ventilateur"),
                                      width=14)
        style_bouton(self.btn_ventilo)
        self.btn_ventilo.pack(side="right", padx=8, pady=4)
        tk.Frame(c_ctrl, bg=CARTE, height=8).pack(fill="x")

        # colonne droite : console
        col_d = tk.Frame(main, bg=FOND, width=340)
        col_d.pack(side="right", fill="both", padx=(12, 0))
        col_d.pack_propagate(False)

        c_console = tk.Frame(col_d, bg=CARTE)
        c_console.pack(fill="both", expand=True)
        hdr = tk.Frame(c_console, bg=CARTE)
        hdr.pack(fill="x")
        tk.Label(hdr, text="[ CONSOLE ]",
                 font=("Courier", 10, "bold"),
                 bg=CARTE, fg=ACCENT).pack(side="left", padx=12, pady=8)
        tk.Button(hdr, text="EFFACER", command=self._clear_console,
                  font=("Courier", 7), bg=GRIS2, fg=GRIS,
                  relief="flat", cursor="hand2").pack(
                  side="right", padx=8)

        self.console = tk.Text(c_console, bg=CONSOLE, fg=VERT,
                                 font=("Courier", 8),
                                 state="disabled", wrap="word",
                                 relief="flat", bd=0)
        self.console.pack(fill="both", expand=True, padx=6, pady=6)
        self.console.tag_configure("TIME", foreground=GRIS)
        self.console.tag_configure("INFO", foreground=ACCENT)
        self.console.tag_configure("WARN", foreground=ORANGE)
        self.console.tag_configure("ERR",  foreground=ROUGE)

    def _log(self, niveau, msg):
        self.console.configure(state="normal")
        t = time.strftime("%H:%M:%S")
        self.console.insert("end", f"[{t}] ", "TIME")
        self.console.insert("end", f"[{niveau}] {msg}\n", niveau)
        self.console.see("end")
        self.console.configure(state="disabled")

    def _clear_console(self):
        self.console.configure(state="normal")
        self.console.delete("1.0", "end")
        self.console.configure(state="disabled")

    def _toggle_auto(self):
        self.mode_auto = not self.mode_auto
        try:
            db.child("commandes").child(self.uid).update(
                {"auto": self.mode_auto})
            etat = "ON" if self.mode_auto else "OFF"
            self.lbl_auto.configure(
                text="[" + etat + "]",
                fg=VERT if self.mode_auto else ROUGE)
            self.btn_auto.configure(
                text="[DESACTIVER]" if self.mode_auto else "[ACTIVER AUTO]")
            self._log("INFO", "Mode auto: " + etat)
        except Exception as e:
            self._log("ERR", str(e))

    def _toggle(self, cle):
        try:
            data = db.child("commandes").child(self.uid).get().val() or {}
            etat = not data.get(cle, False)
            db.child("commandes").child(self.uid).update({cle: etat})
            self._log("INFO", cle + " → " + ("ON" if etat else "OFF"))
        except Exception as e:
            self._log("ERR", str(e))

    def _boucle_donnees(self):
        while self.running:
            try:
                data = db.child("capteurs").child(self.uid).get().val()
                cmd  = db.child("commandes").child(self.uid).get().val() or {}
                if data:
                    self.after(0, lambda d=data, c=cmd: self._maj_ui(d, c))
            except Exception as e:
                self.after(0, lambda err=e: self._log("ERR", str(err)))
            time.sleep(2)

    def _maj_ui(self, data, cmd):
        temp  = data.get("temperature", None)
        hum   = data.get("humidite_air", None)
        sol   = data.get("humidite_sol", None)
        pluie = data.get("pluie", False)

        if temp: self.lbls["temperature"].configure(text=str(temp) + " °C")
        if hum:  self.lbls["humidite_air"].configure(text=str(hum)  + " %")
        if sol:  self.lbls["humidite_sol"].configure(text=str(sol)  + " %")
        self.lbls["pluie"].configure(
            text="OUI" if pluie else "NON",
            fg=BLEU if pluie else GRIS)

        pompe_on   = cmd.get("pompe", False)
        ventilo_on = cmd.get("ventilateur", False)

        self.lbl_pompe.configure(
            text="[ON]"  if pompe_on else "[OFF]",
            fg=VERT if pompe_on else ROUGE)
        self.btn_pompe.configure(
            text="[DESACTIVER]" if pompe_on else "[ACTIVER]")

        self.lbl_ventilo.configure(
            text="[ON]"  if ventilo_on else "[OFF]",
            fg=VERT if ventilo_on else ROUGE)
        self.btn_ventilo.configure(
            text="[DESACTIVER]" if ventilo_on else "[ACTIVER]")

        self._log("INFO", f"T={temp}°C H={hum}% Sol={sol}% Pluie={pluie}")

# ---- lancement ----
if __name__ == "__main__":
    init_db()
    root = tk.Tk()
    root.title("GreenTech – Serre Intelligente")
    root.geometry("1100x700")
    root.configure(bg=FOND)
    root.resizable(True, True)
    app = App(root)
    root.mainloop()
🔗 Code source complet sur GitHub : github.com/Anzai0/tfe-R
🖥️
Raspberry Pi 4 Model B

Unité centrale du projet, gère tous les capteurs et la logique.

€ 55.00

🔧 Pourquoi ? Le Raspberry Pi est le cerveau du système. Il lit les capteurs, contrôle les actionneurs, envoie les données sur Firebase et fait tourner l'interface Tkinter.


📋 RAM : 4GB · GPIO 40 pins · WiFi intégré · Linux Raspbian

🌡️
Capteur DHT22

Mesure la température et l'humidité de l'air avec précision.

€ 8.50

🔧 Pourquoi ? Surveiller la température et l'humidité est essentiel pour les plantes. Si la température dépasse le seuil défini, le ventilateur s'enclenche automatiquement.


📋 GPIO 4 · Plage : -40°C à 80°C · Précision ±0.5°C · Alimentation 3.3V

🌱
Capteur humidité sol + ADS1115

Mesure l'humidité du sol pour déclencher l'arrosage automatique.

€ 6.00

🔧 Pourquoi ? Le Raspberry Pi n'a pas d'entrée analogique. L'ADS1115 convertit le signal du capteur de sol en numérique pour être lu via I2C.


📋 ADS1115 : I2C 0x48 · Capteur sol : canal P0 · 16 bits · 3.3V

🌧️
Capteur de pluie

Détecte la présence de pluie pour stopper l'arrosage si nécessaire.

€ 3.50

🔧 Pourquoi ? Inutile d'arroser s'il pleut déjà. Ce capteur évite le gaspillage d'eau en coupant automatiquement la pompe lors de précipitations.


📋 ADS1115 canal P1 · Seuil détection : valeur < 10000 · 3.3V

💧
Pompe à eau + Relais

Arrosage automatique déclenché par le niveau d'humidité du sol.

€ 9.00

🔧 Pourquoi ? La pompe est contrôlée via un relais branché sur GPIO 17. En mode auto, elle s'active si le sol est trop sec selon le profil de plante configuré.


📋 GPIO 17 · Relais 5V · Pompe 3-6V · Contrôle ON/OFF via Firebase

🌀
Ventilateur + Relais

Régule la température en s'activant automatiquement si trop chaud.

€ 7.00

🔧 Pourquoi ? En mode automatique, si la température dépasse le seuil défini pour la plante, le ventilateur s'enclenche pour refroidir la serre.


📋 GPIO 27 · Relais 5V · 5V DC · Contrôle via Firebase commandes

📺
Écran LCD I2C 16x2

Affiche les données capteurs localement sans avoir besoin d'un PC.

€ 5.00

🔧 Pourquoi ? Permet de voir la température, humidité, état pompe/ventilateur directement sur la serre, sans connexion à l'interface Tkinter.


📋 I2C adresse 0x27 · 16 colonnes × 2 lignes · 3 écrans rotatifs toutes les 2s

🔔
Buzzer actif + LED rouge

Système d'alerte sonore et visuelle en cas de problème détecté.

€ 2.50

🔧 Pourquoi ? En cas de surchauffe, capteur défaillant ou réservoir vide, le buzzer sonne et la LED rouge s'allume pour alerter immédiatement.


📋 Buzzer : GPIO 18 (PWM) · LED rouge : GPIO 24 · 3.3V

🛒 Panier

    Total : € 0.00
    👩‍💻

    Ravza Karakus

    🎓 6TQJ – Technicienne en Informatique · INRACI Forest

    Passionnée par l'IoT et les systèmes embarqués, j'ai réalisé ce projet de serre intelligente comme Travail de Fin d'Études. Ce projet m'a permis de combiner électronique, programmation Python, cloud Firebase et développement web.

    📬 Contact

    🌐 karakusravza.be
    💻 github.com/Anzai0/tfe-R
    🏫 INRACI · Forest · Bruxelles

    🎯 Ce projet en chiffres

    📦 8 composants matériels
    🐍 2 fichiers Python principaux
    ☁️ Firebase Realtime Database
    🗄️ Base de données MySQL locale
    🌐 Site web déployé sur LWS