#!/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 socket 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 --- MQTT_HOST = os.getenv("MQTT_HOST") MQTT_USER = os.getenv("MQTT_USER") MQTT_PASS = os.getenv("MQTT_PASS") MQTT_PORT = int(os.getenv("MQTT_PORT", "1883")) # Client ID (configurable, sinon suffixé avec le hostname) MQTT_CLIENT_ID = os.getenv( "MQTT_CLIENT_ID_MEUDON", f"Mqtt_meudon_{socket.gethostname()}" ) 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) # Abonnement à TOUT ce qui commence par "Meudon/" result, mid = client.subscribe("Meudon/#") logging.info("Abonné au topic : Meudon/# (result=%s, mid=%s)", result, mid) 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 MySQL 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) # Vérif minimale des variables d'env MQTT if not MQTT_HOST: logging.error("MQTT_HOST_MEUDON manquant !") if not MQTT_USER: logging.warning("MQTT_USER_MEUDON non défini (connexion sans login ?)") if not MQTT_PORT or MQTT_PORT <= 0: logging.error("MQTT_PORT_MEUDON invalide : %s", MQTT_PORT) client = mqtt.Client( client_id=MQTT_CLIENT_ID, 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()