🎉 Improved IHK Project Documentation with Screenshots & Videos 📚

This commit is contained in:
2025-06-05 11:05:23 +02:00
parent f710283798
commit 502d63bc20
58 changed files with 2016 additions and 2894 deletions

View File

@@ -18,8 +18,9 @@ import os
from models import get_db_session, Printer, PlugStatusLog
from utils.logging_config import get_logger
from utils.settings import PRINTERS, TAPO_USERNAME, TAPO_PASSWORD, DEFAULT_TAPO_IPS, TAPO_AUTO_DISCOVERY
from utils.tapo_controller import tapo_controller
# TP-Link Tapo P110 Unterstützung hinzufügen
# TP-Link Tapo P110 Unterstützung prüfen
try:
from PyP100 import PyP100
TAPO_AVAILABLE = True
@@ -81,62 +82,10 @@ class PrinterMonitor:
if self.startup_initialized:
monitor_logger.info("🔄 Steckdosen bereits beim Start initialisiert")
return {}
monitor_logger.info("🚀 Starte Steckdosen-Initialisierung beim Programmstart...")
results = {}
try:
db_session = get_db_session()
printers = db_session.query(Printer).filter(Printer.active == True).all()
if not printers:
monitor_logger.warning("⚠️ Keine aktiven Drucker zur Initialisierung gefunden")
db_session.close()
self.startup_initialized = True
return results
# Alle Steckdosen ausschalten für einheitlichen Startzustand
for printer in printers:
try:
if printer.plug_ip and printer.plug_username and printer.plug_password:
success = self._turn_outlet_off(
printer.plug_ip,
printer.plug_username,
printer.plug_password,
printer_id=printer.id
)
results[printer.name] = success
if success:
monitor_logger.info(f"{printer.name}: Steckdose ausgeschaltet")
# Status in Datenbank aktualisieren
printer.status = "offline"
printer.last_checked = datetime.now()
else:
monitor_logger.warning(f"{printer.name}: Steckdose konnte nicht ausgeschaltet werden")
else:
monitor_logger.warning(f"⚠️ {printer.name}: Unvollständige Steckdosen-Konfiguration")
results[printer.name] = False
except Exception as e:
monitor_logger.error(f"❌ Fehler bei Initialisierung von {printer.name}: {str(e)}")
results[printer.name] = False
# Änderungen speichern
db_session.commit()
db_session.close()
success_count = sum(1 for success in results.values() if success)
total_count = len(results)
monitor_logger.info(f"🎯 Steckdosen-Initialisierung abgeschlossen: {success_count}/{total_count} erfolgreich")
self.startup_initialized = True
except Exception as e:
monitor_logger.error(f"❌ Kritischer Fehler bei Steckdosen-Initialisierung: {str(e)}")
results = {}
# Verwende zentrale tapo_controller Implementierung
results = tapo_controller.initialize_all_outlets()
self.startup_initialized = True
return results
def _turn_outlet_off(self, ip_address: str, username: str, password: str, timeout: int = 5, printer_id: int = None) -> bool:
@@ -153,79 +102,8 @@ class PrinterMonitor:
Returns:
bool: True wenn erfolgreich ausgeschaltet
"""
if not TAPO_AVAILABLE:
monitor_logger.error("⚠️ PyP100-Modul nicht verfügbar - kann Tapo-Steckdose nicht schalten")
# Logging: Fehlgeschlagener Versuch
if printer_id:
try:
PlugStatusLog.log_status_change(
printer_id=printer_id,
status="disconnected",
source="system",
ip_address=ip_address,
error_message="PyP100-Modul nicht verfügbar",
notes="Startup-Initialisierung fehlgeschlagen"
)
except Exception as log_error:
monitor_logger.warning(f"Fehler beim Loggen des Steckdosen-Status: {log_error}")
return False
# IMMER globale Anmeldedaten verwenden (da diese funktionieren)
username = TAPO_USERNAME
password = TAPO_PASSWORD
monitor_logger.debug(f"🔧 Verwende globale Tapo-Anmeldedaten für {ip_address}")
start_time = time.time()
try:
# TP-Link Tapo P100 Verbindung herstellen (P100 statt P110)
from PyP100 import PyP100
p100 = PyP100.P100(ip_address, username, password)
p100.handshake() # Authentifizierung
p100.login() # Login
# Steckdose ausschalten
p100.turnOff()
response_time = int((time.time() - start_time) * 1000) # in Millisekunden
monitor_logger.debug(f"✅ Tapo-Steckdose {ip_address} erfolgreich ausgeschaltet")
# Logging: Erfolgreich ausgeschaltet
if printer_id:
try:
PlugStatusLog.log_status_change(
printer_id=printer_id,
status="off",
source="system",
ip_address=ip_address,
response_time_ms=response_time,
notes="Startup-Initialisierung: Steckdose ausgeschaltet"
)
except Exception as log_error:
monitor_logger.warning(f"Fehler beim Loggen des Steckdosen-Status: {log_error}")
return True
except Exception as e:
response_time = int((time.time() - start_time) * 1000) # in Millisekunden
monitor_logger.debug(f"⚠️ Fehler beim Ausschalten der Tapo-Steckdose {ip_address}: {str(e)}")
# Logging: Fehlgeschlagener Versuch
if printer_id:
try:
PlugStatusLog.log_status_change(
printer_id=printer_id,
status="disconnected",
source="system",
ip_address=ip_address,
response_time_ms=response_time,
error_message=str(e),
notes="Startup-Initialisierung fehlgeschlagen"
)
except Exception as log_error:
monitor_logger.warning(f"Fehler beim Loggen des Steckdosen-Status: {log_error}")
return False
# Verwende zentrale tapo_controller Implementierung
return tapo_controller.turn_off(ip_address, username, password, printer_id)
def get_live_printer_status(self, use_session_cache: bool = True) -> Dict[int, Dict]:
"""
@@ -448,41 +326,8 @@ class PrinterMonitor:
Returns:
bool: True wenn Verbindung erfolgreich
"""
try:
# IP-Adresse validieren
ipaddress.ip_address(ip_address.strip())
import socket
# Erst Port 9999 versuchen (Tapo-Standard)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((ip_address.strip(), 9999))
sock.close()
if result == 0:
return True
# Falls Port 9999 nicht erfolgreich, Port 80 versuchen (HTTP)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((ip_address.strip(), 80))
sock.close()
if result == 0:
return True
# Falls Port 80 nicht erfolgreich, Port 443 versuchen (HTTPS)
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(timeout)
result = sock.connect_ex((ip_address.strip(), 443))
sock.close()
return result == 0
except Exception as e:
monitor_logger.debug(f"❌ Fehler beim Verbindungstest zu {ip_address}: {str(e)}")
return False
# Verwende zentrale tapo_controller Implementierung
return tapo_controller.ping_address(ip_address, timeout)
def _check_outlet_status(self, ip_address: str, username: str, password: str, timeout: int = 5, printer_id: int = None) -> Tuple[bool, str]:
"""
@@ -498,111 +343,8 @@ class PrinterMonitor:
Returns:
Tuple[bool, str]: (Erreichbar, Status) - Status: "on", "off", "unknown"
"""
if not TAPO_AVAILABLE:
monitor_logger.debug("⚠️ PyP100-Modul nicht verfügbar - kann Tapo-Steckdosen-Status nicht abfragen")
# Logging: Modul nicht verfügbar
if printer_id:
try:
PlugStatusLog.log_status_change(
printer_id=printer_id,
status="disconnected",
source="system",
ip_address=ip_address,
error_message="PyP100-Modul nicht verfügbar",
notes="Status-Check fehlgeschlagen"
)
except Exception as log_error:
monitor_logger.warning(f"Fehler beim Loggen des Steckdosen-Status: {log_error}")
return False, "unknown"
# IMMER globale Anmeldedaten verwenden (da diese funktionieren)
username = TAPO_USERNAME
password = TAPO_PASSWORD
monitor_logger.debug(f"🔧 Verwende globale Tapo-Anmeldedaten für {ip_address}")
start_time = time.time()
try:
# TP-Link Tapo P100 Verbindung herstellen (P100 statt P110)
from PyP100 import PyP100
p100 = PyP100.P100(ip_address, username, password)
p100.handshake() # Authentifizierung
p100.login() # Login
# Geräteinformationen abrufen
device_info = p100.getDeviceInfo()
# Status auswerten
device_on = device_info.get('device_on', False)
status = "on" if device_on else "off"
response_time = int((time.time() - start_time) * 1000) # in Millisekunden
monitor_logger.debug(f"✅ Tapo-Steckdose {ip_address}: Status = {status}")
# Logging: Erfolgreicher Status-Check
if printer_id:
try:
# Hole zusätzliche Geräteinformationen falls verfügbar
power_consumption = None
voltage = None
current = None
firmware_version = None
try:
# Versuche Energiedaten zu holen (P110 spezifisch)
energy_usage = p100.getEnergyUsage()
if energy_usage:
power_consumption = energy_usage.get('current_power', None)
voltage = energy_usage.get('voltage', None)
current = energy_usage.get('current', None)
except:
pass # P100 unterstützt keine Energiedaten
try:
firmware_version = device_info.get('fw_ver', None)
except:
pass
PlugStatusLog.log_status_change(
printer_id=printer_id,
status=status,
source="system",
ip_address=ip_address,
power_consumption=power_consumption,
voltage=voltage,
current=current,
response_time_ms=response_time,
firmware_version=firmware_version,
notes="Automatischer Status-Check"
)
except Exception as log_error:
monitor_logger.warning(f"Fehler beim Loggen des Steckdosen-Status: {log_error}")
return True, status
except Exception as e:
response_time = int((time.time() - start_time) * 1000) # in Millisekunden
monitor_logger.debug(f"⚠️ Fehler bei Tapo-Steckdosen-Status-Check {ip_address}: {str(e)}")
# Logging: Fehlgeschlagener Status-Check
if printer_id:
try:
PlugStatusLog.log_status_change(
printer_id=printer_id,
status="disconnected",
source="system",
ip_address=ip_address,
response_time_ms=response_time,
error_message=str(e),
notes="Status-Check fehlgeschlagen"
)
except Exception as log_error:
monitor_logger.warning(f"Fehler beim Loggen des Steckdosen-Status: {log_error}")
return False, "unknown"
# Verwende zentrale tapo_controller Implementierung
return tapo_controller.check_outlet_status(ip_address, username, password, printer_id)
def clear_all_caches(self):
"""Löscht alle Caches (Session und DB)."""
@@ -657,94 +399,10 @@ class PrinterMonitor:
if self.auto_discovered_tapo:
monitor_logger.info("🔍 Tapo-Steckdosen wurden bereits erkannt")
return {}
monitor_logger.info("🔍 Starte automatische Tapo-Steckdosenerkennung...")
results = {}
start_time = time.time()
# 1. Zuerst die Standard-IPs aus der Konfiguration testen
monitor_logger.info(f"🔄 Teste {len(DEFAULT_TAPO_IPS)} Standard-IPs aus der Konfiguration")
for i, ip in enumerate(DEFAULT_TAPO_IPS):
try:
# Fortschrittsmeldung
monitor_logger.info(f"🔍 Teste IP {i+1}/{len(DEFAULT_TAPO_IPS)}: {ip}")
# Reduzierte Timeouts für schnellere Erkennung
ping_success = self._ping_address(ip, timeout=2)
if ping_success:
monitor_logger.info(f"✅ Steckdose mit IP {ip} ist erreichbar")
# Tapo-Verbindung testen mit Timeout-Schutz
if TAPO_AVAILABLE:
try:
# Timeout für Tapo-Verbindung
import signal
def timeout_handler(signum, frame):
raise TimeoutError("Tapo-Verbindung Timeout")
# Nur unter Unix/Linux verfügbar
if hasattr(signal, 'SIGALRM'):
signal.signal(signal.SIGALRM, timeout_handler)
signal.alarm(5) # 5 Sekunden Timeout
try:
from PyP100 import PyP100
p100 = PyP100.P100(ip, TAPO_USERNAME, TAPO_PASSWORD)
p100.handshake()
p100.login()
device_info = p100.getDeviceInfo()
# Timeout zurücksetzen
if hasattr(signal, 'SIGALRM'):
signal.alarm(0)
# Steckdose gefunden und verbunden
nickname = device_info.get('nickname', f"Tapo P110 ({ip})")
state = "on" if device_info.get('device_on', False) else "off"
monitor_logger.info(f"✅ Tapo-Steckdose '{nickname}' ({ip}) gefunden - Status: {state}")
results[ip] = True
# Steckdose in Datenbank speichern/aktualisieren (nicht-blockierend)
try:
self._ensure_tapo_in_database(ip, nickname)
except Exception as db_error:
monitor_logger.warning(f"⚠️ Fehler beim Speichern in DB für {ip}: {str(db_error)}")
except (TimeoutError, Exception) as tapo_error:
if hasattr(signal, 'SIGALRM'):
signal.alarm(0) # Timeout zurücksetzen
monitor_logger.debug(f"❌ IP {ip} ist erreichbar, aber keine Tapo-Steckdose oder Timeout: {str(tapo_error)}")
results[ip] = False
except Exception as outer_error:
monitor_logger.debug(f"❌ Fehler bei Tapo-Test für {ip}: {str(outer_error)}")
results[ip] = False
else:
monitor_logger.warning("⚠️ PyP100-Modul nicht verfügbar - kann Tapo-Verbindung nicht testen")
results[ip] = False
else:
monitor_logger.debug(f"❌ IP {ip} nicht erreichbar")
results[ip] = False
except Exception as e:
monitor_logger.warning(f"❌ Fehler bei Steckdosen-Erkennung für IP {ip}: {str(e)}")
results[ip] = False
# Weiter mit nächster IP - nicht abbrechen
continue
# Erfolgsstatistik berechnen
success_count = sum(1 for success in results.values() if success)
elapsed_time = time.time() - start_time
monitor_logger.info(f"✅ Steckdosen-Erkennung abgeschlossen: {success_count}/{len(results)} Steckdosen gefunden in {elapsed_time:.1f}s")
# Markieren, dass automatische Erkennung durchgeführt wurde
# Verwende zentrale tapo_controller Implementierung
results = tapo_controller.auto_discover_outlets()
self.auto_discovered_tapo = True
return results
def _ensure_tapo_in_database(self, ip_address: str, nickname: str = None) -> bool:
@@ -758,67 +416,8 @@ class PrinterMonitor:
Returns:
bool: True wenn erfolgreich in Datenbank gespeichert/aktualisiert
"""
try:
db_session = get_db_session()
# Prüfen, ob Drucker mit dieser IP bereits existiert
existing_printer = db_session.query(Printer).filter(Printer.plug_ip == ip_address).first()
if existing_printer:
# Drucker aktualisieren, falls nötig
if not existing_printer.plug_username or not existing_printer.plug_password:
existing_printer.plug_username = TAPO_USERNAME
existing_printer.plug_password = TAPO_PASSWORD
monitor_logger.info(f"✅ Drucker {existing_printer.name} mit Tapo-Anmeldedaten aktualisiert")
if nickname and existing_printer.name != nickname and "Tapo P110" not in existing_printer.name:
old_name = existing_printer.name
existing_printer.name = nickname
monitor_logger.info(f"✅ Drucker {old_name} umbenannt zu {nickname}")
# Drucker als aktiv markieren, da Tapo-Steckdose gefunden wurde
if not existing_printer.active:
existing_printer.active = True
monitor_logger.info(f"✅ Drucker {existing_printer.name} als aktiv markiert")
# Status aktualisieren
existing_printer.last_checked = datetime.now()
db_session.commit()
db_session.close()
return True
else:
# Neuen Drucker erstellen, falls keiner existiert
printer_name = nickname or f"Tapo P110 ({ip_address})"
mac_address = f"tapo:{ip_address.replace('.', '-')}" # Pseudo-MAC-Adresse
new_printer = Printer(
name=printer_name,
model="TP-Link Tapo P110",
location="Automatisch erkannt",
ip_address=ip_address, # Drucker-IP setzen wir gleich Steckdosen-IP
mac_address=mac_address,
plug_ip=ip_address,
plug_username=TAPO_USERNAME,
plug_password=TAPO_PASSWORD,
status="offline",
active=True,
last_checked=datetime.now()
)
db_session.add(new_printer)
db_session.commit()
monitor_logger.info(f"✅ Neuer Drucker '{printer_name}' mit Tapo-Steckdose {ip_address} erstellt")
db_session.close()
return True
except Exception as e:
monitor_logger.error(f"❌ Fehler beim Speichern der Tapo-Steckdose {ip_address}: {str(e)}")
try:
db_session.rollback()
db_session.close()
except:
pass
return False
# Verwende zentrale tapo_controller Implementierung
return tapo_controller._ensure_outlet_in_database(ip_address, nickname)
# Globale Instanz
printer_monitor = PrinterMonitor()