#!/usr/bin/env python3 """ Mqtt_meudon.py Récupère les mesures MQTT du site Meudon et les insère dans la table Sondes.Meudon. """ import os import logging from logging.handlers import RotatingFileHandler import mysql.connector from mysql.connector import Error import paho.mqtt.client as mqtt from paho.mqtt.client import CallbackAPIVersion from dotenv import load_dotenv # ========================= # Chargement du .env # ========================= load_dotenv() # --- MySQL (commun) --- DB_HOST = os.getenv("DB_HOST") DB_USER = os.getenv("DB_USER") DB_PASS = os.getenv("DB_PASS") DB_NAME = os.getenv("DB_NAME") # --- MQTT Meudon --- MQTT_HOST = os.getenv("MQTT_HOST_MEUDON", os.getenv("MQTT_HOST")) MQTT_USER = os.getenv("MQTT_USER_MEUDON", os.getenv("MQTT_USER")) MQTT_PASS = os.getenv("MQTT_PASS_MEUDON", os.getenv("MQTT_PASS")) MQTT_PORT = int(os.getenv("MQTT_PORT_MEUDON", os.getenv("MQTT_PORT", 1883))) GYRO_TOPIC_MEUDON = os.getenv("GYRO_MQTT_TOPIC_MEUDON", "Meudon/gyrophare") # Nom de la table de destination TABLE_NAME = "Meudon" # ========================= # Logging # ========================= def setup_logging(): logger = logging.getLogger() logger.setLevel(logging.INFO) formatter = logging.Formatter( "%(asctime)s - %(levelname)s - %(message)s", datefmt="%Y-%m-%d %H:%M:%S" ) # Console console = logging.StreamHandler() console.setFormatter(formatter) logger.addHandler(console) # Logs fichier (même logique que Saclay) log_dir = os.getenv("LOG_DIR", "./Logs") try: os.makedirs(log_dir, exist_ok=True) file_handler = RotatingFileHandler( os.path.join(log_dir, "Mqtt_meudon.log"), maxBytes=1_000_000, backupCount=5, encoding="utf-8", ) file_handler.setFormatter(formatter) logger.addHandler(file_handler) except Exception as e: logging.warning("Impossible de créer le fichier de log : %s", e) # ========================= # Accès MySQL # ========================= def insert_temperature(sonde: str, temperature: float) -> None: """ Insère une mesure dans la table Sondes.Meudon. La colonne Date utilise CURRENT_TIMESTAMP par défaut dans MySQL. """ try: conn = mysql.connector.connect( host=DB_HOST, user=DB_USER, password=DB_PASS, database=DB_NAME, ) cursor = conn.cursor() sql = f"INSERT INTO {TABLE_NAME} (Sonde, Temperature) VALUES (%s, %s)" cursor.execute(sql, (sonde, temperature)) conn.commit() logging.info("Insertion OK (Meudon) -> %s = %.2f", sonde, temperature) except Error as e: logging.exception("Erreur MySQL (Meudon) pour la sonde %s : %s", sonde, e) finally: try: if cursor: cursor.close() if conn and conn.is_connected(): conn.close() except Exception: pass # ========================= # Callbacks MQTT (API v2) # ========================= def on_connect(client, userdata, flags, reason_code, properties=None): if reason_code == 0: logging.info("Connecté au broker MQTT Meudon (%s)", MQTT_HOST) client.subscribe("Meudon/mod02/#") logging.info("Abonné au topic : Meudon/#") else: logging.error("Échec de connexion MQTT (Meudon), code retour = %s", reason_code) def on_message(client, userdata, msg: mqtt.MQTTMessage): topic = msg.topic payload_raw = msg.payload.decode("utf-8", errors="ignore").strip() logging.debug("Msg reçu (Meudon) : topic=%s payload=%s", topic, payload_raw) # On ignore le gyrophare if topic == GYRO_TOPIC_MEUDON: logging.debug("Topic gyrophare Meudon ignoré : %s", topic) return # Nom de la sonde = dernier segment du topic sonde = topic.split("/")[-1] if "/" in topic else topic # Conversion du payload en float try: value = float(payload_raw.replace(",", ".")) except ValueError: logging.warning( "Payload non numérique (Meudon), mesure ignorée (topic=%s, payload=%s)", topic, payload_raw, ) return insert_temperature(sonde, value) # ========================= # Programme principal # ========================= def main(): setup_logging() logging.info("Démarrage du script Mqtt_meudon") # Vérif minimale des variables d'env for var in ["DB_HOST", "DB_USER", "DB_PASS", "DB_NAME"]: if os.getenv(var) in (None, ""): logging.error("Variable d'environnement %s manquante !", var) client = mqtt.Client( client_id="Mqtt_meudon_client", callback_api_version=CallbackAPIVersion.VERSION2 ) client.username_pw_set(MQTT_USER, MQTT_PASS) client.on_connect = on_connect client.on_message = on_message try: client.connect(MQTT_HOST, MQTT_PORT, keepalive=60) except Exception as e: logging.exception("Impossible de se connecter au broker MQTT Meudon : %s", e) return logging.info("Boucle MQTT Meudon en cours (Ctrl+C pour arrêter)...") try: client.loop_forever() except KeyboardInterrupt: logging.info("Arrêt demandé par l'utilisateur (Meudon).") finally: client.disconnect() logging.info("Client MQTT Meudon déconnecté.") if __name__ == "__main__": main()