maj de 18h38

This commit is contained in:
2026-04-01 18:39:00 +02:00
parent 0ab04110ad
commit 41312b0f75
9 changed files with 522 additions and 39 deletions

View File

@@ -19,7 +19,7 @@ class SystemePorteRFID:
Gère le lecteur RFID et la LED de la porte. Gère le lecteur RFID et la LED de la porte.
L'authentification est maintenant gérée par le serveur Flask et MariaDB. L'authentification est maintenant gérée par le serveur Flask et MariaDB.
""" """
self.pinLed = 21 self.pinLed = 4
GPIO.setup(self.pinLed, GPIO.OUT, initial=GPIO.LOW) GPIO.setup(self.pinLed, GPIO.OUT, initial=GPIO.LOW)
self.lecteur = SimpleMFRC522() self.lecteur = SimpleMFRC522()

View File

@@ -1,8 +1,13 @@
import tm1637 import tm1637
import time as t import time as t
_display = None
display = tm1637.TM1637(clk=4, dio=17) def get_display():
display.brightness(2) global _display
if _display is None:
_display = tm1637.TM1637(clk=4, dio=17)
_display.brightness(2)
return _display
def afficher_temperature(temperature, temperature_moyenne): def afficher_temperature(temperature, temperature_moyenne):
print(f"Test affichage: Cible {temperature} | Moyenne {temperature_moyenne}") print(f"Test affichage: Cible {temperature} | Moyenne {temperature_moyenne}")
@@ -10,8 +15,8 @@ def afficher_temperature(temperature, temperature_moyenne):
temp1 = int(temperature) temp1 = int(temperature)
temp2 = int(temperature_moyenne) temp2 = int(temperature_moyenne)
texte_ecran = f"{temp1:02d}{temp2:02d}" texte_ecran = f"{temp1:02d}{temp2:02d}"
disp = get_display()
display.show(texte_ecran) disp.show(texte_ecran)
except Exception as e: except Exception as e:
print(f"Erreur d'affichage : {e}") print(f"Erreur d'affichage : {e}")

205
composants/test/ALARM_V2.py Normal file
View File

@@ -0,0 +1,205 @@
import RPi.GPIO as GPIO
import time
import threading
GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False)
PIN_LED_R = 17
PIN_LED_G = 22
PIN_LED_B = 27
PIN_PIR = 15
PIN_BUZZER = 18
ROWS = [5, 6, 13, 19]
COLS = [26, 12, 16, 20]
KEYPAD_MAP = [
['1', '2', '3', 'A'],
['4', '5', '6', 'B'],
['7', '8', '9', 'C'],
['*', '0', '#', 'D'],
]
CODE_SECRET = "1234"
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)
etat = "desarmee"
etat_lock = threading.Lock()
_stop_buzzer = threading.Event()
_thread_buzzer = None
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()
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)
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)
while GPIO.input(col) == GPIO.LOW:
pass
GPIO.output(row, GPIO.HIGH)
return KEYPAD_MAP[i][j]
GPIO.output(row, GPIO.HIGH)
return None
def lire_code(timeout=30):
saisi = ""
debut = time.time()
print(" Code (# pour valider, * pour effacer) : ", end="", flush=True)
while True:
if time.time() - debut > timeout:
print("\n [Timeout — saisie annulée]")
return ""
touche = lire_touche()
if touche is None:
time.sleep(0.05)
continue
if touche == '#':
print()
return saisi
elif touche == '*':
if saisi:
saisi = saisi[:-1]
print("\b \b", end="", flush=True)
elif touche.isdigit():
saisi += touche
print("*", end="", flush=True)
time.sleep(0.05)
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)
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()
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)
def boucle_principale():
global etat
passer_en_desarmee()
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
if etat_local == "desarmee":
print(" → Saisir le code pour ARMER :")
code = lire_code() # CORRECTION ICI
if code == CODE_SECRET:
print(" ✔ Code correct → armement")
passer_en_armee()
elif code != "":
print(" ✘ Code incorrect")
bip(nb=1, duree=0.4)
elif etat_local == "armee":
time.sleep(0.1)
elif etat_local == "declenchee":
print(" → Saisir le code pour DÉSARMER :")
code = lire_code() # CORRECTION ICI
if code == CODE_SECRET:
print(" ✔ Code correct → désarmement")
passer_en_desarmee() # AJOUT : pour que l'alarme s'arrête vraiment
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.")
if __name__ == "__main__":
boucle_principale()

View File

@@ -1,7 +1,9 @@
import os import os
import sys import sys
from fastapi import FastAPI from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import RPi.GPIO as GPIO import RPi.GPIO as GPIO
import uvicorn
GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BCM)
GPIO.setwarnings(False) GPIO.setwarnings(False)
@@ -13,7 +15,7 @@ sys.path.insert(0, composants)
from lumieres import SystemeLumieres from lumieres import SystemeLumieres
from thermostat import SystemeThermostat from thermostat import SystemeThermostat
#from volets import SystemeVolets #from volets import SystemeVolets
from etatsystemes import EtatSysteme from etatsysteme import EtatSysteme
from septsegments import afficher_temperature from septsegments import afficher_temperature
app = FastAPI(title="L'API des loustiques") app = FastAPI(title="L'API des loustiques")
@@ -56,13 +58,33 @@ async def read_temp():
return {"success": False, "message": "Impossible de lire le capteur DHT11"} return {"success": False, "message": "Impossible de lire le capteur DHT11"}
etatSysteme.signalerOk() etatSysteme.signalerOk()
afficher_temperature(temp) afficher_temperature(temp, 18)
return {"success": True, "temperature": temp} return {"success": True, "temperature": temp}
except Exception as e: except Exception as e:
etatSysteme.signalerProbleme() etatSysteme.signalerProbleme()
return {"success": False, "message": str(e)} return {"success": False, "message": str(e)}
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Autorise tous les sites web (le fameux "*")
allow_credentials=False, # (Doit être False quand on met "*")
allow_methods=["*"], # Autorise toutes les méthodes (GET, POST, etc.)
allow_headers=["*"], # Autorise tous les en-têtes
)
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn # On prépare les chemins proprement pour éviter les erreurs de parenthèses
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True) # (Vérifie bien que le dossier 'web_secu' est bien dans le dossier racine de ton Pi 2)
chemin_cle = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'key.pem')
chemin_cert = os.path.join(BASE_DIR, 'web_secu', 'ssl', 'cert.pem')
# On lance Uvicorn avec la bonne syntaxe
uvicorn.run(
"main:app",
host="0.0.0.0",
port=8000,
ssl_keyfile=chemin_cle,
ssl_certfile=chemin_cert
)

222
flask/index.html Normal file
View File

@@ -0,0 +1,222 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Loustiques Home - Connexion</title>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: system-ui, sans-serif;
background: #1f1f1f;
color: #f0f0f0;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
}
.carte {
width: 380px;
max-width: 95vw;
background: #2a2a2a;
border-radius: 8px;
padding: 40px 36px;
}
h1 {
font-size: 26px;
font-weight: 700;
margin-bottom: 8px;
text-align: center;
}
.soustitre {
font-size: 14px;
color: #888;
margin-bottom: 36px;
text-align: center;
}
.champ {
margin-bottom: 18px;
}
label {
display: block;
font-size: 13px;
font-weight: 500;
margin-bottom: 6px;
color: #aaa;
}
input {
width: 100%;
padding: 11px 14px;
font-size: 14px;
font-family: inherit;
border: 1px solid #3a3a3a;
border-radius: 6px;
outline: none;
transition: border-color 0.15s;
color: #f0f0f0;
background: #333;
}
input:focus {
border-color: #2563eb;
}
input::placeholder {
color: #555;
}
button {
width: 100%;
margin-top: 10px;
padding: 12px;
background: #2563eb;
color: #fff;
border: none;
border-radius: 6px;
font-size: 14px;
font-weight: 600;
font-family: inherit;
cursor: pointer;
transition: background 0.15s;
}
button:hover { background: #1d4ed8; }
button:active { background: #1e40af; }
button:disabled { opacity: 0.4; cursor: not-allowed; }
.message {
margin-top: 14px;
font-size: 13px;
display: none;
}
.message.error {
display: block;
color: #f87171;
}
.message.success {
display: block;
color: #4ade80;
}
.footer {
margin-top: 28px;
font-size: 12px;
color: #555;
}
</style>
</head>
<body>
<div class="carte">
<h1>Loustiques Home</h1>
<p class="soustitre">Connectez-vous via mot de passe ou avec votre bagde pour accéder à votre espace.</p>
<div class="champ">
<label for="username">Identifiant</label>
<input type="text" id="username" placeholder="Nom du loustique" autocomplete="username" />
</div>
<div class="champ">
<label for="password">Mot de passe</label>
<input type="password" id="password" placeholder="Wola ouais" autocomplete="current-password" />
</div>
<button id="btn" onclick="handleLogin()">Se connecter</button>
<div class="message" id="msg"></div>
<p class="footer">Accès réservé aux utilisateurs ajoutés par les loustiques.</p>
</div>
<script>
// 1. Écoute de la touche Entrée
["username", "password"].forEach(id => {
document.getElementById(id).addEventListener("keydown", e => {
if (e.key === "Enter") handleLogin();
});
});
// 2. Fonction de connexion manuelle (Mot de passe)
async function handleLogin() {
const username = document.getElementById("username").value.trim();
const password = document.getElementById("password").value;
const btn = document.getElementById("btn");
const msg = document.getElementById("msg");
msg.className = "message";
if (!username || !password) {
msg.className = "message error";
msg.textContent = "Veuillez remplir tous les champs.";
return;
}
btn.disabled = true;
btn.textContent = "Vérification...";
try {
const res = await fetch("/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password })
});
const data = await res.json();
if (data.success) {
msg.className = "message success";
msg.textContent = "Connexion réussie !";
window.location.href = "/dashboard";
} else {
msg.className = "message error";
msg.textContent = data.message || "Identifiants incorrects.";
}
} catch {
msg.className = "message error";
msg.textContent = "Impossible de contacter le serveur.";
} finally {
btn.disabled = false;
btn.textContent = "Se connecter";
}
}
// 3. Écoute automatique du badge (RFID) - SORTI DE LA FONCTION !
setInterval(async () => {
try {
// Attention : Vérifie que cette route correspond bien à celle dans main.py
// (J'avais mis /check-rfid-login dans mon exemple précédent)
const res = await fetch('/check-rfid-login');
const data = await res.json();
if (data.success) {
const msg = document.getElementById("msg");
const btn = document.getElementById("btn");
const inputs = document.querySelectorAll("input");
btn.disabled = true;
inputs.forEach(input => input.disabled = true);
msg.className = "message success";
msg.textContent = "Badge reconnu ! Bienvenue " + data.username + "...";
setTimeout(() => {
window.location.href = "/dashboard";
}, 1000);
}
} catch (e) {
// Erreurs ignorées silencieusement
}
}, 1500);
</script>
</body>
</html>

View File

@@ -10,14 +10,14 @@ import re
app = Flask(__name__) app = Flask(__name__)
Talisman(app, force_https=True, Talisman(app, force_https=True,
content_security_policy=False ) content_security_policy=False)
current_user = None 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 alarme import SystemeAlarme
from lumiere import SystemeLumieres from lumieres import SystemeLumieres
from board1main import * from board1main import *
@app.route("/") @app.route("/")
@@ -48,21 +48,45 @@ def call_led():
else: else:
SystemeLumieres.eteindreLumieres() SystemeLumieres.eteindreLumieres()
return jsonify({"success": True}) return jsonify({"success": True})
# Variable temporaire pour stocker le dernier badge scanné
dernier_badge_scanne = None
@app.route("/rfid-scan", methods=["POST"]) @app.route("/rfid-scan", methods=["POST"])
def rfid_scan(): def rfid_scan():
global dernier_badge_scanne global dernier_badge_scanne
data = request.get_json() data = request.get_json()
badge_id = str(data.get("badge_id")) badge_id = data.get("badge_id")
# On va créer cette fonction dans ton fichier auth.py juste après
username = auth.get_user_by_rfid(badge_id) username = auth.get_user_by_rfid(badge_id)
if username: if username:
# Le badge est dans la base de données ! On autorise.
dernier_badge_scanne = username dernier_badge_scanne = username
return jsonify({"success": True, "username": username}) return jsonify({"success": True, "username": username})
else: else:
# Badge inconnu dans la BDD # Badge inconnu
return jsonify({"success": False}) return jsonify({"success": False})
@app.route("/check-rfid-login", methods=["GET"])
def check_rfid_login():
global dernier_badge_scanne
global current_user
# Si le Raspberry Pi a signalé un badge validé récemment
if dernier_badge_scanne:
user = dernier_badge_scanne
# On valide la connexion côté serveur
current_user = user
# On vide la variable pour ne pas le reconnecter en boucle à l'infini
dernier_badge_scanne = None
return jsonify({"success": True, "username": user})
return jsonify({"success": False})
@app.route("/alarme",methods=["POST"]) @app.route("/alarme",methods=["POST"])
@@ -107,6 +131,23 @@ 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("/api/relais-pi2/<action>", methods=["GET"])
def relais_pi2(action):
"""
Flask sert de relais. Le navigateur demande à Flask, et Flask demande au Pi 2.
"""
try:
# L'adresse de ton Pi 2
url_pi2 = f"https://pi32.local:8000/{action}"
# Le Pi 1 fait la requête ! verify=False permet d'ignorer le faux certificat
reponse = requests.get(url_pi2, timeout=5, verify=False)
return jsonify(reponse.json())
except Exception as e:
return jsonify({"success": False, "message": str(e)})
if __name__ == "__main__": if __name__ == "__main__":
print("[*] Démarrage du lecteur RFID et de l'alarme en arrière-plan...") print("[*] Démarrage du lecteur RFID et de l'alarme en arrière-plan...")

View File

@@ -46,14 +46,14 @@ Vérification des certificats SSL
================================== ==================================
EOF EOF
bash web_secu/ssl.sh bash ../web_secu/ssl.sh
cat << 'EOF' cat << 'EOF'
============================= =============================
Vérification du daemon Avahi Vérification du daemon Avahi
============================ ============================
EOF EOF
bash web_secu/avahi.sh bash ../web_secu/avahi.sh
cat << 'EOF' cat << 'EOF'

View File

@@ -268,14 +268,14 @@
<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="callLed()"> <button class="action-btn" onclick="call_led_up()">
<span class="a-label">💡 LED</span> <span class="a-label">💡 UP LED</span>
<span class="a-sub">Contrôler la LED</span> <span class="a-sub">Allumer la LED</span>
<span class="a-arrow"></span> <span class="a-arrow"></span>
</button> </button>
<button class="action-btn" onclick="callAlarm()"> <button class="action-btn" onclick="callAlarm()">
<span class="a-label">Alarme</span> <span class="a-label">DOWN led</span>
<span class="a-sub">Armer l'alarme</span> <span class="a-sub">Eteindre la led</span>
<span class="a-arrow"></span></button> <span class="a-arrow"></span></button>
<button class="action-btn" onclick="callAlarm()"> <button class="action-btn" onclick="callAlarm()">
<span class="a-label">Alarme</span> <span class="a-label">Alarme</span>
@@ -306,17 +306,7 @@
setInterval(updateClock, 1000); setInterval(updateClock, 1000);
async function callLed() {
try {
const res = await fetch('/led', {
method: 'POST',
headers: { 'Content-Type': 'application/json' }
});
showToast("LED activée !");
} catch {
showToast("Erreur lors de l'appel LED.");
}
}
async function callAlarm() { async function callAlarm() {
try { try {
const res = await fetch('/alarme', { const res = await fetch('/alarme', {
@@ -329,7 +319,7 @@
} }
async function call_led_down() { async function call_led_down() {
try { try {
const res = await fetch('http://pi32.local/down_led', { const res = await fetch('https://pi32.local:8000down_led', {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
@@ -340,16 +330,14 @@
} }
async function call_led_up() { async function call_led_up() {
try { try {
const res = await fetch('http://pi32.local/up_led', { const res = await fetch('https://pi32.local:8000/up_led', {
method: 'GET', method: 'GET',
headers: { 'Content-Type': 'application/json' } headers: { 'Content-Type': 'application/json' }
}); });
showToast("led activée !"); showToast("LED allumée !");
} catch { } catch {
showToast("Erreur lors de l'appel board1."); showToast("Erreur lors de l'appel Pi 2.");
} }
} }

View File

@@ -192,7 +192,7 @@
setInterval(async () => { setInterval(async () => {
try { try {
const res = await fetch('/check-rfid-login'); const res = await fetch('/rfid-scan');
const data = await res.json(); const data = await res.json();
if (data.success) { if (data.success) {