""" Tapo-Steckdosen-Steuerung Blueprint Eigenständige Web-Interface für direkte Tapo-Steckdosen-Kontrolle """ from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for from flask_login import login_required, current_user from datetime import datetime import ipaddress import time from blueprints.admin_unified import admin_required from utils.hardware_integration import get_drucker_steuerung, get_tapo_controller from utils.logging_config import get_logger from utils.monitoring_analytics import performance_tracker from utils.security_suite import require_permission, Permission from models import get_db_session, Printer # Blueprint initialisieren tapo_blueprint = Blueprint('tapo', __name__, url_prefix='/tapo') # Logger konfigurieren tapo_logger = get_logger("tapo_control") @tapo_blueprint.route("/") @login_required @require_permission(Permission.CONTROL_PRINTER) def tapo_dashboard(): """Haupt-Dashboard für Tapo-Steckdosen-Steuerung - zeigt immer alle 6 Mercedes-Benz Arbeitsplätze.""" try: tapo_logger.info(f"Tapo Dashboard aufgerufen von Benutzer: {current_user.name}") # Importiere die festen Konfigurationen try: from config.settings import DEFAULT_TAPO_IPS, PRINTERS, FIXED_PRINTER_COUNT, ALWAYS_SHOW_ALL_SOCKETS except ImportError: # Fallback-Konfiguration DEFAULT_TAPO_IPS = ["192.168.1.201", "192.168.1.202", "192.168.1.203", "192.168.1.204", "192.168.1.205", "192.168.1.206"] FIXED_PRINTER_COUNT = 6 ALWAYS_SHOW_ALL_SOCKETS = True tapo_logger.warning("Fallback-Konfiguration verwendet da config.settings nicht importiert werden konnte") # Alle Drucker aus der Datenbank laden db_session = get_db_session() db_printers = {printer.plug_ip: printer for printer in db_session.query(Printer).filter( Printer.active == True, Printer.plug_ip.isnot(None) ).all()} # Status aller 6 konfigurierten Steckdosen abrufen outlets_status = {} online_count = 0 # Gehe durch alle 6 festen Steckdosen-IPs for i, tapo_ip in enumerate(DEFAULT_TAPO_IPS[:FIXED_PRINTER_COUNT], 1): try: # Prüfe ob Drucker in der Datenbank existiert db_printer = db_printers.get(tapo_ip) if db_printer: # Drucker ist in der Datenbank - verwende echte Daten printer_name = db_printer.name printer_id = db_printer.id location = db_printer.location or f"Arbeitsplatz {i}" model = db_printer.model or "3D-Drucker" else: # Drucker nicht in der Datenbank - verwende Standardnamen printer_name = f"3D-Drucker {i}" printer_id = None location = f"Arbeitsplatz {i} (nicht konfiguriert)" model = "3D-Drucker" tapo_logger.debug(f"Prüfe Tapo-Steckdose {i}: {tapo_ip} für {printer_name}") # Status der Steckdose prüfen try: controller = get_tapo_controller() reachable, status = controller.check_outlet_status( tapo_ip, printer_id=printer_id ) if reachable: online_count += 1 tapo_logger.info(f"✅ Steckdose {i} ({tapo_ip}) erreichbar - Status: {status}") else: tapo_logger.warning(f"⚠️ Steckdose {i} ({tapo_ip}) nicht erreichbar") except Exception as status_error: tapo_logger.error(f"❌ Fehler beim Status-Check für Steckdose {i} ({tapo_ip}): {status_error}") reachable = False status = 'error' # Steckdose zu den Ergebnissen hinzufügen outlets_status[tapo_ip] = { 'printer_name': printer_name, 'printer_id': printer_id, 'ip': tapo_ip, 'status': status, 'reachable': reachable, 'location': location, 'model': model, 'position': i, # Position für Sortierung 'configured_in_db': db_printer is not None } # Fehlermeldung hinzufügen falls nicht erreichbar if not reachable: outlets_status[tapo_ip]['error'] = f"Steckdose {i} ist offline oder nicht erreichbar" except Exception as e: tapo_logger.error(f"❌ Fehler beim Verarbeiten von Steckdose {i} ({tapo_ip}): {e}") outlets_status[tapo_ip] = { 'printer_name': f"3D-Drucker {i}", 'printer_id': None, 'ip': tapo_ip, 'status': 'error', 'reachable': False, 'location': f"Arbeitsplatz {i} (Fehler)", 'model': "3D-Drucker", 'position': i, 'configured_in_db': False, 'error': f"Systemfehler: {str(e)}" } db_session.close() # Sortiere die Outlets nach Position sorted_outlets = dict(sorted(outlets_status.items(), key=lambda x: x[1]['position'])) tapo_logger.info(f"Dashboard geladen: {len(sorted_outlets)} Steckdosen konfiguriert, {online_count} online") return render_template('tapo_control.html', outlets=sorted_outlets, total_outlets=len(sorted_outlets), online_outlets=online_count, fixed_layout=True) # Flag für festes Layout except Exception as e: tapo_logger.error(f"Fehler beim Laden des Tapo-Dashboards: {e}") flash(f"Fehler beim Laden der Tapo-Steckdosen: {str(e)}", "error") # Auch bei Fehlern die 6 Standard-Steckdosen anzeigen error_outlets = {} for i in range(1, 7): tapo_ip = f"192.168.1.20{i}" error_outlets[tapo_ip] = { 'printer_name': f"3D-Drucker {i}", 'printer_id': None, 'ip': tapo_ip, 'status': 'error', 'reachable': False, 'location': f"Arbeitsplatz {i}", 'model': "3D-Drucker", 'position': i, 'configured_in_db': False, 'error': "Systemfehler beim Laden" } return render_template('tapo_control.html', outlets=error_outlets, total_outlets=6, online_outlets=0, fixed_layout=True) @tapo_blueprint.route("/control", methods=["POST"]) @login_required @require_permission(Permission.CONTROL_PRINTER) def control_outlet(): """Schaltet eine Tapo-Steckdose direkt ein oder aus.""" try: data = request.get_json() ip = data.get('ip') action = data.get('action') # 'on' oder 'off' if not ip or not action: return jsonify({ 'success': False, 'error': 'IP-Adresse und Aktion sind erforderlich' }), 400 # IP-Adresse validieren try: ipaddress.ip_address(ip) except ValueError: return jsonify({ 'success': False, 'error': 'Ungültige IP-Adresse' }), 400 if action not in ['on', 'off']: return jsonify({ 'success': False, 'error': 'Ungültige Aktion. Nur "on" oder "off" erlaubt.' }), 400 # Steckdose schalten state = action == 'on' controller = get_tapo_controller() success = controller.toggle_plug(ip, state) if success: action_text = "eingeschaltet" if state else "ausgeschaltet" tapo_logger.info(f"✅ Tapo-Steckdose {ip} erfolgreich {action_text} durch {current_user.name}") return jsonify({ 'success': True, 'message': f'Steckdose {ip} erfolgreich {action_text}', 'ip': ip, 'action': action, 'timestamp': datetime.now().isoformat(), 'user': current_user.name }) else: action_text = "einschalten" if state else "ausschalten" tapo_logger.error(f"❌ Fehler beim {action_text} der Steckdose {ip}") return jsonify({ 'success': False, 'error': f'Fehler beim {action_text} der Steckdose {ip}' }), 500 except Exception as e: tapo_logger.error(f"Unerwarteter Fehler bei Steckdosen-Steuerung: {e}") return jsonify({ 'success': False, 'error': f'Interner Serverfehler: {str(e)}' }), 500 @tapo_blueprint.route("/status/", methods=["GET"]) @login_required @require_permission(Permission.CONTROL_PRINTER) def get_outlet_status(ip): """Holt den aktuellen Status einer spezifischen Tapo-Steckdose.""" try: # IP-Adresse validieren try: ipaddress.ip_address(ip) except ValueError: return jsonify({ 'success': False, 'error': 'Ungültige IP-Adresse' }), 400 # Status prüfen controller = get_tapo_controller() reachable, status = controller.check_outlet_status(ip) return jsonify({ 'success': True, 'ip': ip, 'status': status, 'reachable': reachable, 'timestamp': datetime.now().isoformat() }) except Exception as e: tapo_logger.error(f"Fehler beim Abrufen des Status für {ip}: {e}") return jsonify({ 'success': False, 'error': f'Fehler beim Status-Check: {str(e)}' }), 500 @tapo_blueprint.route("/discover", methods=["POST"]) @login_required @admin_required def discover_outlets(): """Startet die automatische Erkennung von Tapo-Steckdosen.""" try: tapo_logger.info(f"🔍 Starte Tapo-Steckdosen-Erkennung auf Anfrage von {current_user.name}") # Discovery starten controller = get_tapo_controller() results = controller.auto_discover_outlets() success_count = sum(1 for success in results.values() if success) total_count = len(results) return jsonify({ 'success': True, 'message': f'Erkennung abgeschlossen: {success_count}/{total_count} Steckdosen gefunden', 'results': results, 'discovered_count': success_count, 'total_tested': total_count, 'timestamp': datetime.now().isoformat() }) except Exception as e: tapo_logger.error(f"Fehler bei der Steckdosen-Erkennung: {e}") return jsonify({ 'success': False, 'error': f'Fehler bei der Erkennung: {str(e)}' }), 500 @tapo_blueprint.route("/test/", methods=["POST"]) @login_required @require_permission(Permission.CONTROL_PRINTER) def test_connection(ip): """Testet die Verbindung zu einer spezifischen Tapo-Steckdose.""" try: # IP-Adresse validieren try: ipaddress.ip_address(ip) except ValueError: return jsonify({ 'success': False, 'error': 'Ungültige IP-Adresse' }), 400 # Verbindung testen controller = get_tapo_controller() test_result = controller.test_connection(ip) return jsonify({ 'success': True, 'ip': ip, 'test_result': test_result, 'timestamp': datetime.now().isoformat() }) except Exception as e: tapo_logger.error(f"Fehler beim Verbindungstest für {ip}: {e}") return jsonify({ 'success': False, 'error': f'Fehler beim Verbindungstest: {str(e)}' }), 500 @tapo_blueprint.route("/all-status", methods=["GET"]) @login_required @require_permission(Permission.CONTROL_PRINTER) def get_all_status(): """Holt den Status aller konfigurierten Tapo-Steckdosen.""" try: # Alle Tapo-Steckdosen aus der Datenbank laden db_session = get_db_session() printers_with_tapo = db_session.query(Printer).filter( Printer.active == True, Printer.plug_ip.isnot(None) ).all() all_status = {} online_count = 0 total_count = len(printers_with_tapo) tapo_logger.info(f"Status-Abfrage für {total_count} Tapo-Steckdosen gestartet") for printer in printers_with_tapo: try: tapo_logger.debug(f"Prüfe Status für {printer.plug_ip} ({printer.name})") controller = get_tapo_controller() reachable, status = controller.check_outlet_status( printer.plug_ip, printer_id=printer.id ) if reachable: online_count += 1 tapo_logger.debug(f"✅ {printer.plug_ip} erreichbar - Status: {status}") else: tapo_logger.debug(f"⚠️ {printer.plug_ip} nicht erreichbar") all_status[printer.plug_ip] = { 'printer_name': printer.name, 'printer_id': printer.id, 'status': status, 'reachable': reachable, 'location': printer.location or "Unbekannt", 'last_checked': time.time() } except Exception as e: tapo_logger.warning(f"❌ Status-Check für {printer.plug_ip} fehlgeschlagen: {e}") all_status[printer.plug_ip] = { 'printer_name': printer.name, 'printer_id': printer.id, 'status': 'error', 'reachable': False, 'location': printer.location or "Unbekannt", 'error': str(e), 'last_checked': time.time() } db_session.close() # Zusammenfassung loggen tapo_logger.info(f"Status-Abfrage abgeschlossen: {online_count}/{total_count} Steckdosen erreichbar") response_data = { 'success': True, 'outlets': all_status, 'summary': { 'total': total_count, 'online': online_count, 'offline': total_count - online_count, 'last_update': time.time() }, 'timestamp': datetime.now().isoformat() } return jsonify(response_data) except Exception as e: tapo_logger.error(f"Fehler beim Abrufen aller Tapo-Status: {e}") return jsonify({ 'success': False, 'error': str(e), 'outlets': {}, 'summary': {'total': 0, 'online': 0, 'offline': 0} }), 500 @tapo_blueprint.route("/manual-control", methods=["GET", "POST"]) @login_required @admin_required def manual_control(): """Manuelle Steuerung für beliebige IP-Adressen (Admin-only).""" if request.method == 'GET': return render_template('tapo_manual_control.html') try: ip = request.form.get('ip') action = request.form.get('action') if not ip or not action: flash('IP-Adresse und Aktion sind erforderlich', 'error') return redirect(url_for('tapo.manual_control')) # IP-Adresse validieren try: ipaddress.ip_address(ip) except ValueError: flash('Ungültige IP-Adresse', 'error') return redirect(url_for('tapo.manual_control')) if action not in ['on', 'off', 'status']: flash('Ungültige Aktion', 'error') return redirect(url_for('tapo.manual_control')) if action == 'status': # Status abfragen controller = get_tapo_controller() reachable, status = controller.check_outlet_status(ip) if reachable: flash(f'Steckdose {ip} ist {status.upper()}', 'success') else: flash(f'Steckdose {ip} ist nicht erreichbar', 'error') else: # Ein/Ausschalten state = action == 'on' controller = get_tapo_controller() success = controller.toggle_plug(ip, state) if success: action_text = "eingeschaltet" if state else "ausgeschaltet" flash(f'Steckdose {ip} erfolgreich {action_text}', 'success') tapo_logger.info(f"✅ Manuelle Steuerung: {ip} {action_text} durch {current_user.name}") else: action_text = "einschalten" if state else "ausschalten" flash(f'Fehler beim {action_text} der Steckdose {ip}', 'error') return redirect(url_for('tapo.manual_control')) except Exception as e: tapo_logger.error(f"Fehler bei manueller Steuerung: {e}") flash(f'Fehler: {str(e)}', 'error') return redirect(url_for('tapo.manual_control')) @tapo_blueprint.route("/control-form", methods=["POST"]) @login_required @require_permission(Permission.CONTROL_PRINTER) def control_outlet_form(): """Formular-basierte Steckdosen-Steuerung ohne JavaScript.""" try: ip = request.form.get('ip') action = request.form.get('action') # 'on' oder 'off' if not ip or not action: flash('IP-Adresse und Aktion sind erforderlich', 'error') return redirect(url_for('tapo.tapo_dashboard')) # IP-Adresse validieren try: ipaddress.ip_address(ip) except ValueError: flash('Ungültige IP-Adresse', 'error') return redirect(url_for('tapo.tapo_dashboard')) if action not in ['on', 'off']: flash('Ungültige Aktion. Nur "on" oder "off" erlaubt.', 'error') return redirect(url_for('tapo.tapo_dashboard')) # Steckdose schalten state = action == 'on' controller = get_tapo_controller() success = controller.toggle_plug(ip, state) if success: action_text = "eingeschaltet" if state else "ausgeschaltet" flash(f'Steckdose {ip} erfolgreich {action_text}', 'success') tapo_logger.info(f"✅ Steckdose {ip} erfolgreich {action_text} durch {current_user.name} (Formular)") else: action_text = "einschalten" if state else "ausschalten" flash(f'Fehler beim {action_text} der Steckdose {ip}', 'error') tapo_logger.error(f"❌ Fehler beim {action_text} der Steckdose {ip} durch {current_user.name}") return redirect(url_for('tapo.tapo_dashboard')) except Exception as e: tapo_logger.error(f"Unerwarteter Fehler bei Formular-Steckdosen-Steuerung: {e}") flash(f'Systemfehler: {str(e)}', 'error') return redirect(url_for('tapo.tapo_dashboard')) @tapo_blueprint.route("/test-connection-form", methods=["POST"]) @login_required @require_permission(Permission.CONTROL_PRINTER) def test_connection_form(): """Formular-basierter Verbindungstest ohne JavaScript.""" try: ip = request.form.get('ip') if not ip: flash('IP-Adresse ist erforderlich', 'error') return redirect(url_for('tapo.tapo_dashboard')) # IP-Adresse validieren try: ipaddress.ip_address(ip) except ValueError: flash('Ungültige IP-Adresse', 'error') return redirect(url_for('tapo.tapo_dashboard')) # Verbindung testen controller = get_tapo_controller() test_result = controller.test_connection(ip) if test_result.get('success', False): flash(f'✅ Verbindung zu {ip} erfolgreich!', 'success') tapo_logger.info(f"✅ Verbindungstest zu {ip} erfolgreich - {current_user.name}") else: error_msg = test_result.get('error', 'Unbekannter Fehler') flash(f'❌ Verbindung zu {ip} fehlgeschlagen: {error_msg}', 'error') tapo_logger.warning(f"❌ Verbindungstest zu {ip} fehlgeschlagen - {current_user.name}: {error_msg}") return redirect(url_for('tapo.tapo_dashboard')) except Exception as e: tapo_logger.error(f"Fehler beim Formular-Verbindungstest: {e}") flash(f'Fehler beim Verbindungstest: {str(e)}', 'error') return redirect(url_for('tapo.tapo_dashboard'))