relancement cuisines Saclay et Meudon
This commit is contained in:
2
.env
2
.env
@@ -32,7 +32,9 @@ OVH_APPLICATION_SECRET=5ca392a0a728e2395edd426bb1e11ad6
|
|||||||
OVH_CONSUMER_KEY=305f2e8611e58b83930de84ee65c99f9
|
OVH_CONSUMER_KEY=305f2e8611e58b83930de84ee65c99f9
|
||||||
OVH_SMS_SERVICE=sms-jm164396-1
|
OVH_SMS_SERVICE=sms-jm164396-1
|
||||||
OVH_SMS_SENDER=DOMO91FR
|
OVH_SMS_SENDER=DOMO91FR
|
||||||
|
SMS_RECEIVER=+33635164680
|
||||||
ALERT_SMS_TO_SACLAY==Michel:+33635164680
|
ALERT_SMS_TO_SACLAY==Michel:+33635164680
|
||||||
ALERT_SMS_TO_MEUDON=Michel:+33635164680
|
ALERT_SMS_TO_MEUDON=Michel:+33635164680
|
||||||
|
|
||||||
RESERVE_SACLAY=Nicolas:+33682069405,Sabrina:+33650270939,Mirceta:+33601162960
|
RESERVE_SACLAY=Nicolas:+33682069405,Sabrina:+33650270939,Mirceta:+33601162960
|
||||||
RESERVE_MEUDON=Sekou:+33625903364,Damien:+33680388259,Manon:+33631127248
|
RESERVE_MEUDON=Sekou:+33625903364,Damien:+33680388259,Manon:+33631127248
|
||||||
|
|||||||
@@ -134,6 +134,48 @@ def lire_seuils_depuis_db(site: str):
|
|||||||
finally:
|
finally:
|
||||||
cnx.close()
|
cnx.close()
|
||||||
|
|
||||||
|
def lire_cfg_chambres(site: str):
|
||||||
|
"""
|
||||||
|
Retourne un dict {sonde: {"temp_max": float, "active": bool, "entretien": bool}}
|
||||||
|
depuis Chambres_froides pour le site.
|
||||||
|
"""
|
||||||
|
sql = """
|
||||||
|
SELECT Sonde, Temp_Max, Etat, En_entretien
|
||||||
|
FROM Chambres_froides
|
||||||
|
WHERE Lieu=%s
|
||||||
|
"""
|
||||||
|
cnx = get_db()
|
||||||
|
cfg = {}
|
||||||
|
try:
|
||||||
|
cur = cnx.cursor()
|
||||||
|
cur.execute(sql, (site,))
|
||||||
|
for sonde, temp_max, etat, en_entretien in cur.fetchall():
|
||||||
|
cfg[str(sonde)] = {
|
||||||
|
"temp_max": float(temp_max),
|
||||||
|
"active": str(etat).upper() == "ON",
|
||||||
|
"entretien": bool(int(en_entretien or 0)),
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
except MySQLError as err:
|
||||||
|
log.exception("Erreur DB (lire_cfg_chambres): %s", err)
|
||||||
|
return cfg
|
||||||
|
finally:
|
||||||
|
cnx.close()
|
||||||
|
|
||||||
|
def compute_site_alarm(last_values: list[dict], cfg: dict[str, dict], hysteresis: float = 0.0):
|
||||||
|
"""
|
||||||
|
Retourne (is_on: bool, trigger: (sonde, temp, seuil) | None)
|
||||||
|
"""
|
||||||
|
for row in last_values:
|
||||||
|
sonde = str(row["Sonde"])
|
||||||
|
meta = cfg.get(sonde)
|
||||||
|
if not meta or not meta["active"] or meta["entretien"]:
|
||||||
|
continue
|
||||||
|
temp = float(row["Temperature"])
|
||||||
|
if temp > float(meta["temp_max"]) + 0.0:
|
||||||
|
return True, (sonde, temp, float(meta["temp_max"]))
|
||||||
|
return False, None
|
||||||
|
|
||||||
def depassement_depuis_30min(site: str, sonde: str, seuil: float) -> bool:
|
def depassement_depuis_30min(site: str, sonde: str, seuil: float) -> bool:
|
||||||
table = site
|
table = site
|
||||||
cnx = get_db()
|
cnx = get_db()
|
||||||
@@ -359,8 +401,12 @@ class MQTTPublisher:
|
|||||||
def __init__(self, site: str):
|
def __init__(self, site: str):
|
||||||
self.enabled = (_mqtt_ok and (os.getenv("GYRO_MODE", "").lower() == "mqtt"))
|
self.enabled = (_mqtt_ok and (os.getenv("GYRO_MODE", "").lower() == "mqtt"))
|
||||||
self.site = site
|
self.site = site
|
||||||
self.topic = (os.getenv(f"GYRO_MQTT_TOPIC_{site}") or
|
self.topic = (
|
||||||
os.getenv(f"GYRO_MQTT_TOPIC_{site.capitalize()}"))
|
os.getenv(f"GYRO_MQTT_TOPIC_{site}") or
|
||||||
|
os.getenv(f"GYRO_MQTT_TOPIC_{site.upper()}") or
|
||||||
|
os.getenv("GYRO_MQTT_TOPIC") or
|
||||||
|
f"Sondes/{site}/Gyro/cmd"
|
||||||
|
)
|
||||||
self.last_state: bool | None = None
|
self.last_state: bool | None = None
|
||||||
|
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
@@ -416,9 +462,9 @@ class MQTTPublisher:
|
|||||||
if self.last_state is not None and self.last_state == on:
|
if self.last_state is not None and self.last_state == on:
|
||||||
return
|
return
|
||||||
|
|
||||||
payload = "on" if on else "off"
|
payload = "ON" if on else "OFF"
|
||||||
try:
|
try:
|
||||||
r = self.client.publish(self.topic, payload=payload, qos=1, retain=True)
|
r = self.client.publish(self.topic, payload=payload, qos=2, retain=True)
|
||||||
r.wait_for_publish(timeout=3)
|
r.wait_for_publish(timeout=3)
|
||||||
if r.rc != 0:
|
if r.rc != 0:
|
||||||
log.warning("MQTT publish rc=%s (topic=%s)", r.rc, self.topic)
|
log.warning("MQTT publish rc=%s (topic=%s)", r.rc, self.topic)
|
||||||
@@ -437,21 +483,33 @@ def notifier_sur_depassement(site: str, sonde: str, temp: float, seuil: float):
|
|||||||
subject, sms_text, email_body = build_alert_text(site, sonde, temp, seuil)
|
subject, sms_text, email_body = build_alert_text(site, sonde, temp, seuil)
|
||||||
notifier.send_sms(sms_text)
|
notifier.send_sms(sms_text)
|
||||||
notifier.send_email(subject, email_body)
|
notifier.send_email(subject, email_body)
|
||||||
try: beacon.set(True)
|
|
||||||
except Exception: pass
|
|
||||||
|
|
||||||
def notifier_acquittement(site: str, sonde: str, temp: float, seuil: float):
|
def notifier_acquittement(site: str, sonde: str, temp: float, seuil: float):
|
||||||
subject, sms_text, _ = build_ok_text(site, sonde, temp, seuil)
|
subject, sms_text, _ = build_ok_text(site, sonde, temp, seuil)
|
||||||
notifier.send_sms(sms_text)
|
notifier.send_sms(sms_text)
|
||||||
try:
|
|
||||||
if not any_alert_open(site):
|
|
||||||
beacon.set(False)
|
|
||||||
except Exception: pass
|
|
||||||
|
|
||||||
# ========= Cycle & boucle =========
|
# ========= Cycle & boucle =========
|
||||||
def run_monitor_cycle(site: str = SITE):
|
def run_monitor_cycle(site: str = SITE):
|
||||||
|
# 1) Lecture mesures + config
|
||||||
sondes = lire_sondes_depuis_db(site)
|
sondes = lire_sondes_depuis_db(site)
|
||||||
seuils = lire_seuils_depuis_db(site)
|
cfg = lire_cfg_chambres(site)
|
||||||
|
|
||||||
|
# 2) Gyro instantané
|
||||||
|
try:
|
||||||
|
gyro_on, trigger = compute_site_alarm(sondes, cfg, hysteresis=float(os.getenv("GYRO_HYSTERESIS", "0.0")))
|
||||||
|
if trigger:
|
||||||
|
s, t, se = trigger
|
||||||
|
log.info("Gyro %s => ON (déclenché par %s: %.2f > %.2f)", site, s, t, se)
|
||||||
|
else:
|
||||||
|
log.info("Gyro %s => OFF (aucun dépassement)", site)
|
||||||
|
beacon.set(gyro_on)
|
||||||
|
except Exception as e:
|
||||||
|
log.exception("Erreur calcul/publish gyrophare: %s", e)
|
||||||
|
|
||||||
|
# 3) Alertes “officielles” (inchangées) avec temporisation 30 min
|
||||||
|
# On reconstitue un dict seuils à partir de la cfg (et on ignore les sondes OFF).
|
||||||
|
seuils = {s: meta["temp_max"] for s, meta in cfg.items() if meta.get("active", False)}
|
||||||
|
|
||||||
for r in sondes:
|
for r in sondes:
|
||||||
nom = str(r["Sonde"])
|
nom = str(r["Sonde"])
|
||||||
temp = float(r["Temperature"])
|
temp = float(r["Temperature"])
|
||||||
@@ -460,7 +518,7 @@ def run_monitor_cycle(site: str = SITE):
|
|||||||
now = now_paris()
|
now = now_paris()
|
||||||
if temp > seuil:
|
if temp > seuil:
|
||||||
if depassement_depuis_30min(site, nom, seuil):
|
if depassement_depuis_30min(site, nom, seuil):
|
||||||
# Ouvrir si pas déjà ouvert → notifier seulement si ouverture réelle
|
# Ouvrir si pas déjà ouverte → notifier seulement si création
|
||||||
try:
|
try:
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
if open_alert(conn, f"Alertes_{site}", nom, now):
|
if open_alert(conn, f"Alertes_{site}", nom, now):
|
||||||
@@ -468,7 +526,7 @@ def run_monitor_cycle(site: str = SITE):
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
else:
|
else:
|
||||||
# Fermer si ouvert → notifier seulement si fermeture réelle
|
# Fermer si ouverte → notifier seulement si fermeture réelle
|
||||||
try:
|
try:
|
||||||
conn = get_db()
|
conn = get_db()
|
||||||
if close_alert(conn, f"Alertes_{site}", nom):
|
if close_alert(conn, f"Alertes_{site}", nom):
|
||||||
@@ -476,7 +534,6 @@ def run_monitor_cycle(site: str = SITE):
|
|||||||
finally:
|
finally:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
def run_monitor_loop(site: str = SITE, period_sec: int = 300):
|
def run_monitor_loop(site: str = SITE, period_sec: int = 300):
|
||||||
log.info("%s démarré (site=%s, période=%ss) ✅", PROGRAM_NAME, site, period_sec)
|
log.info("%s démarré (site=%s, période=%ss) ✅", PROGRAM_NAME, site, period_sec)
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -115,6 +115,49 @@ def lire_sondes_depuis_db(site: str):
|
|||||||
finally:
|
finally:
|
||||||
cnx.close()
|
cnx.close()
|
||||||
|
|
||||||
|
def lire_cfg_chambres(site: str):
|
||||||
|
"""
|
||||||
|
Retourne un dict {sonde: {"temp_max": float, "active": bool, "entretien": bool}}
|
||||||
|
depuis Chambres_froides pour le site.
|
||||||
|
"""
|
||||||
|
sql = """
|
||||||
|
SELECT Sonde, Temp_Max, Etat, En_entretien
|
||||||
|
FROM Chambres_froides
|
||||||
|
WHERE Lieu=%s
|
||||||
|
"""
|
||||||
|
cnx = get_db()
|
||||||
|
cfg: dict[str, dict] = {}
|
||||||
|
try:
|
||||||
|
cur = cnx.cursor()
|
||||||
|
cur.execute(sql, (site,))
|
||||||
|
for sonde, temp_max, etat, en_entretien in cur.fetchall():
|
||||||
|
cfg[str(sonde)] = {
|
||||||
|
"temp_max": float(temp_max),
|
||||||
|
"active": str(etat).upper() == "ON",
|
||||||
|
"entretien": bool(int(en_entretien or 0)),
|
||||||
|
}
|
||||||
|
return cfg
|
||||||
|
except MySQLError as err:
|
||||||
|
log.exception("Erreur DB (lire_cfg_chambres): %s", err)
|
||||||
|
return cfg
|
||||||
|
finally:
|
||||||
|
cnx.close()
|
||||||
|
|
||||||
|
def compute_site_alarm(last_values: list[dict], cfg: dict[str, dict], hysteresis: float = 0.0):
|
||||||
|
"""
|
||||||
|
Retourne (is_on: bool, trigger: tuple[str,float,float] | None)
|
||||||
|
trigger = (sonde, temp, seuil) si dépassement détecté.
|
||||||
|
"""
|
||||||
|
for row in last_values:
|
||||||
|
sonde = str(row["Sonde"])
|
||||||
|
meta = cfg.get(sonde)
|
||||||
|
if not meta or not meta["active"] or meta["entretien"]:
|
||||||
|
continue
|
||||||
|
temp = float(row["Temperature"])
|
||||||
|
if temp > float(meta["temp_max"]) + 0.0:
|
||||||
|
return True, (sonde, temp, float(meta["temp_max"]))
|
||||||
|
return False, None
|
||||||
|
|
||||||
def lire_seuils_depuis_db(site: str):
|
def lire_seuils_depuis_db(site: str):
|
||||||
sql = """
|
sql = """
|
||||||
SELECT Sonde, Temp_Max
|
SELECT Sonde, Temp_Max
|
||||||
@@ -360,8 +403,12 @@ class MQTTPublisher:
|
|||||||
def __init__(self, site: str):
|
def __init__(self, site: str):
|
||||||
self.enabled = (_mqtt_ok and (os.getenv("GYRO_MODE", "").lower() == "mqtt"))
|
self.enabled = (_mqtt_ok and (os.getenv("GYRO_MODE", "").lower() == "mqtt"))
|
||||||
self.site = site
|
self.site = site
|
||||||
self.topic = (os.getenv(f"GYRO_MQTT_TOPIC_{site}") or
|
self.topic = (
|
||||||
os.getenv(f"GYRO_MQTT_TOPIC_{site.capitalize()}"))
|
os.getenv(f"GYRO_MQTT_TOPIC_{site}") or
|
||||||
|
os.getenv(f"GYRO_MQTT_TOPIC_{site.upper()}") or
|
||||||
|
os.getenv("GYRO_MQTT_TOPIC") or
|
||||||
|
f"Sondes/{site}/Gyro/cmd"
|
||||||
|
)
|
||||||
self.last_state: bool | None = None
|
self.last_state: bool | None = None
|
||||||
|
|
||||||
if not self.enabled:
|
if not self.enabled:
|
||||||
@@ -417,9 +464,9 @@ class MQTTPublisher:
|
|||||||
if self.last_state is not None and self.last_state == on:
|
if self.last_state is not None and self.last_state == on:
|
||||||
return
|
return
|
||||||
|
|
||||||
payload = "on" if on else "off"
|
payload = "ON" if on else "OFF"
|
||||||
try:
|
try:
|
||||||
r = self.client.publish(self.topic, payload=payload, qos=1, retain=True)
|
r = self.client.publish(self.topic, payload=payload, qos=2, retain=True)
|
||||||
r.wait_for_publish(timeout=3)
|
r.wait_for_publish(timeout=3)
|
||||||
if r.rc != 0:
|
if r.rc != 0:
|
||||||
log.warning("MQTT publish rc=%s (topic=%s)", r.rc, self.topic)
|
log.warning("MQTT publish rc=%s (topic=%s)", r.rc, self.topic)
|
||||||
@@ -438,22 +485,34 @@ def notifier_sur_depassement(site: str, sonde: str, temp: float, seuil: float):
|
|||||||
subject, sms_text, email_body = build_alert_text(site, sonde, temp, seuil)
|
subject, sms_text, email_body = build_alert_text(site, sonde, temp, seuil)
|
||||||
notifier.send_sms(sms_text)
|
notifier.send_sms(sms_text)
|
||||||
notifier.send_email(subject, email_body)
|
notifier.send_email(subject, email_body)
|
||||||
try: beacon.set(True)
|
|
||||||
except Exception: pass
|
|
||||||
|
|
||||||
def notifier_acquittement(site: str, sonde: str, temp: float, seuil: float):
|
def notifier_acquittement(site: str, sonde: str, temp: float, seuil: float):
|
||||||
subject, sms_text, _ = build_ok_text(site, sonde, temp, seuil)
|
subject, sms_text, _ = build_ok_text(site, sonde, temp, seuil)
|
||||||
notifier.send_sms(sms_text)
|
notifier.send_sms(sms_text)
|
||||||
try:
|
|
||||||
if not any_alert_open(site):
|
|
||||||
beacon.set(False)
|
|
||||||
except Exception: pass
|
|
||||||
|
|
||||||
# ========= Cycle & boucle =========
|
# ========= Cycle & boucle =========
|
||||||
def run_monitor_cycle(site: str = SITE):
|
def run_monitor_cycle(site: str = SITE):
|
||||||
sondes = lire_sondes_depuis_db(site)
|
# 1) Lecture dernières mesures + config chambres
|
||||||
seuils = lire_seuils_depuis_db(site)
|
last_rows = lire_sondes_depuis_db(site) # [{'Sonde','Temperature','Date'}]
|
||||||
for r in sondes:
|
cfg = lire_cfg_chambres(site) # {sonde: {temp_max, active, entretien}}
|
||||||
|
|
||||||
|
# 2) Gyro instantané : ON si >=1 sonde active & non en entretien dépasse son seuil
|
||||||
|
try:
|
||||||
|
gyro_on, trigger = compute_site_alarm(last_rows, cfg, hysteresis=float(os.getenv("GYRO_HYSTERESIS", "0.0")))
|
||||||
|
if trigger:
|
||||||
|
s, t, se = trigger
|
||||||
|
log.info("Gyro %s => ON (déclenché par %s: %.2f > %.2f)", site, s, t, se)
|
||||||
|
else:
|
||||||
|
log.info("Gyro %s => OFF (aucun dépassement)", site)
|
||||||
|
beacon.set(gyro_on)
|
||||||
|
except Exception as e:
|
||||||
|
log.exception("Erreur calcul/publish gyrophare: %s", e)
|
||||||
|
|
||||||
|
# 3) Alertes "officielles" (inchangées) avec temporisation 30 min
|
||||||
|
# On reconstitue un dict seuils pour réutiliser ta logique existante en dessous.
|
||||||
|
seuils = {s: meta["temp_max"] for s, meta in cfg.items() if meta.get("active", False)}
|
||||||
|
|
||||||
|
for r in last_rows:
|
||||||
nom = str(r["Sonde"])
|
nom = str(r["Sonde"])
|
||||||
temp = float(r["Temperature"])
|
temp = float(r["Temperature"])
|
||||||
seuil = float(seuils.get(nom, 6.0))
|
seuil = float(seuils.get(nom, 6.0))
|
||||||
@@ -478,6 +537,7 @@ def run_monitor_cycle(site: str = SITE):
|
|||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run_monitor_loop(site: str = SITE, period_sec: int = 300):
|
def run_monitor_loop(site: str = SITE, period_sec: int = 300):
|
||||||
log.info("%s démarré (site=%s, période=%ss) ✅", PROGRAM_NAME, site, period_sec)
|
log.info("%s démarré (site=%s, période=%ss) ✅", PROGRAM_NAME, site, period_sec)
|
||||||
while True:
|
while True:
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import os, logging
|
||||||
|
|
||||||
|
def setup_logger(filename: str):
|
||||||
|
# si l'argument est un chemin absolu, on le respecte
|
||||||
|
if os.path.isabs(filename):
|
||||||
|
log_path = filename
|
||||||
|
else:
|
||||||
|
# ancien comportement (ex: /var/log/Cuisine_Saclay/<filename>)
|
||||||
|
base = "/var/log/Cuisine_Saclay"
|
||||||
|
os.makedirs(base, exist_ok=True)
|
||||||
|
log_path = os.path.join(base, filename)
|
||||||
|
|
||||||
|
os.makedirs(os.path.dirname(log_path), exist_ok=True)
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
filename=log_path,
|
||||||
|
format="%(asctime)s %(levelname)s %(message)s"
|
||||||
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import argparse
|
||||||
|
import paho.mqtt.client as mqtt_client
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import logging
|
||||||
|
from logger_config import setup_logger
|
||||||
|
from utils_db import connect_to_mysql
|
||||||
|
from functools import partial
|
||||||
|
|
||||||
|
def on_message(table_sql, _client, _userdata, msg):
|
||||||
|
try:
|
||||||
|
logging.info(f"Message reçu sur {msg.topic}: {msg.payload.decode()}")
|
||||||
|
cursor = mydb.cursor()
|
||||||
|
sonde_name = '/'.join(msg.topic.split('/')[1:])
|
||||||
|
sql = f"INSERT INTO {table_sql} (Sonde, Temperature) VALUES (%s, %s)"
|
||||||
|
val = (sonde_name, msg.payload.decode())
|
||||||
|
cursor.execute(sql, val)
|
||||||
|
mydb.commit()
|
||||||
|
logging.info(f"Insertion réussie : {val}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Erreur lors de l'insertion du message : {e}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
parser.add_argument("--log", required=True, help="Nom du fichier de log")
|
||||||
|
parser.add_argument("--table", required=True, help="Nom complet de la table SQL")
|
||||||
|
parser.add_argument("--topic", required=True, help="Topic MQTT à écouter")
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
# 📋 Initialiser le logger
|
||||||
|
setup_logger(args.log)
|
||||||
|
|
||||||
|
# 🔑 Charger les variables d'environnement
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
# 🔌 Connexion MySQL
|
||||||
|
mydb = connect_to_mysql()
|
||||||
|
|
||||||
|
# 📡 Connexion MQTT
|
||||||
|
try:
|
||||||
|
client = mqtt_client.Client()
|
||||||
|
client.username_pw_set("Bwps", "scJ5ACj2keRfI^")
|
||||||
|
client.on_message = partial(on_message, args.table)
|
||||||
|
client.connect("54.36.188.119", 1883, 60)
|
||||||
|
client.subscribe(args.topic)
|
||||||
|
logging.info(f"Connexion MQTT réussie et abonnement au topic '{args.topic}'.")
|
||||||
|
client.loop_forever()
|
||||||
|
except Exception as err:
|
||||||
|
logging.error(f"Erreur MQTT : {err}")
|
||||||
|
|||||||
@@ -27,10 +27,10 @@ logging.basicConfig(
|
|||||||
load_dotenv('/home/debian/Gestion_sondes/.env')
|
load_dotenv('/home/debian/Gestion_sondes/.env')
|
||||||
|
|
||||||
# OVH SMS
|
# OVH SMS
|
||||||
APP_KEY = os.getenv('OVH_APP_KEY')
|
APP_KEY = os.getenv('OVH_APPLICATION_KEY')
|
||||||
APP_SECRET = os.getenv('OVH_APP_SECRET')
|
APP_SECRET = os.getenv('OVH_APPLICATION_SECRET')
|
||||||
CONSUMER_KEY = os.getenv('OVH_CONSUMER_KEY')
|
CONSUMER_KEY = os.getenv('OVH_CONSUMER_KEY')
|
||||||
SERVICE_NAME = os.getenv('OVH_SERVICE_NAME')
|
SERVICE_NAME = os.getenv('OVH_SMS_SERVICE')
|
||||||
SMS_RECEIVER = os.getenv('SMS_RECEIVER')
|
SMS_RECEIVER = os.getenv('SMS_RECEIVER')
|
||||||
SMS_SENDER = os.getenv('OVH_SMS_SENDER')
|
SMS_SENDER = os.getenv('OVH_SMS_SENDER')
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import mysql.connector
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
import os
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
|
||||||
|
def connect_to_mysql():
|
||||||
|
return mysql.connector.connect(
|
||||||
|
host=os.getenv("DB_HOST"),
|
||||||
|
user=os.getenv("DB_USER"),
|
||||||
|
password=os.getenv("DB_PASS"),
|
||||||
|
database=os.getenv("DB_NAME")
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_latest_chaufferie():
|
||||||
|
conn = connect_to_mysql()
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
query = """
|
||||||
|
SELECT Sonde, Temperature, Date, Topic
|
||||||
|
FROM Sondes.Chaufferie
|
||||||
|
WHERE Date >= NOW() - INTERVAL 5 MINUTE
|
||||||
|
ORDER BY Date DESC \
|
||||||
|
"""
|
||||||
|
cursor.execute(query)
|
||||||
|
result = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def get_history_by_sonde(sonde):
|
||||||
|
conn = connect_to_mysql()
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
query = """
|
||||||
|
SELECT Sonde, Temperature, Date
|
||||||
|
FROM Sondes.Chaufferie
|
||||||
|
WHERE Sonde = %s
|
||||||
|
AND Date >= NOW() - INTERVAL 1 DAY \
|
||||||
|
|
||||||
|
"""
|
||||||
|
cursor.execute(query, (sonde,))
|
||||||
|
result = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def lire_alertes_sondes():
|
||||||
|
conn = connect_to_mysql()
|
||||||
|
cursor = conn.cursor(dictionary=True)
|
||||||
|
query = """
|
||||||
|
SELECT Id, Sonde, Debut_defaut, Etat
|
||||||
|
FROM Sondes.Alertes_Chaufferie
|
||||||
|
WHERE Etat != 'Acquitté'
|
||||||
|
ORDER BY Debut_defaut DESC \
|
||||||
|
"""
|
||||||
|
cursor.execute(query)
|
||||||
|
result = cursor.fetchall()
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def acquitter_alerte(id_alerte):
|
||||||
|
conn = connect_to_mysql()
|
||||||
|
cursor = conn.cursor()
|
||||||
|
query = "UPDATE Sondes.Alertes_Chaufferie SET Etat = 'Acquitté' WHERE Id = %s"
|
||||||
|
cursor.execute(query, (id_alerte,))
|
||||||
|
conn.commit()
|
||||||
|
cursor.close()
|
||||||
|
conn.close()
|
||||||
|
|||||||
52
app/utils_sms.py
Normal file
52
app/utils_sms.py
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
import os
|
||||||
|
import ovh
|
||||||
|
from dotenv import load_dotenv
|
||||||
|
|
||||||
|
load_dotenv()
|
||||||
|
|
||||||
|
def envoyer_sms(message: str, lieu: str = ""):
|
||||||
|
try:
|
||||||
|
client = ovh.Client(
|
||||||
|
endpoint=os.getenv("OVH_ENDPOINT"),
|
||||||
|
application_key=os.getenv("OVH_APP_KEY"),
|
||||||
|
application_secret=os.getenv("OVH_APP_SECRET"),
|
||||||
|
consumer_key=os.getenv("OVH_CONSUMER_KEY"),
|
||||||
|
)
|
||||||
|
|
||||||
|
services = client.get('/sms/')
|
||||||
|
if not services:
|
||||||
|
print("❌ Aucun service SMS OVH trouvé", flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
service_name = services[0]
|
||||||
|
numero_dest = os.getenv("SMS_RECEIVER")
|
||||||
|
sender = os.getenv("OVH_SMS_SENDER")
|
||||||
|
|
||||||
|
if numero_dest.startswith('+'):
|
||||||
|
numero_dest = '00' + numero_dest[1:]
|
||||||
|
|
||||||
|
if not numero_dest or not numero_dest.isdigit():
|
||||||
|
print(f"❌ Numéro de téléphone invalide ou manquant : '{numero_dest}'", flush=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
payload = {
|
||||||
|
"sender": sender,
|
||||||
|
"receivers": [numero_dest],
|
||||||
|
"message": message, # Pas d'encodage ni de nettoyage ici
|
||||||
|
"priority": "high",
|
||||||
|
"noStopClause": False
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
print("📤 Requête envoyée à OVH :")
|
||||||
|
print(payload)
|
||||||
|
|
||||||
|
result = client.post(f'/sms/{service_name}/jobs', **payload)
|
||||||
|
|
||||||
|
print(f"📱 SMS envoyé à {numero_dest} pour {lieu}. Job ID : {result['ids']}", flush=True)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Erreur envoi SMS : {e}", flush=True)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
envoyer_sms("Test SMS OVH", lieu="utils_sms")
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
mysql-connector-python~=9.4.0
|
mysql-connector-python~=9.4.0
|
||||||
pandas~=2.3.1
|
pandas~=2.3.1
|
||||||
DateTime~=5.5
|
DateTime~=5.5
|
||||||
streamlit~=1.48.1
|
streamlit
|
||||||
matplotlib~=3.10.1
|
matplotlib~=3.10.1
|
||||||
paho-mqtt~=2.1.0
|
paho-mqtt~=2.1.0
|
||||||
requests~=2.32.5
|
requests~=2.32.5
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ echo "🔷 Dossier NAS : $NAS_DIR (hôte $NAS_HOST)"
|
|||||||
|
|
||||||
# 1) Pré-check SSH & droits écriture NAS
|
# 1) Pré-check SSH & droits écriture NAS
|
||||||
echo "🔷 Test SSH NAS…"
|
echo "🔷 Test SSH NAS…"
|
||||||
if ! ssh "SSH_OPTS "$NAS_HOST" "mkdir -p '$NAS_DIR' && test -w '$NAS_DIR' && echo __SSH_OK__"; then
|
if ! ssh $SSH_OPTS "$NAS_HOST" "mkdir -p '$NAS_DIR' && test -w '$NAS_DIR' && echo __SSH_OK__"; then
|
||||||
echo "❌ Impossible d écrire sur $NAS_HOST:$NAS_DIR (clé SSH ? user ? droits ? SSH NAS activé ?)"
|
echo "❌ Impossible d écrire sur $NAS_HOST:$NAS_DIR (clé SSH ? user ? droits ? SSH NAS activé ?)"
|
||||||
exit 20
|
exit 20
|
||||||
fi
|
fi
|
||||||
|
|||||||
Reference in New Issue
Block a user