Compare commits
13 Commits
8735d4967a
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 865fde00c2 | |||
| f275ad60f0 | |||
| 9770617cd4 | |||
| a14ebc3243 | |||
| a30b9a9ba2 | |||
| 01bf68a77f | |||
| fe093d6c9d | |||
| 23bc302168 | |||
| 1a93d18369 | |||
| a8f236427e | |||
|
|
825f153f51 | ||
|
|
9977ce7797 | ||
|
|
cd9d837135 |
15
LICENSE
Normal file
15
LICENSE
Normal 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
|
||||||
26
README.md
26
README.md
@@ -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 l’automatisation reste incomplète et l’arborescence des deux Raspberry Pi n’est pas identique. Des améliorations doivent encore être apportées aux scripts afin d’assurer une automatisation complète. Il est également possible que certaines informations du README soient manquantes ou ne soient plus en adéquation avec l’arborescence, 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**
|
||||||
@@ -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.")
|
|
||||||
|
|
||||||
|
|
||||||
# ════════════════════════════════════════════════════════════════════════════
|
|
||||||
@@ -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
12
composants/byPanda/LDR.py
Normal 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"
|
||||||
@@ -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.
|
CODE_SECRET = "1234"
|
||||||
"""
|
|
||||||
|
|
||||||
# -----------------------------
|
|
||||||
# Définition des pins physiques
|
|
||||||
# -----------------------------
|
|
||||||
self.pinPir = 15
|
|
||||||
self.pinBuzzer = 18
|
|
||||||
|
|
||||||
self.pinLedRouge = 17
|
GPIO.setup(PIN_LED_R, GPIO.OUT, initial=GPIO.LOW)
|
||||||
self.pinLedVerte = 27
|
GPIO.setup(PIN_LED_G, GPIO.OUT, initial=GPIO.LOW)
|
||||||
self.pinLedBleue = 22
|
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)
|
||||||
|
|
||||||
# Clavier 4x4
|
for row in ROWS:
|
||||||
# 4 lignes + 4 colonnes
|
GPIO.setup(row, GPIO.OUT, initial=GPIO.HIGH)
|
||||||
self.lignes = [5, 6, 13, 19]
|
for col in COLS:
|
||||||
self.colonnes = [26, 12, 16, 20]
|
GPIO.setup(col, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
||||||
|
|
||||||
# Disposition classique d'un clavier 4x4
|
etat = "Désarmée"
|
||||||
self.touches = [
|
etat_lock = threading.Lock()
|
||||||
["1", "2", "3", "A"],
|
|
||||||
["4", "5", "6", "B"],
|
|
||||||
["7", "8", "9", "C"],
|
|
||||||
["*", "0", "#", "D"]
|
|
||||||
]
|
|
||||||
|
|
||||||
# -----------------------------
|
_stop_buzzer = threading.Event()
|
||||||
# Variables de fonctionnement
|
_thread_buzzer = None
|
||||||
# -----------------------------
|
|
||||||
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()
|
def led(r=False, g=False, b=False):
|
||||||
self.mettreAJourEtat()
|
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 initialiserGPIO(self):
|
def bip(nb=1, duree=0.08, pause=0.12):
|
||||||
"""Configure les broches du Raspberry Pi pour l'alarme."""
|
for _ in range(nb):
|
||||||
GPIO.setup(self.pinPir, GPIO.IN)
|
GPIO.output(PIN_BUZZER, GPIO.HIGH)
|
||||||
GPIO.setup(self.pinBuzzer, GPIO.OUT, initial=GPIO.LOW)
|
time.sleep(duree)
|
||||||
|
GPIO.output(PIN_BUZZER, GPIO.LOW)
|
||||||
|
time.sleep(pause)
|
||||||
|
|
||||||
GPIO.setup(self.pinLedRouge, GPIO.OUT, initial=GPIO.LOW)
|
def _buzzer_continu(stop_event):
|
||||||
GPIO.setup(self.pinLedVerte, GPIO.OUT, initial=GPIO.LOW)
|
while not stop_event.is_set():
|
||||||
GPIO.setup(self.pinLedBleue, GPIO.OUT, initial=GPIO.LOW)
|
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)
|
||||||
|
|
||||||
for pin in self.lignes:
|
def etat_alarme():
|
||||||
GPIO.setup(pin, GPIO.OUT, initial=GPIO.LOW)
|
with etat_lock:
|
||||||
|
return etat
|
||||||
|
|
||||||
for pin in self.colonnes:
|
|
||||||
GPIO.setup(pin, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)
|
|
||||||
|
|
||||||
def definirCouleur(self, rouge, vert, bleu):
|
def lire_touche():
|
||||||
"""
|
for i, row in enumerate(ROWS):
|
||||||
Allume la LED RGB selon la couleur voulue.
|
GPIO.output(row, GPIO.LOW)
|
||||||
|
for j, col in enumerate(COLS):
|
||||||
Paramètres :
|
if GPIO.input(col) == GPIO.LOW:
|
||||||
- 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)
|
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)
|
|
||||||
246
composants/byPanda/alarme_test.py
Normal file
246
composants/byPanda/alarme_test.py
Normal 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)
|
||||||
@@ -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()
|
||||||
@@ -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
|
||||||
GPIO.setup(bouton_up, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
|
||||||
GPIO.setup(bouton_down, GPIO.IN, pull_up_down=GPIO.PUD_UP)
|
# --- 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_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()
|
|
||||||
@@ -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é"
|
||||||
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 get_etat(etat):
|
||||||
|
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("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()
|
||||||
|
|||||||
15
composants/byPanda/ledRGB.py
Normal file
15
composants/byPanda/ledRGB.py
Normal 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)
|
||||||
@@ -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()
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
import time as t
|
import time as t
|
||||||
|
##
|
||||||
GPIO.setmode(GPIO.BOARD)
|
GPIO.setmode(GPIO.BOARD)
|
||||||
|
|
||||||
pir = 10
|
pir = 10
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
import time as t
|
import time as t
|
||||||
|
##
|
||||||
GPIO.setmode(GPIO.BOARD)
|
GPIO.setmode(GPIO.BOARD)
|
||||||
|
|
||||||
r, g, b = 11, 13, 15
|
r, g, b = 11, 13, 15
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
from mfrc522 import SimpleMFRC522 as RFIDReader
|
from mfrc522 import SimpleMFRC522 as RFIDReader
|
||||||
|
##
|
||||||
reader = RFIDReader()
|
reader = RFIDReader()
|
||||||
|
|
||||||
print("En attente...")
|
print("En attente...")
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import RPi.GPIO as GPIO
|
import RPi.GPIO as GPIO
|
||||||
import time as t
|
import time as t
|
||||||
|
##
|
||||||
GPIO.setmode(GPIO.BOARD)
|
GPIO.setmode(GPIO.BOARD)
|
||||||
|
|
||||||
servo = 12
|
servo = 12
|
||||||
|
|||||||
@@ -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 l’architecture.
|
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 d’assurer 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é d’utiliser le script fourni.
|
|
||||||
|
|
||||||
### 1. Cloner le projet
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone <repo_url>
|
|
||||||
cd <repo>
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Lancer l’installation automatique
|
|
||||||
|
|
||||||
Le script `main.sh` permet de :
|
|
||||||
|
|
||||||
* créer un environnement virtuel Python (`venv`)
|
|
||||||
* installer toutes les dépendances nécessaires
|
|
||||||
* configurer l’environnement 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 l’installation 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
|
||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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__":
|
||||||
|
|||||||
@@ -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>
|
<div class="card-value" id="ldr-display">--</div>
|
||||||
|
<div class="card-sub">Capteur LDR (Pi 2)</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;
|
||||||
|
|||||||
@@ -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>
|
||||||
@@ -139,15 +139,13 @@
|
|||||||
<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;
|
||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user