diff --git a/.env b/.env index f9d079e..2ad1ef7 100644 --- a/.env +++ b/.env @@ -1,18 +1,18 @@ # connexion mysql -DB_HOST=localhost +DB_HOST=192.168.1.100 DB_USER=sondes DB_PASS=TX.)-U1!zq5Axdk4 DB_NAME=Sondes DB_USER2=journal_connexions -DB_PASS2=uu%O6sHgqY%gl&iSMML +DB_PASS2=wQ%geAx*2%%HiE2a!9S DB_NAME2=Acces AUTH_USERS=[{"user":"Michel","pass":"210462"}] # MQTT -MQTT_HOST=162.19.78.131 -MQTT_USER=sondes +MQTT_HOST=192.168.1.100 +MQTT_USER=Sondes MQTT_PASS=3J@bjYP0 MQTT_PORT=1883 diff --git a/.idea/modules.xml b/.idea/modules.xml index 5c9a7d8..0decf6b 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -3,6 +3,7 @@ + \ No newline at end of file diff --git a/app/Mqtt_Meudon.py b/app/Mqtt_Meudon.py index d7ba5b8..277bd6d 100644 --- a/app/Mqtt_Meudon.py +++ b/app/Mqtt_Meudon.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 """ -Mqtt_Meudon.py (version nettoyée) +Mqtt_Meudon.py -------------------------------- - S'abonne à Meudon/# sur le broker MQTT - Parse les messages (topic -> nom de sonde, payload -> température) @@ -36,24 +36,30 @@ from mysql.connector.cursor import MySQLCursor # ========================= # Configuration (ENV) # ========================= +from dotenv import load_dotenv -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", "") +load_dotenv() -DB_HOST = os.getenv("DB_HOST", "127.0.0.1") -DB_USER = os.getenv("DB_USER", "root") -DB_PASS = os.getenv("DB_PASS", "") +DB_HOST = os.getenv("DB_HOST", "localhost") +DB_USER = os.getenv("DB_USER") +DB_PASS = os.getenv("DB_PASS") DB_NAME = os.getenv("DB_NAME", "Sondes") + DB_TABLE = os.getenv("DB_TABLE", "Meudon") - -LOG_FILE = os.getenv("LOG_FILE", "Mqtt_meudon.log") -LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() - DB_POOL_SIZE = int(os.getenv("DB_POOL_SIZE", "5")) +MQTT_HOST = os.getenv("MQTT_HOST", "192.168.1.100") +MQTT_USER = os.getenv("MQTT_USER", "Sondes") +MQTT_PASS = os.getenv("MQTT_PASS", "3J@bjYP0") +MQTT_PORT = int(os.getenv("MQTT_PORT", "1883")) +GYRO_TOPIC_MEUDON = os.getenv("GYRO_MQTT_TOPIC_MEUDON", "Meudon/gyrophare") + +LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO").upper() +LOG_FILE = os.getenv( + "LOG_FILE", + "/home/domo91/Gestion_sondes/Logs/cuisine_meudon_script.log" +) # ========================= # Logging # ========================= diff --git a/app/Mqtt_saclay.py b/app/Mqtt_saclay.py index 2fe586f..b6ecb77 100644 --- a/app/Mqtt_saclay.py +++ b/app/Mqtt_saclay.py @@ -26,8 +26,8 @@ DB_USER = os.getenv("DB_USER") DB_PASS = os.getenv("DB_PASS") DB_NAME = os.getenv("DB_NAME") -MQTT_HOST = "162.19.78.131" -MQTT_USER = "sondes" +MQTT_HOST = "192.168.1.100" +MQTT_USER = "Sondes" MQTT_PASS = "3J@bjYP0" MQTT_PORT = int(os.getenv("MQTT_PORT", 1883)) diff --git a/app/domo91.py b/app/domo91.py index 745787f..6957084 100644 --- a/app/domo91.py +++ b/app/domo91.py @@ -949,7 +949,8 @@ if st.session_state.get("authenticated"): except (ValueError, TypeError): return "" - styled_df = df_sonde.style.applymap(surlignage_temp, subset=["Temperature"]) + + styled_df = df_sonde.style.map(surlignage_temp, subset=["Temperature"]) st.dataframe(styled_df, use_container_width=True) st.subheader("📈 Évolution de la température") diff --git a/app/surveillance_releves.py b/app/surveillance_releves.py index 95398d8..ce34092 100644 --- a/app/surveillance_releves.py +++ b/app/surveillance_releves.py @@ -17,6 +17,7 @@ import json import logging import os import sys +import re import mysql.connector from contextlib import closing @@ -51,8 +52,12 @@ load_dotenv(ENV_PATH, override=True) # CONFIGURATION # ============================================================ -TABLES = ["Saclay", "Meudon"] -TABLES_SET = set(TABLES) +# Table de configuration : seuls les sites avec Actif = 'ON' seront surveillés. +TABLE_SITES_SURVEILLANCE = "Sites_Surveillance" + +# Sécurité / secours : utilisé uniquement si la table Sites_Surveillance est absente +# ou si elle ne retourne aucun site actif. +TABLES_FALLBACK = ["Saclay", "Meudon"] DELAI_MINUTES = 15 RAPPEL_HEURES = 6 @@ -269,8 +274,23 @@ def envoyer_notifications(site: str, sujet: str, message: str) -> None: # ACCES BASE # ============================================================ +def is_safe_identifier(name: str) -> bool: + """ + Autorise uniquement les noms simples de tables/sites : + lettres, chiffres et underscore. + Cela évite toute injection SQL via un nom de table dynamique. + """ + return bool(re.fullmatch(r"[A-Za-z0-9_]+", name or "")) + + +def quote_identifier(name: str) -> str: + if not is_safe_identifier(name): + raise ValueError(f"Nom de table/site invalide : {name!r}") + return f"`{name}`" + + def get_last_update(cursor, table: str) -> datetime | None: - cursor.execute(f"SELECT MAX(Date) FROM `{table}`") + cursor.execute(f"SELECT MAX(Date) FROM {quote_identifier(table)}") row = cursor.fetchone() if not row or row[0] is None: @@ -284,19 +304,77 @@ def get_last_update(cursor, table: str) -> datetime | None: return None +def get_tables_a_surveiller(cursor) -> list[str]: + """ + Lit la table Sites_Surveillance. + Seuls les sites Actif = 'ON' sont surveillés. + Les sites Actif = 'OFF' sont journalisés mais ignorés. + """ + try: + cursor.execute(f""" + SELECT Lieu, Actif, Commentaire + FROM {quote_identifier(TABLE_SITES_SURVEILLANCE)} + ORDER BY Lieu + """) + rows = cursor.fetchall() + + except mysql.connector.Error as e: + logging.error( + "Impossible de lire %s : %s. Utilisation du fallback : %s", + TABLE_SITES_SURVEILLANCE, + e, + ", ".join(TABLES_FALLBACK), + ) + return TABLES_FALLBACK.copy() + + tables_actives: list[str] = [] + + for lieu, actif, commentaire in rows: + lieu = str(lieu).strip() + actif = str(actif).strip().upper() + + if not is_safe_identifier(lieu): + logging.warning("Site ignoré dans %s : nom invalide %r", TABLE_SITES_SURVEILLANCE, lieu) + continue + + if actif == "ON": + tables_actives.append(lieu) + else: + if commentaire: + logging.info("⏸️ %s ignoré : surveillance OFF (%s)", lieu, commentaire) + else: + logging.info("⏸️ %s ignoré : surveillance OFF", lieu) + + if not tables_actives: + logging.warning( + "Aucun site actif dans %s. Utilisation du fallback : %s", + TABLE_SITES_SURVEILLANCE, + ", ".join(TABLES_FALLBACK), + ) + return TABLES_FALLBACK.copy() + + logging.info("Sites surveillés : %s", ", ".join(tables_actives)) + return tables_actives + + # ============================================================ # TRAITEMENT DES TABLES # ============================================================ def traiter_table(cursor, table: str, limite: datetime, + tables_autorisees: set[str], defauts_en_cours: list[str], alertes_envoyees: list[str], erreurs_sql: list[str]) -> None: """ Gère la surveillance d'une table. """ - if table not in TABLES_SET: - logging.warning(f"Table ignorée (non whitelistée) : {table}") + if table not in tables_autorisees: + logging.warning(f"Table ignorée (non autorisée) : {table}") + return + + if not is_safe_identifier(table): + logging.warning(f"Table ignorée (nom invalide) : {table}") return try: @@ -386,11 +464,15 @@ def main() -> None: with closing(cnx): cursor = cast(MySQLCursor, cnx.cursor()) with closing(cursor): - for table in TABLES: + tables_a_surveiller = get_tables_a_surveiller(cursor) + tables_autorisees = set(tables_a_surveiller) + + for table in tables_a_surveiller: traiter_table( cursor=cursor, table=table, limite=limite, + tables_autorisees=tables_autorisees, defauts_en_cours=defauts_en_cours, alertes_envoyees=alertes_envoyees, erreurs_sql=erreurs_sql,