"Update file structure and tests for job scheduler integration"
This commit is contained in:
parent
482e04723d
commit
c7aa7e75b0
@ -1283,76 +1283,85 @@ def kiosk_restart_system():
|
|||||||
@measure_execution_time(logger=printers_logger, task_name="Drucker-Status-Prüfung")
|
@measure_execution_time(logger=printers_logger, task_name="Drucker-Status-Prüfung")
|
||||||
def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
|
def check_printer_status(ip_address: str, timeout: int = 7) -> Tuple[str, bool]:
|
||||||
"""
|
"""
|
||||||
Überprüft den Status eines Druckers über TP-Link Tapo P110-Steckdosenabfrage.
|
Überprüft den Status eines Druckers anhand der IP-Adresse.
|
||||||
|
Gibt den Status und die Erreichbarkeit zurück.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ip_address: IP-Adresse der Drucker-Steckdose
|
ip_address: IP-Adresse des Druckers oder der Steckdose
|
||||||
timeout: Timeout in Sekunden (Standard: 7)
|
timeout: Timeout in Sekunden
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
Tuple[str, bool]: (Status, Aktiv) - Status ist "online" oder "offline", Aktiv ist True/False
|
Tuple[str, bool]: (Status, Erreichbarkeit)
|
||||||
"""
|
"""
|
||||||
if not ip_address or ip_address.strip() == "":
|
status = "offline"
|
||||||
printers_logger.debug(f"Keine IP-Adresse angegeben")
|
reachable = False
|
||||||
return "offline", False
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# TCP-Verbindung zu Port 80 oder 443 testen (statt Ping)
|
# Überprüfen, ob die Steckdose online ist
|
||||||
try:
|
import socket
|
||||||
import socket
|
|
||||||
# Zuerst Port 80 versuchen
|
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
sock.settimeout(timeout)
|
|
||||||
result = sock.connect_ex((ip_address.strip(), 80))
|
|
||||||
sock.close()
|
|
||||||
|
|
||||||
connection_ok = result == 0
|
# Erst Port 9999 versuchen (Tapo-Standard)
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
result = sock.connect_ex((ip_address, 9999))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
# Falls Port 80 nicht erfolgreich, Port 443 testen
|
if result == 0:
|
||||||
if not connection_ok:
|
reachable = True
|
||||||
|
try:
|
||||||
|
# TP-Link Tapo Steckdose mit PyP100 überprüfen
|
||||||
|
from PyP100 import PyP100
|
||||||
|
p100 = PyP100.P100(ip_address, TAPO_USERNAME, TAPO_PASSWORD)
|
||||||
|
p100.handshake() # Authentifizierung
|
||||||
|
p100.login() # Login
|
||||||
|
|
||||||
|
# Geräteinformationen abrufen
|
||||||
|
device_info = p100.getDeviceInfo()
|
||||||
|
|
||||||
|
# Status auswerten
|
||||||
|
if device_info.get('device_on', False):
|
||||||
|
status = "online"
|
||||||
|
else:
|
||||||
|
status = "standby"
|
||||||
|
|
||||||
|
printers_logger.info(f"✅ Tapo-Steckdose {ip_address}: Status = {status}")
|
||||||
|
except Exception as e:
|
||||||
|
printers_logger.error(f"❌ Fehler bei Tapo-Status-Check für {ip_address}: {str(e)}")
|
||||||
|
reachable = False
|
||||||
|
status = "error"
|
||||||
|
else:
|
||||||
|
# Alternativ HTTP/HTTPS versuchen
|
||||||
|
try:
|
||||||
|
# HTTP auf Port 80
|
||||||
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
sock.settimeout(timeout)
|
sock.settimeout(timeout)
|
||||||
result = sock.connect_ex((ip_address.strip(), 443))
|
result = sock.connect_ex((ip_address, 80))
|
||||||
sock.close()
|
sock.close()
|
||||||
connection_ok = result == 0
|
|
||||||
except:
|
|
||||||
connection_ok = False
|
|
||||||
|
|
||||||
if not connection_ok:
|
if result == 0:
|
||||||
printers_logger.debug(f"Keine Verbindung zu {ip_address} möglich")
|
reachable = True
|
||||||
return "offline", False
|
status = "online" # Standarddrucker ohne Tapo-Steckdose
|
||||||
|
else:
|
||||||
|
# HTTPS auf Port 443
|
||||||
|
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
sock.settimeout(timeout)
|
||||||
|
result = sock.connect_ex((ip_address, 443))
|
||||||
|
sock.close()
|
||||||
|
|
||||||
# TP-Link Tapo P100-Verbindung aufbauen
|
if result == 0:
|
||||||
try:
|
reachable = True
|
||||||
# Passwort aus config/settings.py (mit 'A' am Ende)
|
status = "online"
|
||||||
from config.settings import TAPO_USERNAME, TAPO_PASSWORD
|
except Exception as e:
|
||||||
|
printers_logger.error(f"❌ Fehler bei Socket-Check für {ip_address}: {str(e)}")
|
||||||
# Verbindung aufbauen
|
reachable = False
|
||||||
from PyP100 import PyP100
|
status = "error"
|
||||||
p100 = PyP100.P100(ip_address, TAPO_USERNAME, TAPO_PASSWORD)
|
|
||||||
p100.handshake() # Authentifizierung
|
|
||||||
p100.login() # Login
|
|
||||||
|
|
||||||
# Geräteinformationen abrufen
|
|
||||||
device_info = p100.getDeviceInfo()
|
|
||||||
|
|
||||||
# Status auswerten
|
|
||||||
device_on = device_info.get('device_on', False)
|
|
||||||
|
|
||||||
if device_on:
|
|
||||||
printers_logger.debug(f"Steckdose {ip_address} ist eingeschaltet")
|
|
||||||
return "online", True
|
|
||||||
else:
|
|
||||||
printers_logger.debug(f"Steckdose {ip_address} ist ausgeschaltet")
|
|
||||||
return "offline", False
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
printers_logger.debug(f"Fehler bei Tapo-Verbindung zu {ip_address}: {str(e)}")
|
|
||||||
return "offline", False
|
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
printers_logger.error(f"Fehler bei Status-Check für {ip_address}: {str(e)}")
|
printers_logger.error(f"❌ Fehler bei Verbindungsprüfung zu {ip_address}: {str(e)}")
|
||||||
return "offline", False
|
status = "error"
|
||||||
|
reachable = False
|
||||||
|
|
||||||
|
return status, reachable
|
||||||
|
|
||||||
@measure_execution_time(logger=printers_logger, task_name="Mehrere-Drucker-Status-Prüfung")
|
@measure_execution_time(logger=printers_logger, task_name="Mehrere-Drucker-Status-Prüfung")
|
||||||
def check_multiple_printers_status(printers: List[Dict], timeout: int = 7) -> Dict[int, Tuple[str, bool]]:
|
def check_multiple_printers_status(printers: List[Dict], timeout: int = 7) -> Dict[int, Tuple[str, bool]]:
|
||||||
@ -4541,7 +4550,7 @@ def upload_asset():
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Datei speichern
|
# Datei speichern
|
||||||
result = file_manager.save_file(file, 'assets', current_user.id, 'asset', metadata)
|
result = save_asset_file(file, current_user.id, metadata)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
relative_path, absolute_path, file_metadata = result
|
relative_path, absolute_path, file_metadata = result
|
||||||
@ -4593,7 +4602,7 @@ def upload_log():
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Datei speichern
|
# Datei speichern
|
||||||
result = file_manager.save_file(file, 'logs', current_user.id, 'log', metadata)
|
result = save_log_file(file, current_user.id, metadata)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
relative_path, absolute_path, file_metadata = result
|
relative_path, absolute_path, file_metadata = result
|
||||||
@ -4645,7 +4654,7 @@ def upload_backup():
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Datei speichern
|
# Datei speichern
|
||||||
result = file_manager.save_file(file, 'backups', current_user.id, 'backup', metadata)
|
result = save_backup_file(file, current_user.id, metadata)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
relative_path, absolute_path, file_metadata = result
|
relative_path, absolute_path, file_metadata = result
|
||||||
@ -4696,7 +4705,7 @@ def upload_temp_file():
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Datei speichern
|
# Datei speichern
|
||||||
result = file_manager.save_file(file, 'temp', current_user.id, 'temp', metadata)
|
result = save_temp_file(file, current_user.id, metadata)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
relative_path, absolute_path, file_metadata = result
|
relative_path, absolute_path, file_metadata = result
|
||||||
|
Binary file not shown.
@ -517,7 +517,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="mt-4 pt-4 border-t border-green-200 dark:border-green-800">
|
<div class="mt-4 pt-4 border-t border-green-200 dark:border-green-800">
|
||||||
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
<div class="text-xs text-mercedes-gray dark:text-slate-400">
|
||||||
<span class="font-medium" id="online-percentage">0%</span> aller Drucker
|
<span class="font-medium" id="online-percentage">0%</span> % aller Drucker
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@ from datetime import datetime
|
|||||||
|
|
||||||
# Anmeldedaten für Tapo-Steckdosen
|
# Anmeldedaten für Tapo-Steckdosen
|
||||||
TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
|
TAPO_USERNAME = "till.tomczak@mercedes-benz.com"
|
||||||
TAPO_PASSWORD = "744563017196"
|
TAPO_PASSWORD = "744563017196A"
|
||||||
|
|
||||||
# Standard-IPs für Tapo-Steckdosen
|
# Standard-IPs für Tapo-Steckdosen
|
||||||
# (falls nicht verfügbar, passen Sie diese an die tatsächlichen IPs in Ihrem Netzwerk an)
|
# (falls nicht verfügbar, passen Sie diese an die tatsächlichen IPs in Ihrem Netzwerk an)
|
||||||
|
@ -389,6 +389,22 @@ def save_avatar_file(file, user_id: int) -> Optional[Tuple[str, str, Dict]]:
|
|||||||
"""Speichert eine Avatar-Datei"""
|
"""Speichert eine Avatar-Datei"""
|
||||||
return file_manager.save_file(file, 'avatars', user_id, 'avatar')
|
return file_manager.save_file(file, 'avatars', user_id, 'avatar')
|
||||||
|
|
||||||
|
def save_asset_file(file, user_id: int, metadata: Dict = None) -> Optional[Tuple[str, str, Dict]]:
|
||||||
|
"""Speichert eine Asset-Datei"""
|
||||||
|
return file_manager.save_file(file, 'assets', user_id, 'asset', metadata)
|
||||||
|
|
||||||
|
def save_log_file(file, user_id: int, metadata: Dict = None) -> Optional[Tuple[str, str, Dict]]:
|
||||||
|
"""Speichert eine Log-Datei"""
|
||||||
|
return file_manager.save_file(file, 'logs', user_id, 'log', metadata)
|
||||||
|
|
||||||
|
def save_backup_file(file, user_id: int, metadata: Dict = None) -> Optional[Tuple[str, str, Dict]]:
|
||||||
|
"""Speichert eine Backup-Datei"""
|
||||||
|
return file_manager.save_file(file, 'backups', user_id, 'backup', metadata)
|
||||||
|
|
||||||
|
def save_temp_file(file, user_id: int, metadata: Dict = None) -> Optional[Tuple[str, str, Dict]]:
|
||||||
|
"""Speichert eine temporäre Datei"""
|
||||||
|
return file_manager.save_file(file, 'temp', user_id, 'temp', metadata)
|
||||||
|
|
||||||
def delete_file(relative_path: str) -> bool:
|
def delete_file(relative_path: str) -> bool:
|
||||||
"""Löscht eine Datei"""
|
"""Löscht eine Datei"""
|
||||||
return file_manager.delete_file(relative_path)
|
return file_manager.delete_file(relative_path)
|
||||||
|
@ -33,6 +33,7 @@ class BackgroundTaskScheduler:
|
|||||||
self._stop_event = threading.Event()
|
self._stop_event = threading.Event()
|
||||||
self._running = False
|
self._running = False
|
||||||
self._start_time: Optional[datetime] = None
|
self._start_time: Optional[datetime] = None
|
||||||
|
self.logger = get_scheduler_logger()
|
||||||
|
|
||||||
def register_task(self,
|
def register_task(self,
|
||||||
task_id: str,
|
task_id: str,
|
||||||
@ -55,9 +56,8 @@ class BackgroundTaskScheduler:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True wenn erfolgreich, False wenn die ID bereits existiert
|
bool: True wenn erfolgreich, False wenn die ID bereits existiert
|
||||||
"""
|
"""
|
||||||
logger = get_scheduler_logger()
|
|
||||||
if task_id in self._tasks:
|
if task_id in self._tasks:
|
||||||
logger.error(f"Task mit ID {task_id} existiert bereits")
|
self.logger.error(f"Task mit ID {task_id} existiert bereits")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._tasks[task_id] = {
|
self._tasks[task_id] = {
|
||||||
@ -70,7 +70,7 @@ class BackgroundTaskScheduler:
|
|||||||
"next_run": datetime.now() if enabled else None
|
"next_run": datetime.now() if enabled else None
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.info(f"Task {task_id} registriert: Intervall {interval}s, Enabled: {enabled}")
|
self.logger.info(f"Task {task_id} registriert: Intervall {interval}s, Enabled: {enabled}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def update_task(self,
|
def update_task(self,
|
||||||
@ -92,9 +92,8 @@ class BackgroundTaskScheduler:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True wenn erfolgreich, False wenn die ID nicht existiert
|
bool: True wenn erfolgreich, False wenn die ID nicht existiert
|
||||||
"""
|
"""
|
||||||
logger = get_scheduler_logger()
|
|
||||||
if task_id not in self._tasks:
|
if task_id not in self._tasks:
|
||||||
logger.error(f"Task mit ID {task_id} existiert nicht")
|
self.logger.error(f"Task mit ID {task_id} existiert nicht")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
task = self._tasks[task_id]
|
task = self._tasks[task_id]
|
||||||
@ -115,7 +114,7 @@ class BackgroundTaskScheduler:
|
|||||||
else:
|
else:
|
||||||
task["next_run"] = None
|
task["next_run"] = None
|
||||||
|
|
||||||
logger.info(f"Task {task_id} aktualisiert: Intervall {task['interval']}s, Enabled: {task['enabled']}")
|
self.logger.info(f"Task {task_id} aktualisiert: Intervall {task['interval']}s, Enabled: {task['enabled']}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def remove_task(self, task_id: str) -> bool:
|
def remove_task(self, task_id: str) -> bool:
|
||||||
@ -128,13 +127,12 @@ class BackgroundTaskScheduler:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True wenn erfolgreich, False wenn die ID nicht existiert
|
bool: True wenn erfolgreich, False wenn die ID nicht existiert
|
||||||
"""
|
"""
|
||||||
logger = get_scheduler_logger()
|
|
||||||
if task_id not in self._tasks:
|
if task_id not in self._tasks:
|
||||||
logger.error(f"Task mit ID {task_id} existiert nicht")
|
self.logger.error(f"Task mit ID {task_id} existiert nicht")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
del self._tasks[task_id]
|
del self._tasks[task_id]
|
||||||
logger.info(f"Task {task_id} entfernt")
|
self.logger.info(f"Task {task_id} entfernt")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_task_info(self, task_id: Optional[str] = None) -> Union[Dict, List[Dict]]:
|
def get_task_info(self, task_id: Optional[str] = None) -> Union[Dict, List[Dict]]:
|
||||||
@ -217,9 +215,8 @@ class BackgroundTaskScheduler:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True wenn erfolgreich gestartet, False wenn bereits läuft
|
bool: True wenn erfolgreich gestartet, False wenn bereits läuft
|
||||||
"""
|
"""
|
||||||
logger = get_scheduler_logger()
|
|
||||||
if self._running:
|
if self._running:
|
||||||
logger.warning("Scheduler läuft bereits")
|
self.logger.warning("Scheduler läuft bereits")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._stop_event.clear()
|
self._stop_event.clear()
|
||||||
@ -229,7 +226,7 @@ class BackgroundTaskScheduler:
|
|||||||
self._running = True
|
self._running = True
|
||||||
self._start_time = datetime.now()
|
self._start_time = datetime.now()
|
||||||
|
|
||||||
logger.info("Scheduler gestartet")
|
self.logger.info("Scheduler gestartet")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def stop(self) -> bool:
|
def stop(self) -> bool:
|
||||||
@ -239,9 +236,8 @@ class BackgroundTaskScheduler:
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True wenn erfolgreich gestoppt, False wenn nicht läuft
|
bool: True wenn erfolgreich gestoppt, False wenn nicht läuft
|
||||||
"""
|
"""
|
||||||
logger = get_scheduler_logger()
|
|
||||||
if not self._running:
|
if not self._running:
|
||||||
logger.warning("Scheduler läuft nicht")
|
self.logger.warning("Scheduler läuft nicht")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self._stop_event.set()
|
self._stop_event.set()
|
||||||
@ -250,7 +246,7 @@ class BackgroundTaskScheduler:
|
|||||||
|
|
||||||
self._running = False
|
self._running = False
|
||||||
self._start_time = None
|
self._start_time = None
|
||||||
logger.info("Scheduler gestoppt")
|
self.logger.info("Scheduler gestoppt")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def is_running(self) -> bool:
|
def is_running(self) -> bool:
|
||||||
@ -264,8 +260,7 @@ class BackgroundTaskScheduler:
|
|||||||
|
|
||||||
def _run(self) -> None:
|
def _run(self) -> None:
|
||||||
"""Hauptloop des Schedulers."""
|
"""Hauptloop des Schedulers."""
|
||||||
logger = get_scheduler_logger()
|
self.logger.info("Scheduler-Thread gestartet")
|
||||||
logger.info("Scheduler-Thread gestartet")
|
|
||||||
|
|
||||||
while not self._stop_event.is_set():
|
while not self._stop_event.is_set():
|
||||||
now = datetime.now()
|
now = datetime.now()
|
||||||
@ -276,221 +271,249 @@ class BackgroundTaskScheduler:
|
|||||||
|
|
||||||
if now >= task["next_run"]:
|
if now >= task["next_run"]:
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Führe Task {task_id} aus")
|
self.logger.debug(f"Führe Task {task_id} aus")
|
||||||
task["func"](*task["args"], **task["kwargs"])
|
task["func"](*task["args"], **task["kwargs"])
|
||||||
task["last_run"] = now
|
task["last_run"] = now
|
||||||
task["next_run"] = now + timedelta(seconds=task["interval"])
|
task["next_run"] = now + timedelta(seconds=task["interval"])
|
||||||
logger.debug(f"Task {task_id} erfolgreich ausgeführt, nächste Ausführung: {task['next_run']}")
|
self.logger.debug(f"Task {task_id} erfolgreich ausgeführt, nächste Ausführung: {task['next_run']}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Fehler bei Ausführung von Task {task_id}: {str(e)}")
|
self.logger.error(f"Fehler bei Ausführung von Task {task_id}: {str(e)}")
|
||||||
# Trotzdem nächste Ausführung planen
|
# Trotzdem nächste Ausführung planen
|
||||||
task["next_run"] = now + timedelta(seconds=task["interval"])
|
task["next_run"] = now + timedelta(seconds=task["interval"])
|
||||||
|
|
||||||
# Schlafenszeit berechnen (1 Sekunde oder weniger)
|
# Schlafenszeit berechnen (1 Sekunde oder weniger)
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
|
|
||||||
logger.info("Scheduler-Thread beendet")
|
self.logger.info("Scheduler-Thread beendet")
|
||||||
|
|
||||||
|
def toggle_plug(self, ip: str, state: bool, username: str = None, password: str = None) -> bool:
|
||||||
|
"""
|
||||||
|
Schaltet eine TP-Link Tapo P100/P110-Steckdose ein oder aus.
|
||||||
|
|
||||||
def toggle_plug(printer_id: int, state: bool) -> bool:
|
Args:
|
||||||
"""
|
ip: IP-Adresse der Steckdose
|
||||||
Schaltet eine Tapo-Steckdose ein oder aus.
|
state: True = Ein, False = Aus
|
||||||
|
username: Benutzername für die Steckdose (optional)
|
||||||
|
password: Passwort für die Steckdose (optional)
|
||||||
|
|
||||||
Args:
|
Returns:
|
||||||
printer_id: ID des Druckers
|
bool: True wenn erfolgreich geschaltet
|
||||||
state: True für ein, False für aus
|
"""
|
||||||
|
|
||||||
Returns:
|
|
||||||
bool: True wenn erfolgreich, False wenn fehlgeschlagen
|
|
||||||
"""
|
|
||||||
logger = get_logger("printers")
|
|
||||||
db_session = get_db_session()
|
|
||||||
|
|
||||||
try:
|
|
||||||
printer = db_session.query(Printer).get(printer_id)
|
|
||||||
|
|
||||||
if not printer:
|
|
||||||
logger.error(f"Drucker mit ID {printer_id} nicht gefunden")
|
|
||||||
db_session.close()
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Konfiguration validieren
|
|
||||||
if not printer.plug_ip or not printer.plug_username or not printer.plug_password:
|
|
||||||
logger.error(f"Unvollständige Steckdosen-Konfiguration für Drucker {printer.name}")
|
|
||||||
db_session.close()
|
|
||||||
return False
|
|
||||||
|
|
||||||
# Importiere PyP100 für Tapo-Unterstützung
|
|
||||||
try:
|
try:
|
||||||
from PyP100 import PyP100
|
# PyP100 importieren
|
||||||
except ImportError:
|
try:
|
||||||
logger.error("PyP100-Modul nicht verfügbar - kann Tapo-Steckdosen nicht steuern")
|
from PyP100 import PyP100
|
||||||
db_session.close()
|
except ImportError:
|
||||||
|
self.logger.error("❌ PyP100-Modul nicht installiert - Steckdose kann nicht geschaltet werden")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Anmeldedaten aus Einstellungen verwenden, falls nicht angegeben
|
||||||
|
if not username or not password:
|
||||||
|
from config.settings import TAPO_USERNAME, TAPO_PASSWORD
|
||||||
|
username = TAPO_USERNAME
|
||||||
|
password = TAPO_PASSWORD
|
||||||
|
self.logger.debug(f"🔧 Verwende globale Tapo-Anmeldedaten für {ip}")
|
||||||
|
|
||||||
|
# P100-Verbindung herstellen (P100 statt P110 verwenden)
|
||||||
|
p100 = PyP100.P100(ip, username, password)
|
||||||
|
|
||||||
|
# Handshake und Login durchführen
|
||||||
|
p100.handshake()
|
||||||
|
p100.login()
|
||||||
|
|
||||||
|
# Steckdose schalten
|
||||||
|
if state:
|
||||||
|
p100.turnOn()
|
||||||
|
self.logger.info(f"✅ Tapo-Steckdose {ip} erfolgreich eingeschaltet")
|
||||||
|
else:
|
||||||
|
p100.turnOff()
|
||||||
|
self.logger.info(f"✅ Tapo-Steckdose {ip} erfolgreich ausgeschaltet")
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
action = "ein" if state else "aus"
|
||||||
|
self.logger.error(f"❌ Fehler beim {action}schalten der Tapo-Steckdose {ip}: {str(e)}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Verwende die in der Datenbank gespeicherten Anmeldedaten
|
def toggle_printer_plug(self, printer_id: int, state: bool) -> bool:
|
||||||
# Fallback zu config/settings.py wenn nicht vorhanden
|
"""
|
||||||
username = printer.plug_username
|
Schaltet die Steckdose eines Druckers ein oder aus.
|
||||||
password = printer.plug_password
|
|
||||||
|
|
||||||
if not username or not password:
|
Args:
|
||||||
from config.settings import TAPO_USERNAME, TAPO_PASSWORD
|
printer_id: ID des Druckers
|
||||||
username = TAPO_USERNAME
|
state: True für ein, False für aus
|
||||||
password = TAPO_PASSWORD
|
|
||||||
logger.debug(f"Verwende globale Tapo-Anmeldedaten für {printer.name}")
|
|
||||||
|
|
||||||
# TP-Link Tapo P100 Verbindung herstellen
|
Returns:
|
||||||
p100 = PyP100.P100(printer.plug_ip, username, password)
|
bool: True wenn erfolgreich, False wenn fehlgeschlagen
|
||||||
p100.handshake() # Authentifizierung
|
"""
|
||||||
p100.login() # Login
|
|
||||||
|
|
||||||
# Steckdose schalten
|
|
||||||
if state:
|
|
||||||
p100.turnOn()
|
|
||||||
logger.info(f"Steckdose für {printer.name} eingeschaltet")
|
|
||||||
else:
|
|
||||||
p100.turnOff()
|
|
||||||
logger.info(f"Steckdose für {printer.name} ausgeschaltet")
|
|
||||||
|
|
||||||
# Status in Datenbank aktualisieren
|
|
||||||
printer.status = "online" if state else "offline"
|
|
||||||
printer.last_checked = datetime.now()
|
|
||||||
db_session.commit()
|
|
||||||
db_session.close()
|
|
||||||
|
|
||||||
return True
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Fehler beim Schalten der Steckdose für Drucker {printer_id}: {str(e)}")
|
|
||||||
try:
|
try:
|
||||||
|
# Drucker aus Datenbank holen
|
||||||
|
db_session = get_db_session()
|
||||||
|
printer = db_session.query(Printer).get(printer_id)
|
||||||
|
|
||||||
|
if not printer:
|
||||||
|
self.logger.error(f"❌ Drucker mit ID {printer_id} nicht gefunden")
|
||||||
|
db_session.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Konfiguration validieren
|
||||||
|
if not printer.plug_ip:
|
||||||
|
self.logger.error(f"❌ Unvollständige Steckdosen-Konfiguration für Drucker {printer.name}")
|
||||||
|
db_session.close()
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Steckdose schalten
|
||||||
|
success = self.toggle_plug(
|
||||||
|
ip=printer.plug_ip,
|
||||||
|
state=state,
|
||||||
|
username=printer.plug_username,
|
||||||
|
password=printer.plug_password
|
||||||
|
)
|
||||||
|
|
||||||
|
if success:
|
||||||
|
# Status in Datenbank aktualisieren
|
||||||
|
printer.status = "online" if state else "offline"
|
||||||
|
printer.last_checked = datetime.now()
|
||||||
|
db_session.commit()
|
||||||
|
self.logger.info(f"✅ Status für Drucker {printer.name} aktualisiert: {'online' if state else 'offline'}")
|
||||||
|
|
||||||
|
db_session.close()
|
||||||
|
return success
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
action = "ein" if state else "aus"
|
||||||
|
self.logger.error(f"❌ Fehler beim {action}schalten der Steckdose für Drucker {printer_id}: {str(e)}")
|
||||||
|
try:
|
||||||
|
db_session.close()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _check_jobs(self) -> None:
|
||||||
|
"""
|
||||||
|
Überprüft und verwaltet Druckjobs:
|
||||||
|
- Startet anstehende Jobs
|
||||||
|
- Beendet abgelaufene Jobs
|
||||||
|
"""
|
||||||
|
db_session = get_db_session()
|
||||||
|
|
||||||
|
try:
|
||||||
|
now = datetime.now()
|
||||||
|
|
||||||
|
# 1. Anstehende Jobs starten
|
||||||
|
pending_jobs = db_session.query(Job).filter(
|
||||||
|
Job.status == "scheduled",
|
||||||
|
Job.start_at <= now
|
||||||
|
).all()
|
||||||
|
|
||||||
|
for job in pending_jobs:
|
||||||
|
self.logger.info(f"🚀 Starte Job {job.id}: {job.name}")
|
||||||
|
|
||||||
|
# Steckdose einschalten
|
||||||
|
if self.toggle_printer_plug(job.printer_id, True):
|
||||||
|
# Job als laufend markieren
|
||||||
|
job.status = "running"
|
||||||
|
db_session.commit()
|
||||||
|
self.logger.info(f"✅ Job {job.id} gestartet")
|
||||||
|
else:
|
||||||
|
self.logger.error(f"❌ Konnte Steckdose für Job {job.id} nicht einschalten")
|
||||||
|
|
||||||
|
# 2. Abgelaufene Jobs beenden
|
||||||
|
running_jobs = db_session.query(Job).filter(
|
||||||
|
Job.status == "running",
|
||||||
|
Job.end_at <= now
|
||||||
|
).all()
|
||||||
|
|
||||||
|
for job in running_jobs:
|
||||||
|
self.logger.info(f"🏁 Beende Job {job.id}: {job.name}")
|
||||||
|
|
||||||
|
# Steckdose ausschalten
|
||||||
|
if self.toggle_printer_plug(job.printer_id, False):
|
||||||
|
# Job als beendet markieren
|
||||||
|
job.status = "finished"
|
||||||
|
job.actual_end_time = now
|
||||||
|
db_session.commit()
|
||||||
|
self.logger.info(f"✅ Job {job.id} beendet")
|
||||||
|
else:
|
||||||
|
self.logger.error(f"❌ Konnte Steckdose für Job {job.id} nicht ausschalten")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
self.logger.error(f"❌ Fehler bei Überprüfung der Jobs: {str(e)}")
|
||||||
|
try:
|
||||||
|
db_session.rollback()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
finally:
|
||||||
db_session.close()
|
db_session.close()
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def test_tapo_connection(ip_address: str, username: str = None, password: str = None) -> dict:
|
def test_tapo_connection(ip_address: str, username: str = None, password: str = None) -> dict:
|
||||||
"""
|
"""
|
||||||
Testet die Verbindung zu einer Tapo-Steckdose und gibt detaillierte Informationen zurück.
|
Testet die Verbindung zu einer TP-Link Tapo P110-Steckdose.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
ip_address: IP-Adresse der Steckdose
|
ip_address: IP-Adresse der Steckdose
|
||||||
username: Benutzername (optional, verwendet globale Konfiguration als Fallback)
|
username: Benutzername für die Steckdose (optional)
|
||||||
password: Passwort (optional, verwendet globale Konfiguration als Fallback)
|
password: Passwort für die Steckdose (optional)
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
dict: Testergebnis mit Status und Informationen
|
dict: Ergebnis mit Status und Informationen
|
||||||
"""
|
"""
|
||||||
logger = get_logger("printers")
|
logger = get_logger("tapo")
|
||||||
result = {
|
result = {
|
||||||
"success": False,
|
"success": False,
|
||||||
"error": None,
|
"message": "",
|
||||||
"device_info": None,
|
"device_info": None,
|
||||||
"status": "unknown"
|
"error": None
|
||||||
}
|
}
|
||||||
|
|
||||||
# Fallback zu globalen Anmeldedaten
|
|
||||||
if not username or not password:
|
|
||||||
username = TAPO_USERNAME
|
|
||||||
password = TAPO_PASSWORD
|
|
||||||
logger.debug(f"🔧 Verwende globale Tapo-Anmeldedaten für {ip_address}")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
logger.debug(f"Teste Tapo-Verbindung zu {ip_address}")
|
# Importiere PyP100 für Tapo-Unterstützung
|
||||||
p110 = PyP110.P110(ip_address, username, password)
|
try:
|
||||||
p110.handshake() # Authentifizierung
|
from PyP100 import PyP100
|
||||||
p110.login() # Login
|
except ImportError:
|
||||||
|
result["message"] = "PyP100-Modul nicht verfügbar"
|
||||||
|
result["error"] = "ModuleNotFound"
|
||||||
|
logger.error("PyP100-Modul nicht verfügbar - kann Tapo-Steckdosen nicht testen")
|
||||||
|
return result
|
||||||
|
|
||||||
|
# Verwende globale Anmeldedaten falls nicht angegeben
|
||||||
|
if not username or not password:
|
||||||
|
from config.settings import TAPO_USERNAME, TAPO_PASSWORD
|
||||||
|
username = TAPO_USERNAME
|
||||||
|
password = TAPO_PASSWORD
|
||||||
|
logger.debug(f"Verwende globale Tapo-Anmeldedaten für {ip_address}")
|
||||||
|
|
||||||
|
# TP-Link Tapo P100 Verbindung herstellen
|
||||||
|
p100 = PyP100.P100(ip_address, username, password)
|
||||||
|
p100.handshake() # Authentifizierung
|
||||||
|
p100.login() # Login
|
||||||
|
|
||||||
# Geräteinformationen abrufen
|
# Geräteinformationen abrufen
|
||||||
device_info = p110.getDeviceInfo()
|
device_info = p100.getDeviceInfo()
|
||||||
result["device_info"] = device_info
|
|
||||||
result["status"] = "on" if device_info.get('device_on', False) else "off"
|
|
||||||
result["success"] = True
|
|
||||||
|
|
||||||
logger.debug(f"✅ Tapo-Verbindung zu {ip_address} erfolgreich")
|
result["success"] = True
|
||||||
|
result["message"] = "Verbindung erfolgreich"
|
||||||
|
result["device_info"] = device_info
|
||||||
|
|
||||||
|
logger.info(f"Tapo-Verbindung zu {ip_address} erfolgreich: {device_info.get('nickname', 'Unbekannt')}")
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
result["success"] = False
|
||||||
|
result["message"] = f"Verbindungsfehler: {str(e)}"
|
||||||
result["error"] = str(e)
|
result["error"] = str(e)
|
||||||
logger.warning(f"❌ Tapo-Verbindung zu {ip_address} fehlgeschlagen: {str(e)}")
|
logger.error(f"Fehler bei Tapo-Test zu {ip_address}: {str(e)}")
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def check_jobs():
|
# Scheduler-Instanz erzeugen
|
||||||
"""
|
|
||||||
Überprüft alle geplanten und laufenden Jobs und schaltet Steckdosen entsprechend.
|
|
||||||
|
|
||||||
Diese Funktion wird vom Scheduler regelmäßig aufgerufen und:
|
|
||||||
1. Prüft, ob geplante Jobs gestartet werden müssen
|
|
||||||
2. Prüft, ob laufende Jobs beendet werden müssen
|
|
||||||
3. Aktualisiert den Status der Jobs
|
|
||||||
"""
|
|
||||||
logger = get_logger("jobs")
|
|
||||||
db_session = get_db_session()
|
|
||||||
|
|
||||||
try:
|
|
||||||
now = datetime.now()
|
|
||||||
|
|
||||||
# Geplante Jobs abrufen (mit 5 Minuten Puffer für vergangene Jobs)
|
|
||||||
scheduled_jobs = db_session.query(Job).options(
|
|
||||||
joinedload(Job.printer)
|
|
||||||
).filter(
|
|
||||||
Job.status == "scheduled",
|
|
||||||
Job.start_at <= now
|
|
||||||
).all()
|
|
||||||
|
|
||||||
# Laufende Jobs abrufen (mit 5 Minuten Sicherheitspuffer)
|
|
||||||
running_jobs = db_session.query(Job).options(
|
|
||||||
joinedload(Job.printer)
|
|
||||||
).filter(
|
|
||||||
Job.status == "running",
|
|
||||||
Job.end_at <= now - timedelta(minutes=5) # 5 Minuten Sicherheitspuffer
|
|
||||||
).all()
|
|
||||||
|
|
||||||
# Geplante Jobs starten
|
|
||||||
for job in scheduled_jobs:
|
|
||||||
logger.info(f"Starte geplanten Job {job.id}: {job.name} für Drucker {job.printer.name}")
|
|
||||||
|
|
||||||
# Steckdose einschalten
|
|
||||||
if toggle_plug(job.printer_id, True):
|
|
||||||
# Job als laufend markieren
|
|
||||||
job.status = "running"
|
|
||||||
job.end_at = job.start_at + timedelta(minutes=job.duration_minutes)
|
|
||||||
db_session.commit()
|
|
||||||
logger.info(f"Job {job.id} gestartet: läuft bis {job.end_at}")
|
|
||||||
else:
|
|
||||||
logger.error(f"Fehler beim Starten von Job {job.id}: Steckdose konnte nicht eingeschaltet werden")
|
|
||||||
|
|
||||||
# Beendete Jobs stoppen
|
|
||||||
for job in running_jobs:
|
|
||||||
logger.info(f"Beende laufenden Job {job.id}: {job.name} für Drucker {job.printer.name}")
|
|
||||||
|
|
||||||
# Steckdose ausschalten
|
|
||||||
if toggle_plug(job.printer_id, False):
|
|
||||||
# Job als beendet markieren
|
|
||||||
job.status = "finished"
|
|
||||||
job.actual_end_time = now
|
|
||||||
db_session.commit()
|
|
||||||
logger.info(f"Job {job.id} beendet: tatsächliche Endzeit {job.actual_end_time}")
|
|
||||||
else:
|
|
||||||
logger.error(f"Fehler beim Beenden von Job {job.id}: Steckdose konnte nicht ausgeschaltet werden")
|
|
||||||
|
|
||||||
db_session.close()
|
|
||||||
except Exception as e:
|
|
||||||
logger.error(f"Fehler im Job-Scheduler: {str(e)}")
|
|
||||||
db_session.close()
|
|
||||||
|
|
||||||
|
|
||||||
# Globaler Scheduler
|
|
||||||
scheduler = BackgroundTaskScheduler()
|
scheduler = BackgroundTaskScheduler()
|
||||||
|
|
||||||
# Job-Überprüfungs-Task registrieren (alle 60 Sekunden)
|
# Standardaufgaben registrieren
|
||||||
scheduler.register_task(
|
scheduler.register_task("check_jobs", scheduler._check_jobs, interval=60)
|
||||||
task_id="check_jobs",
|
|
||||||
func=check_jobs,
|
|
||||||
interval=60,
|
|
||||||
enabled=True
|
|
||||||
)
|
|
||||||
|
|
||||||
# Alias für Kompatibilität
|
# Alias für Kompatibilität
|
||||||
JobScheduler = BackgroundTaskScheduler
|
JobScheduler = BackgroundTaskScheduler
|
||||||
|
Loading…
x
Reference in New Issue
Block a user