Files
Gestion_sondes/app/Monitor.py
2025-09-06 13:48:49 +02:00

208 lines
8.2 KiB
Python

import os
import time
from datetime import datetime, timedelta
from pathlib import Path
from utils_db import connect_to_mysql
from dotenv import load_dotenv
from utils_sms import envoyer_sms
# === AJOUT GYRO (MQTT) ===
import paho.mqtt.client as mqtt
from contextlib import closing
# -----------------------------------------------------------
# Logs
# -----------------------------------------------------------
if os.name != 'nt':
log_dir = Path('/home/debian/Gestion_sondes/Logs')
else:
log_dir = Path.cwd() / 'Logs'
log_dir.mkdir(parents=True, exist_ok=True)
# -----------------------------------------------------------
# Env
# -----------------------------------------------------------
load_dotenv()
ENVOI_SMS = os.getenv("ENVOI_SMS") == "1"
# === AJOUT GYRO (MQTT) ===
MQTT_HOST = os.getenv("MQTT_HOST", "127.0.0.1")
MQTT_PORT = int(os.getenv("MQTT_PORT", "1883"))
MQTT_USER = os.getenv("MQTT_USER", "")
MQTT_PASS = os.getenv("MQTT_PASS", "")
MQTT_QOS = 1
GYRO_PUBLISH_GLOBAL = os.getenv("GYRO_PUBLISH_GLOBAL", "0") == "1"
print("▶️ Lancement Monitor.py")
# --- Suivi des alertes actives pour rappels ---
alertes_actives = {}
# === AJOUT GYRO (MQTT) ===
def publish_gyro_states(states_by_site: dict):
"""Publie Alarmes/<Site>/Gyro = ON|OFF (retained). Optionnel : Alarmes/Global/Gyro."""
client = mqtt.Client(client_id="MonitorGyroPublisher", clean_session=True)
if MQTT_USER or MQTT_PASS:
client.username_pw_set(MQTT_USER, MQTT_PASS)
client.connect(MQTT_HOST, MQTT_PORT, keepalive=30)
client.loop_start()
try:
for site, state in states_by_site.items():
topic = f"Alarmes/{site}/Gyro"
client.publish(topic, state, qos=MQTT_QOS, retain=True)
print(f"📣 MQTT publish {topic} = {state} (retain)", flush=True)
if GYRO_PUBLISH_GLOBAL:
global_state = "ON" if any(v == "ON" for v in states_by_site.values()) else "OFF"
client.publish("Alarmes/Global/Gyro", global_state, qos=MQTT_QOS, retain=True)
print(f"📣 MQTT publish Alarmes/Global/Gyro = {global_state} (retain)", flush=True)
finally:
client.loop_stop()
client.disconnect()
# --- Fonction de surveillance ---
def surveiller():
global alertes_actives
log_entries = []
try:
conn = connect_to_mysql()
cursor = conn.cursor(dictionary=True)
# Liste des lieux
cursor.execute("SELECT DISTINCT Lieu FROM `Chambres_froides`")
lieux = [row['Lieu'] for row in cursor.fetchall()]
for lieu in lieux:
table_temp = lieu
table_alertes = f"Alertes_{lieu}"
# Sondes actives et non en entretien
cursor.execute("""
SELECT Sonde, Temp_Max
FROM Sondes.Chambres_froides
WHERE Lieu = %s
AND Etat = 'ON'
AND En_entretien = 0
""", (lieu,))
sondes = cursor.fetchall()
for sonde in sondes:
nom_sonde = sonde['Sonde']
seuil = sonde['Temp_Max']
# Derniers relevés (30 min = 6 pas de 5 min)
cursor.execute(f"""
SELECT Date, Temperature FROM {table_temp}
WHERE Sonde = %s
ORDER BY Date DESC LIMIT 6
""", (nom_sonde,))
releves = cursor.fetchall()
# Logging détaillé
for r in releves:
log_entries.append({
"Date": r['Date'],
"Lieu": lieu,
"Sonde": nom_sonde,
"Température": r['Temperature'],
"Seuil": seuil,
"État": "Dépassement" if r['Temperature'] > seuil else "Normal"
})
# Détection dépassement > 30 min
if len(releves) == 6:
toutes_hors_seuil = all(r['Temperature'] > seuil for r in releves)
plus_ancien = releves[-1]['Date']
maintenant = datetime.now()
if toutes_hors_seuil and (maintenant - plus_ancien >= timedelta(minutes=30)):
cursor.execute(f"""
SELECT COUNT(*) as total FROM {table_alertes}
WHERE Sonde=%s AND Status='En cours'
""", (nom_sonde,))
en_cours = cursor.fetchone()
if en_cours['total'] == 0:
cursor.execute(
f"INSERT INTO {table_alertes} (Sonde, Debut_defaut, Status) VALUES (%s, NOW(), 'En cours')",
(nom_sonde,)
)
print(f"🚨 Alerte déclenchée pour {nom_sonde} ({lieu})", flush=True)
message = (
f"La sonde '{nom_sonde}' du site '{lieu}' a dépassé le seuil de {seuil}°C "
f"depuis plus de 30 minutes.\nHeure : {maintenant.strftime('%Y-%m-%d %H:%M:%S')}"
)
if ENVOI_SMS:
envoyer_sms(lieu, message)
alertes_actives[nom_sonde] = maintenant
else:
# Rappel SMS toutes les 1h si toujours en défaut
dernier_envoi = alertes_actives.get(nom_sonde)
if dernier_envoi and (maintenant - dernier_envoi >= timedelta(hours=1)):
message = (
f"La sonde '{nom_sonde}' du site '{lieu}' est TOUJOURS en dépassement de seuil (>{seuil}°C).\n"
f"Heure : {maintenant.strftime('%Y-%m-%d %H:%M:%S')}"
)
if ENVOI_SMS:
envoyer_sms(lieu, message)
alertes_actives[nom_sonde] = maintenant
# Vérifier retour à la normale (Acquittement)
cursor.execute(f"""
SELECT Temperature FROM {table_temp}
WHERE Sonde = %s
ORDER BY Date DESC LIMIT 1
""", (nom_sonde,))
derniere = cursor.fetchone()
if derniere and derniere['Temperature'] <= seuil:
cursor.execute(f"""
UPDATE {table_alertes}
SET Status = 'Acquitté'
WHERE Sonde = %s AND Status IN ('En cours', 'Test')
""", (nom_sonde,))
if nom_sonde in alertes_actives:
del alertes_actives[nom_sonde]
# --- À ce stade : tables d'alertes mises à jour.
# On calcule l'état GYRO par site et on publie MQTT.
# === AJOUT GYRO (calcul + publication) ===
states = {}
for lieu in lieux:
table_alertes = f"Alertes_{lieu}"
# Etat actif si Status <> 'Acquitté'
cursor.execute(f"SELECT EXISTS(SELECT 1 FROM {table_alertes} WHERE Status <> 'Acquitté' LIMIT 1) AS actif")
actif = cursor.fetchone()
states[lieu] = "ON" if actif and list(actif.values())[0] == 1 else "OFF"
# Commit avant publication MQTT (les états reflètent la DB)
conn.commit()
# Publie ON/OFF par site (retain)
publish_gyro_states(states)
cursor.close()
conn.close()
# Ecriture log CSV
if log_entries:
import pandas as pd
df_logs = pd.DataFrame(log_entries)
try:
df_logs.to_csv(log_dir / "monitor.csv", sep=";", index=False)
print(f"✅ Log écrit dans {log_dir}/monitor.csv", flush=True)
except Exception as e:
print(f"❌ Erreur lors de l'écriture du fichier de log : {e}", flush=True)
except Exception as e:
print(f"Erreur : {e}", flush=True)
# --- Boucle principale ---
while True:
print(f"📡 Vérification à {datetime.now()}", flush=True)
surveiller()
time.sleep(300) # 5 minutes