system2mqtt/main.py

184 lines
6.9 KiB
Python

#!/usr/bin/env python3
import os
import sys
import json
import time
import socket
import platform
import importlib
import paho.mqtt.client as mqtt
from typing import Dict, Any, List
import yaml
# Load configuration
with open('config.yaml', 'r') as f:
config = yaml.safe_load(f)
# MQTT Configuration
MQTT_HOST = config['mqtt']['host']
MQTT_PORT = config['mqtt']['port']
MQTT_USERNAME = config['mqtt']['username']
MQTT_PASSWORD = config['mqtt']['password']
MQTT_CLIENT_ID = config['mqtt']['client_id']
MQTT_DISCOVERY_PREFIX = config['mqtt']['discovery_prefix']
# Get hostname
HOSTNAME = socket.gethostname()
# Get OS information
OS_INFO = {
'system': platform.system(),
'release': platform.release(),
'version': platform.version(),
'machine': platform.machine(),
'processor': platform.processor()
}
def get_collectors() -> List[str]:
"""Get list of available collector scripts."""
collectors = []
for file in os.listdir('collectors'):
if file.endswith('.py') and not file.startswith('__'):
collectors.append(file[:-3])
return collectors
def load_collector(collector_name: str) -> Any:
"""Load a collector module."""
try:
return importlib.import_module(f'collectors.{collector_name}')
except ImportError as e:
print(f"Error loading collector {collector_name}: {e}")
return None
def get_device_info() -> Dict[str, Any]:
"""Get device information for Home Assistant."""
return {
"identifiers": [f"system2mqtt_{HOSTNAME}"],
"name": f"System Metrics - {HOSTNAME}",
"model": OS_INFO['system'],
"manufacturer": "system2mqtt",
"sw_version": OS_INFO['version'],
"configuration_url": f"http://{HOSTNAME}",
"hw_version": OS_INFO['machine']
}
def get_device_attributes() -> Dict[str, Any]:
"""Get additional device attributes."""
return {
"operating_system": OS_INFO['system'],
"os_release": OS_INFO['release'],
"os_version": OS_INFO['version'],
"architecture": OS_INFO['machine'],
"processor": OS_INFO['processor']
}
def on_connect(client: mqtt.Client, userdata: Any, flags: Dict[str, Any], rc: int) -> None:
"""Callback for when the client connects to the MQTT broker."""
print(f"Connected with result code {rc}")
# Subscribe to any topics if needed
# client.subscribe("$SYS/#")
def on_disconnect(client: mqtt.Client, userdata: Any, rc: int) -> None:
"""Callback for when the client disconnects from the MQTT broker."""
print(f"Disconnected with result code {rc}")
if rc != 0:
print("Unexpected disconnection. Attempting to reconnect...")
def main():
"""Main function."""
# Create MQTT client
client = mqtt.Client(client_id=MQTT_CLIENT_ID)
client.username_pw_set(MQTT_USERNAME, MQTT_PASSWORD)
client.on_connect = on_connect
client.on_disconnect = on_disconnect
# Connect to MQTT broker
try:
client.connect(MQTT_HOST, MQTT_PORT, 60)
except Exception as e:
print(f"Failed to connect to MQTT broker: {e}")
sys.exit(1)
# Start the MQTT client loop
client.loop_start()
# Get list of collectors
collectors = get_collectors()
print(f"Found collectors: {collectors}")
# Main loop
while True:
try:
# Load and run each collector
for collector_name in collectors:
collector = load_collector(collector_name)
if collector:
try:
# Get metrics from collector
metrics = collector.collect_metrics()
# Add device info to each entity
device_info = get_device_info()
device_attributes = get_device_attributes()
for entity in metrics.get('entities', []):
# Create discovery topic
discovery_topic = f"{MQTT_DISCOVERY_PREFIX}/sensor/system2mqtt_{HOSTNAME}_{entity['sensor_id']}/config"
# Create discovery payload
discovery_payload = {
"name": entity['name'],
"unique_id": f"system2mqtt_{HOSTNAME}_{entity['sensor_id']}",
"state_topic": f"system2mqtt/{HOSTNAME}/{entity['sensor_id']}/state",
"state_class": entity['state_class'],
"unit_of_measurement": entity['unit_of_measurement'],
"device_class": entity['device_class'],
"icon": entity.get('icon'),
"device": device_info,
"availability": {
"topic": f"system2mqtt/{HOSTNAME}/status",
"payload_available": "online",
"payload_not_available": "offline"
},
"json_attributes_topic": f"system2mqtt/{HOSTNAME}/{entity['sensor_id']}/attributes"
}
# Publish discovery message
client.publish(discovery_topic, json.dumps(discovery_payload), retain=True)
# Publish state
state_topic = f"system2mqtt/{HOSTNAME}/{entity['sensor_id']}/state"
client.publish(state_topic, entity['value'], retain=True)
# Publish attributes
attributes = entity.get('attributes', {})
attributes.update(device_attributes) # Add device attributes
attributes_topic = f"system2mqtt/{HOSTNAME}/{entity['sensor_id']}/attributes"
client.publish(attributes_topic, json.dumps(attributes), retain=True)
except Exception as e:
print(f"Error collecting metrics from {collector_name}: {e}")
continue
# Publish availability status
client.publish(f"system2mqtt/{HOSTNAME}/status", "online", retain=True)
# Wait before next collection
time.sleep(60) # Collect metrics every minute
except KeyboardInterrupt:
print("Stopping...")
# Publish offline status before stopping
client.publish(f"system2mqtt/{HOSTNAME}/status", "offline", retain=True)
break
except Exception as e:
print(f"Error in main loop: {e}")
time.sleep(60) # Wait before retrying
# Stop the MQTT client loop and disconnect
client.loop_stop()
client.disconnect()
if __name__ == "__main__":
main()