Compare commits

..

1 Commits

Author SHA1 Message Date
8735d4967a maj 2026-04-02 14:51:43 +02:00
17 changed files with 986 additions and 779 deletions

View File

@@ -18,13 +18,10 @@ L'application repose sur un serveur **Flask** (Python) qui expose une API et ser
loustiques-home/ loustiques-home/
├── flask/ ├── flask/
│ ├── main.py # Point d'entrée Flask — routes et API │ ├── main.py # Point d'entrée Flask — routes et API
├── .env # Variable d'environnement
│ ├── auth.py # Authentification et gestion des utilisateurs │ ├── auth.py # Authentification et gestion des utilisateurs
│ ├── add_user.py # Ajout d'utilisateur (appelé par l'admin) │ ├── add_user.py # Ajout d'utilisateur (appelé par l'admin)
│ ├── led.py # Contrôle de la LED via GPIO │ ├── led.py # Contrôle de la LED via GPIO
│ ├── log.py │ ├── log.py # Configuration du système de logs
├── run_flask.sh # Script de démarrage du serveur
├── requirement.txt # Dépendances Python # Configuration du système de logs
│ └── templates/ │ └── templates/
│ ├── index.html # Page de connexion │ ├── index.html # Page de connexion
│ ├── dashboard.html # Tableau de bord principal │ ├── dashboard.html # Tableau de bord principal
@@ -36,7 +33,9 @@ loustiques-home/
│ └── ssl/ │ └── ssl/
│ ├── cert.pem # Certificat SSL (généré automatiquement) │ ├── cert.pem # Certificat SSL (généré automatiquement)
│ └── key.pem # Clé privée SSL (générée automatiquement) │ └── key.pem # Clé privée SSL (générée automatiquement)
├── run_flask.sh # Script de démarrage du serveur
├── requirement.txt # Dépendances Python
└── .env # Variables d'environnement (non versionné)
``` ```
--- ---
@@ -59,10 +58,6 @@ sudo bash run_flask.sh
Génère un certificat SSL auto-signé (`cert.pem` + `key.pem`) dans `web_secu/ssl/`. Le certificat est valable 365 jours et est émis pour `loustiques.local`. Si le certificat existe déjà, le script ne le régénère pas. Génère un certificat SSL auto-signé (`cert.pem` + `key.pem`) dans `web_secu/ssl/`. Le certificat est valable 365 jours et est émis pour `loustiques.local`. Si le certificat existe déjà, le script ne le régénère pas.
## Note
le script run_flash.sh s'occupe de la génération pour que le serveuf flask ai d'office un certificat valide
``` ```
C=BE / ST=Brabant Wallon / L=Louvain-La-Neuve / O=Les Loustiques / OU=EPHEC / CN=loustiques.local C=BE / ST=Brabant Wallon / L=Louvain-La-Neuve / O=Les Loustiques / OU=EPHEC / CN=loustiques.local
``` ```
@@ -103,7 +98,6 @@ Gère l'authentification des utilisateurs et la récupération des comptes.
- `init()` — ouvre une connexion MySQL à partir des variables d'environnement `.env` - `init()` — ouvre une connexion MySQL à partir des variables d'environnement `.env`
- `login(username, password)` — vérifie les identifiants en comparant le mot de passe avec le hash bcrypt stocké en base - `login(username, password)` — vérifie les identifiants en comparant le mot de passe avec le hash bcrypt stocké en base
- `get_users()` — retourne la liste de tous les utilisateurs (username, rôle, date de création) - `get_users()` — retourne la liste de tous les utilisateurs (username, rôle, date de création)
- `get_user_by_rfid` - s'occupe de récupérer et chercher l'utilisateur lié à son RFID dans la base de données
--- ---
@@ -113,6 +107,11 @@ Permet d'ajouter un nouvel utilisateur en base de données. Cette fonction est a
--- ---
### `led.py`
Contient la fonction `led(utilisateur)` qui simule (ou déclenche réellement sur le Pi) l'allumage d'une LED. Le code GPIO est commenté pour permettre les tests hors Raspberry Pi. Chaque déclenchement est logué avec le nom de l'utilisateur qui en est à l'origine.
---
### `main.py` ### `main.py`
@@ -241,16 +240,9 @@ CREATE TABLE Auth (
| `bcrypt` | Hashage des mots de passe | | `bcrypt` | Hashage des mots de passe |
| `python-dotenv` | Chargement du fichier `.env` | | `python-dotenv` | Chargement du fichier `.env` |
| `gpiozero` | Contrôle des GPIOs du Raspberry Pi | | `gpiozero` | Contrôle des GPIOs du Raspberry Pi |
| `mfrc` | Bibliothèque destiné au RFID
--- ---
### Note
On a essayé de le finaliser au mieux, mais lautomatisation reste incomplète et larborescence des deux Raspberry Pi nest pas identique. Des améliorations doivent encore être apportées aux scripts afin dassurer une automatisation complète. Il est également possible que certaines informations du README soient manquantes ou ne soient plus en adéquation avec larborescence, celle-ci ayant subi de nombreuses mises à jour techniques et logiques.
## Groupe ## Groupe
Projet réalisé par **Les Loustiques** — étudiants à l'**EPHEC** (Louvain-La-Neuve). Projet réalisé par **Les Loustiques** — étudiants à l'**EPHEC** (Louvain-La-Neuve).
©️ **Les loustiques**

View File

@@ -0,0 +1,243 @@
import RPi.GPIO as GPIO
import time
import threading
# ── Numérotation BCM ────────────────────────────────────────────────────────
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
# ── Broches ─────────────────────────────────────────────────────────────────
PIN_LED_R = 17
PIN_LED_G = 22
PIN_LED_B = 27
PIN_PIR = 15
PIN_BUZZER = 18
# Keypad 4x4 — 4 lignes (sorties) + 4 colonnes (entrées pull-up)
ROWS = [5, 6, 13, 19] # R1 R2 R3 R4
COLS = [26, 12, 16, 20] # C1 C2 C3 C4
KEYPAD_MAP = [
['1', '2', '3', 'A'],
['4', '5', '6', 'B'],
['7', '8', '9', 'C'],
['*', '0', '#', 'D'],
]
# ── Code secret (modifiable ici) ─────────────────────────────────────────────
CODE_SECRET = "1234"
# ── Configuration GPIO ───────────────────────────────────────────────────────
GPIO.setup(PIN_LED_R, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(PIN_LED_G, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(PIN_LED_B, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(PIN_BUZZER, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(PIN_PIR, GPIO.IN)
for row in ROWS:
GPIO.setup(row, GPIO.OUT, initial=GPIO.HIGH)
for col in COLS:
GPIO.setup(col, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# ── État global ──────────────────────────────────────────────────────────────
etat = "desarmee"
etat_lock = threading.Lock()
_stop_buzzer = threading.Event()
_thread_buzzer = None
# ════════════════════════════════════════════════════════════════════════════
# LED RGB
# ════════════════════════════════════════════════════════════════════════════
def led(r=False, g=False, b=False):
"""Allume la LED RGB avec la couleur voulue."""
GPIO.output(PIN_LED_R, GPIO.HIGH if r else GPIO.LOW)
GPIO.output(PIN_LED_G, GPIO.HIGH if g else GPIO.LOW)
GPIO.output(PIN_LED_B, GPIO.HIGH if b else GPIO.LOW)
def led_bleu(): led(b=True)
def led_vert(): led(g=True)
def led_rouge(): led(r=True)
def led_off(): led()
# ════════════════════════════════════════════════════════════════════════════
# Buzzer
# ════════════════════════════════════════════════════════════════════════════
def bip(nb=1, duree=0.08, pause=0.12):
"""Émet nb bip(s) courts."""
for _ in range(nb):
GPIO.output(PIN_BUZZER, GPIO.HIGH)
time.sleep(duree)
GPIO.output(PIN_BUZZER, GPIO.LOW)
time.sleep(pause)
def _buzzer_continu(stop_event: threading.Event):
"""Boucle interne : buzzer ON/OFF jusqu'à stop_event."""
while not stop_event.is_set():
GPIO.output(PIN_BUZZER, GPIO.HIGH)
time.sleep(0.5)
GPIO.output(PIN_BUZZER, GPIO.LOW)
time.sleep(0.5)
GPIO.output(PIN_BUZZER, GPIO.LOW)
# ════════════════════════════════════════════════════════════════════════════
# Keypad 4x4
# ════════════════════════════════════════════════════════════════════════════
def lire_touche():
"""
Scan matriciel : met chaque ligne à LOW tour à tour
et lit les colonnes. Retourne la touche ou None.
"""
for i, row in enumerate(ROWS):
GPIO.output(row, GPIO.LOW)
for j, col in enumerate(COLS):
if GPIO.input(col) == GPIO.LOW:
time.sleep(0.05) # anti-rebond
while GPIO.input(col) == GPIO.LOW:
pass # attente relâchement
GPIO.output(row, GPIO.HIGH)
return KEYPAD_MAP[i][j]
GPIO.output(row, GPIO.HIGH)
return None
def lire_code(nb_chiffres=4, timeout=30):
"""
Attend nb_chiffres touches numériques sur le keypad.
Retourne la chaîne saisie ou '' si timeout.
"""
saisi = ""
debut = time.time()
print(" Code : ", end="", flush=True)
while len(saisi) < nb_chiffres:
if time.time() - debut > timeout:
print("\n [Timeout — saisie annulée]")
return ""
touche = lire_touche()
if touche and touche.isdigit():
saisi += touche
print("*", end="", flush=True)
time.sleep(0.05)
print()
return saisi
# ════════════════════════════════════════════════════════════════════════════
# Transitions d'état
# ════════════════════════════════════════════════════════════════════════════
def passer_en_desarmee():
global etat, _thread_buzzer
_stop_buzzer.set()
if _thread_buzzer and _thread_buzzer.is_alive():
_thread_buzzer.join()
with etat_lock:
etat = "desarmee"
led_bleu()
print("[ÉTAT] ● DÉSARMÉE — LED bleue")
def passer_en_armee():
global etat
with etat_lock:
etat = "armee"
led_vert()
bip(nb=2) # 2 petits bips = armée avec succès
print("[ÉTAT] ● ARMÉE — LED verte — PIR actif")
def passer_en_declenchee():
global etat, _thread_buzzer
with etat_lock:
etat = "declenchee"
led_rouge()
print("[ÉTAT] ● DÉCLENCHÉE — LED rouge — buzzer actif")
_stop_buzzer.clear()
_thread_buzzer = threading.Thread(
target=_buzzer_continu, args=(_stop_buzzer,), daemon=True
)
_thread_buzzer.start()
# ════════════════════════════════════════════════════════════════════════════
# Thread : surveillance PIR
# ════════════════════════════════════════════════════════════════════════════
def _surveiller_pir(stop_evt: threading.Event):
"""Lit le PIR toutes les 100 ms. Déclenche si mouvement et armée."""
print("[PIR] Surveillance démarrée")
while not stop_evt.is_set():
with etat_lock:
etat_local = etat
if etat_local == "armee" and GPIO.input(PIN_PIR) == GPIO.HIGH:
print("[PIR] ⚠ Mouvement détecté !")
passer_en_declenchee()
time.sleep(0.1)
# ════════════════════════════════════════════════════════════════════════════
# Boucle principale
# ════════════════════════════════════════════════════════════════════════════
def boucle_principale():
global etat
# Démarrage : LED bleue (désarmée)
passer_en_desarmee()
# Thread PIR en arrière-plan
stop_pir = threading.Event()
thread_pir = threading.Thread(
target=_surveiller_pir, args=(stop_pir,), daemon=True
)
thread_pir.start()
print("\n=== Système d'alarme démarré ===")
print(" Tapez le code sur le keypad pour armer / désarmer.\n")
try:
while True:
with etat_lock:
etat_local = etat
# ── DÉSARMÉE : attente d'un code pour armer ──────────────────────
if etat_local == "desarmee":
print(" → Saisir le code pour ARMER :")
code = lire_code(nb_chiffres=len(CODE_SECRET))
if code == CODE_SECRET:
print(" ✔ Code correct → armement")
passer_en_armee()
elif code != "":
print(" ✘ Code incorrect")
bip(nb=1, duree=0.4) # 1 bip long = erreur
# ── ARMÉE : le thread PIR gère le déclenchement ──────────────────
elif etat_local == "armee":
time.sleep(0.1)
# ── DÉCLENCHÉE : attente du code pour désarmer ───────────────────
elif etat_local == "declenchee":
print(" → Saisir le code pour DÉSARMER :")
code = lire_code(nb_chiffres=len(CODE_SECRET))
if code == CODE_SECRET:
print(" ✔ Code correct → désarmement")
passer_en_desarmee()
elif code != "":
print(" ✘ Code incorrect — alarme maintenue")
except KeyboardInterrupt:
print("\n[INFO] Arrêt demandé (Ctrl+C)")
finally:
stop_pir.set()
_stop_buzzer.set()
led_off()
GPIO.cleanup()
print("[INFO] GPIO libérés. Fin du programme.")
# ════════════════════════════════════════════════════════════════════════════

View File

@@ -1,10 +1,14 @@
import Adafruit_DHT as dht import Adafruit_DHT as dht
# On définit juste le capteur et la broche (Rappel : 25 en BCM = broche physique 22)
capteur = dht.DHT11 capteur = dht.DHT11
pin = 25 pin = 25
def lire_temperature(): def lire_temperature():
humidite, temperature = dht.read_retry(capteur, pin) humidite, temperature = dht.read_retry(capteur, pin)
# On renvoie la température au script principal !
if temperature is not None: if temperature is not None:
return temperature return temperature
else: else:
return 0 return 0 # Sécurité si le capteur bugge, pour ne pas faire planter l'affichage

View File

@@ -1,12 +0,0 @@
import RPi.GPIO as GPIO
LDR_PIN = 20
GPIO.setmode(GPIO.BCM)
GPIO.setup(LDR_PIN, GPIO.IN)
def lire_etat():
if GPIO.input(LDR_PIN) == GPIO.HIGH:
return "Nuit"
return "Jour"

View File

@@ -1,186 +1,246 @@
import RPi.GPIO as GPIO
import time import time
import threading import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False) GPIO.setwarnings(False)
PIN_LED_R = 17
PIN_LED_G = 22
PIN_LED_B = 3
PIN_PIR = 23
PIN_BUZZER = 18
ROWS = [5, 6, 13, 19] class SystemeAlarme:
COLS = [26, 12, 16, 20] def __init__(self):
"""
Initialise les composants liés à l'alarme.
KEYPAD_MAP = [ Cette classe gère uniquement la logique locale de sécurité :
['1', '2', '3', 'A'], - le capteur PIR
['4', '5', '6', 'B'], - le buzzer
['7', '8', '9', 'C'], - la LED RGB de statut
['*', '0', '#', 'D'], - le clavier 4x4
]
CODE_SECRET = "1234" Elle ne dépend d'aucune autre partie du projet.
"""
# -----------------------------
# Définition des pins physiques
# -----------------------------
self.pinPir = 15
self.pinBuzzer = 18
GPIO.setup(PIN_LED_R, GPIO.OUT, initial=GPIO.LOW) self.pinLedRouge = 17
GPIO.setup(PIN_LED_G, GPIO.OUT, initial=GPIO.LOW) self.pinLedVerte = 27
GPIO.setup(PIN_LED_B, GPIO.OUT, initial=GPIO.LOW) self.pinLedBleue = 22
GPIO.setup(PIN_BUZZER, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(PIN_PIR, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
for row in ROWS: # Clavier 4x4
GPIO.setup(row, GPIO.OUT, initial=GPIO.HIGH) # 4 lignes + 4 colonnes
for col in COLS: self.lignes = [5, 6, 13, 19]
GPIO.setup(col, GPIO.IN, pull_up_down=GPIO.PUD_UP) self.colonnes = [26, 12, 16, 20]
etat = "Désarmée" # Disposition classique d'un clavier 4x4
etat_lock = threading.Lock() self.touches = [
["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"]
]
_stop_buzzer = threading.Event() # -----------------------------
_thread_buzzer = None # Variables de fonctionnement
# -----------------------------
self.codeSecret = "1234"
self.codeSaisi = ""
# Etats possibles :
# - desarme
# - arme
# - alarme
self.etat = "desarme"
# Anti-rebond clavier
self.derniereLecture = 0
self.delaiLecture = 0.25
def led(r=False, g=False, b=False): self.initialiserGPIO()
GPIO.output(PIN_LED_R, GPIO.HIGH if r else GPIO.LOW) self.mettreAJourEtat()
GPIO.output(PIN_LED_G, GPIO.HIGH if g else GPIO.LOW)
GPIO.output(PIN_LED_B, GPIO.HIGH if b else GPIO.LOW)
def bip(nb=1, duree=0.08, pause=0.12): def initialiserGPIO(self):
for _ in range(nb): """Configure les broches du Raspberry Pi pour l'alarme."""
GPIO.output(PIN_BUZZER, GPIO.HIGH) GPIO.setup(self.pinPir, GPIO.IN)
time.sleep(duree) GPIO.setup(self.pinBuzzer, GPIO.OUT, initial=GPIO.LOW)
GPIO.output(PIN_BUZZER, GPIO.LOW)
time.sleep(pause)
def _buzzer_continu(stop_event): GPIO.setup(self.pinLedRouge, GPIO.OUT, initial=GPIO.LOW)
while not stop_event.is_set(): GPIO.setup(self.pinLedVerte, GPIO.OUT, initial=GPIO.LOW)
GPIO.output(PIN_BUZZER, GPIO.HIGH) GPIO.setup(self.pinLedBleue, GPIO.OUT, initial=GPIO.LOW)
time.sleep(0.5)
GPIO.output(PIN_BUZZER, GPIO.LOW)
time.sleep(0.5)
GPIO.output(PIN_BUZZER, GPIO.LOW)
def etat_alarme(): for pin in self.lignes:
with etat_lock: GPIO.setup(pin, GPIO.OUT, initial=GPIO.LOW)
return etat
for pin in self.colonnes:
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
def lire_touche(): def definirCouleur(self, rouge, vert, bleu):
for i, row in enumerate(ROWS): """
GPIO.output(row, GPIO.LOW) Allume la LED RGB selon la couleur voulue.
for j, col in enumerate(COLS):
if GPIO.input(col) == GPIO.LOW:
time.sleep(0.05)
while GPIO.input(col) == GPIO.LOW:
pass
GPIO.output(row, GPIO.HIGH)
return KEYPAD_MAP[i][j]
GPIO.output(row, GPIO.HIGH)
return None
def lire_code(nb_chiffres=4, timeout=30): Paramètres :
saisi = "" - rouge : True / False
debut = time.time() - vert : True / False
print(" Code : ", end="", flush=True) - bleu : True / False
while len(saisi) < nb_chiffres: """
if time.time() - debut > timeout: GPIO.output(self.pinLedRouge, GPIO.HIGH if rouge else GPIO.LOW)
print("\n [Timeout]") GPIO.output(self.pinLedVerte, GPIO.HIGH if vert else GPIO.LOW)
return "" GPIO.output(self.pinLedBleue, GPIO.HIGH if bleu else GPIO.LOW)
touche = lire_touche()
if touche and touche.isdigit():
saisi += touche
print("*", end="", flush=True)
time.sleep(0.05)
print()
return saisi
def mettreAJourEtat(self):
"""
Met à jour les sorties selon l'état actuel du système.
def passer_en_desarmee(): - desarme : LED verte, buzzer éteint
global etat, _thread_buzzer - arme : LED bleue, buzzer éteint
_stop_buzzer.set() - alarme : LED rouge, buzzer allumé
if _thread_buzzer and _thread_buzzer.is_alive(): """
_thread_buzzer.join() if self.etat == "desarme":
with etat_lock: self.definirCouleur(False, True, False)
etat = "Désarmée" GPIO.output(self.pinBuzzer, GPIO.LOW)
led(b=True) # Bleu
print("[ÉTAT] ● DÉSARMÉE")
def passer_en_armee(): elif self.etat == "arme":
global etat self.definirCouleur(False, False, True)
print("[ÉTAT] ● ARMEMENT... Stabilisation capteur (10s)") GPIO.output(self.pinBuzzer, GPIO.LOW)
led(r=True, g=True)
time.sleep(10)
with etat_lock: elif self.etat == "alarme":
etat = "Armée" self.definirCouleur(True, False, False)
led(g=True) GPIO.output(self.pinBuzzer, GPIO.HIGH)
bip(nb=2)
print("[ÉTAT] ● ARMÉE — Surveillance active !")
def passer_en_declenchee(): def armer(self):
global etat, _thread_buzzer """Passe le système en mode armé."""
with etat_lock: self.etat = "arme"
etat = "Déclenchée" self.codeSaisi = ""
led(r=True) # Rouge self.mettreAJourEtat()
print("[ÉTAT] ● DÉCLENCHÉE !") print("Alarme activée.")
_stop_buzzer.clear()
_thread_buzzer = threading.Thread(target=_buzzer_continu, args=(_stop_buzzer,), daemon=True)
_thread_buzzer.start()
def _surveiller_pir(stop_evt): def desarmer(self):
print("[PIR] Thread de surveillance prêt") """Passe le système en mode désarmé."""
while not stop_evt.is_set(): self.etat = "desarme"
with etat_lock: self.codeSaisi = ""
etat_local = etat self.mettreAJourEtat()
print("Alarme désactivée.")
if etat_local == "Armée": def declencherAlarme(self):
if GPIO.input(PIN_PIR) == GPIO.HIGH: """
time.sleep(0.3) Déclenche l'alarme si un mouvement est détecté alors
if GPIO.input(PIN_PIR) == GPIO.HIGH: que le système est armé.
passer_en_declenchee() """
if self.etat != "alarme":
self.etat = "alarme"
self.codeSaisi = ""
self.mettreAJourEtat()
print("Intrusion détectée : alarme déclenchée.")
time.sleep(0.1) def lireClavier(self):
"""
Scanne le clavier 4x4.
Retour :
- la touche détectée
- None si aucune touche n'est pressée
"""
maintenant = time.time()
def boucle_principale(): if maintenant - self.derniereLecture < self.delaiLecture:
passer_en_desarmee() return None
stop_pir = threading.Event() for indexLigne, ligne in enumerate(self.lignes):
thread_pir = threading.Thread(target=_surveiller_pir, args=(stop_pir,), daemon=True) GPIO.output(ligne, GPIO.HIGH)
thread_pir.start()
print("\n=== Système prêt (Code: " + CODE_SECRET + ") ===") for indexColonne, colonne in enumerate(self.colonnes):
if GPIO.input(colonne) == GPIO.HIGH:
GPIO.output(ligne, GPIO.LOW)
self.derniereLecture = maintenant
try: # Petite attente pour éviter la lecture multiple
while True: time.sleep(0.05)
etat_actuel = etat_alarme()
if etat_actuel == "Désarmée": return self.touches[indexLigne][indexColonne]
print("→ Saisir code pour ARMER :")
if lire_code() == CODE_SECRET:
passer_en_armee()
elif etat_actuel == "Armée": GPIO.output(ligne, GPIO.LOW)
print("→ Système ARMÉ (Code pour DÉSARMER) :")
if lire_code() == CODE_SECRET:
passer_en_desarmee()
elif etat_actuel == "Déclenchée": return None
print("→ Saisir code pour DÉSARMER :")
if lire_code() == CODE_SECRET:
passer_en_desarmee()
time.sleep(0.2) def validerCode(self):
"""
Vérifie le code saisi.
except KeyboardInterrupt: Si le code est correct :
print("\n[INFO] Arrêt demandé par l'utilisateur") - alarme désarmée -> armée
finally: - alarme armée -> désarmée
stop_pir.set() - alarme déclenchée -> désarmée
_stop_buzzer.set()
led(False, False, False)
GPIO.cleanup()
print("[INFO] GPIO libérés. Fin du programme.")
Si le code est faux :
- on efface la saisie
"""
if self.codeSaisi == self.codeSecret:
if self.etat == "desarme":
self.armer()
else:
self.desarmer()
else:
print("Code incorrect.")
self.codeSaisi = ""
def traiterClavier(self, touche):
"""
Gère la logique du clavier :
- chiffres : ajout au code saisi
- * : efface la saisie
- # : valide le code
"""
if touche is None:
return
print("Touche appuyée :", touche)
if touche == "*":
self.codeSaisi = ""
print("Saisie effacée.")
return
if touche == "#":
self.validerCode()
return
if touche.isdigit():
if len(self.codeSaisi) < 8:
self.codeSaisi += touche
print("Code en cours :", "*" * len(self.codeSaisi))
def surveillerPIR(self):
"""
Vérifie le capteur de mouvement.
Si un mouvement est détecté alors que l'alarme est armée,
on passe en état d'alarme.
"""
mouvement = GPIO.input(self.pinPir) == GPIO.HIGH
if self.etat == "arme" and mouvement:
self.declencherAlarme()
def mettreAJour(self):
"""
Fonction appelée en boucle dans le programme principal.
Elle :
- lit le clavier
- traite la touche appuyée
- surveille le PIR
- synchronise LED et buzzer avec l'état courant
"""
touche = self.lireClavier()
self.traiterClavier(touche)
self.surveillerPIR()
self.mettreAJourEtat()
def cleanup(self):
"""
Remet les sorties dans un état propre à la fermeture.
"""
GPIO.output(self.pinBuzzer, GPIO.LOW)
self.definirCouleur(False, False, False)

View File

@@ -1,246 +0,0 @@
import time
import RPi.GPIO as GPIO
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
class SystemeAlarme:
def __init__(self):
"""
Initialise les composants liés à l'alarme.
Cette classe gère uniquement la logique locale de sécurité :
- le capteur PIR
- le buzzer
- la LED RGB de statut
- le clavier 4x4
Elle ne dépend d'aucune autre partie du projet.
"""
# -----------------------------
# Définition des pins physiques
# -----------------------------
self.pinPir = 15
self.pinBuzzer = 18
self.pinLedRouge = 17
self.pinLedVerte = 27
self.pinLedBleue = 22
# Clavier 4x4
# 4 lignes + 4 colonnes
self.lignes = [5, 6, 13, 19]
self.colonnes = [26, 12, 16, 20]
# Disposition classique d'un clavier 4x4
self.touches = [
["1", "2", "3", "A"],
["4", "5", "6", "B"],
["7", "8", "9", "C"],
["*", "0", "#", "D"]
]
# -----------------------------
# Variables de fonctionnement
# -----------------------------
self.codeSecret = "1234"
self.codeSaisi = ""
# Etats possibles :
# - desarme
# - arme
# - alarme
self.etat = "desarme"
# Anti-rebond clavier
self.derniereLecture = 0
self.delaiLecture = 0.25
self.initialiserGPIO()
self.mettreAJourEtat()
def initialiserGPIO(self):
"""Configure les broches du Raspberry Pi pour l'alarme."""
GPIO.setup(self.pinPir, GPIO.IN)
GPIO.setup(self.pinBuzzer, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(self.pinLedRouge, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(self.pinLedVerte, GPIO.OUT, initial=GPIO.LOW)
GPIO.setup(self.pinLedBleue, GPIO.OUT, initial=GPIO.LOW)
for pin in self.lignes:
GPIO.setup(pin, GPIO.OUT, initial=GPIO.LOW)
for pin in self.colonnes:
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
def definirCouleur(self, rouge, vert, bleu):
"""
Allume la LED RGB selon la couleur voulue.
Paramètres :
- rouge : True / False
- vert : True / False
- bleu : True / False
"""
GPIO.output(self.pinLedRouge, GPIO.HIGH if rouge else GPIO.LOW)
GPIO.output(self.pinLedVerte, GPIO.HIGH if vert else GPIO.LOW)
GPIO.output(self.pinLedBleue, GPIO.HIGH if bleu else GPIO.LOW)
def mettreAJourEtat(self):
"""
Met à jour les sorties selon l'état actuel du système.
- desarme : LED verte, buzzer éteint
- arme : LED bleue, buzzer éteint
- alarme : LED rouge, buzzer allumé
"""
if self.etat == "desarme":
self.definirCouleur(False, True, False)
GPIO.output(self.pinBuzzer, GPIO.LOW)
elif self.etat == "arme":
self.definirCouleur(False, False, True)
GPIO.output(self.pinBuzzer, GPIO.LOW)
elif self.etat == "alarme":
self.definirCouleur(True, False, False)
GPIO.output(self.pinBuzzer, GPIO.HIGH)
def armer(self):
"""Passe le système en mode armé."""
self.etat = "arme"
self.codeSaisi = ""
self.mettreAJourEtat()
print("Alarme activée.")
def desarmer(self):
"""Passe le système en mode désarmé."""
self.etat = "desarme"
self.codeSaisi = ""
self.mettreAJourEtat()
print("Alarme désactivée.")
def declencherAlarme(self):
"""
Déclenche l'alarme si un mouvement est détecté alors
que le système est armé.
"""
if self.etat != "alarme":
self.etat = "alarme"
self.codeSaisi = ""
self.mettreAJourEtat()
print("Intrusion détectée : alarme déclenchée.")
def lireClavier(self):
"""
Scanne le clavier 4x4.
Retour :
- la touche détectée
- None si aucune touche n'est pressée
"""
maintenant = time.time()
if maintenant - self.derniereLecture < self.delaiLecture:
return None
for indexLigne, ligne in enumerate(self.lignes):
GPIO.output(ligne, GPIO.HIGH)
for indexColonne, colonne in enumerate(self.colonnes):
if GPIO.input(colonne) == GPIO.HIGH:
GPIO.output(ligne, GPIO.LOW)
self.derniereLecture = maintenant
# Petite attente pour éviter la lecture multiple
time.sleep(0.05)
return self.touches[indexLigne][indexColonne]
GPIO.output(ligne, GPIO.LOW)
return None
def validerCode(self):
"""
Vérifie le code saisi.
Si le code est correct :
- alarme désarmée -> armée
- alarme armée -> désarmée
- alarme déclenchée -> désarmée
Si le code est faux :
- on efface la saisie
"""
if self.codeSaisi == self.codeSecret:
if self.etat == "desarme":
self.armer()
else:
self.desarmer()
else:
print("Code incorrect.")
self.codeSaisi = ""
def traiterClavier(self, touche):
"""
Gère la logique du clavier :
- chiffres : ajout au code saisi
- * : efface la saisie
- # : valide le code
"""
if touche is None:
return
print("Touche appuyée :", touche)
if touche == "*":
self.codeSaisi = ""
print("Saisie effacée.")
return
if touche == "#":
self.validerCode()
return
if touche.isdigit():
if len(self.codeSaisi) < 8:
self.codeSaisi += touche
print("Code en cours :", "*" * len(self.codeSaisi))
def surveillerPIR(self):
"""
Vérifie le capteur de mouvement.
Si un mouvement est détecté alors que l'alarme est armée,
on passe en état d'alarme.
"""
mouvement = GPIO.input(self.pinPir) == GPIO.HIGH
if self.etat == "arme" and mouvement:
self.declencherAlarme()
def mettreAJour(self):
"""
Fonction appelée en boucle dans le programme principal.
Elle :
- lit le clavier
- traite la touche appuyée
- surveille le PIR
- synchronise LED et buzzer avec l'état courant
"""
touche = self.lireClavier()
self.traiterClavier(touche)
self.surveillerPIR()
self.mettreAJourEtat()
def cleanup(self):
"""
Remet les sorties dans un état propre à la fermeture.
"""
GPIO.output(self.pinBuzzer, GPIO.LOW)
self.definirCouleur(False, False, False)

View File

@@ -1,24 +1,36 @@
import time import time
import threading from ALARM_V1 import *
import alarme
from porterfid import SystemePorteRFID from porterfid import SystemePorteRFID
import RPi.GPIO as GPIO
# ------------------------------------------------------------
# board1main.py
# ------------------------------------------------------------
# Ce fichier lance uniquement la logique locale de la board 1.
# Il ne dépend pas du site web, de la base de données ni de Flask.
# Son rôle est simplement de faire tourner :
# - le système d'alarme
# - le système de porte RFID
# ------------------------------------------------------------
alarme = SystemeAlarme()
porte = SystemePorteRFID() porte = SystemePorteRFID()
def call_board1(): def call_board1():
thread_alarme = threading.Thread(target=alarme.boucle_principale, daemon=True)
thread_alarme.start()
print("[BOARD 1] Système d'alarme lancé en arrière-plan.")
try: try:
while True: while True:
# Mise à jour des deux modules locaux
ALARM_V1.boucle_principale()
porte.mettreAJour() porte.mettreAJour()
time.sleep(0.3) time.sleep(0.05)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nArrêt manuel du programme.")
finally:
porte.cleanup() porte.cleanup()
GPIO.cleanup() alarme.cleanup()
print("\nArrêt manuel du programme.")
finally:
# On remet les sorties dans un état propre avant de quitter
alarme.cleanup()
porte.cleanup()

View File

@@ -3,44 +3,54 @@ import time as t
from septsegments import afficher_temperature from septsegments import afficher_temperature
from DHT11 import lire_temperature from DHT11 import lire_temperature
# --- Configuration des Pins --- GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
bouton_up = 23 bouton_up = 23
bouton_down = 24 bouton_down = 24
GPIO.setup(bouton_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
# --- Variables Globales Partagées --- GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
temperature_cible = 18
def setup_boutons():
"""Initialisation des GPIO (à appeler au début)"""
GPIO.setmode(GPIO.BCM)
GPIO.setup(bouton_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def test_boutons(): def test_boutons():
global temperature_cible
setup_boutons()
temperature_DHT = lire_temperature() temperature_DHT = lire_temperature()
temperature_cible = 18
etatPrecedent_up = GPIO.input(bouton_up) etatPrecedent_up = GPIO.input(bouton_up)
etatPrecedent_down = GPIO.input(bouton_down) etatPrecedent_down = GPIO.input(bouton_down)
print(f"Thermostat lancé ! Cible actuelle : {temperature_cible}°C") print("Thermostat lancé ! Appuie sur UP (23) ou DOWN (24).")
afficher_temperature(temperature_DHT, temperature_cible)
while True: while True:
etat_up = GPIO.input(bouton_up) etat_up = GPIO.input(bouton_up)
etat_down = GPIO.input(bouton_down) etat_down = GPIO.input(bouton_down)
if etat_up == 0 and etatPrecedent_up == 1: if etat_up != etatPrecedent_up:
temperature_cible = min(40, temperature_cible + 1) if etat_up == 0:
print(f"Bouton UP -> Nouvelle cible : {temperature_cible}") print("Bouton UP Appuyé ⬆️")
afficher_temperature(lire_temperature(), temperature_cible) temperature_cible += 1
if temperature_cible >= 40:
temperature_cible = 40
if etat_down == 0 and etatPrecedent_down == 1: afficher_temperature(temperature_DHT, temperature_cible)
temperature_cible = max(0, temperature_cible - 1) etatPrecedent_up = etat_up
print(f"Bouton DOWN -> Nouvelle cible : {temperature_cible}") -
afficher_temperature(lire_temperature(), temperature_cible) if etat_down != etatPrecedent_down:
if etat_down == 0:
print("Bouton DOWN Appuyé ⬇️")
temperature_cible -= 1
if temperature_cible <= 0:
temperature_cible = 0
afficher_temperature(temperature_DHT, temperature_cible)
etatPrecedent_down = etat_down
etatPrecedent_up = etat_up
etatPrecedent_down = etat_down
t.sleep(0.05) t.sleep(0.05)
if __name__ == "__main__":
try:
test_boutons()
except KeyboardInterrupt:
print("\nFin du test")
GPIO.cleanup()

View File

@@ -1,60 +1,43 @@
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import time as t import time as t
GPIO.setmode(GPIO.BOARD)
GPIO.setwarnings(False)
servo = 12
GPIO.setup(servo, GPIO.OUT)
servo_pin = 18 pwm = GPIO.PWM(servo, 50)
bouton_up = 27 pwm.start(0)
bouton_down = 16 bouton_up = 13
etat_porte = "Fermé" bouton_down = 36
GPIO.setup(bouton_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def get_etat(etat): GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
global etat_porte
etat_porte = etat
print(f"État mis à jour : {etat_porte}")
def test_boutons(): def test_boutons():
global etat_porte
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
GPIO.setup(servo_pin, GPIO.OUT)
GPIO.setup(bouton_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
pwm = GPIO.PWM(servo_pin, 50)
pwm.start(0)
etatPrecedent_up = GPIO.input(bouton_up) etatPrecedent_up = GPIO.input(bouton_up)
etatPrecedent_down = GPIO.input(bouton_down) etatPrecedent_down = GPIO.input(bouton_down)
print("Thread Servo : Démarré et prêt.") print("Test lancé ! Appuie sur UP (23) pour monter, DOWN (24) pour descendre.")
while True:
etat_up = GPIO.input(bouton_up)
etat_down = GPIO.input(bouton_down)
if etat_up != etatPrecedent_up:
if etat_up == 0:
print("Bouton UP Appuyé ⬆️")
print("Volet ouvert")
pwm.ChangeDutyCycle(2)
etatPrecedent_up = etat_up
if etat_down != etatPrecedent_down:
if etat_down == 0:
print("Bouton DOWN Appuyé ⬇️")
print("Volet fermé")
pwm.ChangeDutyCycle(7)
etatPrecedent_down = etat_down
t.sleep(0.05)
if name == "main":
try: try:
while True: test_boutons()
etat_up = GPIO.input(bouton_up) except KeyboardInterrupt:
etat_down = GPIO.input(bouton_down) print("\nFin du test")
if etat_up != etatPrecedent_up:
if etat_up == GPIO.LOW:
get_etat('Ouvert')
pwm.ChangeDutyCycle(2.5)
t.sleep(0.5)
pwm.ChangeDutyCycle(0)
etatPrecedent_up = etat_up
if etat_down != etatPrecedent_down:
if etat_down == GPIO.LOW:
get_etat('Fermé')
pwm.ChangeDutyCycle(7.5)
t.sleep(0.5)
pwm.ChangeDutyCycle(0)
etatPrecedent_down = etat_down
t.sleep(0.05)
except Exception as e:
print(f"Erreur dans le thread servo : {e}")
finally:
pwm.stop()
#test_boutons()

View File

@@ -1,15 +0,0 @@
import RPi.GPIO as GPIO
import time as t
GPIO.setmode(GPIO.BOARD)
r, g, b = 11, 13, 15
GPIO.setup(r, GPIO.OUT)
GPIO.setup(g, GPIO.OUT)
GPIO.setup(b, GPIO.OUT)
while True:
GPIO.output(r, 1); t.sleep(1); GPIO.output(r, 0)
GPIO.output(g, 1); t.sleep(1); GPIO.output(g, 0)
GPIO.output(b, 1); t.sleep(1); GPIO.output(b, 0)

26
composants/test/LDR.py Normal file
View File

@@ -0,0 +1,26 @@
import RPi.GPIO as GPIO
import time
# Configuration
LDR_PIN = 20# Broche GPIO connectée au circuit LDR
SEUIL = 500 # Valeur de seuil à ajuster (0-1024)
GPIO.setmode(GPIO.BCM)
GPIO.setup(LDR_PIN, GPIO.IN)
def lire_ldr():
# Simulation de lecture analogique (nécessite MCP3008 ou circuit RC)
# Pour cet exemple, on simplifie par une lecture numérique
return GPIO.input(LDR_PIN)
try:
while True:
luminosite = lire_ldr()
if luminosite < SEUIL:
print("Nuit : Allumage lumière")
else:
print("Jour : Extinction lumière")
time.sleep(1)
except KeyboardInterrupt:
GPIO.cleanup()

View File

@@ -1,79 +1,103 @@
🏠 Projet IoT : API de Contrôle Domotique (Raspberry Pi 2) # Serveur FastAPI
Ce dépôt contient le code du Serveur API déployé sur le deuxième Raspberry Pi. Son rôle est de piloter les composants physiques (LED, Thermostat, Afficheurs) en répondant aux requêtes envoyées par le Raspberry Pi 1. ## Description
🏗️ Architecture du Système
Le projet repose sur une architecture distribuée : Ce serveur **FastAPI** est destiné à être déployé sur le **deuxième Raspberry Pi** de larchitecture.
Raspberry Pi 1 (Client) : Envoie les requêtes HTTP/HTTPS. Son rôle principal est de **gérer et traiter toutes les requêtes envoyées par le Raspberry Pi 1**, afin de centraliser la logique de traitement et dassurer une communication fluide entre les deux.
Raspberry Pi 2 (Serveur - Ce repo) : Reçoit les ordres, exécute la logique métier via les GPIO et renvoie l'état du système. ---
🛠️ Installation Rapide ## Architecture
Un script d'automatisation est fourni pour configurer l'environnement, les bases de données et les dépendances. * **Raspberry Pi 1**
Bash
# 1. Rendre le script exécutable * Envoie les requêtes (HTTP/API)
* Sert de client / déclencheur
* **Raspberry Pi 2 (ce serveur)**
* Héberge le serveur FastAPI
* Reçoit, traite et répond aux requêtes
* Exécute la logique métier
---
## Installation
Pour garantir une installation propre et optimale, il est recommandé dutiliser le script fourni.
### 1. Cloner le projet
```bash
git clone <repo_url>
cd <repo>
```
### 2. Lancer linstallation automatique
Le script `main.sh` permet de :
* créer un environnement virtuel Python (`venv`)
* installer toutes les dépendances nécessaires
* configurer lenvironnement correctement
```bash
chmod +x main.sh chmod +x main.sh
./main.sh
```
# 2. Lancer l'installation (nécessite les droits sudo) ---
sudo ./main.sh
Ce que fait le script main.sh : ## Environnement virtuel
Mise à jour du système (apt update & upgrade). Le projet utilise **Python venv** pour isoler les dépendances.
Installation optionnelle de MariaDB et phpMyAdmin. Si besoin, activation manuelle :
Configuration d'un environnement virtuel Python (venv). ```bash
source venv/bin/activate
```
Installation des dépendances via requirement.txt. ---
🚀 Lancement du Serveur ## Dépendances
Pour démarrer l'API, utilisez le script de lancement qui vérifie les prérequis (Python, SSL, Avahi) avant de lancer Uvicorn : Les dépendances sont listées dans le fichier :
Bash
chmod +x run_api.sh ```
sudo ./run_api.sh requirements.txt
```
Note : Le serveur utilise HTTPS. Les certificats doivent être présents dans ../web_secu/ssl/. Elles sont automatiquement installées via le script `main.sh`.
📡 Points d'entrée de l'API (Endpoints)
L'API est documentée automatiquement (Swagger) via FastAPI à l'adresse : https://<IP_DU_PI>:8000/docs ---
Méthode Route Description
GET /up_led Allume les lumières et active le mode manuel.
GET /down_led Éteint les lumières et active le mode manuel.
GET /temperature Lit le capteur DHT11 et affiche la valeur sur le 7 segments.
📂 Structure du Code Python (main.py)
Le serveur utilise une approche modulaire pour gérer les composants : ## Lancement du serveur
SystemeLumieres : Gestion des LEDs. Une fois linstallation terminée, le serveur peut être lancé avec :
SystemeThermostat : Lecture des données capteurs. ```
python main
EtatSysteme : Gestion visuelle des erreurs/succès (LED d'état). ```
afficher_temperature : Pilotage de l'afficheur TM1637. ---
📋 Dépendances Principales ## Objectif
FastAPI & Uvicorn : Framework web et serveur ASGI. Ce serveur a pour objectif de :
RPi.GPIO : Contrôle des broches du Raspberry Pi. * centraliser le traitement des requêtes
* améliorer les performances globales du système
* permettre une architecture distribuée entre plusieurs Raspberry Pi
Adafruit_DHT : Lecture des capteurs d'humidité et température. ---
python-tm1637 : Driver pour l'afficheur 7 segments. ## Notes
⚠️ Notes de Sécurité * Assurez-vous que les deux Raspberry Pi sont sur le même réseau.
* Vérifiez les ports et adresses IP pour permettre la communication entre les deux machines.
* Adapter la configuration si nécessaire selon votre environnement.
Le serveur est configuré avec CORS autorisant toutes les origines ("*") pour faciliter le développement. ---
La communication est sécurisée par SSL (TLS).
Le service Avahi est utilisé pour la résolution de noms sur le réseau local.
API Développée par les loustiques

View File

@@ -4,7 +4,6 @@ from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import uvicorn import uvicorn
import threading
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False) GPIO.setwarnings(False)
@@ -18,11 +17,6 @@ from thermostat import SystemeThermostat
#from volets import SystemeVolets #from volets import SystemeVolets
from etatsysteme import EtatSysteme from etatsysteme import EtatSysteme
from septsegments import afficher_temperature from septsegments import afficher_temperature
import bouton_servo
import bouton
import LDR
app = FastAPI(title="L'API des loustiques") app = FastAPI(title="L'API des loustiques")
app.add_middleware( app.add_middleware(
@@ -62,39 +56,26 @@ async def eteindre_led():
etatSysteme.signalerProbleme() etatSysteme.signalerProbleme()
return {"success": False, "message": str(e)} return {"success": False, "message": str(e)}
@app.get("/temperature") @app.get("/temperature")
async def read_temp(): async def read_temp():
try: try:
temp = controleur_thermostat.lireTemperature() temp = controleur_thermostat.lireTemperature()
if temp is None: if temp is None:
# On renvoie une valeur par défaut ou 0 pour éviter le undefined etatSysteme.signalerProbleme()
return {"success": False, "temperature": "--", "message": "Erreur DHT11"} return {"success": False, "message": "Impossible de lire le capteur DHT11"}
afficher_temperature(temp, bouton.temperature_cible) etatSysteme.signalerOk()
afficher_temperature(temp, 18)
return {"success": True, "temperature": temp} return {"success": True, "temperature": temp}
except Exception as e: except Exception as e:
return {"success": False, "temperature": "Erreur", "message": str(e)} etatSysteme.signalerProbleme()
@app.get("/volet_status")
async def volet_status():
try:
actuel = bouton_servo.etat_porte
return {"success": True, "status": actuel}
except Exception as e:
return {"success": False, "message": str(e)} return {"success": False, "message": str(e)}
@app.get("/luminosite")
def get_luminosite():
return ({"success": True, "status": LDR.lire_etat()})
if __name__ == "__main__": if __name__ == "__main__":
t1 = threading.Thread(target=bouton_servo.test_boutons, daemon=True)
t2 = threading.Thread(target=bouton.test_boutons, daemon=True)
t1.start()
t2.start()
chemin_cle = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'key.pem') chemin_cle = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'key.pem')
chemin_cert = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'cert.pem') chemin_cert = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'cert.pem')
uvicorn.run( uvicorn.run(

View File

@@ -140,12 +140,14 @@
</div> </div>
<script> <script>
// 1. Écoute de la touche Entrée
["username", "password"].forEach(id => { ["username", "password"].forEach(id => {
document.getElementById(id).addEventListener("keydown", e => { document.getElementById(id).addEventListener("keydown", e => {
if (e.key === "Enter") handleLogin(); if (e.key === "Enter") handleLogin();
}); });
}); });
// 2. Fonction de connexion manuelle (Mot de passe)
async function handleLogin() { async function handleLogin() {
const username = document.getElementById("username").value.trim(); const username = document.getElementById("username").value.trim();
const password = document.getElementById("password").value; const password = document.getElementById("password").value;
@@ -188,6 +190,7 @@
} }
} }
// 3. Écoute automatique du badge (RFID) - SORTI DE LA FONCTION !
setInterval(async () => { setInterval(async () => {
try { try {
// Attention : Vérifie que cette route correspond bien à celle dans main.py // Attention : Vérifie que cette route correspond bien à celle dans main.py

View File

@@ -18,14 +18,9 @@ current_user = None
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
composants = os.path.join(BASE_DIR, "composants", "byPanda") composants = os.path.join(BASE_DIR, "composants", "byPanda")
sys.path.insert(0, composants) sys.path.insert(0, composants)
from alarme import SystemeAlarme
from lumieres import SystemeLumieres from lumieres import SystemeLumieres
from board1main import call_board1 from board1main import *
import alarme
@app.route("/") @app.route("/")
def index(): def index():
@@ -81,13 +76,10 @@ def check_rfid_login():
return jsonify({"success": True, "username": user}) return jsonify({"success": True, "username": user})
return jsonify({"success": False}) return jsonify({"success": False})
"""
@app.route("/alarme",methods=["POST"]) @app.route("/alarme",methods=["POST"])
def armer_alarme(): def armer_alarme():
SystemeAlarme.armer() SystemeAlarme.armer()
return jsonify({"success": True}) return jsonify({"success": True})
"""
@app.route("/admin") @app.route("/admin")
def admin_page(): def admin_page():
return render_template("admin.html") return render_template("admin.html")
@@ -95,6 +87,8 @@ def admin_page():
@app.route("/admin/logs") @app.route("/admin/logs")
def logs_page(): def logs_page():
return render_template("log.html") return render_template("log.html")
@app.route("/admin/logs/data") @app.route("/admin/logs/data")
def get_logs(): def get_logs():
try: try:
@@ -120,33 +114,32 @@ def get_users():
users = auth.get_users() users = auth.get_users()
return jsonify({"success": True, "users": users}) return jsonify({"success": True, "users": users})
@app.route("/alarme_status")
def get_alarme_status_info():
try:
# On appelle la fonction corrigée dans alarme.py
statut = alarme.etat_alarme()
return jsonify({
"success": True,
"status": statut # Retournera "Désarmée", "Armée" ou "Déclenchée"
})
except Exception as e:
return jsonify({"success": False, "message": str(e)}), 500
@app.route("/api/<action>", methods=["GET"]) @app.route("/api/<action>", methods=["GET"])
def relais_pi2(action): def relais_pi2(action):
url_pi2 = f"https://pi32.local:8000/{action}" url_pi2 = f"https://pi32.local:8000/{action}"
print(f"\n[RELAIS] 1. Tentative de contact avec le Pi 2 : {url_pi2}")
try: try:
reponse = requests.get(url_pi2, timeout=5, verify=False) reponse = requests.get(url_pi2, timeout=5, verify=False)
print(f"[RELAIS] 2. Code HTTP reçu du Pi 2 : {reponse.status_code}")
print(f"[RELAIS] 3. Texte brut reçu du Pi 2 : {reponse.text}")
if not reponse.ok: if not reponse.ok:
return jsonify({"success": False, "message": "Erreur Pi 2"}), reponse.status_code return jsonify({
"success": False,
"message": f"Le Pi 2 a refusé la requête (Code {reponse.status_code})"
}), reponse.status_code
return jsonify(reponse.json()) # Si tout va bien, on tente d'extraire le JSON
try:
data = reponse.json()
return jsonify(data)
except ValueError:
print("[RELAIS] 4. ERREUR : Le Pi 2 n'a pas renvoyé de JSON valide.")
return jsonify({"success": False, "message": "Réponse invalide du Pi 2"}), 502
except Exception as e: except Exception as e:
# On ne garde que l'erreur si vraiment ça plante print(f"[RELAIS] ERREUR CRITIQUE : Impossible de joindre le Pi 2. Raison : {e}")
print(f"[RELAIS] ERREUR : {e}") return jsonify({"success": False, "message": f"Erreur de connexion : {str(e)}"}), 500
return jsonify({"success": False, "message": str(e)}), 500
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -6,36 +6,203 @@
<title>Loustiques Home — Dashboard</title> <title>Loustiques Home — Dashboard</title>
<style> <style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; background: #1f1f1f; color: #f0f0f0; min-height: 100vh; }
header { display: flex; align-items: center; justify-content: space-between; padding: 18px 32px; border-bottom: 1px solid #2e2e2e; background: #1a1a1a; } body {
.logo { font-size: 17px; font-weight: 700; } font-family: system-ui, sans-serif;
.header-right { display: flex; align-items: center; gap: 20px; } background: #1f1f1f;
.status-dot { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #888; } color: #f0f0f0;
.dot { width: 7px; height: 7px; border-radius: 50%; background: #4ade80; animation: pulse 2s infinite; } min-height: 100vh;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 18px 32px;
border-bottom: 1px solid #2e2e2e;
background: #1a1a1a;
}
.logo {
font-size: 17px;
font-weight: 700;
}
.header-right {
display: flex;
align-items: center;
gap: 20px;
}
.status-dot {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
color: #888;
}
.dot {
width: 7px;
height: 7px;
border-radius: 50%;
background: #4ade80;
animation: pulse 2s infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } } @keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
.logout { font-family: inherit; font-size: 12px; color: #888; background: none; border: 1px solid #3a3a3a; padding: 6px 14px; border-radius: 6px; cursor: pointer; transition: color 0.15s, border-color 0.15s; }
.logout {
font-family: inherit;
font-size: 12px;
color: #888;
background: none;
border: 1px solid #3a3a3a;
padding: 6px 14px;
border-radius: 6px;
cursor: pointer;
transition: color 0.15s, border-color 0.15s;
}
.logout:hover { color: #f0f0f0; border-color: #666; } .logout:hover { color: #f0f0f0; border-color: #666; }
main { max-width: 1100px; margin: 0 auto; padding: 36px 32px; }
.welcome { margin-bottom: 36px; text-align: center; } main {
.welcome .label { font-size: 14px; letter-spacing: 0.15em; text-transform: uppercase; color: #2563eb; margin-bottom: 8px; } max-width: 1100px;
.welcome h1 { font-size: 30px; font-weight: 700; } margin: 0 auto;
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 14px; margin-bottom: 32px; } padding: 36px 32px;
.card { background: #2a2a2a; border: 1px solid #333; border-radius: 8px; padding: 22px; position: relative; } }
.card-label { font-size: 11px; letter-spacing: 0.1em; text-transform: uppercase; color: #888; margin-bottom: 10px; }
.card-value { font-size: 22px; font-weight: 700; } .welcome {
.card-sub { font-size: 12px; color: #666; margin-top: 6px; } margin-bottom: 36px;
.card-icon { position: absolute; top: 20px; right: 20px; width: 28px; height: 28px; opacity: 0.15; } text-align: center;
.section-title { font-size: 11px; letter-spacing: 0.15em; text-transform: uppercase; color: #666; margin-bottom: 14px; display: flex; align-items: center; gap: 10px; } }
.welcome .label {
font-size: 14px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: #2563eb;
margin-bottom: 8px;
}
.welcome h1 {
font-size: 30px;
font-weight: 700;
}
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px;
margin-bottom: 32px;
}
.card {
background: #2a2a2a;
border: 1px solid #333;
border-radius: 8px;
padding: 22px;
position: relative;
}
.card-label {
font-size: 11px;
letter-spacing: 0.1em;
text-transform: uppercase;
color: #888;
margin-bottom: 10px;
}
.card-value {
font-size: 22px;
font-weight: 700;
}
.card-sub {
font-size: 12px;
color: #666;
margin-top: 6px;
}
.card-icon {
position: absolute;
top: 20px;
right: 20px;
width: 28px;
height: 28px;
opacity: 0.15;
}
.section-title {
font-size: 11px;
letter-spacing: 0.15em;
text-transform: uppercase;
color: #666;
margin-bottom: 14px;
display: flex;
align-items: center;
gap: 10px;
}
.section-title::after { content: ''; flex: 1; height: 1px; background: #2e2e2e; } .section-title::after { content: ''; flex: 1; height: 1px; background: #2e2e2e; }
.actions { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; }
.action-btn { background: #2a2a2a; border: 1px solid #333; border-radius: 8px; padding: 18px 20px; cursor: pointer; text-align: left; color: #f0f0f0; position: relative; transition: border-color 0.15s, background 0.15s; } .actions {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 12px;
}
.action-btn {
background: #2a2a2a;
border: 1px solid #333;
border-radius: 8px;
padding: 18px 20px;
cursor: pointer;
text-align: left;
color: #f0f0f0;
position: relative;
transition: border-color 0.15s, background 0.15s;
}
.action-btn:hover { border-color: #2563eb; background: #2e2e3a; } .action-btn:hover { border-color: #2563eb; background: #2e2e3a; }
.action-btn:active { transform: scale(0.98); } .action-btn:active { transform: scale(0.98); }
.action-btn .a-label { font-size: 14px; font-weight: 600; display: block; margin-bottom: 4px; }
.action-btn .a-sub { font-size: 12px; color: #ffffff; } .action-btn .a-label {
.action-btn .a-arrow { position: absolute; right: 16px; top: 50%; transform: translateY(-50%); color: #555; font-size: 18px; transition: color 0.15s, right 0.15s; } font-size: 14px;
font-weight: 600;
display: block;
margin-bottom: 4px;
}
.action-btn .a-sub {
font-size: 11px;
color: #888;
}
.action-btn .a-arrow {
position: absolute;
right: 16px;
top: 50%;
transform: translateY(-50%);
color: #555;
font-size: 18px;
transition: color 0.15s, right 0.15s;
}
.action-btn:hover .a-arrow { color: #2563eb; right: 12px; } .action-btn:hover .a-arrow { color: #2563eb; right: 12px; }
.toast { position: fixed; bottom: 24px; right: 24px; background: #2a2a2a; border: 1px solid #333; border-radius: 8px; padding: 12px 18px; font-size: 13px; color: #f0f0f0; display: flex; align-items: center; gap: 10px; transform: translateY(20px); opacity: 0; transition: transform 0.3s, opacity 0.3s; z-index: 100; }
.toast {
position: fixed;
bottom: 24px;
right: 24px;
background: #2a2a2a;
border: 1px solid #333;
border-radius: 8px;
padding: 12px 18px;
font-size: 13px;
color: #f0f0f0;
display: flex;
align-items: center;
gap: 10px;
transform: translateY(20px);
opacity: 0;
transition: transform 0.3s, opacity 0.3s;
z-index: 100;
}
.toast.show { transform: translateY(0); opacity: 1; } .toast.show { transform: translateY(0); opacity: 1; }
.toast-dot { width: 6px; height: 6px; border-radius: 50%; background: #4ade80; flex-shrink: 0; } .toast-dot { width: 6px; height: 6px; border-radius: 50%; background: #4ade80; flex-shrink: 0; }
</style> </style>
@@ -57,74 +224,66 @@
</div> </div>
<div class="grid"> <div class="grid">
<div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><polyline points="9 22 9 12 15 12 15 22"/></svg>
<div class="card-label">Statut</div>
<div class="card-value" style="color:#4ade80;">En ligne</div>
<div class="card-sub">Serveur opérationnel</div>
</div>
<div class="card"> <div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg> <svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/></svg>
<div class="card-label">Heure locale</div> <div class="card-label">Heure locale</div>
<div class="card-value" id="clock">--:--</div> <div class="card-value" id="clock">--:--</div>
<div class="card-sub" id="date-display">--</div> <div class="card-sub" id="date-display">--</div></div>
</div>
<div class="card"> <div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
<div class="card-label">Température</div> <div class="card-label">Température</div>
<div class="card-value" id="temp-display">-- °C</div> <div class="card-value" id="temp-display">-- °C</div>
<div class="card-sub">Capteur DHT11 (Pi 2)</div>
</div>
<div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5">
<path d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364-6.364l-.707.707M6.343 17.657l-.707.707m12.728 0l-.707-.707M6.343 6.343l-.707-.707M12 8a4 4 0 100 8 4 4 0 000-8z"/>
</svg>
<div class="card-label">Luminosité</div>
<div class="card-value" id="ldr-display">--</div>
<div class="card-sub">Capteur LDR (Pi 2)</div>
</div>
<div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="3" y="3" width="18" height="18" rx="2" ry="2"></rect><line x1="9" y1="3" x2="9" y2="21"></line></svg>
<div class="card-label">Porte / Volet</div>
<div class="card-value" id="door-display">--</div>
<div class="card-sub">État du moteur (Pi 2)</div>
</div>
<div class="card"> <div class="card"></div>
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg> <div class="card-label">Porte</div>
<div class="card-label">Alarme</div> <div class="card-sub" id="date-display">--</div>
<div class="card-value" id="alarm-display">--</div>
<div class="card-sub">Sécurité (Pi 1)</div>
</div> </div>
<div class="card"> <div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg> <svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<div class="card-label">Raspberry Pi 1</div> <div class="card-label">Raspberry Pi 1 (actuelle)</div>
<div class="card-value" id="status-pi44">Vérification...</div> <div class="card-value" style="color:green;">Actif</div>
<div class="card-sub">Serveur Flask</div> <div class="card-sub">Flask 3.1</div>
</div> </div>
<div class="card"> <div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg> <svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><rect x="2" y="3" width="20" height="14" rx="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/></svg>
<div class="card-label">Raspberry Pi 2</div> <div class="card-label">Raspberry Pi 2 distant </div>
<div class="card-value" id="status-pi32">Vérification...</div> <div class="card-value" style="color:green;">Actif</div>
<div class="card-sub">Serveur FastAPI</div> <div class="card-sub">FastAPi</div>
</div>
<div class="card">
<svg class="card-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M23 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/></svg>
<div class="card-label">Session</div>
<div class="card-value" style="color: green;">Authentifiée</div>
<div class="card-sub">Accès autorisé</div>
</div> </div>
</div> </div>
<div class="section-title">Actions rapides</div> <div class="section-title">Actions rapides</div>
<div class="actions"> <div class="actions">
<button class="action-btn" onclick="call_led_up()"> <button class="action-btn" onclick="call_led_up()">
<span class="a-label">💡 UP LED</span> <span class="a-label">💡 UP LED</span>
<span class="a-sub">Allumer la LED (Pi 2)</span> <span class="a-sub">Allumer la LED</span>
<span class="a-arrow"></span> <span class="a-arrow"></span>
</button> </button>
<button class="action-btn" onclick="call_led_down()">
<button class="action-btn" onclick="call_led_down()"> <span class="a-label">DOWN led</span>
<span class="a-label">💡 DOWN LED</span> <span class="a-sub">Eteindre la led</span>
<span class="a-sub">Éteindre la LED (Pi 2)</span> <span class="a-arrow"></span></button>
<span class="a-arrow"></span> <button class="action-btn" onclick="callAlarm()">
</button> <span class="a-label">Alarme</span>
<span class="a-sub">Désarmer l'alarme</span>
<span class="a-arrow"></span></button>
<button class="action-btn" onclick="go_admin()"> <button class="action-btn" onclick="go_admin()">
<span class="a-label">Administration</span> <span class="a-label">Administration</span>
<span class="a-sub">Gérer les utilisateurs</span> <span class="a-sub">Administrer les loustiques</span>
<span class="a-arrow"></span> <span class="a-arrow"></span>
</button> </button>
</div> </div>
@@ -138,89 +297,78 @@
<script> <script>
function updateClock() { function updateClock() {
const now = new Date(); const now = new Date();
document.getElementById("clock").textContent = now.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" }); document.getElementById("clock").textContent =
document.getElementById("date-display").textContent = now.toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long" }); now.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" });
document.getElementById("date-display").textContent =
now.toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long" });
} }
setInterval(updateClock, 1000);
updateClock(); updateClock();
async function updateAll() { setInterval(updateClock, 1000);
console.log("Rafraîchissement des données...");
/*
async function callAlarm() {
try { try {
const resA = await fetch('/alarme_status'); const res = await fetch('/alarme', {
const dataA = await resA.json(); method: 'POST',
const elA = document.getElementById("alarm-display"); headers: { 'Content-Type': 'application/json' }
const elS1 = document.getElementById("status-pi44"); });
showToast("alarme activée !");
} catch {
showToast("Erreur lors de l'appel alarme.");
}*/
if (dataA.success) { async function get_temperature() {
elA.textContent = dataA.status;
elA.style.color = (dataA.status === "desarmee") ? "#4ade80" : "#f87171";
elS1.textContent = "Actif";
elS1.style.color = "#4ade80";
}
} catch (e) {
document.getElementById("status-pi44").textContent = "Hors ligne";
document.getElementById("status-pi44").style.color = "#f87171";
}
try { try {
const resL = await fetch('/api/luminosite'); const res = await fetch('/api/temperature', {
const dataL = await resL.json(); method: 'GET',
const elL = document.getElementById("ldr-display"); headers: { 'Content-Type': 'application/json' }
});
if (dataL.success) { const data = await res.json();
elL.textContent = dataL.status;
elL.style.color = (dataL.status === "Jour") ? "#fbbf24" : "#818cf8";
}
} catch (e) {
console.log("Erreur de lecture luminosité");
document.getElementById("ldr-display").textContent = "Erreur";
}
try {
const resV = await fetch('/api/volet_status');
const dataV = await resV.json();
const elV = document.getElementById("door-display");
const elS2 = document.getElementById("status-pi32");
if (dataV.success) { if (data.success) {
elV.textContent = dataV.status; document.getElementById("temp-display").textContent = data.temperature + " °C";
elV.style.color = (dataV.status === "Ouvert") ? "#4ade80" : "#f87171";
elS2.textContent = "Actif";
elS2.style.color = "#4ade80";
} else { } else {
elS2.textContent = "Injoignable"; document.getElementById("temp-display").textContent = "Erreur";
elS2.style.color = "#fbbf24"; console.error("Erreur de température :", data.message);
} }
} catch (e) { } catch (e) {
document.getElementById("status-pi32").textContent = "Hors ligne"; document.getElementById("temp-display").textContent = "Hors ligne";
document.getElementById("status-pi32").style.color = "#f87171"; console.error("Impossible de joindre le relais pour la température.");
} }
}
get_temperature();
setInterval(get_temperature, 60000);
async function call_led_down() {
try { try {
const resT = await fetch('/api/temperature'); const res = await fetch('/api/down_led', {
const dataT = await resT.json(); method: 'GET',
if (dataT.success) { headers: { 'Content-Type': 'application/json' }
document.getElementById("temp-display").textContent = dataT.temperature + " °C"; });
} showToast("led activée !");
} catch (e) { } catch {
document.getElementById("temp-display").textContent = "-- °C"; showToast("Erreur lors de l'appel board1.");
} }}
}
setInterval(updateAll, 15000);
updateAll();
async function call_led_up() { async function call_led_up() {
try { try {
await fetch('/api/up_led'); const res = await fetch('/api/up_led', {
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
showToast("LED allumée !"); showToast("LED allumée !");
} catch { showToast("Erreur Pi 2"); } } catch {
showToast("Erreur lors de l'appel Pi 2.");
}
} }
async function call_led_down() {
try {
await fetch('/api/down_led'); function go_admin() {
showToast("LED éteinte !"); window.location.href = "/admin";
} catch { showToast("Erreur Pi 2"); }
} }
function go_admin() { window.location.href = "/admin"; }
function showToast(msg) { function showToast(msg) {
const toast = document.getElementById("toast"); const toast = document.getElementById("toast");
document.getElementById("toast-text").textContent = msg; document.getElementById("toast-text").textContent = msg;

View File

@@ -120,7 +120,7 @@
<div class="carte"> <div class="carte">
<h1>Loustiques Home</h1> <h1>Loustiques Home</h1>
<p class="soustitre">Connectez-vous via mot de passe ou avec votre bagde pour accéder à votre espace.</p> <p class="soustitre">Connectez-vous pour accéder à votre espace.</p>
<div class="champ"> <div class="champ">
<label for="username">Identifiant</label> <label for="username">Identifiant</label>
@@ -129,7 +129,7 @@
<div class="champ"> <div class="champ">
<label for="password">Mot de passe</label> <label for="password">Mot de passe</label>
<input type="password" id="password" placeholder="Wola ouais" autocomplete="current-password" /> <input type="password" id="password" placeholder="Mot de passe du loustique" autocomplete="current-password" />
</div> </div>
<button id="btn" onclick="handleLogin()">Se connecter</button> <button id="btn" onclick="handleLogin()">Se connecter</button>
@@ -139,13 +139,15 @@
<p class="footer">Accès réservé aux utilisateurs ajoutés par les loustiques.</p> <p class="footer">Accès réservé aux utilisateurs ajoutés par les loustiques.</p>
</div> </div>
<script> <script>
["username", "password"].forEach(id => { ["username", "password"].forEach(id => {
document.getElementById(id).addEventListener("keydown", e => { document.getElementById(id).addEventListener("keydown", e => {
if (e.key === "Enter") handleLogin(); if (e.key === "Enter") handleLogin();
}); });
}); });
-
async function handleLogin() { async function handleLogin() {
const username = document.getElementById("username").value.trim(); const username = document.getElementById("username").value.trim();
const password = document.getElementById("password").value; const password = document.getElementById("password").value;
@@ -187,12 +189,10 @@
btn.textContent = "Se connecter"; btn.textContent = "Se connecter";
} }
} }
setInterval(async () => { setInterval(async () => {
try { try {
// Attention : Vérifie que cette route correspond bien à celle dans main.py
// (J'avais mis /check-rfid-login dans mon exemple précédent) const res = await fetch('/rfid-scan');
const res = await fetch('/check-rfid-login');
const data = await res.json(); const data = await res.json();
if (data.success) { if (data.success) {
@@ -211,9 +211,10 @@
}, 1000); }, 1000);
} }
} catch (e) { } catch (e) {
// Erreurs ignorées silencieusement
} }
}, 1500); }, 2000);
</script> </script>
</body> </body>
</html> </html>