""" 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 basic_stats = { 'total_power_consumption': 15.2, 'current_power': 450.0, 'active_devices': 3, 'total_devices': 5 } 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/") @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 # Mock-Energiedaten für Template energy_data = { 'current_power': 125.5, 'daily_consumption': 2.4, 'monthly_consumption': 45.8, 'daily_runtime': '8h 30m', 'monthly_runtime': '156h 45m', 'last_update': datetime.now().strftime('%d.%m.%Y %H:%M:%S') } return render_template( 'energy_device_details.html', device=printer, energy_data=energy_data, 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("/overview", methods=["GET"]) @login_required def api_energy_overview(): """ API-Endpunkt für Energieübersicht (für Dashboard). Returns: JSON: Grundlegende Energiestatistiken """ try: # Mock-Daten für Demo overview_data = { 'total_power_consumption': 15.2, 'current_power': 450.0, 'active_devices': 3, 'total_devices': 5, 'timestamp': datetime.now().isoformat() } return jsonify(overview_data) except Exception as e: energy_logger.error(f"❌ Fehler beim Abrufen der Energieübersicht: {str(e)}") return jsonify({'error': str(e)}), 500 @energy_api_blueprint.route("/devices", methods=["GET"]) @login_required def api_energy_devices(): """ API-Endpunkt für Geräteliste mit Energiedaten. Returns: JSON: Liste aller Geräte mit Energieinformationen """ try: with get_db_session() as db_session: printers = db_session.query(Printer).all() devices = [] for printer in printers: devices.append({ 'id': printer.id, 'name': printer.name, 'model': printer.model, 'status': printer.status, 'current_power': 0.0, # Mock-Daten 'daily_consumption': 0.0, 'plug_ip': printer.plug_ip }) return jsonify({'devices': devices}) except Exception as e: energy_logger.error(f"❌ Fehler beim Abrufen der Geräteliste: {str(e)}") return jsonify({'error': str(e)}), 500 @energy_api_blueprint.route("/device/", methods=["GET"]) @login_required def api_energy_device_details(device_id): """ API-Endpunkt für Gerätespezifische Energiedaten. Args: device_id: ID des Geräts Returns: JSON: Detaillierte Energiedaten für das Gerät """ try: with get_db_session() as db_session: printer = db_session.query(Printer).filter(Printer.id == device_id).first() if not printer: return jsonify({'error': 'Gerät nicht gefunden'}), 404 # Mock-Daten für Demo device_data = { 'current_power': 125.5, 'daily_consumption': 2.4, 'monthly_consumption': 45.8, 'daily_runtime': '8h 30m', 'monthly_runtime': '156h 45m', 'last_update': datetime.now().isoformat() } return jsonify(device_data) except Exception as e: energy_logger.error(f"❌ Fehler beim Abrufen der Gerätedaten: {str(e)}") return jsonify({'error': str(e)}), 500 @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/", 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")