diff --git a/README.md b/README.md index 779cf4d..502a4d7 100644 --- a/README.md +++ b/README.md @@ -1,79 +1,115 @@ # 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 -- Modulare Struktur für Metrik-Sammler -- Einfache Erweiterbarkeit durch neue Sammler -- Automatische Erkennung in Home Assistant -- Verschlüsselte MQTT-Kommunikation -- Detaillierte Geräteinformationen in Home Assistant +- Modular structure for metric collectors +- Easy extensibility through new collectors +- Automatic discovery in Home Assistant +- Encrypted MQTT communication +- Detailed device information in Home Assistant +- Individual update intervals per collector ## Installation -1. Repository klonen: +1. Clone the repository: ```bash git clone https://github.com/yourusername/system2mqtt.git cd system2mqtt ``` -2. Python-Abhängigkeiten installieren: +2. Install Python dependencies: ```bash python3 -m venv .venv source .venv/bin/activate pip install -r requirements.txt ``` -3. Konfiguration anpassen: +3. Configure the system: ```yaml mqtt: - host: "mqtt.example.com" # MQTT Broker Adresse + host: "mqtt.example.com" # MQTT Broker Address port: 1883 # MQTT Port username: "your_username" password: "your_password" client_id: "system2mqtt" 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 -# Starten des Systems +# Start the system ./run.sh start -# Stoppen des Systems +# Stop the system ./run.sh stop -# MQTT Topics aufräumen +# Clean up MQTT topics ./run.sh cleanup ``` -## Sammler +## Collectors ### System Metrics -Sammelt grundlegende Systemmetriken: -- Last Boot Zeit -- Load Average (1, 5, 15 Minuten) -- Speichernutzung (Gesamt, Verfügbar, Verwendet) -- Swap-Nutzung (Gesamt, Verfügbar, Verwendet) -- CPU-Auslastung -- Speicherauslastung -- Swap-Auslastung +Collects basic system metrics: +- Last Boot Time +- Load Average (1, 5, 15 minutes) +- Memory Usage (Total, Available, Used) +- Swap Usage (Total, Available, Used) +- CPU Usage +- Memory Usage +- Swap Usage + +Default Update Interval: 60 seconds ### CPU Temperature -Sammelt CPU-Temperaturdaten: -- Unterstützt Linux und FreeBSD -- Automatische Erkennung des Betriebssystems -- Korrekte Einheit (°C) und Device Class (temperature) +Collects CPU temperature data: +- Supports Linux and FreeBSD +- Automatic OS detection +- 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 { @@ -81,46 +117,44 @@ Das Datenaustauschformat ist versioniert und folgt den Home Assistant Spezifikat { "sensor_id": "unique_sensor_id", "name": "Sensor Name", - "value": "Sensor Value", - "state_class": "measurement|total|total_increasing", - "unit_of_measurement": "Unit", - "device_class": "temperature|humidity|pressure|...", - "icon": "mdi:icon-name", - "entity_category": "diagnostic|config|system", + "value": "sensor_value", + "state_class": "measurement", + "unit_of_measurement": "unit", + "device_class": "device_class", + "icon": "mdi:icon", "attributes": { "friendly_name": "Friendly Name", - "source": "Data Source", - "additional_info": "Additional Information" + "additional_attributes": "values" } } ] } ``` -### Felder +### Fields -- `sensor_id`: Eindeutige ID für den Sensor (wird für MQTT-Topics verwendet) -- `name`: Anzeigename des Sensors -- `value`: Aktueller Wert des Sensors -- `state_class`: Art der Messung (measurement, total, total_increasing) -- `unit_of_measurement`: Einheit der Messung -- `device_class`: Art des Sensors (temperature, humidity, pressure, etc.) -- `icon`: Material Design Icon Name (mdi:...) -- `entity_category`: Kategorie des Sensors (diagnostic, config, system) -- `attributes`: Zusätzliche Informationen als Key-Value-Paare +- `sensor_id`: Unique ID for the sensor (used for MQTT topics) +- `name`: Display name of the sensor +- `value`: Current value of the sensor +- `state_class`: Type of measurement (measurement, total, total_increasing) +- `unit_of_measurement`: Unit of measurement +- `device_class`: Type of sensor (temperature, humidity, pressure, etc.) +- `icon`: Material Design Icon name (mdi:...) +- `entity_category`: Category of the sensor (diagnostic, config, system) +- `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 -Das System nutzt die MQTT Discovery-Funktion von Home Assistant. Die Sensoren werden automatisch erkannt und erscheinen in Home Assistant mit: -- Korrektem Namen und Icon -- Aktuellen Werten -- Historischen Daten -- Detaillierten Geräteinformationen +The system uses Home Assistant's MQTT Discovery feature. Sensors are automatically detected and appear in Home Assistant with: +- Correct name and icon +- Current values +- Historical data +- Detailed device information -## Lizenz +## License MIT License \ No newline at end of file diff --git a/collectors/cpu_temperature.py b/collectors/cpu_temperature.py index 0e1ea0e..9ad284c 100644 --- a/collectors/cpu_temperature.py +++ b/collectors/cpu_temperature.py @@ -7,6 +7,9 @@ import glob from typing import Dict, Any, Optional, List, Tuple import sys +# Default update interval in seconds +DEFAULT_INTERVAL = 30 # 30 seconds + def get_temperature_linux_coretemp() -> List[Tuple[float, str]]: """Get CPU temperatures using coretemp module.""" temps = [] diff --git a/collectors/system_metrics.py b/collectors/system_metrics.py index 663fe33..2a49c78 100644 --- a/collectors/system_metrics.py +++ b/collectors/system_metrics.py @@ -5,6 +5,9 @@ import time from datetime import datetime from typing import Dict, Any +# Default update interval in seconds +DEFAULT_INTERVAL = 60 # 1 minute + def collect_metrics() -> Dict[str, Any]: """Collect system metrics and return them in the required format.""" # Get system metrics diff --git a/collectors/zfs_pools.py b/collectors/zfs_pools.py index b4f55cf..6b387be 100644 --- a/collectors/zfs_pools.py +++ b/collectors/zfs_pools.py @@ -4,6 +4,9 @@ import subprocess from typing import Dict, Any, List import json +# Default update interval in seconds +DEFAULT_INTERVAL = 300 # 5 minutes + def get_zfs_pools() -> List[Dict[str, Any]]: """Get information about ZFS pools.""" try: diff --git a/config.yaml.example b/config.yaml.example index 0bd06f5..93ac16d 100644 --- a/config.yaml.example +++ b/config.yaml.example @@ -1,8 +1,54 @@ +# MQTT Configuration mqtt: + # MQTT Broker Address host: "mqtt.example.com" + + # MQTT Port (Default: 1883 for unencrypted, 8883 for TLS) 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}" + + # Home Assistant Discovery Prefix discovery_prefix: "homeassistant" - state_prefix: "system2mqtt" \ No newline at end of file + + # 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 \ No newline at end of file diff --git a/system2mqtt.py b/system2mqtt.py index 97bd31e..657dcfa 100644 --- a/system2mqtt.py +++ b/system2mqtt.py @@ -8,9 +8,10 @@ import time import yaml import platform import paho.mqtt.client as mqtt -from typing import Dict, Any +from typing import Dict, Any, List import importlib.util import glob +from datetime import datetime, timedelta class System2MQTT: def __init__(self, config_path: str = "config.yaml"): @@ -20,6 +21,7 @@ class System2MQTT: self.connected = False self.device_info = self._get_device_info() 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]: """Load configuration from YAML file.""" @@ -74,7 +76,7 @@ class System2MQTT: """Generate state topic from sensor_id.""" 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.""" collectors = [] collector_dir = os.path.join(os.path.dirname(__file__), 'collectors') @@ -92,8 +94,19 @@ class System2MQTT: spec.loader.exec_module(module) if hasattr(module, 'collect_metrics'): - collectors.append(module) - print(f"Loaded collector: {module_name}") + # Get interval from config or use collector's default + 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 @@ -144,14 +157,30 @@ class System2MQTT: 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): """Collect metrics from all collectors and publish them.""" for collector in self.collectors: + if not self.should_run_collector(collector): + continue + try: - data = collector.collect_metrics() + data = collector['module'].collect_metrics() self.process_collector_data(data) + self.last_run[collector['name']] = datetime.now() + print(f"Updated {collector['name']} metrics") 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): """Connect to MQTT broker.""" @@ -179,10 +208,11 @@ def main(): # Initial collection system2mqtt.collect_and_publish() - # Keep collecting metrics every 60 seconds + # Main loop - check every second if any collector needs to run while True: - time.sleep(60) system2mqtt.collect_and_publish() + time.sleep(1) # Check every second + except KeyboardInterrupt: print("\nShutting down...") finally: