Added different intervals for collectors and made them configurable.

This commit is contained in:
Christian Busch 2025-05-22 09:57:21 +02:00
parent 85154fcd60
commit 6e6f22f5d4
6 changed files with 186 additions and 67 deletions

146
README.md
View File

@ -1,79 +1,115 @@
# system2mqtt # system2mqtt
Ein System zur Überwachung von Hosts durch Sammeln von Metriken und Senden via MQTT an Home Assistant. A system for monitoring hosts by collecting metrics and sending them to Home Assistant via MQTT.
## Features ## Features
- Modulare Struktur für Metrik-Sammler - Modular structure for metric collectors
- Einfache Erweiterbarkeit durch neue Sammler - Easy extensibility through new collectors
- Automatische Erkennung in Home Assistant - Automatic discovery in Home Assistant
- Verschlüsselte MQTT-Kommunikation - Encrypted MQTT communication
- Detaillierte Geräteinformationen in Home Assistant - Detailed device information in Home Assistant
- Individual update intervals per collector
## Installation ## Installation
1. Repository klonen: 1. Clone the repository:
```bash ```bash
git clone https://github.com/yourusername/system2mqtt.git git clone https://github.com/yourusername/system2mqtt.git
cd system2mqtt cd system2mqtt
``` ```
2. Python-Abhängigkeiten installieren: 2. Install Python dependencies:
```bash ```bash
python3 -m venv .venv python3 -m venv .venv
source .venv/bin/activate source .venv/bin/activate
pip install -r requirements.txt pip install -r requirements.txt
``` ```
3. Konfiguration anpassen: 3. Configure the system:
```yaml ```yaml
mqtt: mqtt:
host: "mqtt.example.com" # MQTT Broker Adresse host: "mqtt.example.com" # MQTT Broker Address
port: 1883 # MQTT Port port: 1883 # MQTT Port
username: "your_username" username: "your_username"
password: "your_password" password: "your_password"
client_id: "system2mqtt" client_id: "system2mqtt"
discovery_prefix: "homeassistant" # Home Assistant Discovery Prefix discovery_prefix: "homeassistant" # Home Assistant Discovery Prefix
collectors:
# Default interval for all collectors (in seconds)
default_interval: 60
# Specific intervals for individual collectors
intervals:
zfs_pools: 300 # ZFS Pools every 5 minutes
cpu_temperature: 30 # CPU Temperature every 30 seconds
system_metrics: 60 # System Metrics every minute
``` ```
## Verwendung ## Usage
Das System wird über das `run.sh` Skript gesteuert: The system is controlled via the `run.sh` script:
```bash ```bash
# Starten des Systems # Start the system
./run.sh start ./run.sh start
# Stoppen des Systems # Stop the system
./run.sh stop ./run.sh stop
# MQTT Topics aufräumen # Clean up MQTT topics
./run.sh cleanup ./run.sh cleanup
``` ```
## Sammler ## Collectors
### System Metrics ### System Metrics
Sammelt grundlegende Systemmetriken: Collects basic system metrics:
- Last Boot Zeit - Last Boot Time
- Load Average (1, 5, 15 Minuten) - Load Average (1, 5, 15 minutes)
- Speichernutzung (Gesamt, Verfügbar, Verwendet) - Memory Usage (Total, Available, Used)
- Swap-Nutzung (Gesamt, Verfügbar, Verwendet) - Swap Usage (Total, Available, Used)
- CPU-Auslastung - CPU Usage
- Speicherauslastung - Memory Usage
- Swap-Auslastung - Swap Usage
Default Update Interval: 60 seconds
### CPU Temperature ### CPU Temperature
Sammelt CPU-Temperaturdaten: Collects CPU temperature data:
- Unterstützt Linux und FreeBSD - Supports Linux and FreeBSD
- Automatische Erkennung des Betriebssystems - Automatic OS detection
- Korrekte Einheit (°C) und Device Class (temperature) - Correct unit (°C) and device class (temperature)
## Datenformat Default Update Interval: 30 seconds
Das Datenaustauschformat ist versioniert und folgt den Home Assistant Spezifikationen. Jeder Collector gibt ein JSON-Objekt zurück mit folgender Struktur: ### ZFS Pools
Collects information about ZFS pools:
- Pool Health
- Total Size
- Used Space
- Free Space
- Usage Percentage
- Additional Attributes (readonly, dedup, altroot)
Default Update Interval: 300 seconds (5 minutes)
## Update Intervals
Each collector has a predefined default update interval that can be overridden in the configuration file:
1. Default intervals are defined in the collector files
2. These intervals can be customized per collector in `config.yaml`
3. If no specific interval is defined in the configuration, the collector's default interval is used
4. If no default interval is defined in the collector, the global `default_interval` from the configuration is used
## Data Format
The data exchange format is versioned and follows Home Assistant specifications. Each collector returns a JSON object with the following structure:
```json ```json
{ {
@ -81,46 +117,44 @@ Das Datenaustauschformat ist versioniert und folgt den Home Assistant Spezifikat
{ {
"sensor_id": "unique_sensor_id", "sensor_id": "unique_sensor_id",
"name": "Sensor Name", "name": "Sensor Name",
"value": "Sensor Value", "value": "sensor_value",
"state_class": "measurement|total|total_increasing", "state_class": "measurement",
"unit_of_measurement": "Unit", "unit_of_measurement": "unit",
"device_class": "temperature|humidity|pressure|...", "device_class": "device_class",
"icon": "mdi:icon-name", "icon": "mdi:icon",
"entity_category": "diagnostic|config|system",
"attributes": { "attributes": {
"friendly_name": "Friendly Name", "friendly_name": "Friendly Name",
"source": "Data Source", "additional_attributes": "values"
"additional_info": "Additional Information"
} }
} }
] ]
} }
``` ```
### Felder ### Fields
- `sensor_id`: Eindeutige ID für den Sensor (wird für MQTT-Topics verwendet) - `sensor_id`: Unique ID for the sensor (used for MQTT topics)
- `name`: Anzeigename des Sensors - `name`: Display name of the sensor
- `value`: Aktueller Wert des Sensors - `value`: Current value of the sensor
- `state_class`: Art der Messung (measurement, total, total_increasing) - `state_class`: Type of measurement (measurement, total, total_increasing)
- `unit_of_measurement`: Einheit der Messung - `unit_of_measurement`: Unit of measurement
- `device_class`: Art des Sensors (temperature, humidity, pressure, etc.) - `device_class`: Type of sensor (temperature, humidity, pressure, etc.)
- `icon`: Material Design Icon Name (mdi:...) - `icon`: Material Design Icon name (mdi:...)
- `entity_category`: Kategorie des Sensors (diagnostic, config, system) - `entity_category`: Category of the sensor (diagnostic, config, system)
- `attributes`: Zusätzliche Informationen als Key-Value-Paare - `attributes`: Additional information as key-value pairs
### Versionierung ### Versioning
Das Format ist versioniert, um zukünftige Erweiterungen zu ermöglichen. Die aktuelle Version ist 1.0. The format is versioned to allow for future extensions. The current version is 1.0.
## Home Assistant Integration ## Home Assistant Integration
Das System nutzt die MQTT Discovery-Funktion von Home Assistant. Die Sensoren werden automatisch erkannt und erscheinen in Home Assistant mit: The system uses Home Assistant's MQTT Discovery feature. Sensors are automatically detected and appear in Home Assistant with:
- Korrektem Namen und Icon - Correct name and icon
- Aktuellen Werten - Current values
- Historischen Daten - Historical data
- Detaillierten Geräteinformationen - Detailed device information
## Lizenz ## License
MIT License MIT License

View File

@ -7,6 +7,9 @@ import glob
from typing import Dict, Any, Optional, List, Tuple from typing import Dict, Any, Optional, List, Tuple
import sys import sys
# Default update interval in seconds
DEFAULT_INTERVAL = 30 # 30 seconds
def get_temperature_linux_coretemp() -> List[Tuple[float, str]]: def get_temperature_linux_coretemp() -> List[Tuple[float, str]]:
"""Get CPU temperatures using coretemp module.""" """Get CPU temperatures using coretemp module."""
temps = [] temps = []

View File

@ -5,6 +5,9 @@ import time
from datetime import datetime from datetime import datetime
from typing import Dict, Any from typing import Dict, Any
# Default update interval in seconds
DEFAULT_INTERVAL = 60 # 1 minute
def collect_metrics() -> Dict[str, Any]: def collect_metrics() -> Dict[str, Any]:
"""Collect system metrics and return them in the required format.""" """Collect system metrics and return them in the required format."""
# Get system metrics # Get system metrics

View File

@ -4,6 +4,9 @@ import subprocess
from typing import Dict, Any, List from typing import Dict, Any, List
import json import json
# Default update interval in seconds
DEFAULT_INTERVAL = 300 # 5 minutes
def get_zfs_pools() -> List[Dict[str, Any]]: def get_zfs_pools() -> List[Dict[str, Any]]:
"""Get information about ZFS pools.""" """Get information about ZFS pools."""
try: try:

View File

@ -1,8 +1,54 @@
# MQTT Configuration
mqtt: mqtt:
# MQTT Broker Address
host: "mqtt.example.com" host: "mqtt.example.com"
# MQTT Port (Default: 1883 for unencrypted, 8883 for TLS)
port: 1883 port: 1883
username: "system2mqtt"
password: "your_secure_password" # MQTT Username
username: "your_username"
# MQTT Password
password: "your_password"
# MQTT Client ID (will be extended with hostname)
client_id: "system2mqtt_{hostname}" client_id: "system2mqtt_{hostname}"
# Home Assistant Discovery Prefix
discovery_prefix: "homeassistant" discovery_prefix: "homeassistant"
state_prefix: "system2mqtt"
# MQTT State Prefix for sensors
state_prefix: "system2mqtt"
# Collector Configuration
collectors:
# Default interval for all collectors (in seconds)
# Used when no specific interval is defined
default_interval: 60
# Specific intervals for individual collectors
# These override the collector's default intervals
intervals:
# ZFS Pools are updated every 5 minutes
zfs_pools: 300
# CPU Temperature is updated every 30 seconds
cpu_temperature: 30
# System Metrics are updated every minute
system_metrics: 60
# Notes:
# 1. The default intervals for collectors are:
# - zfs_pools: 300 seconds (5 minutes)
# - cpu_temperature: 30 seconds
# - system_metrics: 60 seconds (1 minute)
#
# 2. These intervals can be overridden here
#
# 3. If no specific interval is defined, the collector's
# default interval will be used
#
# 4. If no default interval is defined in the collector,
# the global default_interval will be used

View File

@ -8,9 +8,10 @@ import time
import yaml import yaml
import platform import platform
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
from typing import Dict, Any from typing import Dict, Any, List
import importlib.util import importlib.util
import glob import glob
from datetime import datetime, timedelta
class System2MQTT: class System2MQTT:
def __init__(self, config_path: str = "config.yaml"): def __init__(self, config_path: str = "config.yaml"):
@ -20,6 +21,7 @@ class System2MQTT:
self.connected = False self.connected = False
self.device_info = self._get_device_info() self.device_info = self._get_device_info()
self.collectors = self._load_collectors() self.collectors = self._load_collectors()
self.last_run = {} # Speichert den Zeitpunkt des letzten Laufs für jeden Sammler
def _load_config(self, config_path: str) -> Dict[str, Any]: def _load_config(self, config_path: str) -> Dict[str, Any]:
"""Load configuration from YAML file.""" """Load configuration from YAML file."""
@ -74,7 +76,7 @@ class System2MQTT:
"""Generate state topic from sensor_id.""" """Generate state topic from sensor_id."""
return f"system2mqtt/{self.hostname}/{sensor_id}/state" return f"system2mqtt/{self.hostname}/{sensor_id}/state"
def _load_collectors(self) -> list: def _load_collectors(self) -> List[Dict[str, Any]]:
"""Load all collector modules from the collectors directory.""" """Load all collector modules from the collectors directory."""
collectors = [] collectors = []
collector_dir = os.path.join(os.path.dirname(__file__), 'collectors') collector_dir = os.path.join(os.path.dirname(__file__), 'collectors')
@ -92,8 +94,19 @@ class System2MQTT:
spec.loader.exec_module(module) spec.loader.exec_module(module)
if hasattr(module, 'collect_metrics'): if hasattr(module, 'collect_metrics'):
collectors.append(module) # Get interval from config or use collector's default
print(f"Loaded collector: {module_name}") default_interval = getattr(module, 'DEFAULT_INTERVAL', self.config['collectors']['default_interval'])
interval = self.config['collectors']['intervals'].get(
module_name,
default_interval
)
collectors.append({
'module': module,
'name': module_name,
'interval': interval
})
print(f"Loaded collector: {module_name} (interval: {interval}s)")
return collectors return collectors
@ -144,14 +157,30 @@ class System2MQTT:
retain=True retain=True
) )
def should_run_collector(self, collector: Dict[str, Any]) -> bool:
"""Check if a collector should run based on its interval."""
now = datetime.now()
last_run = self.last_run.get(collector['name'])
if last_run is None:
return True
interval = timedelta(seconds=collector['interval'])
return (now - last_run) >= interval
def collect_and_publish(self): def collect_and_publish(self):
"""Collect metrics from all collectors and publish them.""" """Collect metrics from all collectors and publish them."""
for collector in self.collectors: for collector in self.collectors:
if not self.should_run_collector(collector):
continue
try: try:
data = collector.collect_metrics() data = collector['module'].collect_metrics()
self.process_collector_data(data) self.process_collector_data(data)
self.last_run[collector['name']] = datetime.now()
print(f"Updated {collector['name']} metrics")
except Exception as e: except Exception as e:
print(f"Error collecting metrics from {collector.__name__}: {e}") print(f"Error collecting metrics from {collector['name']}: {e}")
def connect(self): def connect(self):
"""Connect to MQTT broker.""" """Connect to MQTT broker."""
@ -179,10 +208,11 @@ def main():
# Initial collection # Initial collection
system2mqtt.collect_and_publish() system2mqtt.collect_and_publish()
# Keep collecting metrics every 60 seconds # Main loop - check every second if any collector needs to run
while True: while True:
time.sleep(60)
system2mqtt.collect_and_publish() system2mqtt.collect_and_publish()
time.sleep(1) # Check every second
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nShutting down...") print("\nShutting down...")
finally: finally: