Déploiement d'une Serre Intelligente Pilotée par Raspberry Pi — TFE 2025–2026
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.
Automatisation complète d'une serre agricole via IoT et Cloud
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.
Capteurs
→ Raspberry Pi (code_systeme.py)
→ Firebase Realtime DB
→ Interface Tkinter (code_tkinter.py)
+ Stockage local MySQL · Affichage LCD local · Alertes buzzer/LED
Architecture, composants et fonctionnement du système
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.
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.
Travail de Fin d'Études · INRACI Forest · 6TQJ · 2025–2026
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.
Code complet des deux fichiers principaux.
# -*- 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")
# -*- 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()
Tous les composants utilisés. Ajoutez-les au panier pour calculer le coût total.
Unité centrale du projet, gère tous les capteurs et la logique.
🔧 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
Mesure la température et l'humidité de l'air avec précision.
🔧 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
Mesure l'humidité du sol pour déclencher l'arrosage automatique.
🔧 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
Détecte la présence de pluie pour stopper l'arrosage si nécessaire.
🔧 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
Arrosage automatique déclenché par le niveau d'humidité du sol.
🔧 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
Régule la température en s'activant automatiquement si trop chaud.
🔧 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
Affiche les données capteurs localement sans avoir besoin d'un PC.
🔧 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
Système d'alerte sonore et visuelle en cas de problème détecté.
🔧 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
Étudiante en informatique · INRACI Forest · TFE 2025–2026
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.
🌐 karakusravza.be
💻 github.com/Anzai0/tfe-R
🏫 INRACI · Forest · Bruxelles
📦 8 composants matériels
🐍 2 fichiers Python principaux
☁️ Firebase Realtime Database
🗄️ Base de données MySQL locale
🌐 Site web déployé sur LWS