Remise en état des userforms inventaire
This commit is contained in:
@@ -27,7 +27,7 @@ try:
|
||||
_ovh_available = True
|
||||
except Exception:
|
||||
ovh = None # type: ignore
|
||||
class OVHAPIError(Exception): pass
|
||||
class OVHAPIError(Exception): ...
|
||||
_ovh_available = False
|
||||
|
||||
# MQTT
|
||||
@@ -44,7 +44,7 @@ if not log.handlers:
|
||||
logging.basicConfig(level=level, format="%(asctime)s %(levelname)s %(message)s")
|
||||
|
||||
# ========= DB utils =========
|
||||
def open_alert(conn, table_alertes: str, sonde: str, dt: datetime) -> bool:
|
||||
def open_alert(conn, table_alertes: str, sonde: str, dt_: datetime) -> bool:
|
||||
"""
|
||||
Ouvre UNE alerte si aucune alerte 'En cours' n'existe encore pour la sonde.
|
||||
Retourne True si une nouvelle alerte a été créée (→ notifier par mail & SMS client).
|
||||
@@ -59,7 +59,7 @@ def open_alert(conn, table_alertes: str, sonde: str, dt: datetime) -> bool:
|
||||
return False # déjà ouverte
|
||||
cur.execute(
|
||||
f"INSERT INTO `{table_alertes}` (Sonde, Debut_defaut, Etat) VALUES (%s, %s, 'En cours')",
|
||||
(sonde, dt.strftime('%Y-%m-%d %H:%M:%S'))
|
||||
(sonde, dt_.strftime('%Y-%m-%d %H:%M:%S'))
|
||||
)
|
||||
conn.commit()
|
||||
cur.close()
|
||||
@@ -92,6 +92,45 @@ def get_db():
|
||||
autocommit=True,
|
||||
)
|
||||
|
||||
# --- Journalisation Gyro en table dédiée `Gyro` ---
|
||||
def insert_gyro_log(lieu: str, etat: str, topic: str, payload_raw: str,
|
||||
qos: int | None, retained: int | None, when: datetime):
|
||||
cnx = get_db()
|
||||
try:
|
||||
cur = cnx.cursor()
|
||||
cur.execute(
|
||||
"INSERT INTO Sondes.Gyro (Lieu, Sonde, Etat, Date, Topic, Payload, QoS, Retained) "
|
||||
"VALUES (%s, %s, %s, %s, %s, %s, %s, %s)",
|
||||
(
|
||||
lieu,
|
||||
os.getenv("GYRO_SONDE_NAME", "Gyro"),
|
||||
etat, # 'ON' ou 'OFF'
|
||||
when.strftime('%Y-%m-%d %H:%M:%S'),
|
||||
topic,
|
||||
payload_raw,
|
||||
qos,
|
||||
retained
|
||||
)
|
||||
)
|
||||
cnx.commit()
|
||||
log.info("Gyro inséré: %s %s (%s)", lieu, etat, topic)
|
||||
except MySQLError as err:
|
||||
log.exception("Erreur DB insert_gyro_log: %s", err)
|
||||
finally:
|
||||
cnx.close()
|
||||
|
||||
def should_insert_gyro(lieu: str, etat: str, sonde: str = "Gyro") -> bool:
|
||||
sql = "SELECT Etat FROM Sondes.Gyro WHERE Lieu=%s AND Sonde=%s ORDER BY Date DESC LIMIT 1"
|
||||
cnx = get_db()
|
||||
try:
|
||||
cur = cnx.cursor()
|
||||
cur.execute(sql, (lieu, sonde))
|
||||
row = cur.fetchone()
|
||||
return (row is None) or (row[0] != etat)
|
||||
finally:
|
||||
cnx.close()
|
||||
|
||||
# --- Lecture des dernières mesures de température (en ignorant lignes d'état) ---
|
||||
def lire_sondes_depuis_db(site: str):
|
||||
table = site
|
||||
sql = f"""
|
||||
@@ -100,8 +139,10 @@ def lire_sondes_depuis_db(site: str):
|
||||
JOIN (
|
||||
SELECT Sonde, MAX(Date) AS MaxDate
|
||||
FROM `{table}`
|
||||
WHERE Temperature IS NOT NULL
|
||||
GROUP BY Sonde
|
||||
) t2 ON t1.Sonde=t2.Sonde AND t1.Date=t2.MaxDate
|
||||
WHERE t1.Temperature IS NOT NULL
|
||||
"""
|
||||
cnx = get_db()
|
||||
try:
|
||||
@@ -109,7 +150,7 @@ def lire_sondes_depuis_db(site: str):
|
||||
cur.execute(sql)
|
||||
rows = cur.fetchall()
|
||||
for r in rows:
|
||||
r["Temperature"] = float(r["Temperature"])
|
||||
r["Temperature"] = float(r["Temperature"]) # garanti NOT NULL
|
||||
return rows
|
||||
except MySQLError as err:
|
||||
log.exception("Erreur DB (lire_sondes_depuis_db): %s", err)
|
||||
@@ -117,6 +158,7 @@ def lire_sondes_depuis_db(site: str):
|
||||
finally:
|
||||
cnx.close()
|
||||
|
||||
|
||||
def lire_cfg_chambres(site: str):
|
||||
"""
|
||||
Retourne {sonde: {"temp_max": float, "active": bool, "entretien": bool}}
|
||||
@@ -518,13 +560,70 @@ class MQTTPublisher:
|
||||
self.client.tls_set()
|
||||
|
||||
try:
|
||||
# Attacher le callback avant de s'abonner
|
||||
self.client.on_message = self._on_message
|
||||
|
||||
self.client.connect(host, port, keepalive=30)
|
||||
|
||||
# Abonnements (depuis env ou valeurs par défaut raisonnables)
|
||||
subs_env = (
|
||||
os.getenv(f"GYRO_MQTT_SUB_{site}") or
|
||||
os.getenv(f"GYRO_MQTT_SUB_{site.upper()}") or
|
||||
os.getenv("GYRO_MQTT_SUB") or
|
||||
""
|
||||
)
|
||||
subs = [t.strip() for t in subs_env.split(",") if t.strip()]
|
||||
if not subs:
|
||||
subs = [
|
||||
self.topic, # ex: Sondes/Saclay/Gyro/cmd
|
||||
f"Sondes/{site}/Gyro/#",
|
||||
f"{site}/Gyro/#",
|
||||
"Gyro/#",
|
||||
]
|
||||
for t in subs:
|
||||
try:
|
||||
self.client.subscribe(t, qos=2)
|
||||
log.info("MQTT subscribe: %s", t)
|
||||
except Exception as e:
|
||||
log.warning("Subscribe échoué (%s): %s", t, e)
|
||||
|
||||
self.client.loop_start()
|
||||
log.info("MQTT connecté (%s:%s), topic=%s", host, port, self.topic)
|
||||
except Exception as e:
|
||||
log.exception("MQTT connexion impossible: %s", e)
|
||||
self.enabled = False
|
||||
|
||||
# --- Callback réception MQTT ---
|
||||
def _on_message(self, client, userdata, msg):
|
||||
lieu = self.site
|
||||
topic = msg.topic
|
||||
payload_raw = msg.payload.decode(errors="ignore").strip()
|
||||
upper = payload_raw.upper()
|
||||
|
||||
# 1) Évènements gyrophare
|
||||
if upper in ("ON", "OFF") or "gyro" in topic.lower() or "gyrophare" in topic.lower():
|
||||
etat = upper if upper in ("ON", "OFF") else ("ON" if "on" in upper else "OFF")
|
||||
try:
|
||||
if should_insert_gyro(lieu, etat):
|
||||
insert_gyro_log(
|
||||
lieu=lieu,
|
||||
etat=etat,
|
||||
topic=topic,
|
||||
payload_raw=payload_raw,
|
||||
qos=getattr(msg, "qos", None),
|
||||
retained=getattr(msg, "retain", None),
|
||||
when=now_paris()
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception("Insert Gyro échoué: %s", e)
|
||||
return # ne pas poursuivre vers un parse température ici
|
||||
|
||||
# 2) Pas du gyro → ignorer ici (la collecte T° est gérée ailleurs)
|
||||
try:
|
||||
float(payload_raw.replace(",", "."))
|
||||
except ValueError:
|
||||
log.debug("Payload non géré (ni gyro ni nombre): %s %s", topic, payload_raw)
|
||||
|
||||
def set(self, on: bool):
|
||||
if not self.enabled:
|
||||
return
|
||||
@@ -533,11 +632,27 @@ class MQTTPublisher:
|
||||
payload = "ON" if on else "OFF"
|
||||
try:
|
||||
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)
|
||||
try:
|
||||
r.wait_for_publish(timeout=3)
|
||||
except Exception:
|
||||
pass
|
||||
if getattr(r, 'rc', 0) != 0:
|
||||
log.warning("MQTT publish rc=%s (topic=%s)", getattr(r, 'rc', None), self.topic)
|
||||
else:
|
||||
log.info("Gyro %s -> %s (MQTT)", self.site, payload.upper())
|
||||
# Enregistrer en base l'événement gyro
|
||||
try:
|
||||
insert_gyro_log(
|
||||
lieu=self.site,
|
||||
etat=payload,
|
||||
topic=self.topic,
|
||||
payload_raw=payload,
|
||||
qos=2,
|
||||
retained=1 if getattr(r, 'is_published', lambda: False)() else None,
|
||||
when=now_paris()
|
||||
)
|
||||
except Exception as e:
|
||||
log.exception("Insert événement gyro en base a échoué: %s", e)
|
||||
self.last_state = on
|
||||
except Exception as e:
|
||||
log.exception("MQTT publish erreur: %s", e)
|
||||
@@ -759,13 +874,13 @@ def run_monitor_cycle(site: str = SITE):
|
||||
nom = str(r["Sonde"])
|
||||
temp = float(r["Temperature"])
|
||||
seuil = float(seuils.get(nom, 6.0))
|
||||
now = now_paris()
|
||||
now_ = now_paris()
|
||||
|
||||
if temp > seuil:
|
||||
if depassement_depuis_30min(site, nom, seuil):
|
||||
try:
|
||||
conn = get_db()
|
||||
if open_alert(conn, f"Alertes_{site}", nom, now):
|
||||
if open_alert(conn, f"Alertes_{site}", nom, now_):
|
||||
notifier_sur_depassement(site, nom, temp, seuil) # MAIL + SMS client
|
||||
finally:
|
||||
conn.close()
|
||||
@@ -822,4 +937,3 @@ if __name__ == "__main__":
|
||||
run_monitor_cycle(SITE)
|
||||
else:
|
||||
run_monitor_loop(SITE, period_sec=args.period)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user