🛠️ "Implementiere TP-Link Tapo P110 Unterstützung für Druckerüberwachung und -steuerung"

- Aktualisiere die `check_printer_status()` Funktion zur Verwendung des PyP100-Moduls für die Tapo-Steckdosen.
- Füge neue API-Endpunkte hinzu: `test-tapo` für die Verbindungstests einzelner Drucker und `test-all-tapo` für Massentests.
- Verbessere die Fehlerbehandlung und Logging für Tapo-Verbindungen.
- Aktualisiere die Benutzeroberfläche, um den Datei-Upload als optional zu kennzeichnen.
- Implementiere umfassende Tests für die Tapo-Verbindungen in `debug_drucker_erkennung.py` und verbessere die Validierung der Konfiguration in `job_scheduler.py`.
This commit is contained in:
2025-05-29 21:19:30 +02:00
parent da1d531c16
commit de1b87f833
8 changed files with 585 additions and 178 deletions

View File

@@ -13,11 +13,19 @@ from typing import Dict, Tuple, List, Optional
from flask import session
from sqlalchemy import func
from sqlalchemy.orm import Session
import os
from models import get_db_session, Printer
from utils.logging_config import get_logger
from config.settings import PRINTERS
# TP-Link Tapo P110 Unterstützung hinzufügen
try:
from PyP100 import PyP110
TAPO_AVAILABLE = True
except ImportError:
TAPO_AVAILABLE = False
# Logger initialisieren
monitor_logger = get_logger("printer_monitor")
@@ -110,57 +118,35 @@ class PrinterMonitor:
def _turn_outlet_off(self, ip_address: str, username: str, password: str, timeout: int = 5) -> bool:
"""
Schaltet eine Smart-Steckdose aus.
Schaltet eine TP-Link Tapo P110-Steckdose aus.
Args:
ip_address: IP-Adresse der Steckdose
username: Benutzername für die Steckdose
password: Passwort für die Steckdose
timeout: Timeout in Sekunden
timeout: Timeout in Sekunden (wird ignoriert, da PyP100 eigenes Timeout hat)
Returns:
bool: True wenn erfolgreich ausgeschaltet
"""
if not TAPO_AVAILABLE:
monitor_logger.error("⚠️ PyP100-Modul nicht verfügbar - kann Tapo-Steckdose nicht schalten")
return False
try:
# Für TP-Link Tapo und ähnliche Smart Plugs
auth = (username, password)
# TP-Link Tapo P110 Verbindung herstellen
p110 = PyP110.P110(ip_address, username, password)
p110.handshake() # Authentifizierung
p110.login() # Login
# Verschiedene API-Endpunkte versuchen
endpoints = [
f"http://{ip_address}/relay/off",
f"http://{ip_address}/api/relay/off",
f"http://{ip_address}/set?relay=off",
f"http://{ip_address}/control?action=off"
]
# Steckdose ausschalten
p110.turnOff()
monitor_logger.debug(f"✅ Tapo-Steckdose {ip_address} erfolgreich ausgeschaltet")
return True
for endpoint in endpoints:
try:
response = requests.post(endpoint, auth=auth, timeout=timeout)
if response.status_code in [200, 201, 204]:
monitor_logger.debug(f"✅ Steckdose {ip_address} ausgeschaltet via {endpoint}")
return True
except requests.RequestException:
continue
# Wenn spezifische Endpunkte fehlschlagen, versuche generische JSON-API
try:
payload = {"system": {"set_relay_state": {"state": 0}}}
response = requests.post(
f"http://{ip_address}/api",
json=payload,
auth=auth,
timeout=timeout
)
if response.status_code in [200, 201]:
monitor_logger.debug(f"✅ Steckdose {ip_address} ausgeschaltet via JSON-API")
return True
except requests.RequestException:
pass
except Exception as e:
monitor_logger.debug(f"⚠️ Fehler beim Ausschalten der Steckdose {ip_address}: {str(e)}")
return False
monitor_logger.debug(f"⚠️ Fehler beim Ausschalten der Tapo-Steckdose {ip_address}: {str(e)}")
return False
def get_live_printer_status(self, use_session_cache: bool = True) -> Dict[int, Dict]:
"""
@@ -364,7 +350,6 @@ class PrinterMonitor:
ipaddress.ip_address(ip_address.strip())
# Platform-spezifische Ping-Befehle
import os
if os.name == 'nt': # Windows
cmd = ['ping', '-n', '1', '-w', str(timeout * 1000), ip_address.strip()]
else: # Unix/Linux/macOS
@@ -386,79 +371,40 @@ class PrinterMonitor:
def _check_outlet_status(self, ip_address: str, username: str, password: str, timeout: int = 5) -> Tuple[bool, str]:
"""
Überprüft den Status einer Smart-Steckdose.
Überprüft den Status einer TP-Link Tapo P110-Steckdose.
Args:
ip_address: IP-Adresse der Steckdose
username: Benutzername für die Steckdose
password: Passwort für die Steckdose
timeout: Timeout in Sekunden
timeout: Timeout in Sekunden (wird ignoriert, da PyP100 eigenes Timeout hat)
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")
return False, "unknown"
try:
auth = (username, password)
# TP-Link Tapo P110 Verbindung herstellen
p110 = PyP110.P110(ip_address, username, password)
p110.handshake() # Authentifizierung
p110.login() # Login
# Verschiedene API-Endpunkte für Status-Abfrage versuchen
status_endpoints = [
f"http://{ip_address}/status",
f"http://{ip_address}/api/status",
f"http://{ip_address}/relay/status",
f"http://{ip_address}/state"
]
# Geräteinformationen abrufen
device_info = p110.getDeviceInfo()
for endpoint in status_endpoints:
try:
response = requests.get(endpoint, auth=auth, timeout=timeout)
if response.status_code == 200:
try:
data = response.json()
# Verschiedene JSON-Strukturen handhaben
if isinstance(data, dict):
# TP-Link Format
if 'system' in data and 'get_sysinfo' in data['system']:
relay_state = data['system']['get_sysinfo'].get('relay_state')
return True, "on" if relay_state == 1 else "off"
# Direktes Format
if 'relay_state' in data:
return True, "on" if data['relay_state'] in [1, True, "on"] else "off"
if 'state' in data:
return True, "on" if data['state'] in [1, True, "on"] else "off"
if 'power' in data:
return True, "on" if data['power'] in [1, True, "on"] else "off"
# Wenn JSON-Parsing funktioniert, aber Format unbekannt
return True, "unknown"
except ValueError:
# Nicht-JSON Antwort - versuche Text-Parsing
text = response.text.lower()
if "on" in text or "1" in text or "true" in text:
return True, "on"
elif "off" in text or "0" in text or "false" in text:
return True, "off"
return True, "unknown"
except requests.RequestException:
continue
# Status auswerten
device_on = device_info.get('device_on', False)
status = "on" if device_on else "off"
monitor_logger.debug(f"✅ Tapo-Steckdose {ip_address}: Status = {status}")
return True, status
# Wenn spezifische Endpunkte fehlschlagen, einfache Konnektivitätsprüfung
try:
response = requests.get(f"http://{ip_address}", auth=auth, timeout=timeout)
if response.status_code in [200, 401, 403]: # Erreichbar, aber möglicherweise Authentifizierungsproblem
return True, "unknown"
except requests.RequestException:
pass
except Exception as e:
monitor_logger.debug(f"⚠️ Fehler bei Steckdosen-Status-Check {ip_address}: {str(e)}")
return False, "unknown"
monitor_logger.debug(f"⚠️ Fehler bei Tapo-Steckdosen-Status-Check {ip_address}: {str(e)}")
return False, "unknown"
def clear_all_caches(self):
"""Löscht alle Caches (Session und DB)."""