#!/usr/bin/env python3 """ Mqtt_saclay.py Récupère les mesures MQTT du site Saclay et les insère dans la table Sondes.Saclay. """ 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() 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_HOST = "54.36.188.119" MQTT_USER = "Bwps" MQTT_PASS = "scJ5ACj2keRfI^" MQTT_PORT = int(os.getenv("MQTT_PORT", 1883)) GYRO_TOPIC_SACLAY = os.getenv("GYRO_MQTT_TOPIC_SACLAY", "Saclay/gyrophare") TABLE_NAME = "Saclay" # ========================= # 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 log_dir = os.getenv("LOG_DIR", "./Logs") try: os.makedirs(log_dir, exist_ok=True) file_handler = RotatingFileHandler( os.path.join(log_dir, "Mqtt_saclay.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: 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 -> %s = %.2f", sonde, temperature) except Error as e: logging.exception("Erreur MySQL 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 (%s)", MQTT_HOST) client.subscribe("Saclay/#") logging.info("Abonné au topic : Saclay/#") else: logging.error("Échec de connexion MQTT, 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 : topic=%s payload=%s", topic, payload_raw) if topic == GYRO_TOPIC_SACLAY: return # on ignore le gyrophare sonde = topic.split("/")[-1] if "/" in topic else topic try: value = float(payload_raw.replace(",", ".")) except ValueError: logging.warning("Payload non numérique (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_saclay") client = mqtt.Client( client_id="Mqtt_saclay_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 : %s", e) return logging.info("Boucle MQTT en cours (Ctrl+C pour arrêter)...") try: client.loop_forever() except KeyboardInterrupt: logging.info("Arrêt demandé par l'utilisateur.") finally: client.disconnect() logging.info("Déconnexion MQTT.") if __name__ == "__main__": main()