6 Commits

Author SHA1 Message Date
c0a8929084 Fix: Gérer les OID invalides avec un avertissement
Plutôt que de générer une erreur lorsqu'un OID SNMP renvoie une valeur vide ou non-convertible, le système émet désormais un simple avertissement.

Cela évite de polluer les logs d'erreurs pour des cas non-bloquants, comme un port débranché qui renvoie une chaîne vide au lieu d'un entier.
2026-02-15 21:12:15 +01:00
6c7248f08d Mise à jour de la documentation (humaine + IA) 2025-11-06 17:08:37 +01:00
7888999ec3 feat: Ajoute la transformation de valeur par opération mathématique
Cette modification introduit la possibilité d'appliquer une opération mathématique simple (addition, soustraction, multiplication, division) sur les valeurs SNMP récupérées.

L'utilisateur peut désormais définir une clé 'operation' dans la configuration d'un OID (par exemple, 'value / 1000') pour normaliser les données avant leur envoi à MQTT. La documentation dans `config.yaml` a été mise à jour avec un exemple.
2025-11-06 17:05:20 +01:00
ac476df7ce fix: les capteurs 'data_size' ont l'attribut 'total_increasing' 2025-09-09 22:33:37 +02:00
f48a9d1417 Aide au codage 2025-09-09 22:32:57 +02:00
b7331faba0 logging INFO par défaut 2025-08-24 16:57:21 +02:00
4 changed files with 158 additions and 3 deletions

79
GEMINI.md Normal file
View File

@@ -0,0 +1,79 @@
# Fichier d'aide pour Gemini
Ce document fournit un résumé concis du projet `snmp2mqtt` pour aider au développement et à la maintenance assistés par l'IA.
## 1. Objectif du Projet
Le projet `snmp2mqtt` est une passerelle écrite en Python qui a pour but de :
1. **Interroger** des équipements réseau (routeurs, switchs, etc.) via le protocole **SNMP**.
2. **Récupérer** des métriques spécifiques (trafic, statut des ports, etc.) définies par des OIDs.
3. **Publier** ces données sur un broker **MQTT**.
4. **S'intégrer automatiquement** avec **Home Assistant** grâce au mécanisme de "MQTT Discovery", permettant de créer des capteurs sans configuration manuelle côté Home Assistant.
## 2. Architecture et Technologies
- **Langage** : Python 3
- **Dépendances principales** (`requirements.txt`) :
- `pysnmp>=7.0.0` : Pour la communication SNMP asynchrone.
- `paho-mqtt>=2.0.0` : Pour la communication avec le broker MQTT.
- `PyYAML>=6.0.0` : Pour le parsing du fichier de configuration.
- **Configuration** : Un unique fichier `config.yaml` centralise tous les paramètres (MQTT, appareils, OIDs).
- **Exécution** : Le script utilise le multi-threading. Un thread est démarré pour chaque appareil défini dans la configuration, ce qui permet une surveillance parallèle et isolée.
## 3. Structure du Code (`snmp2mqtt.py`)
Le script principal est organisé de la manière suivante :
1. **`main()`** : Point d'entrée. Il parse les arguments (`--config`), charge la configuration et appelle `process_devices()`.
2. **`process_devices(config)`** :
- Orchestre le lancement des threads.
- Crée et démarre une instance de `DeviceMonitorThread` pour chaque appareil.
- Gère l'arrêt propre (`graceful shutdown`) en attendant que tous les threads se terminent.
3. **`DeviceMonitorThread(threading.Thread)`** :
- Classe qui encapsule la logique de surveillance pour un seul appareil.
- `run()` : méthode principale du thread.
- Établit la connexion MQTT.
- Publie la configuration de découverte automatique pour Home Assistant (une seule fois au démarrage).
- Entre dans une boucle infinie qui :
- Appelle `get_snmp()` pour récupérer les données.
- Publie l'état des capteurs et le statut de disponibilité (`online`/`offline`) sur MQTT.
- Attend un intervalle (`sleep_interval`) avant la prochaine interrogation.
4. **`get_snmp(req)`** :
- Fonction `async` qui utilise `pysnmp` pour exécuter les requêtes `GET` SNMP pour tous les OIDs d'un appareil.
- Traite la clé optionnelle `operation` pour appliquer une transformation mathématique.
- Retourne un dictionnaire contenant les valeurs formatées.
5. **Fonctions de configuration et MQTT** :
- `load_config()` : Charge et valide le fichier `config.yaml`.
- `connect_mqtt()` : Initialise le client MQTT.
- `publish()` : Wrapper pour publier les messages MQTT.
- `publish_ha_autodiscovery_config()` : Construit et publie les messages de configuration pour Home Assistant MQTT Discovery.
- `apply_operation(value, operation_str)` : Fonction d'aide qui applique de manière sécurisée une opération mathématique simple à une valeur.
## 4. Flux de Données
```
[Appareil SNMP] <--- (Requête SNMP GET) --- [snmp2mqtt.py / Thread]
|
| (Réponse SNMP)
v
[snmp2mqtt.py / Thread] --- (Publication MQTT) ---> [Broker MQTT]
|
| (MQTT Discovery & State)
v
[Home Assistant]
```
## 5. Comment développer
- **Environnement** :
1. Créer un environnement virtuel : `python3 -m venv .venv`
2. Activer l'environnement : `source .venv/bin/activate`
3. Installer les dépendances : `pip install -r requirements.txt`
- **Configuration** :
- Copier et modifier `config.yaml` pour pointer vers un broker MQTT de test et un appareil SNMP accessible.
- **Lancement** :
- `python snmp2mqtt.py --config config.yaml`
- **Points clés à modifier** :
- Pour ajouter une nouvelle fonctionnalité à un capteur Home Assistant, modifier `create_ha_sensor_config()`.
- Pour changer la logique de récupération SNMP, modifier `get_snmp()`.
- Pour ajouter de nouveaux paramètres de configuration, mettre à jour `load_config()` pour la validation.

View File

@@ -10,6 +10,7 @@ Passerelle SNMP vers MQTT pour l'intégration Home Assistant. Ce script Python s
-**SNMP asynchrone** : Requêtes SNMP non-bloquantes pour de meilleures performances -**SNMP asynchrone** : Requêtes SNMP non-bloquantes pour de meilleures performances
- 🔄 **Surveillance en temps réel** : Mise à jour continue des métriques réseau - 🔄 **Surveillance en temps réel** : Mise à jour continue des métriques réseau
- 📊 **Métriques réseau** : Trafic entrant/sortant et statut des interfaces - 📊 **Métriques réseau** : Trafic entrant/sortant et statut des interfaces
- 🔢 **Transformation de données** : Appliquez des opérations mathématiques simples (division, multiplication...) pour normaliser les valeurs.
## Architecture ## Architecture
@@ -119,6 +120,7 @@ Chaque OID dans la liste `oids` doit contenir :
| `HA_device_class` | string | ✅ | Classe d'équipement Home Assistant | | `HA_device_class` | string | ✅ | Classe d'équipement Home Assistant |
| `HA_platform` | string | ✅ | Plateforme Home Assistant ("sensor", "binary_sensor") | | `HA_platform` | string | ✅ | Plateforme Home Assistant ("sensor", "binary_sensor") |
| `HA_unit` | string | ❌ | Unité de mesure pour le capteur | | `HA_unit` | string | ❌ | Unité de mesure pour le capteur |
| `operation` | string | ❌ | Opération mathématique à appliquer (ex: "value / 1000") |
### Classes d'équipements Home Assistant courantes ### Classes d'équipements Home Assistant courantes
@@ -338,6 +340,15 @@ devices:
HA_device_class: "connectivity" HA_device_class: "connectivity"
HA_platform: "binary_sensor" HA_platform: "binary_sensor"
# Exemple avec transformation de valeur (température en millidegrés -> degrés)
# - name: "temperature"
# oid: ".1.3.6.1.4.1.14988.1.1.6.1.0" # OID pour la température sur MikroTik
# type: "int"
# operation: "value / 1000"
# HA_device_class: "temperature"
# HA_platform: "sensor"
# HA_unit: "°C"
# Switch réseau # Switch réseau
switch_bureau: switch_bureau:
ip: "192.168.10.5" ip: "192.168.10.5"
@@ -458,7 +469,7 @@ Chaque thread est clairement identifié dans les logs :
## Logs et debugging ## Logs et debugging
Le script utilise le module `logging` de Python avec le niveau DEBUG par défaut. Les logs incluent : Le script utilise le module `logging` de Python avec le niveau INFO par défaut. Les logs incluent :
- Chargement de la configuration - Chargement de la configuration
- Connexions MQTT - Connexions MQTT

View File

@@ -40,6 +40,17 @@ devices:
HA_device_class: "connectivity" HA_device_class: "connectivity"
HA_platform: "binary_sensor" HA_platform: "binary_sensor"
# Example of a temperature sensor that returns the value in millidegrees.
# The 'operation' key allows performing a simple calculation.
# The placeholder 'value' will be replaced by the SNMP value.
# - name: "temperature"
# oid: ".1.3.6.1.4.1.XXXX.1.1.1.5.1.3.1" # Example OID
# type: "int"
# operation: "value / 1000"
# HA_device_class: "temperature"
# HA_platform: "sensor"
# HA_unit: "°C"
# OID Configuration Reference: # OID Configuration Reference:
# - name: Unique identifier for this metric (used in MQTT topics and Home Assistant) # - name: Unique identifier for this metric (used in MQTT topics and Home Assistant)
# - oid: SNMP Object Identifier # - oid: SNMP Object Identifier

View File

@@ -207,6 +207,46 @@ def publish(topic, client, data, retain, qos):
logging.error(f"Failed to send message to topic {topic}") logging.error(f"Failed to send message to topic {topic}")
def apply_operation(value, operation_str):
"""
Applies a simple mathematical operation to a value.
e.g., operation_str = "value / 1000"
"""
if 'value' not in operation_str:
logging.error(f"Invalid operation string: 'value' placeholder missing in '{operation_str}'")
return value
expression = operation_str.replace('value', str(value))
try:
parts = expression.split()
if len(parts) != 3:
logging.error(f"Invalid operation format: '{operation_str}'. Expected 'value <operator> <operand>'")
return value
val = float(parts[0])
operator = parts[1]
operand = float(parts[2])
if operator == '+':
return val + operand
elif operator == '-':
return val - operand
elif operator == '*':
return val * operand
elif operator == '/':
if operand == 0:
logging.warning(f"Attempted division by zero in operation: {operation_str}")
return value
return val / operand
else:
logging.error(f"Unsupported operator: '{operator}' in '{operation_str}'")
return value
except (ValueError, IndexError) as e:
logging.error(f"Could not parse operation string: '{operation_str}'. Error: {e}")
return value
async def get_snmp(req): async def get_snmp(req):
"""Asynchronously retrieve SNMP data from device using new pysnmp API""" """Asynchronously retrieve SNMP data from device using new pysnmp API"""
data = {} data = {}
@@ -239,13 +279,24 @@ async def get_snmp(req):
else: else:
for varBind in varBinds: for varBind in varBinds:
logging.debug(f"{req['device_name']} {oid['name']} => {oid['type'](varBind[1])}") logging.debug(f"{req['device_name']} {oid['name']} => {oid['type'](varBind[1])}")
# Cast to the right type
value = oid['type'](varBind[1])
# Apply operation if defined
if 'operation' in oid:
value = apply_operation(value, oid['operation'])
if oid['type'] == bool: if oid['type'] == bool:
if bool(varBind[1]): if bool(value):
data.update({oid["name"]: "ON"}) data.update({oid["name"]: "ON"})
else: else:
data.update({oid["name"]: "OFF"}) data.update({oid["name"]: "OFF"})
else: else:
data.update({oid["name"]: oid["type"](varBind[1])}) data.update({oid["name"]: value})
except ValueError as e:
logging.warning(f"{req['device_name']} OID {oid['oid']} ({oid['name']}) returned an invalid value: {e}")
continue
except Exception as e: except Exception as e:
logging.error(f"{req['device_name']} Exception getting OID {oid['oid']}: {e}") logging.error(f"{req['device_name']} Exception getting OID {oid['oid']}: {e}")
continue continue
@@ -286,6 +337,9 @@ def create_ha_sensor_config(req, oid):
# Add device class if specified # Add device class if specified
if 'HA_device_class' in oid: if 'HA_device_class' in oid:
config['device_class'] = oid['HA_device_class'] config['device_class'] = oid['HA_device_class']
# Add state_class for total_increasing counters like data size
if oid['HA_device_class'] == 'data_size':
config['state_class'] = 'total_increasing'
# Add unit of measurement if specified # Add unit of measurement if specified
if 'HA_unit' in oid: if 'HA_unit' in oid: