🎉 Feature: Enhanced Admin Guest Requests API & Startup Initialization Documentation 📚
This commit is contained in:
Binary file not shown.
Binary file not shown.
@ -33,6 +33,7 @@ except ImportError:
|
||||
# MYP Models & Utils
|
||||
from models import get_db_session, Printer, PlugStatusLog
|
||||
from utils.logging_config import get_logger
|
||||
import os
|
||||
|
||||
# Logger
|
||||
hardware_logger = get_logger("hardware_integration")
|
||||
@ -353,27 +354,55 @@ class DruckerSteuerung:
|
||||
hardware_logger.warning(f"⚠️ Simulation: Steckdose {ip} würde {'eingeschaltet' if einschalten else 'ausgeschaltet'}")
|
||||
return True # Simulation immer erfolgreich
|
||||
|
||||
try:
|
||||
action = "einschalten" if einschalten else "ausschalten"
|
||||
hardware_logger.debug(f"🔌 Versuche Steckdose {ip} zu {action}")
|
||||
|
||||
# P100-Verbindung herstellen
|
||||
p100 = PyP100(ip, self.tapo_username, self.tapo_password)
|
||||
p100.handshake()
|
||||
p100.login()
|
||||
|
||||
# Schalten
|
||||
if einschalten:
|
||||
p100.turnOn()
|
||||
else:
|
||||
p100.turnOff()
|
||||
|
||||
hardware_logger.info(f"✅ Steckdose {ip} erfolgreich {action}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
hardware_logger.error(f"❌ Fehler beim Schalten der Steckdose {ip}: {e}")
|
||||
# Zuerst Netzwerk-Erreichbarkeit prüfen
|
||||
if not self._erweiterte_netzwerk_prüfung(ip):
|
||||
hardware_logger.error(f"❌ Steckdose {ip} ist im Netzwerk nicht erreichbar")
|
||||
return False
|
||||
|
||||
retry_count = 0
|
||||
max_retries = 3
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
action = "einschalten" if einschalten else "ausschalten"
|
||||
hardware_logger.debug(f"🔌 Versuche Steckdose {ip} zu {action} (Versuch {retry_count + 1}/{max_retries})")
|
||||
|
||||
# P100-Verbindung herstellen mit Timeout
|
||||
p100 = PyP100(ip, self.tapo_username, self.tapo_password)
|
||||
p100.handshake()
|
||||
p100.login()
|
||||
|
||||
# Schalten
|
||||
if einschalten:
|
||||
p100.turnOn()
|
||||
else:
|
||||
p100.turnOff()
|
||||
|
||||
hardware_logger.info(f"✅ Steckdose {ip} erfolgreich {action}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
retry_count += 1
|
||||
error_msg = str(e)
|
||||
|
||||
# Spezifische Fehlerbehandlung
|
||||
if "Connection refused" in error_msg:
|
||||
hardware_logger.error(f"❌ Verbindung zu {ip} verweigert - Steckdose antwortet nicht auf Port 80")
|
||||
elif "timeout" in error_msg.lower():
|
||||
hardware_logger.error(f"❌ Zeitüberschreitung bei Verbindung zu {ip}")
|
||||
elif "handshake" in error_msg.lower():
|
||||
hardware_logger.error(f"❌ Tapo-Handshake fehlgeschlagen für {ip} - Möglicherweise falsche Credentials")
|
||||
elif "login" in error_msg.lower():
|
||||
hardware_logger.error(f"❌ Tapo-Login fehlgeschlagen für {ip} - Benutzername/Passwort prüfen")
|
||||
else:
|
||||
hardware_logger.error(f"❌ Fehler beim Schalten der Steckdose {ip}: {e}")
|
||||
|
||||
if retry_count < max_retries:
|
||||
hardware_logger.info(f"🔄 Warte 2 Sekunden vor erneutem Versuch...")
|
||||
time.sleep(2)
|
||||
|
||||
hardware_logger.error(f"❌ Alle {max_retries} Versuche für Steckdose {ip} fehlgeschlagen")
|
||||
return False
|
||||
|
||||
def _drucker_status_pruefen(self, drucker: Printer) -> str:
|
||||
"""Prüft den aktuellen Status eines Druckers"""
|
||||
@ -397,6 +426,51 @@ class DruckerSteuerung:
|
||||
except:
|
||||
return False
|
||||
|
||||
def _erweiterte_netzwerk_prüfung(self, ip: str) -> bool:
|
||||
"""
|
||||
Erweiterte Netzwerk-Prüfung mit mehreren Tests.
|
||||
|
||||
Args:
|
||||
ip: IP-Adresse zum Prüfen
|
||||
|
||||
Returns:
|
||||
bool: True wenn erreichbar
|
||||
"""
|
||||
hardware_logger.debug(f"🔍 Erweiterte Netzwerk-Prüfung für {ip}")
|
||||
|
||||
# Test 1: Port 80 (HTTP)
|
||||
if self._ping_test(ip, timeout=2):
|
||||
hardware_logger.debug(f"✅ {ip} auf Port 80 erreichbar")
|
||||
return True
|
||||
|
||||
# Test 2: Port 9999 (Tapo-spezifisch für manche Modelle)
|
||||
try:
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(2)
|
||||
result = sock.connect_ex((ip, 9999))
|
||||
sock.close()
|
||||
if result == 0:
|
||||
hardware_logger.debug(f"✅ {ip} auf Port 9999 erreichbar")
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
# Test 3: ICMP Ping (falls verfügbar)
|
||||
try:
|
||||
import subprocess
|
||||
# Windows und Linux kompatibel
|
||||
param = '-n' if os.name == 'nt' else '-c'
|
||||
command = ['ping', param, '1', '-w', '2000', ip]
|
||||
result = subprocess.run(command, capture_output=True, text=True, timeout=3)
|
||||
if result.returncode == 0:
|
||||
hardware_logger.debug(f"✅ {ip} via ICMP Ping erreichbar")
|
||||
return True
|
||||
except:
|
||||
pass
|
||||
|
||||
hardware_logger.warning(f"⚠️ {ip} ist über keine Methode erreichbar")
|
||||
return False
|
||||
|
||||
def _status_log_erstellen(self, drucker_id: int, action: str, grund: str):
|
||||
"""Erstellt einen Eintrag im Status-Log"""
|
||||
try:
|
||||
@ -443,33 +517,171 @@ class DruckerSteuerung:
|
||||
# Legacy-Format: (reachable, status)
|
||||
return (True, 'online')
|
||||
|
||||
# Zuerst Netzwerk-Erreichbarkeit prüfen
|
||||
if not self._erweiterte_netzwerk_prüfung(ip):
|
||||
hardware_logger.warning(f"⚠️ Steckdose {ip} ist im Netzwerk nicht erreichbar")
|
||||
return (False, 'unreachable')
|
||||
|
||||
retry_count = 0
|
||||
max_retries = 2
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
# Tapo P100/P110 Verbindung
|
||||
p100 = PyP100(ip, self.tapo_username, self.tapo_password)
|
||||
p100.handshake()
|
||||
p100.login()
|
||||
|
||||
# Device Info abrufen
|
||||
device_info = p100.getDeviceInfo()
|
||||
|
||||
if device_info and 'error_code' in device_info:
|
||||
if device_info['error_code'] == 0:
|
||||
device_on = device_info.get('result', {}).get('device_on', False)
|
||||
|
||||
hardware_logger.debug(f"✅ Steckdose {ip}: {'EIN' if device_on else 'AUS'}")
|
||||
|
||||
# Legacy-Format: (reachable, status)
|
||||
return (True, 'on' if device_on else 'off')
|
||||
else:
|
||||
hardware_logger.warning(f"⚠️ Steckdose {ip} Error Code: {device_info['error_code']}")
|
||||
return (False, 'error')
|
||||
else:
|
||||
hardware_logger.error(f"❌ Steckdose {ip}: Keine gültige Antwort")
|
||||
return (False, 'unreachable')
|
||||
|
||||
except Exception as e:
|
||||
retry_count += 1
|
||||
error_msg = str(e)
|
||||
|
||||
if "Connection refused" in error_msg:
|
||||
hardware_logger.error(f"❌ Verbindung zu {ip} verweigert")
|
||||
elif "timeout" in error_msg.lower():
|
||||
hardware_logger.error(f"❌ Zeitüberschreitung bei {ip}")
|
||||
elif "handshake" in error_msg.lower():
|
||||
hardware_logger.error(f"❌ Handshake-Fehler bei {ip}")
|
||||
else:
|
||||
hardware_logger.error(f"❌ Fehler beim Prüfen von Steckdose {ip}: {e}")
|
||||
|
||||
if retry_count < max_retries:
|
||||
time.sleep(1)
|
||||
|
||||
return (False, 'unreachable')
|
||||
|
||||
def ping_address(self, ip: str, timeout: int = 5) -> bool:
|
||||
"""
|
||||
Prüft die Netzwerk-Erreichbarkeit einer IP-Adresse.
|
||||
|
||||
Args:
|
||||
ip: IP-Adresse zum Testen
|
||||
timeout: Timeout in Sekunden
|
||||
|
||||
Returns:
|
||||
bool: True wenn erreichbar, False sonst
|
||||
"""
|
||||
hardware_logger.debug(f"📡 Teste Netzwerk-Erreichbarkeit: {ip}")
|
||||
|
||||
try:
|
||||
# Tapo P100/P110 Verbindung
|
||||
# Socket-basierter Ping-Test auf Port 80 (HTTP)
|
||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
sock.settimeout(timeout)
|
||||
result = sock.connect_ex((ip, 80))
|
||||
sock.close()
|
||||
|
||||
is_reachable = (result == 0)
|
||||
hardware_logger.debug(f"📡 {ip}: {'✅ erreichbar' if is_reachable else '❌ nicht erreichbar'}")
|
||||
|
||||
return is_reachable
|
||||
|
||||
except Exception as e:
|
||||
hardware_logger.debug(f"❌ Ping-Test für {ip} fehlgeschlagen: {e}")
|
||||
return False
|
||||
|
||||
def turn_off(self, ip: str, username: str = None, password: str = None, printer_id: int = None) -> bool:
|
||||
"""
|
||||
Schaltet eine Tapo-Steckdose aus.
|
||||
|
||||
Args:
|
||||
ip: IP-Adresse der Steckdose
|
||||
username: Benutzername (wird ignoriert, verwendet interne Credentials)
|
||||
password: Passwort (wird ignoriert, verwendet interne Credentials)
|
||||
printer_id: Optional - ID des Druckers für Logging
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich ausgeschaltet
|
||||
"""
|
||||
hardware_logger.debug(f"🔴 Schalte Steckdose aus: {ip}" + (f" (Drucker ID: {printer_id})" if printer_id else ""))
|
||||
|
||||
if not TAPO_AVAILABLE:
|
||||
hardware_logger.info(f"🔄 SIMULATION: Steckdose {ip} ausgeschaltet")
|
||||
return True
|
||||
|
||||
try:
|
||||
# P100-Verbindung mit internen Credentials
|
||||
p100 = PyP100(ip, self.tapo_username, self.tapo_password)
|
||||
p100.handshake()
|
||||
p100.login()
|
||||
|
||||
# Device Info abrufen
|
||||
device_info = p100.getDeviceInfo()
|
||||
# Steckdose ausschalten
|
||||
p100.turnOff()
|
||||
|
||||
hardware_logger.info(f"✅ Steckdose {ip} erfolgreich ausgeschaltet")
|
||||
|
||||
# Status-Log erstellen falls Drucker-ID verfügbar
|
||||
if printer_id:
|
||||
try:
|
||||
self._status_log_erstellen(printer_id, 'turned_off', 'Startup-Initialisierung')
|
||||
except:
|
||||
pass # Fehler beim Logging nicht kritisch
|
||||
|
||||
return True
|
||||
|
||||
if device_info and 'error_code' in device_info:
|
||||
if device_info['error_code'] == 0:
|
||||
device_on = device_info.get('result', {}).get('device_on', False)
|
||||
|
||||
hardware_logger.debug(f"✅ Steckdose {ip}: {'EIN' if device_on else 'AUS'}")
|
||||
|
||||
# Legacy-Format: (reachable, status)
|
||||
return (True, 'online' if device_on else 'offline')
|
||||
else:
|
||||
hardware_logger.warning(f"⚠️ Steckdose {ip} Error Code: {device_info['error_code']}")
|
||||
return (False, 'error')
|
||||
else:
|
||||
hardware_logger.error(f"❌ Steckdose {ip}: Keine gültige Antwort")
|
||||
return (False, 'unreachable')
|
||||
|
||||
except Exception as e:
|
||||
hardware_logger.error(f"❌ Fehler beim Prüfen von Steckdose {ip}: {e}")
|
||||
return (False, 'unreachable')
|
||||
hardware_logger.error(f"❌ Fehler beim Ausschalten der Steckdose {ip}: {e}")
|
||||
return False
|
||||
|
||||
def turn_on(self, ip: str, username: str = None, password: str = None, printer_id: int = None) -> bool:
|
||||
"""
|
||||
Schaltet eine Tapo-Steckdose ein.
|
||||
|
||||
Args:
|
||||
ip: IP-Adresse der Steckdose
|
||||
username: Benutzername (wird ignoriert, verwendet interne Credentials)
|
||||
password: Passwort (wird ignoriert, verwendet interne Credentials)
|
||||
printer_id: Optional - ID des Druckers für Logging
|
||||
|
||||
Returns:
|
||||
bool: True wenn erfolgreich eingeschaltet
|
||||
"""
|
||||
hardware_logger.debug(f"🟢 Schalte Steckdose ein: {ip}" + (f" (Drucker ID: {printer_id})" if printer_id else ""))
|
||||
|
||||
if not TAPO_AVAILABLE:
|
||||
hardware_logger.info(f"🔄 SIMULATION: Steckdose {ip} eingeschaltet")
|
||||
return True
|
||||
|
||||
try:
|
||||
# P100-Verbindung mit internen Credentials
|
||||
p100 = PyP100(ip, self.tapo_username, self.tapo_password)
|
||||
p100.handshake()
|
||||
p100.login()
|
||||
|
||||
# Steckdose einschalten
|
||||
p100.turnOn()
|
||||
|
||||
hardware_logger.info(f"✅ Steckdose {ip} erfolgreich eingeschaltet")
|
||||
|
||||
# Status-Log erstellen falls Drucker-ID verfügbar
|
||||
if printer_id:
|
||||
try:
|
||||
self._status_log_erstellen(printer_id, 'turned_on', 'Manuell')
|
||||
except:
|
||||
pass # Fehler beim Logging nicht kritisch
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
hardware_logger.error(f"❌ Fehler beim Einschalten der Steckdose {ip}: {e}")
|
||||
return False
|
||||
|
||||
# ===== GLOBALE INSTANZ =====
|
||||
|
||||
|
@ -39,6 +39,7 @@ class BackgroundTaskScheduler:
|
||||
self._running = False
|
||||
self._start_time: Optional[datetime] = None
|
||||
self.logger = get_scheduler_logger()
|
||||
self._outlets_initialized = False # Flag für einmalige Initialisierung
|
||||
|
||||
def register_task(self,
|
||||
task_id: str,
|
||||
@ -713,6 +714,188 @@ class BackgroundTaskScheduler:
|
||||
db_session.rollback()
|
||||
db_session.close()
|
||||
|
||||
def initialize_all_outlets_on_startup(self) -> Dict[str, bool]:
|
||||
"""
|
||||
Initialisiert alle konfigurierten Steckdosen beim Systemstart.
|
||||
|
||||
Schaltet alle im Netzwerk erreichbaren Tapo-Steckdosen aus, um einen
|
||||
einheitlichen Startzustand (aus = frei) zu gewährleisten.
|
||||
|
||||
Returns:
|
||||
Dict[str, bool]: Ergebnis der Initialisierung pro Drucker
|
||||
"""
|
||||
if self._outlets_initialized:
|
||||
self.logger.info("🔄 Steckdosen bereits initialisiert - überspringe")
|
||||
return {}
|
||||
|
||||
self.logger.info("🚀 Starte Steckdosen-Initialisierung beim Systemstart...")
|
||||
results = {}
|
||||
success_count = 0
|
||||
total_count = 0
|
||||
unreachable_count = 0
|
||||
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
|
||||
# Alle aktiven Drucker mit Steckdosen-Konfiguration laden
|
||||
printers = db_session.query(Printer).filter(
|
||||
Printer.active == True,
|
||||
Printer.plug_ip.isnot(None)
|
||||
).all()
|
||||
|
||||
if not printers:
|
||||
self.logger.warning("⚠️ Keine aktiven Drucker mit Steckdosen-Konfiguration gefunden")
|
||||
db_session.close()
|
||||
return results
|
||||
|
||||
total_count = len(printers)
|
||||
self.logger.info(f"🔍 Prüfe {total_count} konfigurierte Steckdosen...")
|
||||
|
||||
# Tapo-Controller für die Operationen verwenden
|
||||
tapo_controller = get_tapo_controller()
|
||||
|
||||
# Jede Steckdose einzeln verarbeiten
|
||||
for printer in printers:
|
||||
printer_name = printer.name
|
||||
plug_ip = printer.plug_ip
|
||||
|
||||
try:
|
||||
self.logger.debug(f"🔌 Verarbeite {printer_name} ({plug_ip})...")
|
||||
|
||||
# 1. Netzwerk-Erreichbarkeit prüfen
|
||||
is_reachable = tapo_controller.ping_address(plug_ip, timeout=3)
|
||||
|
||||
if not is_reachable:
|
||||
self.logger.warning(f"📡 {printer_name}: Steckdose {plug_ip} nicht erreichbar")
|
||||
results[printer_name] = {
|
||||
'success': False,
|
||||
'reason': 'nicht_erreichbar',
|
||||
'ip': plug_ip
|
||||
}
|
||||
unreachable_count += 1
|
||||
continue
|
||||
|
||||
# 2. Aktuellen Status prüfen
|
||||
reachable, current_status = tapo_controller.check_outlet_status(
|
||||
plug_ip,
|
||||
printer_id=printer.id,
|
||||
debug=True
|
||||
)
|
||||
|
||||
if not reachable:
|
||||
self.logger.warning(f"🔗 {printer_name}: Tapo-Verbindung fehlgeschlagen")
|
||||
results[printer_name] = {
|
||||
'success': False,
|
||||
'reason': 'verbindung_fehlgeschlagen',
|
||||
'ip': plug_ip
|
||||
}
|
||||
unreachable_count += 1
|
||||
continue
|
||||
|
||||
# 3. Steckdose ausschalten (nur wenn nötig)
|
||||
if current_status == "on":
|
||||
self.logger.info(f"🔄 {printer_name}: Schalte Steckdose von 'an' auf 'aus' um...")
|
||||
|
||||
success = tapo_controller.turn_off(
|
||||
plug_ip,
|
||||
printer_id=printer.id
|
||||
)
|
||||
|
||||
if success:
|
||||
self.logger.info(f"✅ {printer_name}: Erfolgreich ausgeschaltet")
|
||||
|
||||
# Drucker-Status in Datenbank aktualisieren
|
||||
printer.status = "offline"
|
||||
printer.last_checked = datetime.now()
|
||||
|
||||
results[printer_name] = {
|
||||
'success': True,
|
||||
'action': 'ausgeschaltet',
|
||||
'previous_status': 'an',
|
||||
'ip': plug_ip
|
||||
}
|
||||
success_count += 1
|
||||
else:
|
||||
self.logger.error(f"❌ {printer_name}: Ausschalten fehlgeschlagen")
|
||||
results[printer_name] = {
|
||||
'success': False,
|
||||
'reason': 'ausschalten_fehlgeschlagen',
|
||||
'ip': plug_ip
|
||||
}
|
||||
|
||||
elif current_status == "off":
|
||||
self.logger.info(f"✓ {printer_name}: Bereits ausgeschaltet - keine Aktion nötig")
|
||||
|
||||
# Status in Datenbank aktualisieren
|
||||
printer.status = "offline"
|
||||
printer.last_checked = datetime.now()
|
||||
|
||||
results[printer_name] = {
|
||||
'success': True,
|
||||
'action': 'bereits_aus',
|
||||
'previous_status': 'aus',
|
||||
'ip': plug_ip
|
||||
}
|
||||
success_count += 1
|
||||
|
||||
else:
|
||||
self.logger.warning(f"⚠️ {printer_name}: Unbekannter Status '{current_status}'")
|
||||
results[printer_name] = {
|
||||
'success': False,
|
||||
'reason': 'unbekannter_status',
|
||||
'status': current_status,
|
||||
'ip': plug_ip
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ {printer_name}: Fehler bei Initialisierung - {str(e)}")
|
||||
results[printer_name] = {
|
||||
'success': False,
|
||||
'reason': 'ausnahme',
|
||||
'error': str(e),
|
||||
'ip': plug_ip
|
||||
}
|
||||
|
||||
# Änderungen in der Datenbank speichern
|
||||
try:
|
||||
db_session.commit()
|
||||
self.logger.debug("💾 Datenbank-Änderungen gespeichert")
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Fehler beim Speichern der Datenbank-Änderungen: {str(e)}")
|
||||
db_session.rollback()
|
||||
|
||||
db_session.close()
|
||||
|
||||
# Zusammenfassung loggen
|
||||
self.logger.info("=" * 60)
|
||||
self.logger.info("🎯 STECKDOSEN-INITIALISIERUNG ABGESCHLOSSEN")
|
||||
self.logger.info(f"📊 Gesamt: {total_count} Steckdosen")
|
||||
self.logger.info(f"✅ Erfolgreich: {success_count}")
|
||||
self.logger.info(f"📡 Nicht erreichbar: {unreachable_count}")
|
||||
self.logger.info(f"❌ Fehlgeschlagen: {total_count - success_count - unreachable_count}")
|
||||
|
||||
if success_count == total_count:
|
||||
self.logger.info("🌟 ALLE Steckdosen erfolgreich initialisiert!")
|
||||
elif success_count > 0:
|
||||
self.logger.info(f"⚡ {success_count}/{total_count} Steckdosen erfolgreich initialisiert")
|
||||
else:
|
||||
self.logger.warning("⚠️ KEINE Steckdose konnte initialisiert werden!")
|
||||
|
||||
self.logger.info("=" * 60)
|
||||
|
||||
# Flag setzen um Mehrfach-Initialisierung zu verhindern
|
||||
self._outlets_initialized = True
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"❌ Kritischer Fehler bei Steckdosen-Initialisierung: {str(e)}")
|
||||
try:
|
||||
db_session.rollback()
|
||||
db_session.close()
|
||||
except:
|
||||
pass
|
||||
|
||||
return results
|
||||
|
||||
|
||||
# Scheduler-Instanz erzeugen
|
||||
scheduler = BackgroundTaskScheduler()
|
||||
|
Reference in New Issue
Block a user