436 lines
16 KiB
Python
436 lines
16 KiB
Python
"""
|
|
Energiemonitoring-Blueprint für MYP Platform
|
|
============================================
|
|
|
|
Dieses Modul stellt alle Routen und Funktionen für die Energieüberwachung
|
|
von TP-Link Tapo P110 Smart Plugs bereit.
|
|
|
|
Funktionalitäten:
|
|
- Live-Energiedaten abrufen
|
|
- Historische Verbrauchsstatistiken
|
|
- Energieverbrauchsdiagramme
|
|
- Energieeffizienz-Analysen
|
|
"""
|
|
|
|
from flask import Blueprint, request, jsonify, render_template
|
|
from flask_login import login_required, current_user
|
|
from datetime import datetime, timedelta
|
|
from typing import Dict, List, Any, Optional
|
|
|
|
from models import Printer, get_db_session, PlugStatusLog
|
|
from utils.logging_config import get_logger, measure_execution_time
|
|
from utils.security_suite import require_permission, Permission
|
|
from utils.hardware_integration import get_tapo_controller
|
|
|
|
# Logger initialisieren
|
|
energy_logger = get_logger("energy_monitoring")
|
|
|
|
# Blueprint erstellen
|
|
energy_blueprint = Blueprint("energy", __name__, url_prefix="/energy")
|
|
energy_api_blueprint = Blueprint("energy_api", __name__, url_prefix="/api/energy")
|
|
|
|
# ===== FRONTEND ROUTEN =====
|
|
|
|
@energy_blueprint.route("/")
|
|
@login_required
|
|
def energy_dashboard():
|
|
"""
|
|
Hauptseite für Energiemonitoring-Dashboard.
|
|
|
|
Returns:
|
|
Rendered HTML template mit Energieübersicht
|
|
"""
|
|
energy_logger.info(f"🔋 Energiemonitoring-Dashboard aufgerufen von {current_user.username}")
|
|
|
|
try:
|
|
# Basis-Statistiken für Template laden
|
|
tapo_controller = get_tapo_controller()
|
|
basic_stats = tapo_controller.get_energy_statistics()
|
|
|
|
return render_template(
|
|
'energy_dashboard.html',
|
|
stats=basic_stats,
|
|
current_user=current_user,
|
|
page_title="Energiemonitoring"
|
|
)
|
|
|
|
except Exception as e:
|
|
energy_logger.error(f"❌ Fehler beim Laden des Energiemonitoring-Dashboards: {str(e)}")
|
|
return render_template(
|
|
'energy_dashboard.html',
|
|
stats={'error': str(e)},
|
|
current_user=current_user,
|
|
page_title="Energiemonitoring"
|
|
)
|
|
|
|
@energy_blueprint.route("/device/<int:device_id>")
|
|
@login_required
|
|
def device_details(device_id):
|
|
"""
|
|
Detailseite für ein spezifisches Energiemonitoring-Gerät.
|
|
|
|
Args:
|
|
device_id: ID des Druckers/Geräts
|
|
|
|
Returns:
|
|
Rendered HTML template mit Gerätedetails
|
|
"""
|
|
energy_logger.info(f"🔍 Gerätedetails für ID {device_id} aufgerufen von {current_user.username}")
|
|
|
|
try:
|
|
with get_db_session() as db_session:
|
|
printer = db_session.query(Printer).filter(Printer.id == device_id).first()
|
|
|
|
if not printer:
|
|
energy_logger.warning(f"Gerät {device_id} nicht gefunden")
|
|
return render_template('errors/404.html'), 404
|
|
|
|
return render_template(
|
|
'energy_device_details.html',
|
|
device=printer,
|
|
current_user=current_user,
|
|
page_title=f"Energiemonitoring - {printer.name}"
|
|
)
|
|
|
|
except Exception as e:
|
|
energy_logger.error(f"❌ Fehler beim Laden der Gerätedetails: {str(e)}")
|
|
return render_template('errors/500.html'), 500
|
|
|
|
# ===== API ENDPUNKTE =====
|
|
|
|
@energy_api_blueprint.route("/dashboard", methods=["GET"])
|
|
@login_required
|
|
@measure_execution_time(logger=energy_logger, task_name="API-Energiemonitoring-Dashboard")
|
|
def api_energy_dashboard():
|
|
"""
|
|
API-Endpunkt für Energiemonitoring-Dashboard-Daten.
|
|
|
|
Returns:
|
|
JSON: Umfassende Energiestatistiken für Dashboard
|
|
"""
|
|
energy_logger.info(f"📊 API-Energiemonitoring-Dashboard von {current_user.username}")
|
|
|
|
try:
|
|
tapo_controller = get_tapo_controller()
|
|
energy_stats = tapo_controller.get_energy_statistics()
|
|
|
|
# Zusätzliche Dashboard-Metriken berechnen
|
|
dashboard_data = {
|
|
'overview': {
|
|
'total_devices': energy_stats['total_devices'],
|
|
'online_devices': energy_stats['online_devices'],
|
|
'offline_devices': energy_stats['total_devices'] - energy_stats['online_devices'],
|
|
'availability_rate': round((energy_stats['online_devices'] / energy_stats['total_devices'] * 100) if energy_stats['total_devices'] > 0 else 0, 1)
|
|
},
|
|
'current_consumption': {
|
|
'total_power': round(energy_stats['total_current_power'], 2),
|
|
'average_power': round(energy_stats['avg_current_power'], 2),
|
|
'max_power_device': None,
|
|
'min_power_device': None
|
|
},
|
|
'energy_totals': {
|
|
'today_total': round(energy_stats['total_today_energy'], 1),
|
|
'month_total': round(energy_stats['total_month_energy'], 1),
|
|
'today_average': round(energy_stats['avg_today_energy'], 1),
|
|
'month_average': round(energy_stats['avg_month_energy'], 1)
|
|
},
|
|
'trend_data': {
|
|
'hourly': energy_stats['hourly_consumption'],
|
|
'daily': energy_stats['daily_consumption'],
|
|
'monthly': energy_stats['monthly_consumption']
|
|
},
|
|
'devices': energy_stats['devices'],
|
|
'timestamp': energy_stats['timestamp']
|
|
}
|
|
|
|
# Max/Min Gerät finden
|
|
if energy_stats['devices']:
|
|
online_devices = [d for d in energy_stats['devices'] if d['online'] and d['current_power'] > 0]
|
|
if online_devices:
|
|
max_device = max(online_devices, key=lambda x: x['current_power'])
|
|
min_device = min(online_devices, key=lambda x: x['current_power'])
|
|
|
|
dashboard_data['current_consumption']['max_power_device'] = {
|
|
'name': max_device['name'],
|
|
'power': round(max_device['current_power'], 2)
|
|
}
|
|
dashboard_data['current_consumption']['min_power_device'] = {
|
|
'name': min_device['name'],
|
|
'power': round(min_device['current_power'], 2)
|
|
}
|
|
|
|
energy_logger.info(f"✅ Dashboard-Daten erfolgreich erstellt: {dashboard_data['overview']['online_devices']} Geräte online")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'data': dashboard_data
|
|
})
|
|
|
|
except Exception as e:
|
|
energy_logger.error(f"❌ Fehler beim Erstellen der Dashboard-Daten: {str(e)}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Fehler beim Laden der Energiedaten',
|
|
'message': str(e)
|
|
}), 500
|
|
|
|
@energy_api_blueprint.route("/device/<int:device_id>", methods=["GET"])
|
|
@login_required
|
|
@measure_execution_time(logger=energy_logger, task_name="API-Gerätespezifische-Energiedaten")
|
|
def api_device_energy_data(device_id):
|
|
"""
|
|
API-Endpunkt für gerätespezifische Energiedaten.
|
|
|
|
Args:
|
|
device_id: ID des Druckers/Geräts
|
|
|
|
Returns:
|
|
JSON: Detaillierte Energiedaten des Geräts
|
|
"""
|
|
energy_logger.info(f"🔍 API-Energiedaten für Gerät {device_id} von {current_user.username}")
|
|
|
|
try:
|
|
with get_db_session() as db_session:
|
|
printer = db_session.query(Printer).filter(Printer.id == device_id).first()
|
|
|
|
if not printer:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Gerät nicht gefunden'
|
|
}), 404
|
|
|
|
if not printer.plug_ip:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Gerät hat keine Energiemonitoring-Steckdose konfiguriert'
|
|
}), 400
|
|
|
|
# Energiedaten vom Tapo Controller abrufen
|
|
tapo_controller = get_tapo_controller()
|
|
reachable, status = tapo_controller.check_outlet_status(printer.plug_ip, printer_id=device_id)
|
|
|
|
if not reachable:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Gerät nicht erreichbar',
|
|
'device_info': {
|
|
'id': printer.id,
|
|
'name': printer.name,
|
|
'location': printer.location,
|
|
'online': False
|
|
}
|
|
}), 503
|
|
|
|
# Detaillierte Energiedaten abrufen
|
|
energy_stats = tapo_controller.get_energy_statistics()
|
|
device_data = None
|
|
|
|
for device in energy_stats['devices']:
|
|
if device['id'] == device_id:
|
|
device_data = device
|
|
break
|
|
|
|
if not device_data:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Energiedaten für Gerät nicht verfügbar'
|
|
}), 404
|
|
|
|
# Historische Logs aus Datenbank
|
|
recent_logs = db_session.query(PlugStatusLog).filter(
|
|
PlugStatusLog.printer_id == device_id,
|
|
PlugStatusLog.timestamp >= datetime.now() - timedelta(hours=24)
|
|
).order_by(PlugStatusLog.timestamp.desc()).limit(100).all()
|
|
|
|
historical_data = []
|
|
for log in recent_logs:
|
|
historical_data.append({
|
|
'timestamp': log.timestamp.isoformat(),
|
|
'power_consumption': log.power_consumption,
|
|
'voltage': log.voltage,
|
|
'current': log.current,
|
|
'status': log.status
|
|
})
|
|
|
|
result = {
|
|
'device_info': {
|
|
'id': printer.id,
|
|
'name': printer.name,
|
|
'model': printer.model,
|
|
'location': printer.location,
|
|
'ip': printer.plug_ip,
|
|
'online': device_data['online']
|
|
},
|
|
'current_metrics': {
|
|
'power': device_data['current_power'],
|
|
'voltage': device_data.get('voltage', 0),
|
|
'current': device_data.get('current', 0),
|
|
'status': 'on' if device_data['current_power'] > 0 else 'off'
|
|
},
|
|
'energy_consumption': {
|
|
'today': device_data['today_energy'],
|
|
'month': device_data['month_energy'],
|
|
'past24h': device_data['past24h'],
|
|
'past30d': device_data['past30d'],
|
|
'past1y': device_data['past1y']
|
|
},
|
|
'historical_logs': historical_data,
|
|
'timestamp': datetime.now().isoformat()
|
|
}
|
|
|
|
energy_logger.info(f"✅ Energiedaten erfolgreich abgerufen für {printer.name}: {device_data['current_power']}W")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'data': result
|
|
})
|
|
|
|
except Exception as e:
|
|
energy_logger.error(f"❌ Fehler beim Abrufen der Gerätedaten: {str(e)}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Fehler beim Abrufen der Energiedaten',
|
|
'message': str(e)
|
|
}), 500
|
|
|
|
@energy_api_blueprint.route("/statistics", methods=["GET"])
|
|
@login_required
|
|
@measure_execution_time(logger=energy_logger, task_name="API-Energiestatistiken")
|
|
def api_energy_statistics():
|
|
"""
|
|
API-Endpunkt für erweiterte Energiestatistiken.
|
|
|
|
Query-Parameter:
|
|
- period: Zeitraum (today, week, month, year)
|
|
|
|
Returns:
|
|
JSON: Erweiterte Energiestatistiken
|
|
"""
|
|
period = request.args.get('period', 'today')
|
|
|
|
energy_logger.info(f"📈 API-Energiestatistiken ({period}) von {current_user.username}")
|
|
|
|
try:
|
|
tapo_controller = get_tapo_controller()
|
|
base_stats = tapo_controller.get_energy_statistics()
|
|
|
|
# Zeitraumspezifische Statistiken
|
|
if period == 'today':
|
|
chart_data = {
|
|
'labels': [f"{i:02d}:00" for i in range(24)],
|
|
'datasets': [{
|
|
'label': 'Stündlicher Verbrauch (Wh)',
|
|
'data': base_stats['hourly_consumption'],
|
|
'borderColor': '#3b82f6',
|
|
'backgroundColor': 'rgba(59, 130, 246, 0.1)',
|
|
'fill': True
|
|
}]
|
|
}
|
|
elif period == 'week' or period == 'month':
|
|
chart_data = {
|
|
'labels': [f"Tag {i+1}" for i in range(30)],
|
|
'datasets': [{
|
|
'label': 'Täglicher Verbrauch (Wh)',
|
|
'data': base_stats['daily_consumption'],
|
|
'borderColor': '#10b981',
|
|
'backgroundColor': 'rgba(16, 185, 129, 0.1)',
|
|
'fill': True
|
|
}]
|
|
}
|
|
elif period == 'year':
|
|
month_names = ['Jan', 'Feb', 'Mär', 'Apr', 'Mai', 'Jun',
|
|
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
|
|
chart_data = {
|
|
'labels': month_names,
|
|
'datasets': [{
|
|
'label': 'Monatlicher Verbrauch (Wh)',
|
|
'data': base_stats['monthly_consumption'],
|
|
'borderColor': '#f59e0b',
|
|
'backgroundColor': 'rgba(245, 158, 11, 0.1)',
|
|
'fill': True
|
|
}]
|
|
}
|
|
else:
|
|
chart_data = {'labels': [], 'datasets': []}
|
|
|
|
result = {
|
|
'period': period,
|
|
'summary': {
|
|
'total_consumption': base_stats['total_today_energy'] if period == 'today' else base_stats['total_month_energy'],
|
|
'average_power': base_stats['avg_current_power'],
|
|
'device_count': base_stats['online_devices']
|
|
},
|
|
'chart_data': chart_data,
|
|
'device_breakdown': base_stats['devices'],
|
|
'timestamp': base_stats['timestamp']
|
|
}
|
|
|
|
energy_logger.info(f"✅ Energiestatistiken erfolgreich erstellt für Zeitraum: {period}")
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'data': result
|
|
})
|
|
|
|
except Exception as e:
|
|
energy_logger.error(f"❌ Fehler beim Erstellen der Energiestatistiken: {str(e)}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Fehler beim Erstellen der Statistiken',
|
|
'message': str(e)
|
|
}), 500
|
|
|
|
@energy_api_blueprint.route("/live", methods=["GET"])
|
|
@login_required
|
|
@measure_execution_time(logger=energy_logger, task_name="API-Live-Energiedaten")
|
|
def api_live_energy_data():
|
|
"""
|
|
API-Endpunkt für Live-Energiedaten (für Real-Time Updates).
|
|
|
|
Returns:
|
|
JSON: Aktuelle Energiedaten aller Geräte
|
|
"""
|
|
energy_logger.debug(f"⚡ Live-Energiedaten von {current_user.username}")
|
|
|
|
try:
|
|
tapo_controller = get_tapo_controller()
|
|
live_stats = tapo_controller.get_energy_statistics()
|
|
|
|
# Nur Live-Daten für minimale Latenz
|
|
live_data = {
|
|
'total_power': round(live_stats['total_current_power'], 2),
|
|
'device_count': live_stats['online_devices'],
|
|
'devices': [
|
|
{
|
|
'id': d['id'],
|
|
'name': d['name'],
|
|
'power': round(d['current_power'], 2),
|
|
'online': d['online']
|
|
}
|
|
for d in live_stats['devices']
|
|
],
|
|
'timestamp': live_stats['timestamp']
|
|
}
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'data': live_data
|
|
})
|
|
|
|
except Exception as e:
|
|
energy_logger.error(f"❌ Fehler beim Abrufen der Live-Energiedaten: {str(e)}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Fehler beim Abrufen der Live-Daten',
|
|
'message': str(e)
|
|
}), 500
|
|
|
|
# Legacy-Funktionen für Rückwärtskompatibilität
|
|
def get_energy_monitoring():
|
|
"""Legacy-Funktion für Energiemonitoring-Zugriff."""
|
|
return get_tapo_controller()
|
|
|
|
# Exportierte Blueprints
|
|
__all__ = ['energy_blueprint', 'energy_api_blueprint', 'get_energy_monitoring']
|
|
|
|
energy_logger.info("✅ Energiemonitoring-Blueprint initialisiert") |