Compare commits

..

13 Commits

Author SHA1 Message Date
865fde00c2 ajout de license APACHE 2.0 2026-04-03 21:20:31 +02:00
f275ad60f0 félicitations 2026-04-03 18:35:33 +02:00
9770617cd4 finally the end of the road 2026-04-02 23:09:57 +02:00
a14ebc3243 finally the end of the road 2026-04-02 22:20:07 +02:00
a30b9a9ba2 finally the end of the road 2026-04-02 22:19:52 +02:00
01bf68a77f maj REadMe 2026-04-02 15:18:22 +02:00
fe093d6c9d maj Readme 2026-04-02 15:15:44 +02:00
23bc302168 maj Readme 2026-04-02 15:05:25 +02:00
1a93d18369 maj Readme 2026-04-02 15:04:31 +02:00
a8f236427e maj 2026-04-02 15:00:09 +02:00
snow
825f153f51 Merge branch 'main' of https://github.com/maxdrk/loustique-home 2026-04-02 11:29:35 +02:00
snow
9977ce7797 WOLA CHANGEMENT DES BUTTONS 2026-04-02 11:29:29 +02:00
GigiTheGiraffe
cd9d837135 piou 2026-04-02 10:37:13 +02:00
18 changed files with 795 additions and 987 deletions

15
LICENSE Normal file
View File

@@ -0,0 +1,15 @@
Copyright 2026 Les Loustiques
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Source: http://opensource.org/licenses/Apache-2.0

View File

@@ -18,10 +18,13 @@ 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 # Configuration du système de logs │ ├── log.py
├── 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
@@ -33,9 +36,7 @@ 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é)
``` ```
--- ---
@@ -58,6 +59,10 @@ 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
``` ```
@@ -98,6 +103,7 @@ 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
--- ---
@@ -107,11 +113,6 @@ 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`
@@ -240,9 +241,16 @@ 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

@@ -1,243 +0,0 @@
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,14 +1,10 @@
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 # Sécurité si le capteur bugge, pour ne pas faire planter l'affichage return 0

12
composants/byPanda/LDR.py Normal file
View File

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

@@ -0,0 +1,246 @@
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,36 +1,24 @@
import time import time
from ALARM_V1 import * import threading
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.05) time.sleep(0.3)
except KeyboardInterrupt: except KeyboardInterrupt:
porte.cleanup()
alarme.cleanup()
print("\nArrêt manuel du programme.") print("\nArrêt manuel du programme.")
finally: finally:
# On remet les sorties dans un état propre avant de quitter
alarme.cleanup()
porte.cleanup() porte.cleanup()
GPIO.cleanup()

View File

@@ -3,54 +3,44 @@ import time as t
from septsegments import afficher_temperature from septsegments import afficher_temperature
from DHT11 import lire_temperature from DHT11 import lire_temperature
GPIO.setmode(GPIO.BCM) # --- Configuration des Pins ---
GPIO.setwarnings(False)
bouton_up = 23 bouton_up = 23
bouton_down = 24 bouton_down = 24
# --- Variables Globales Partagées ---
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_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(bouton_down, 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():
temperature_DHT = lire_temperature()
temperature_cible = 18
global temperature_cible
setup_boutons()
temperature_DHT = lire_temperature()
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("Thermostat lancé ! Appuie sur UP (23) ou DOWN (24).") print(f"Thermostat lancé ! Cible actuelle : {temperature_cible}°C")
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 != etatPrecedent_up: if etat_up == 0 and etatPrecedent_up == 1:
if etat_up == 0: temperature_cible = min(40, temperature_cible + 1)
print("Bouton UP Appuyé ⬆️") print(f"Bouton UP -> Nouvelle cible : {temperature_cible}")
temperature_cible += 1 afficher_temperature(lire_temperature(), temperature_cible)
if temperature_cible >= 40:
temperature_cible = 40 if etat_down == 0 and etatPrecedent_down == 1:
temperature_cible = max(0, temperature_cible - 1)
print(f"Bouton DOWN -> Nouvelle cible : {temperature_cible}")
afficher_temperature(lire_temperature(), temperature_cible)
afficher_temperature(temperature_DHT, temperature_cible)
etatPrecedent_up = etat_up etatPrecedent_up = etat_up
-
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_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,43 +1,60 @@
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)
pwm = GPIO.PWM(servo, 50) servo_pin = 18
pwm.start(0) bouton_up = 27
bouton_up = 13 bouton_down = 16
bouton_down = 36 etat_porte = "Fermé"
def get_etat(etat):
global etat_porte
etat_porte = etat
print(f"État mis à jour : {etat_porte}")
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_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP) GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
def test_boutons(): 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("Test lancé ! Appuie sur UP (23) pour monter, DOWN (24) pour descendre.") print("Thread Servo : Démarré et prêt.")
try:
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 != 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": if etat_up != etatPrecedent_up:
try: if etat_up == GPIO.LOW:
test_boutons() get_etat('Ouvert')
except KeyboardInterrupt: pwm.ChangeDutyCycle(2.5)
print("\nFin du test") 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

@@ -0,0 +1,15 @@
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)

View File

@@ -1,26 +0,0 @@
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,103 +1,79 @@
# Serveur FastAPI 🏠 Projet IoT : API de Contrôle Domotique (Raspberry Pi 2)
## Description 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.
🏗️ Architecture du Système
Ce serveur **FastAPI** est destiné à être déployé sur le **deuxième Raspberry Pi** de larchitecture. Le projet repose sur une architecture distribuée :
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 1 (Client) : Envoie les requêtes HTTP/HTTPS.
--- 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.
## Architecture 🛠️ Installation Rapide
* **Raspberry Pi 1** Un script d'automatisation est fourni pour configurer l'environnement, les bases de données et les dépendances.
Bash
* Envoie les requêtes (HTTP/API) # 1. Rendre le script exécutable
* 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
## Environnement virtuel Ce que fait le script main.sh :
Le projet utilise **Python venv** pour isoler les dépendances. Mise à jour du système (apt update & upgrade).
Si besoin, activation manuelle : Installation optionnelle de MariaDB et phpMyAdmin.
```bash Configuration d'un environnement virtuel Python (venv).
source venv/bin/activate
```
--- Installation des dépendances via requirement.txt.
## Dépendances 🚀 Lancement du Serveur
Les dépendances sont listées dans le fichier : Pour démarrer l'API, utilisez le script de lancement qui vérifie les prérequis (Python, SSL, Avahi) avant de lancer Uvicorn :
Bash
``` chmod +x run_api.sh
requirements.txt sudo ./run_api.sh
```
Elles sont automatiquement installées via le script `main.sh`. Note : Le serveur utilise HTTPS. Les certificats doivent être présents dans ../web_secu/ssl/.
📡 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)
## Lancement du serveur Le serveur utilise une approche modulaire pour gérer les composants :
Une fois linstallation terminée, le serveur peut être lancé avec : SystemeLumieres : Gestion des LEDs.
``` 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.
## Objectif 📋 Dépendances Principales
Ce serveur a pour objectif de : FastAPI & Uvicorn : Framework web et serveur ASGI.
* centraliser le traitement des requêtes RPi.GPIO : Contrôle des broches du Raspberry Pi.
* 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.
## Notes python-tm1637 : Driver pour l'afficheur 7 segments.
* Assurez-vous que les deux Raspberry Pi sont sur le même réseau. ⚠️ Notes de Sécurité
* 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,6 +4,7 @@ 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)
@@ -17,6 +18,11 @@ 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(
@@ -56,26 +62,39 @@ 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:
etatSysteme.signalerProbleme() # On renvoie une valeur par défaut ou 0 pour éviter le undefined
return {"success": False, "message": "Impossible de lire le capteur DHT11"} return {"success": False, "temperature": "--", "message": "Erreur DHT11"}
etatSysteme.signalerOk() afficher_temperature(temp, bouton.temperature_cible)
afficher_temperature(temp, 18)
return {"success": True, "temperature": temp} return {"success": True, "temperature": temp}
except Exception as e: except Exception as e:
etatSysteme.signalerProbleme() return {"success": False, "temperature": "Erreur", "message": str(e)}
@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,14 +140,12 @@
</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;
@@ -190,7 +188,6 @@
} }
} }
// 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,9 +18,14 @@ 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 * from board1main import call_board1
import alarme
@app.route("/") @app.route("/")
def index(): def index():
@@ -76,10 +81,13 @@ 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")
@@ -87,8 +95,6 @@ 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:
@@ -114,32 +120,33 @@ 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({ return jsonify({"success": False, "message": "Erreur Pi 2"}), reponse.status_code
"success": False,
"message": f"Le Pi 2 a refusé la requête (Code {reponse.status_code})"
}), reponse.status_code
# Si tout va bien, on tente d'extraire le JSON return jsonify(reponse.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:
print(f"[RELAIS] ERREUR CRITIQUE : Impossible de joindre le Pi 2. Raison : {e}") # On ne garde que l'erreur si vraiment ça plante
return jsonify({"success": False, "message": f"Erreur de connexion : {str(e)}"}), 500 print(f"[RELAIS] ERREUR : {e}")
return jsonify({"success": False, "message": str(e)}), 500
if __name__ == "__main__": if __name__ == "__main__":

View File

@@ -6,203 +6,36 @@
<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; }
body { header { display: flex; align-items: center; justify-content: space-between; padding: 18px 32px; border-bottom: 1px solid #2e2e2e; background: #1a1a1a; }
font-family: system-ui, sans-serif; .logo { font-size: 17px; font-weight: 700; }
background: #1f1f1f; .header-right { display: flex; align-items: center; gap: 20px; }
color: #f0f0f0; .status-dot { display: flex; align-items: center; gap: 8px; font-size: 12px; color: #888; }
min-height: 100vh; .dot { width: 7px; height: 7px; border-radius: 50%; background: #4ade80; animation: pulse 2s infinite; }
}
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; }
main { .welcome { margin-bottom: 36px; text-align: center; }
max-width: 1100px; .welcome .label { font-size: 14px; letter-spacing: 0.15em; text-transform: uppercase; color: #2563eb; margin-bottom: 8px; }
margin: 0 auto; .welcome h1 { font-size: 30px; font-weight: 700; }
padding: 36px 32px; .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; }
.welcome { .card-value { font-size: 22px; font-weight: 700; }
margin-bottom: 36px; .card-sub { font-size: 12px; color: #666; margin-top: 6px; }
text-align: center; .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; }
.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; }
.actions { .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; }
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-label { .action-btn .a-sub { font-size: 12px; color: #ffffff; }
font-size: 14px; .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-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>
@@ -224,66 +57,74 @@
</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>
<div class="card">
<div class="card-label">Température</div>
<div class="card-value" id="temp-display">-- °C</div>
<div class="card"></div>
<div class="card-label">Porte</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"><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"><path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/></svg>
<div class="card-label">Raspberry Pi 1 (actuelle)</div> <div class="card-label">Température</div>
<div class="card-value" style="color:green;">Actif</div> <div class="card-value" id="temp-display">-- °C</div>
<div class="card-sub">Flask 3.1</div> <div class="card-sub">Capteur DHT11 (Pi 2)</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">
<div class="card-label">Raspberry Pi 2 distant </div> <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"/>
<div class="card-value" style="color:green;">Actif</div> </svg>
<div class="card-sub">FastAPi</div> <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>
<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="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> <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">Session</div> <div class="card-label">Porte / Volet</div>
<div class="card-value" style="color: green;">Authentifiée</div> <div class="card-value" id="door-display">--</div>
<div class="card-sub">Accès autorisé</div> <div class="card-sub">État du moteur (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 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/></svg>
<div class="card-label">Alarme</div>
<div class="card-value" id="alarm-display">--</div>
<div class="card-sub">Sécurité (Pi 1)</div>
</div>
<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>
<div class="card-label">Raspberry Pi 1</div>
<div class="card-value" id="status-pi44">Vérification...</div>
<div class="card-sub">Serveur Flask</div>
</div>
<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>
<div class="card-label">Raspberry Pi 2</div>
<div class="card-value" id="status-pi32">Vérification...</div>
<div class="card-sub">Serveur FastAPI</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</span> <span class="a-sub">Allumer la LED (Pi 2)</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">Administrer les loustiques</span> <span class="a-sub">Gérer les utilisateurs</span>
<span class="a-arrow"></span> <span class="a-arrow"></span>
</button> </button>
</div> </div>
@@ -297,78 +138,89 @@
<script> <script>
function updateClock() { function updateClock() {
const now = new Date(); const now = new Date();
document.getElementById("clock").textContent = document.getElementById("clock").textContent = now.toLocaleTimeString("fr-FR", { hour: "2-digit", minute: "2-digit" });
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" });
document.getElementById("date-display").textContent =
now.toLocaleDateString("fr-FR", { weekday: "long", day: "numeric", month: "long" });
} }
updateClock();
setInterval(updateClock, 1000); setInterval(updateClock, 1000);
updateClock();
async function updateAll() {
console.log("Rafraîchissement des données...");
/*
async function callAlarm() {
try { try {
const res = await fetch('/alarme', { const resA = await fetch('/alarme_status');
method: 'POST', const dataA = await resA.json();
headers: { 'Content-Type': 'application/json' } const elA = document.getElementById("alarm-display");
}); const elS1 = document.getElementById("status-pi44");
showToast("alarme activée !");
} catch {
showToast("Erreur lors de l'appel alarme.");
}*/
async function get_temperature() { if (dataA.success) {
try { elA.textContent = dataA.status;
const res = await fetch('/api/temperature', { elA.style.color = (dataA.status === "desarmee") ? "#4ade80" : "#f87171";
method: 'GET', elS1.textContent = "Actif";
headers: { 'Content-Type': 'application/json' } elS1.style.color = "#4ade80";
});
const data = await res.json();
if (data.success) {
document.getElementById("temp-display").textContent = data.temperature + " °C";
} else {
document.getElementById("temp-display").textContent = "Erreur";
console.error("Erreur de température :", data.message);
} }
} catch (e) { } catch (e) {
document.getElementById("temp-display").textContent = "Hors ligne"; document.getElementById("status-pi44").textContent = "Hors ligne";
console.error("Impossible de joindre le relais pour la température."); document.getElementById("status-pi44").style.color = "#f87171";
} }
}
get_temperature();
setInterval(get_temperature, 60000);
async function call_led_down() {
try { try {
const res = await fetch('/api/down_led', { const resL = await fetch('/api/luminosite');
method: 'GET', const dataL = await resL.json();
headers: { 'Content-Type': 'application/json' } const elL = document.getElementById("ldr-display");
});
showToast("led activée !");
} catch {
showToast("Erreur lors de l'appel board1.");
}}
if (dataL.success) {
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) {
elV.textContent = dataV.status;
elV.style.color = (dataV.status === "Ouvert") ? "#4ade80" : "#f87171";
elS2.textContent = "Actif";
elS2.style.color = "#4ade80";
} else {
elS2.textContent = "Injoignable";
elS2.style.color = "#fbbf24";
}
} catch (e) {
document.getElementById("status-pi32").textContent = "Hors ligne";
document.getElementById("status-pi32").style.color = "#f87171";
}
try {
const resT = await fetch('/api/temperature');
const dataT = await resT.json();
if (dataT.success) {
document.getElementById("temp-display").textContent = dataT.temperature + " °C";
}
} catch (e) {
document.getElementById("temp-display").textContent = "-- °C";
}
}
setInterval(updateAll, 15000);
updateAll();
async function call_led_up() { async function call_led_up() {
try { try {
const res = await fetch('/api/up_led', { await fetch('/api/up_led');
method: 'GET',
headers: { 'Content-Type': 'application/json' }
});
showToast("LED allumée !"); showToast("LED allumée !");
} catch { } catch { showToast("Erreur Pi 2"); }
showToast("Erreur lors de l'appel Pi 2.");
} }
async function call_led_down() {
try {
await fetch('/api/down_led');
showToast("LED éteinte !");
} catch { showToast("Erreur Pi 2"); }
} }
function go_admin() { window.location.href = "/admin"; }
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 pour accéder à votre espace.</p> <p class="soustitre">Connectez-vous via mot de passe ou avec votre bagde 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="Mot de passe du loustique" autocomplete="current-password" /> <input type="password" id="password" placeholder="Wola ouais" autocomplete="current-password" />
</div> </div>
<button id="btn" onclick="handleLogin()">Se connecter</button> <button id="btn" onclick="handleLogin()">Se connecter</button>
@@ -140,14 +140,12 @@
</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;
@@ -189,10 +187,12 @@
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
const res = await fetch('/rfid-scan'); // (J'avais mis /check-rfid-login dans mon exemple précédent)
const res = await fetch('/check-rfid-login');
const data = await res.json(); const data = await res.json();
if (data.success) { if (data.success) {
@@ -211,10 +211,9 @@
}, 1000); }, 1000);
} }
} catch (e) { } catch (e) {
// Erreurs ignorées silencieusement
} }
}, 2000); }, 1500);
</script> </script>
</body> </body>
</html> </html>