relancement cuisines Saclay et Meudon
This commit is contained in:
@@ -134,6 +134,48 @@ def lire_seuils_depuis_db(site: str):
|
||||
finally:
|
||||
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:
|
||||
table = site
|
||||
cnx = get_db()
|
||||
@@ -359,8 +401,12 @@ class MQTTPublisher:
|
||||
def __init__(self, site: str):
|
||||
self.enabled = (_mqtt_ok and (os.getenv("GYRO_MODE", "").lower() == "mqtt"))
|
||||
self.site = site
|
||||
self.topic = (os.getenv(f"GYRO_MQTT_TOPIC_{site}") or
|
||||
os.getenv(f"GYRO_MQTT_TOPIC_{site.capitalize()}"))
|
||||
self.topic = (
|
||||
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
|
||||
|
||||
if not self.enabled:
|
||||
@@ -416,9 +462,9 @@ class MQTTPublisher:
|
||||
if self.last_state is not None and self.last_state == on:
|
||||
return
|
||||
|
||||
payload = "on" if on else "off"
|
||||
payload = "ON" if on else "OFF"
|
||||
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)
|
||||
if r.rc != 0:
|
||||
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)
|
||||
notifier.send_sms(sms_text)
|
||||
notifier.send_email(subject, email_body)
|
||||
try: beacon.set(True)
|
||||
except Exception: pass
|
||||
|
||||
def notifier_acquittement(site: str, sonde: str, temp: float, seuil: float):
|
||||
subject, sms_text, _ = build_ok_text(site, sonde, temp, seuil)
|
||||
notifier.send_sms(sms_text)
|
||||
try:
|
||||
if not any_alert_open(site):
|
||||
beacon.set(False)
|
||||
except Exception: pass
|
||||
|
||||
# ========= Cycle & boucle =========
|
||||
def run_monitor_cycle(site: str = SITE):
|
||||
# 1) Lecture mesures + config
|
||||
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:
|
||||
nom = str(r["Sonde"])
|
||||
temp = float(r["Temperature"])
|
||||
@@ -460,7 +518,7 @@ def run_monitor_cycle(site: str = SITE):
|
||||
now = now_paris()
|
||||
if temp > 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:
|
||||
conn = get_db()
|
||||
if open_alert(conn, f"Alertes_{site}", nom, now):
|
||||
@@ -468,7 +526,7 @@ def run_monitor_cycle(site: str = SITE):
|
||||
finally:
|
||||
conn.close()
|
||||
else:
|
||||
# Fermer si ouvert → notifier seulement si fermeture réelle
|
||||
# Fermer si ouverte → notifier seulement si fermeture réelle
|
||||
try:
|
||||
conn = get_db()
|
||||
if close_alert(conn, f"Alertes_{site}", nom):
|
||||
@@ -476,7 +534,6 @@ def run_monitor_cycle(site: str = SITE):
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
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)
|
||||
while True:
|
||||
|
||||
Reference in New Issue
Block a user