initial commit
This commit is contained in:
266
plugins/librehardwaremonitor.py
Normal file
266
plugins/librehardwaremonitor.py
Normal file
@@ -0,0 +1,266 @@
|
||||
"""
|
||||
Plugin pour LibreHardwareMonitor
|
||||
Utilise l'API REST intégrée de LHM (port 8085 par défaut)
|
||||
"""
|
||||
import requests
|
||||
from typing import Dict, List, Optional, Any
|
||||
from .base import BasePlugin
|
||||
|
||||
|
||||
class LibreHardwareMonitorPlugin(BasePlugin):
|
||||
"""Plugin pour LibreHardwareMonitor"""
|
||||
|
||||
def __init__(self, config: dict):
|
||||
super().__init__(config)
|
||||
self.history = {}
|
||||
self.max_history = 120 # 2 minutes à 1 sample/sec
|
||||
|
||||
def get_id(self) -> str:
|
||||
return 'librehardwaremonitor'
|
||||
|
||||
def get_name(self) -> str:
|
||||
return 'LibreHardwareMonitor'
|
||||
|
||||
def get_default_port(self) -> int:
|
||||
return 8085
|
||||
|
||||
def test_connection(self) -> Dict[str, Any]:
|
||||
"""Teste la connexion à LibreHardwareMonitor"""
|
||||
try:
|
||||
url = f"{self.get_base_url()}/data.json"
|
||||
response = requests.get(url, timeout=5)
|
||||
response.raise_for_status()
|
||||
|
||||
data = response.json()
|
||||
|
||||
# Extraire la version si disponible
|
||||
version = "Inconnue"
|
||||
if data and "Text" in data:
|
||||
version = data.get("Text", "LibreHardwareMonitor")
|
||||
|
||||
# Compter les capteurs
|
||||
sensors = self.parse_sensors(data)
|
||||
|
||||
return {
|
||||
'success': True,
|
||||
'message': f'Connecté - {len(sensors)} capteurs détectés',
|
||||
'version': version,
|
||||
'sensor_count': len(sensors)
|
||||
}
|
||||
|
||||
except requests.exceptions.ConnectionError:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'Impossible de se connecter à {self.get_base_url()}. Vérifiez que LibreHardwareMonitor est lancé avec le serveur web activé.'
|
||||
}
|
||||
except requests.exceptions.Timeout:
|
||||
return {
|
||||
'success': False,
|
||||
'message': 'Timeout - Le serveur ne répond pas'
|
||||
}
|
||||
except Exception as e:
|
||||
return {
|
||||
'success': False,
|
||||
'message': f'Erreur: {str(e)}'
|
||||
}
|
||||
|
||||
def get_data(self) -> Optional[Dict]:
|
||||
"""Récupère les données JSON depuis LHM Remote Server"""
|
||||
try:
|
||||
url = f"{self.get_base_url()}/data.json"
|
||||
response = requests.get(url, timeout=5)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except Exception as e:
|
||||
print(f"Erreur LHM get_data: {e}")
|
||||
return None
|
||||
|
||||
def get_hierarchy(self) -> List[dict]:
|
||||
"""Récupère la hiérarchie des capteurs pour l'admin"""
|
||||
data = self.get_data()
|
||||
if not data:
|
||||
return []
|
||||
|
||||
return self._build_hierarchy(data)
|
||||
|
||||
def _build_hierarchy(self, data: Dict) -> List[dict]:
|
||||
"""Construit la hiérarchie des capteurs"""
|
||||
if not data or "Children" not in data:
|
||||
return []
|
||||
|
||||
def build_tree(node, level=0):
|
||||
node_text = node.get("Text", "")
|
||||
node_value = node.get("Value", "")
|
||||
sensor_id = node.get("SensorId", "")
|
||||
|
||||
result = {
|
||||
"name": node_text,
|
||||
"value": node_value,
|
||||
"id": sensor_id,
|
||||
"level": level,
|
||||
"type": self._guess_sensor_type(node_text, node_value) if node_value else "group",
|
||||
"children": []
|
||||
}
|
||||
|
||||
if "Children" in node and node["Children"]:
|
||||
for child in node["Children"]:
|
||||
result["children"].append(build_tree(child, level + 1))
|
||||
|
||||
return result
|
||||
|
||||
tree = []
|
||||
for child in data.get("Children", []):
|
||||
tree.append(build_tree(child))
|
||||
|
||||
return tree
|
||||
|
||||
def parse_sensors(self, data: Dict) -> List[Dict]:
|
||||
"""Parse les données et retourne une liste plate de capteurs"""
|
||||
sensors = []
|
||||
|
||||
if not data or "Children" not in data:
|
||||
return sensors
|
||||
|
||||
def extract_sensors(node, parent_name="", hardware_name="", hardware_type=""):
|
||||
node_text = node.get("Text", "")
|
||||
current_path = f"{parent_name}/{node_text}" if parent_name else node_text
|
||||
|
||||
# Détecter le type de hardware au niveau 1
|
||||
if not hardware_name and "Children" in node:
|
||||
hw_type = self._guess_hardware_type(node_text)
|
||||
hardware_name = node_text
|
||||
hardware_type = hw_type
|
||||
|
||||
# Si le nœud a une valeur, c'est un capteur
|
||||
if "Value" in node and node["Value"]:
|
||||
sensor_id = node.get("SensorId", "")
|
||||
if not sensor_id:
|
||||
sensor_id = f"{current_path}".replace(" ", "-").lower()
|
||||
|
||||
value_str = node.get("Value", "")
|
||||
raw_value = self._extract_numeric_value(value_str)
|
||||
unit = self._extract_unit(value_str)
|
||||
sensor_type = self._guess_sensor_type(node_text, value_str)
|
||||
|
||||
sensor = {
|
||||
"id": sensor_id,
|
||||
"name": node_text,
|
||||
"value": value_str,
|
||||
"raw_value": raw_value,
|
||||
"type": sensor_type,
|
||||
"unit": unit,
|
||||
"hardware": hardware_name,
|
||||
"hardware_type": hardware_type,
|
||||
"category": parent_name.split("/")[0] if "/" in parent_name else parent_name,
|
||||
"path": current_path,
|
||||
"min": node.get("Min", ""),
|
||||
"max": node.get("Max", ""),
|
||||
}
|
||||
sensors.append(sensor)
|
||||
|
||||
# Parcourir les enfants récursivement
|
||||
if "Children" in node:
|
||||
for child in node["Children"]:
|
||||
extract_sensors(child, current_path, hardware_name, hardware_type)
|
||||
|
||||
for child in data.get("Children", []):
|
||||
extract_sensors(child)
|
||||
|
||||
return sensors
|
||||
|
||||
def _guess_hardware_type(self, name: str) -> str:
|
||||
"""Devine le type de hardware basé sur le nom"""
|
||||
name_lower = name.lower()
|
||||
|
||||
if 'cpu' in name_lower or 'processor' in name_lower:
|
||||
return 'CPU'
|
||||
elif 'gpu' in name_lower or 'graphics' in name_lower or 'nvidia' in name_lower or 'amd' in name_lower or 'radeon' in name_lower or 'geforce' in name_lower:
|
||||
return 'GPU'
|
||||
elif 'memory' in name_lower or 'ram' in name_lower:
|
||||
return 'RAM'
|
||||
elif 'motherboard' in name_lower or 'mainboard' in name_lower:
|
||||
return 'Motherboard'
|
||||
elif 'storage' in name_lower or 'disk' in name_lower or 'ssd' in name_lower or 'hdd' in name_lower or 'nvme' in name_lower:
|
||||
return 'Storage'
|
||||
elif 'network' in name_lower or 'ethernet' in name_lower or 'wifi' in name_lower:
|
||||
return 'Network'
|
||||
elif 'battery' in name_lower:
|
||||
return 'Battery'
|
||||
else:
|
||||
return 'Other'
|
||||
|
||||
def _guess_sensor_type(self, name: str, value: str) -> str:
|
||||
"""Devine le type de capteur basé sur le nom et la valeur"""
|
||||
name_lower = name.lower()
|
||||
value_lower = value.lower()
|
||||
|
||||
if "°c" in value_lower or "temperature" in name_lower or "temp" in name_lower:
|
||||
return "temperature"
|
||||
elif "%" in value or "load" in name_lower or "usage" in name_lower:
|
||||
return "percentage"
|
||||
elif "rpm" in value_lower or "fan" in name_lower:
|
||||
return "fan"
|
||||
elif "mhz" in value_lower or "ghz" in value_lower or "clock" in name_lower:
|
||||
return "frequency"
|
||||
elif "w" in value_lower and "wh" not in value_lower:
|
||||
return "power"
|
||||
elif "v" in value_lower:
|
||||
return "voltage"
|
||||
elif "gb" in value_lower or "mb" in value_lower or "memory" in name_lower:
|
||||
return "memory"
|
||||
else:
|
||||
return "generic"
|
||||
|
||||
def _extract_numeric_value(self, value_str: str) -> float:
|
||||
"""Extrait la valeur numérique d'une chaîne"""
|
||||
try:
|
||||
# Enlever tous les caractères non numériques sauf le point et la virgule
|
||||
clean = value_str.replace("°C", "").replace("%", "").replace("V", "").replace("W", "")
|
||||
clean = clean.replace("MHz", "").replace("GHz", "").replace("GB", "").replace("MB", "")
|
||||
clean = clean.replace("RPM", "").replace(" ", "").replace(",", ".").strip()
|
||||
|
||||
if clean:
|
||||
return float(clean)
|
||||
except (ValueError, AttributeError):
|
||||
pass
|
||||
return 0.0
|
||||
|
||||
def _extract_unit(self, value_str: str) -> str:
|
||||
"""Extrait l'unité d'une valeur"""
|
||||
value_str = value_str.strip()
|
||||
|
||||
units = ['°C', '°F', '%', 'MHz', 'GHz', 'GB', 'MB', 'KB', 'RPM', 'W', 'V', 'A', 'GB/s', 'MB/s']
|
||||
for unit in units:
|
||||
if unit in value_str:
|
||||
return unit
|
||||
return ''
|
||||
|
||||
# === Gestion de l'historique ===
|
||||
|
||||
def update_history(self, sensors: List[Dict]):
|
||||
"""Met à jour l'historique des capteurs pour les graphiques"""
|
||||
for sensor in sensors:
|
||||
sensor_id = sensor.get("id", "")
|
||||
if not sensor_id:
|
||||
continue
|
||||
|
||||
raw_value = sensor.get("raw_value")
|
||||
if raw_value is None:
|
||||
raw_value = self._extract_numeric_value(sensor.get("value", ""))
|
||||
|
||||
if raw_value == 0.0 and "0" not in sensor.get("value", ""):
|
||||
continue
|
||||
|
||||
if sensor_id not in self.history:
|
||||
self.history[sensor_id] = []
|
||||
|
||||
self.history[sensor_id].append(raw_value)
|
||||
|
||||
if len(self.history[sensor_id]) > self.max_history:
|
||||
self.history[sensor_id] = self.history[sensor_id][-self.max_history:]
|
||||
|
||||
def get_history(self, sensor_id: str, count: int = 60) -> List[float]:
|
||||
"""Récupère l'historique d'un capteur"""
|
||||
if sensor_id not in self.history:
|
||||
return []
|
||||
return self.history[sensor_id][-count:]
|
||||
Reference in New Issue
Block a user