3 Commits

Author SHA1 Message Date
4e2dec2441 Home Assistant auto-discovery 2025-08-24 15:15:31 +02:00
083cfcce1d Execution multithread 2025-08-24 14:48:51 +02:00
96b0cd9b99 Fichier README 2025-08-24 14:38:42 +02:00
3 changed files with 497 additions and 94 deletions

279
README.md
View File

@@ -1,3 +1,280 @@
# snmp2mqtt
Script python pour relayer les infos snmp en mqtt
Passerelle SNMP vers MQTT pour l'intégration Home Assistant. Ce script Python surveille les équipements réseau via SNMP et publie les données vers un broker MQTT pour une découverte automatique dans Home Assistant.
## Fonctionnalités
- 🔧 **Configuration YAML flexible** : Tous les paramètres dans un fichier de configuration
- 📡 **Support multi-équipements** : Surveillez plusieurs périphériques réseau
- 🏠 **Intégration Home Assistant** : Découverte automatique des capteurs
-**SNMP asynchrone** : Requêtes SNMP non-bloquantes pour de meilleures performances
- 🔄 **Surveillance en temps réel** : Mise à jour continue des métriques réseau
- 📊 **Métriques réseau** : Trafic entrant/sortant et statut des interfaces
## Architecture
### Composants principaux
- **Client SNMP** : Utilise `pysnmp.hlapi.asyncio.slim` pour la récupération asynchrone des données SNMP
- **Publisher MQTT** : Utilise `paho.mqtt.client` pour publier les données vers un broker MQTT
- **Intégration Home Assistant** : Génère la configuration de découverte automatique compatible avec Home Assistant MQTT Discovery
- **Traitement des données** : Convertit les valeurs des OID SNMP vers les types appropriés (int, bool) pour les capteurs Home Assistant
## Installation
### Prérequis
- Python 3.7 ou supérieur
- Accès réseau aux équipements SNMP à surveiller
- Broker MQTT accessible
### Configuration de l'environnement
```bash
# Cloner le repository
git clone <url-du-repo>
cd snmp2mqtt
# Créer un environnement virtuel
python -m venv venv
source venv/bin/activate # Sur Linux/Mac
# ou
venv\Scripts\activate # Sur Windows
# Installer les dépendances
pip install -r requirements.txt
```
## Configuration
### Structure du fichier config.yaml
Le script utilise un fichier de configuration YAML avec la structure suivante :
```yaml
# Configuration du broker MQTT
mqtt:
broker: "192.168.10.202" # Adresse IP du broker MQTT
port: 1883 # Port du broker MQTT
user: "snmp2mqtt" # Nom d'utilisateur MQTT
password: "votre_mot_de_passe" # Mot de passe MQTT
# Intervalle entre les requêtes SNMP (optionnel, défaut: 2 secondes)
sleep_interval: 2
# Configuration des équipements
devices:
nom_equipement:
ip: "192.168.1.1" # Adresse IP de l'équipement
snmp_community: "public" # Communauté SNMP
oids: # Liste des OID à surveiller
- name: "interface_in" # Nom unique du capteur
oid: ".1.3.6.1.2.1.2.2.1.10.1" # OID SNMP
type: "int" # Type de données (int, bool, str)
HA_device_class: "data_size" # Classe d'équipement HA
HA_platform: "sensor" # Plateforme HA
HA_unit: "bit" # Unité (optionnel)
```
### Paramètres de configuration détaillés
#### Section `mqtt`
| Paramètre | Type | Obligatoire | Description |
|-----------|------|-------------|-------------|
| `broker` | string | ✅ | Adresse IP ou nom d'hôte du broker MQTT |
| `port` | integer | ✅ | Port du broker MQTT (généralement 1883) |
| `user` | string | ✅ | Nom d'utilisateur pour l'authentification MQTT |
| `password` | string | ✅ | Mot de passe pour l'authentification MQTT |
#### Section `devices`
Chaque équipement est défini par une clé (nom de l'équipement) et les paramètres suivants :
| Paramètre | Type | Obligatoire | Description |
|-----------|------|-------------|-------------|
| `ip` | string | ✅ | Adresse IP de l'équipement SNMP |
| `snmp_community` | string | ✅ | Communauté SNMP (généralement "public") |
| `oids` | liste | ✅ | Liste des OID SNMP à surveiller |
#### Configuration des OID
Chaque OID dans la liste `oids` doit contenir :
| Paramètre | Type | Obligatoire | Description |
|-----------|------|-------------|-------------|
| `name` | string | ✅ | Identifiant unique pour ce capteur |
| `oid` | string | ✅ | Identifiant d'objet SNMP |
| `type` | string | ✅ | Type de conversion Python ("int", "bool", "str") |
| `HA_device_class` | string | ✅ | Classe d'équipement Home Assistant |
| `HA_platform` | string | ✅ | Plateforme Home Assistant ("sensor", "binary_sensor") |
| `HA_unit` | string | ❌ | Unité de mesure pour le capteur |
### Classes d'équipements Home Assistant courantes
- `data_size` : Pour les données de trafic réseau
- `connectivity` : Pour le statut des interfaces
- `power_factor` : Pour les pourcentages (CPU, utilisation)
- `temperature` : Pour les températures
- `signal_strength` : Pour la qualité du signal
### OID SNMP couramment utilisés
```yaml
# Interfaces réseau (remplacez X par l'index de l'interface)
- ".1.3.6.1.2.1.2.2.1.10.X" # Octets entrants sur l'interface X
- ".1.3.6.1.2.1.2.2.1.16.X" # Octets sortants sur l'interface X
- ".1.3.6.1.2.1.2.2.1.8.X" # Statut opérationnel (1=actif, 2=inactif)
- ".1.3.6.1.2.1.2.2.1.2.X" # Description de l'interface
# MikroTik spécifique
- ".1.3.6.1.4.1.14988.1.1.3.14.0" # Utilisation CPU
- ".1.3.6.1.4.1.14988.1.1.6.1.0" # Température
```
## Utilisation
### Lancement du script
```bash
# Activer l'environnement virtuel
source venv/bin/activate
# Lancer avec le fichier de configuration
python snmp2mqtt.py --config config.yaml
# Ou avec la forme courte
python snmp2mqtt.py -c config.yaml
```
### Options en ligne de commande
| Option | Description |
|--------|-------------|
| `--config`, `-c` | Chemin vers le fichier de configuration YAML (obligatoire) |
| `--help`, `-h` | Affiche l'aide |
## Intégration Home Assistant
### Topics MQTT générés
Le script génère automatiquement les topics MQTT suivants :
- **Configuration** : `homeassistant/device/{device_id}/config`
- **État** : `SNMP/{device_name}/state`
### Découverte automatique
Les équipements et capteurs sont automatiquement découverts dans Home Assistant via MQTT Discovery. Chaque équipement apparaîtra avec :
- Un identifiant unique basé sur le nom et l'IP
- Des capteurs groupés par équipement
- Des icônes et unités appropriées selon la classe d'équipement
- Des modèles de valeurs JSON pour extraire les données
## Exemple de configuration complète
```yaml
# Configuration MQTT
mqtt:
broker: "192.168.10.202"
port: 1883
user: "snmp2mqtt"
password: "mon_mot_de_passe"
# Intervalle de surveillance
sleep_interval: 5
# Équipements à surveiller
devices:
# Routeur MikroTik Hex
mikrotik_hex:
ip: "192.168.10.2"
snmp_community: "public"
oids:
# Interface Starlink
- name: "starlink_in"
oid: ".1.3.6.1.2.1.2.2.1.10.1"
type: "int"
HA_device_class: "data_size"
HA_platform: "sensor"
HA_unit: "bit"
- name: "starlink_out"
oid: ".1.3.6.1.2.1.2.2.1.16.1"
type: "int"
HA_device_class: "data_size"
HA_platform: "sensor"
HA_unit: "bit"
- name: "starlink_status"
oid: ".1.3.6.1.2.1.2.2.1.8.1"
type: "bool"
HA_device_class: "connectivity"
HA_platform: "binary_sensor"
# Switch réseau
switch_bureau:
ip: "192.168.10.5"
snmp_community: "public"
oids:
- name: "port1_status"
oid: ".1.3.6.1.2.1.2.2.1.8.1"
type: "bool"
HA_device_class: "connectivity"
HA_platform: "binary_sensor"
```
## Dépannage
### Problèmes courants
1. **Erreur "Configuration file not found"**
- Vérifiez le chemin vers le fichier config.yaml
- Assurez-vous que le fichier existe et est lisible
2. **Erreur de connexion MQTT**
- Vérifiez les paramètres du broker (IP, port, credentials)
- Testez la connectivité réseau vers le broker
3. **Pas de données SNMP**
- Vérifiez que l'équipement supporte SNMP
- Testez avec `snmpwalk` : `snmpwalk -v2c -c public IP_EQUIPEMENT`
- Vérifiez les OID utilisés
4. **Capteurs non découverts dans Home Assistant**
- Vérifiez que MQTT Discovery est activé dans Home Assistant
- Surveillez les logs MQTT avec `mosquitto_sub`
### Commandes de test utiles
```bash
# Test SNMP manuel
snmpwalk -v2c -c public 192.168.10.2 1.3.6.1.2.1.2.2.1.10
# Surveillance des messages MQTT
mosquitto_sub -h 192.168.10.202 -u snmp2mqtt -P 'mot_de_passe' -t 'homeassistant/device/+/config'
mosquitto_sub -h 192.168.10.202 -u snmp2mqtt -P 'mot_de_passe' -t 'SNMP/+/state'
# Test de connectivité
ping 192.168.10.2
ping 192.168.10.202
```
## Support multi-équipements
**Note** : Le support complet multi-équipements nécessite une implémentation avec threading/multiprocessing. Actuellement, le script traite le premier équipement de la liste et affiche un avertissement pour les autres.
## Logs et debugging
Le script utilise le module `logging` de Python avec le niveau DEBUG par défaut. Les logs incluent :
- Chargement de la configuration
- Connexions MQTT
- Requêtes SNMP et leurs résultats
- Publication des messages MQTT
- Erreurs et avertissements
## Licence
Ce projet est distribué sous licence libre. Consultez le fichier LICENSE pour plus de détails.

View File

@@ -82,18 +82,6 @@ devices:
HA_device_class: "connectivity"
HA_platform: "binary_sensor"
# Example of how to add another device:
# another_device:
# ip: "192.168.10.3"
# snmp_community: "public"
# oids:
# - name: "cpu_usage"
# oid: ".1.3.6.1.4.1.14988.1.1.3.14.0" # MikroTik CPU usage
# type: "int"
# HA_device_class: "power_factor"
# HA_platform: "sensor"
# HA_unit: "%"
# OID Configuration Reference:
# - name: Unique identifier for this metric (used in MQTT topics and Home Assistant)
# - oid: SNMP Object Identifier

View File

@@ -11,12 +11,99 @@ import yaml
import argparse
import sys
import os
import threading
import signal
import time
logging.basicConfig(
format='(%(levelname)s) %(message)s',
format='(%(levelname)s) [%(threadName)s] %(message)s',
level=logging.DEBUG
)
# Global shutdown flag
shutdown_event = threading.Event()
class DeviceMonitorThread(threading.Thread):
"""Thread class for monitoring a single device"""
def __init__(self, device_name, device_config, mqtt_config, sleep_interval=2):
super().__init__(name=f"Device-{device_name}")
self.device_name = device_name
self.device_config = device_config
self.mqtt_config = mqtt_config.copy()
self.sleep_interval = sleep_interval
self.daemon = True # Dies when main thread dies
# Create unique client ID for this device
self.mqtt_config['client_id'] = f"snmp-mqtt-{device_name}-{random.randint(0, 1000)}"
# Create device request object
self.req = {
"device_name": device_name,
"ip": device_config["ip"],
"snmp_community": device_config["snmp_community"],
"oids": device_config["oids"]
}
def run(self):
"""Main thread execution"""
logging.info(f"Starting monitoring thread for device: {self.device_name} ({self.device_config['ip']})")
try:
# Setup MQTT connection
client = connect_mqtt(self.mqtt_config)
client.loop_start()
state_topic = f"SNMP/{self.device_name}/state"
availability_topic = f"SNMP/{self.device_name}/availability"
logging.info(f"[{self.device_name}] MQTT client connected")
# Publish Home Assistant autodiscovery configuration (only once on startup)
publish_ha_autodiscovery_config(client, self.req)
# Mark device as available
publish(availability_topic, client, "online", True, 1)
logging.info(f"[{self.device_name}] Starting monitoring loop")
# Main monitoring loop
while not shutdown_event.is_set():
try:
# Get SNMP data and publish state
state = asyncio.run(get_snmp(self.req))
publish(state_topic, client, state, False, 0)
logging.debug(f"[{self.device_name}] Published state to {state_topic}: {json.dumps(state)}")
# Update availability (heartbeat)
publish(availability_topic, client, "online", False, 1)
except Exception as e:
logging.error(f"[{self.device_name}] Error in monitoring loop: {e}")
# Mark as offline on error
publish(availability_topic, client, "offline", False, 1)
# Wait for next iteration or shutdown signal
shutdown_event.wait(timeout=self.sleep_interval)
# Cleanup - mark device as offline
publish(availability_topic, client, "offline", True, 1)
client.loop_stop()
client.disconnect()
logging.info(f"[{self.device_name}] Monitoring thread stopped gracefully")
except Exception as e:
logging.error(f"[{self.device_name}] Fatal error in monitoring thread: {e}")
# Try to mark as offline on fatal error
try:
publish(availability_topic, client, "offline", True, 1)
client.disconnect()
except:
pass
logging.info(f"[{self.device_name}] Thread {self.name} finished")
def parse_arguments():
"""Parse command line arguments"""
@@ -103,7 +190,11 @@ def connect_mqtt(mqtt_config):
def publish(topic, client, data, retain, qos):
if isinstance(data, str):
msg = data
else:
msg = json.dumps(data)
result = client.publish(topic=topic, payload=msg, qos=qos, retain=bool(retain))
status = result[0]
if status == 0:
@@ -146,97 +237,144 @@ async def get_snmp(req):
return data
def ha_create_config(req):
ha_config = {}
device = {
"ids": f"{req['device_name']}_{req['ip']}".replace(".", "_"),
def create_ha_device_info(req):
"""Create device information for Home Assistant MQTT Discovery"""
return {
"identifiers": [f"snmp2mqtt_{req['device_name']}_{req['ip']}".replace(".", "_")],
"name": req['device_name'],
"model": "SNMP Device",
"manufacturer": "Network Equipment",
"via_device": "snmp2mqtt"
}
origin = {
"name": "snmp2mqtt"
def create_ha_sensor_config(req, oid):
"""Create Home Assistant MQTT Discovery configuration for a single sensor"""
device_info = create_ha_device_info(req)
sensor_id = f"{req['device_name']}_{req['ip']}_{oid['name']}".replace(".", "_")
config = {
"name": f"{req['device_name']} {oid['name']}",
"unique_id": sensor_id,
"state_topic": f"SNMP/{req['device_name']}/state",
"value_template": f"{{{{ value_json.{oid['name']} }}}}",
"device": device_info,
"origin": {
"name": "snmp2mqtt",
"sw_version": "1.0.0",
"support_url": "https://git.antoineve.me/AntoineVe/snmp2mqtt"
}
ha_config.update({"dev": device, "o": origin})
ha_config.update({"state_topic": f"SNMP/{req['device_name']}/state"})
ha_config.update({"qos": 2})
cmps = {}
}
# Add device class if specified
if 'HA_device_class' in oid:
config['device_class'] = oid['HA_device_class']
# Add unit of measurement if specified
if 'HA_unit' in oid:
config['unit_of_measurement'] = oid['HA_unit']
# Add icon based on device class
icon_mapping = {
'data_size': 'mdi:network',
'connectivity': 'mdi:network-outline',
'power_factor': 'mdi:gauge',
'temperature': 'mdi:thermometer',
'signal_strength': 'mdi:signal'
}
if 'HA_device_class' in oid and oid['HA_device_class'] in icon_mapping:
config['icon'] = icon_mapping[oid['HA_device_class']]
# Add availability topic
config['availability'] = {
"topic": f"SNMP/{req['device_name']}/availability",
"payload_available": "online",
"payload_not_available": "offline"
}
return config
def get_ha_discovery_topic(req, oid):
"""Get the correct Home Assistant MQTT Discovery topic for a sensor"""
platform = oid['HA_platform'] # 'sensor' or 'binary_sensor'
node_id = req['device_name']
object_id = f"{req['device_name']}_{oid['name']}".replace(".", "_")
# Format: homeassistant/<platform>/<node_id>/<object_id>/config
return f"homeassistant/{platform}/{node_id}/{object_id}/config"
def publish_ha_autodiscovery_config(client, req):
"""Publish Home Assistant MQTT Discovery configuration for all sensors of a device"""
logging.info(f"[{req['device_name']}] Publishing Home Assistant autodiscovery configuration")
# Publish availability as online
availability_topic = f"SNMP/{req['device_name']}/availability"
publish(availability_topic, client, "online", True, 1)
# Publish discovery configuration for each OID/sensor
for oid in req['oids']:
cmps.update(
{
f"{req['device_name']}_{req['ip']}_{oid['name']}".replace(".", "_"):
{
"p": oid['HA_platform'],
"device_class": oid['HA_device_class'],
"value_template": f"{{{{ value_json.{oid['name']}}}}}",
"unique_id": f"{req['device_name']}_{req['ip']}_{oid['name']}".replace(".", "_"),
"name": oid['name']
}
})
if "HA_unit" in oid.keys():
cmps.update(
{f"{req['device_name']}_{req['ip']}_{oid['name']}".replace(".", "_"):
{"unit_of_measurement": oid['HA_unit']}})
ha_config.update({"cmps": cmps})
logging.debug(f"config : {json.dumps(ha_config)}")
return ha_config
config = create_ha_sensor_config(req, oid)
topic = get_ha_discovery_topic(req, oid)
# Publish with retain=True so HA discovers it after restarts
publish(topic, client, config, True, 1)
logging.info(f"[{req['device_name']}] Published discovery config for {oid['name']} to {topic}")
# Small delay to avoid overwhelming the broker
time.sleep(0.1)
def send_to_mqtt(device_name, device_config, mqtt_config, sleep_interval=2):
"""Send SNMP data to MQTT for a single device"""
# Create device request object
req = {
"device_name": device_name,
"ip": device_config["ip"],
"snmp_community": device_config["snmp_community"],
"oids": device_config["oids"]
}
config = ha_create_config(req)
client = connect_mqtt(mqtt_config)
client.loop_start()
config_topic = f"homeassistant/device/{config['dev']['ids']}/config"
state_topic = config['state_topic']
while True:
try:
publish(config_topic, client, config, True, 0)
logging.info(f"{config_topic} -> {config}")
except Exception as e:
logging.error(f"Error publishing config for {device_name}: {e}")
pass
try:
state = asyncio.run(get_snmp(req))
publish(state_topic, client, state, False, 0)
logging.info(f"{state_topic} -> {state}")
except Exception as e:
logging.error(f"Error getting SNMP data for {device_name}: {e}")
pass
sleep(sleep_interval)
def signal_handler(signum, frame):
"""Handle shutdown signals gracefully"""
logging.info(f"Received signal {signum}, initiating graceful shutdown...")
shutdown_event.set()
def process_devices(config):
"""Process multiple devices from configuration"""
"""Process multiple devices using threading"""
mqtt_config = config['mqtt'].copy()
mqtt_config['client_id'] = f"snmp-mqtt-{random.randint(0, 1000)}"
# Get sleep interval from config or use default
sleep_interval = config.get('sleep_interval', 2)
if len(config['devices']) == 1:
# Single device mode - run directly
device_name = list(config['devices'].keys())[0]
device_config = config['devices'][device_name]
logging.info(f"Starting monitoring for single device: {device_name}")
send_to_mqtt(device_name, device_config, mqtt_config, sleep_interval)
else:
# Multiple devices mode - would need threading/multiprocessing
# For now, let's process the first device and warn about others
logging.warning(f"Multiple devices detected ({len(config['devices'])}), but only processing the first one")
logging.warning("Multi-device support will require threading implementation")
# Setup signal handlers for graceful shutdown
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
device_name = list(config['devices'].keys())[0]
device_config = config['devices'][device_name]
logging.info(f"Starting monitoring for device: {device_name}")
send_to_mqtt(device_name, device_config, mqtt_config, sleep_interval)
device_threads = []
try:
logging.info(f"Starting monitoring for {len(config['devices'])} device(s)")
# Create and start a thread for each device
for device_name, device_config in config['devices'].items():
thread = DeviceMonitorThread(
device_name=device_name,
device_config=device_config,
mqtt_config=mqtt_config,
sleep_interval=sleep_interval
)
device_threads.append(thread)
thread.start()
logging.info(f"Started thread for device: {device_name}")
# Wait for all threads to complete or shutdown signal
while any(thread.is_alive() for thread in device_threads) and not shutdown_event.is_set():
time.sleep(0.5)
except Exception as e:
logging.error(f"Error in process_devices: {e}")
shutdown_event.set()
# Wait for all threads to finish
logging.info("Waiting for all monitoring threads to finish...")
for thread in device_threads:
if thread.is_alive():
thread.join(timeout=5.0) # Wait max 5 seconds per thread
if thread.is_alive():
logging.warning(f"Thread {thread.name} did not stop gracefully")
logging.info("All monitoring threads have finished")
def main():