🎉 Improved documentation and code organization in Backend 🌐
This commit is contained in:
Binary file not shown.
136
backend/app.py
136
backend/app.py
@ -943,85 +943,26 @@ def api_get_printers():
|
||||
@app.route("/api/printers/status", methods=["GET"])
|
||||
@login_required
|
||||
def api_get_printer_status():
|
||||
"""API-Endpunkt für Drucker-Status"""
|
||||
"""API-Endpunkt für Drucker-Status mit verbessertem Status-Management"""
|
||||
try:
|
||||
from models import get_db_session, Printer
|
||||
# Verwende den neuen TapoStatusManager
|
||||
from utils.tapo_status_manager import tapo_status_manager
|
||||
|
||||
db_session = get_db_session()
|
||||
# Alle Drucker für Status-Abfragen anzeigen (unabhängig von active-Status)
|
||||
printers = db_session.query(Printer).all()
|
||||
# Status für alle Drucker abrufen
|
||||
status_list = tapo_status_manager.get_all_printer_status()
|
||||
|
||||
status_list = []
|
||||
|
||||
# Tapo-Controller nur importieren, wenn benötigt
|
||||
tapo_controller = None
|
||||
has_tapo_printers = any(printer.plug_ip for printer in printers)
|
||||
|
||||
if has_tapo_printers:
|
||||
try:
|
||||
from utils.hardware_integration import tapo_controller
|
||||
app_logger.info(f"✅ Tapo-Controller erfolgreich importiert: {type(tapo_controller)}")
|
||||
except Exception as import_error:
|
||||
app_logger.warning(f"⚠️ Tapo-Controller konnte nicht importiert werden: {str(import_error)}")
|
||||
tapo_controller = None
|
||||
|
||||
for printer in printers:
|
||||
# Basis-Status-Informationen
|
||||
status_dict = {
|
||||
"id": printer.id,
|
||||
"name": printer.name,
|
||||
"status": printer.status or "offline",
|
||||
"location": printer.location,
|
||||
"model": printer.model,
|
||||
"ip_address": printer.ip_address,
|
||||
"active": getattr(printer, 'active', True)
|
||||
}
|
||||
|
||||
# Tapo-Steckdosen-Status prüfen, wenn verfügbar
|
||||
if printer.plug_ip:
|
||||
if tapo_controller:
|
||||
try:
|
||||
reachable, plug_status = tapo_controller.check_outlet_status(
|
||||
printer.plug_ip,
|
||||
printer_id=printer.id
|
||||
)
|
||||
|
||||
status_dict.update({
|
||||
"plug_status": plug_status,
|
||||
"plug_reachable": reachable,
|
||||
"plug_ip": printer.plug_ip,
|
||||
"has_plug": True
|
||||
})
|
||||
except Exception as e:
|
||||
app_logger.warning(f"⚠️ Fehler bei Steckdosen-Status für {printer.name}: {str(e)}")
|
||||
status_dict.update({
|
||||
"plug_status": "error",
|
||||
"plug_reachable": False,
|
||||
"plug_ip": printer.plug_ip,
|
||||
"has_plug": True,
|
||||
"plug_error": str(e)
|
||||
})
|
||||
else:
|
||||
# Tapo-Controller nicht verfügbar
|
||||
status_dict.update({
|
||||
"plug_status": "unavailable",
|
||||
"plug_reachable": False,
|
||||
"plug_ip": printer.plug_ip,
|
||||
"has_plug": True,
|
||||
"plug_error": "Tapo-Controller nicht verfügbar"
|
||||
})
|
||||
# Erweitere Status mit UI-freundlichen Informationen
|
||||
for status in status_list:
|
||||
# Status-Display-Informationen hinzufügen
|
||||
plug_status = status.get("plug_status", "unknown")
|
||||
if plug_status in tapo_status_manager.STATUS_DISPLAY:
|
||||
status["status_display"] = tapo_status_manager.STATUS_DISPLAY[plug_status]
|
||||
else:
|
||||
# Kein Smart-Plug konfiguriert
|
||||
status_dict.update({
|
||||
"plug_status": "no_plug",
|
||||
"plug_reachable": False,
|
||||
"plug_ip": None,
|
||||
"has_plug": False
|
||||
})
|
||||
|
||||
status_list.append(status_dict)
|
||||
|
||||
db_session.close()
|
||||
status["status_display"] = {
|
||||
"text": "Unbekannt",
|
||||
"color": "gray",
|
||||
"icon": "question"
|
||||
}
|
||||
|
||||
app_logger.info(f"✅ API: Status für {len(status_list)} Drucker abgerufen")
|
||||
|
||||
@ -1035,13 +976,44 @@ def api_get_printer_status():
|
||||
|
||||
except Exception as e:
|
||||
app_logger.error(f"❌ API-Fehler beim Abrufen des Drucker-Status: {str(e)}", exc_info=True)
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Fehler beim Laden des Drucker-Status",
|
||||
"details": str(e),
|
||||
"printers": [],
|
||||
"count": 0
|
||||
}), 500
|
||||
|
||||
# Fallback: Mindestens die Drucker-Grunddaten zurückgeben
|
||||
try:
|
||||
from models import get_db_session, Printer
|
||||
db_session = get_db_session()
|
||||
printers = db_session.query(Printer).all()
|
||||
|
||||
basic_status = []
|
||||
for printer in printers:
|
||||
basic_status.append({
|
||||
"id": printer.id,
|
||||
"name": printer.name,
|
||||
"location": printer.location,
|
||||
"model": printer.model,
|
||||
"plug_status": "unreachable",
|
||||
"plug_reachable": False,
|
||||
"has_plug": bool(printer.plug_ip),
|
||||
"error": "Status-Manager nicht verfügbar"
|
||||
})
|
||||
|
||||
db_session.close()
|
||||
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Eingeschränkte Status-Informationen",
|
||||
"printers": basic_status,
|
||||
"count": len(basic_status),
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
except:
|
||||
return jsonify({
|
||||
"success": False,
|
||||
"error": "Fehler beim Laden des Drucker-Status",
|
||||
"details": str(e),
|
||||
"printers": [],
|
||||
"count": 0
|
||||
}), 500
|
||||
|
||||
@app.route("/api/health", methods=["GET"])
|
||||
def api_health_check():
|
||||
|
BIN
backend/backend/database/myp.db-shm
Normal file
BIN
backend/backend/database/myp.db-shm
Normal file
Binary file not shown.
BIN
backend/backend/database/myp.db-wal
Normal file
BIN
backend/backend/database/myp.db-wal
Normal file
Binary file not shown.
Binary file not shown.
@ -7,6 +7,7 @@ from sqlalchemy import and_, or_, func
|
||||
from models import Job, Printer, User, UserPermission, get_cached_session
|
||||
from utils.logging_config import get_logger
|
||||
from utils.job_queue_system import conflict_manager, ConflictType, ConflictSeverity
|
||||
from utils.tapo_status_manager import tapo_status_manager
|
||||
|
||||
calendar_blueprint = Blueprint('calendar', __name__)
|
||||
logger = get_logger("calendar")
|
||||
@ -241,6 +242,49 @@ def api_get_calendar_events():
|
||||
}
|
||||
}
|
||||
|
||||
# Für Admins: Erweiterte Steckdosen-Status-Informationen hinzufügen
|
||||
if current_user.is_admin:
|
||||
# Aktuellen Steckdosen-Status abrufen
|
||||
printer_status = tapo_status_manager.get_printer_status(job.printer_id)
|
||||
|
||||
event["extendedProps"].update({
|
||||
"plugStatus": printer_status.get("plug_status", "unknown"),
|
||||
"plugReachable": printer_status.get("plug_reachable", False),
|
||||
"hasPlug": printer_status.get("has_plug", False),
|
||||
"canControl": printer_status.get("can_control", False),
|
||||
"currentJob": printer_status.get("current_job"),
|
||||
"nextJob": printer_status.get("next_job")
|
||||
})
|
||||
|
||||
# Status-Display-Informationen hinzufügen
|
||||
plug_status = printer_status.get("plug_status", "unknown")
|
||||
if plug_status in tapo_status_manager.STATUS_DISPLAY:
|
||||
status_info = tapo_status_manager.STATUS_DISPLAY[plug_status]
|
||||
event["extendedProps"]["statusDisplay"] = {
|
||||
"text": status_info["text"],
|
||||
"color": status_info["color"],
|
||||
"icon": status_info["icon"]
|
||||
}
|
||||
|
||||
# Tooltip-Informationen hinzufügen
|
||||
tooltip_parts = [
|
||||
f"Job: {job.name}",
|
||||
f"Drucker: {printer_name}",
|
||||
f"Status: {job.status}",
|
||||
f"Benutzer: {user_name}"
|
||||
]
|
||||
|
||||
if printer_status.get("has_plug"):
|
||||
plug_status_text = event["extendedProps"].get("statusDisplay", {}).get("text", "Unbekannt")
|
||||
tooltip_parts.append(f"Steckdose: {plug_status_text}")
|
||||
|
||||
if printer_status.get("plug_reachable"):
|
||||
tooltip_parts.append("✓ Steckdose erreichbar")
|
||||
else:
|
||||
tooltip_parts.append("✗ Steckdose nicht erreichbar")
|
||||
|
||||
event["tooltip"] = "\n".join(tooltip_parts)
|
||||
|
||||
events.append(event)
|
||||
|
||||
# Steckdosen-Status-Events hinzufügen (falls gewünscht)
|
||||
|
1
backend/docs/tapo-integration-implementation.md
Normal file
1
backend/docs/tapo-integration-implementation.md
Normal file
@ -0,0 +1 @@
|
||||
|
@ -3605,3 +3605,34 @@ WHERE users.id = ?
|
||||
2025-06-12 09:43:36 - [app] app - [DEBUG] DEBUG - Request: GET /api/notifications
|
||||
2025-06-12 09:43:36 - [app] app - [DEBUG] DEBUG - Response: 200
|
||||
2025-06-12 09:43:36 - [app] app - [DEBUG] DEBUG - Response: 200
|
||||
2025-06-12 09:59:23 - [app] app - [WARNING] WARNING - DatabaseCleanupManager nicht verfügbar - Fallback auf Legacy-Cleanup
|
||||
2025-06-12 09:59:23 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: backend/database/myp.db
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [CONFIG] Erkannte Umgebung: development
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [CONFIG] Production-Modus: False
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [CONFIG] Verwende Development-Konfiguration
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [DEVELOPMENT] Aktiviere Development-Konfiguration
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ MYP Development Environment Konfiguration aktiviert
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Environment: Development/Testing
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Debug Mode: True
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ SQL Echo: True
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - SQLite für Raspberry Pi optimiert (reduzierte Cache-Größe, SD-Karten I/O)
|
||||
2025-06-12 09:59:24 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
|
||||
2025-06-12 09:59:25 - [app] app - [INFO] INFO - Not Found (404): http://localhost/api/auth/login
|
||||
2025-06-12 10:02:04 - [app] app - [WARNING] WARNING - DatabaseCleanupManager nicht verfügbar - Fallback auf Legacy-Cleanup
|
||||
2025-06-12 10:02:04 - [app] app - [INFO] INFO - Optimierte SQLite-Engine erstellt: backend/database/myp.db
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [CONFIG] Erkannte Umgebung: development
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [CONFIG] Production-Modus: False
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [CONFIG] Verwende Development-Konfiguration
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [DEVELOPMENT] Aktiviere Development-Konfiguration
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ MYP Development Environment Konfiguration aktiviert
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Environment: Development/Testing
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ Debug Mode: True
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - [DEVELOPMENT] ✅ SQL Echo: True
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - SQLite für Raspberry Pi optimiert (reduzierte Cache-Größe, SD-Karten I/O)
|
||||
2025-06-12 10:02:05 - [app] app - [INFO] INFO - Datenbank mit Optimierungen initialisiert
|
||||
2025-06-12 10:02:07 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 1, Status: unreachable, Quelle: system
|
||||
2025-06-12 10:02:09 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 2, Status: unreachable, Quelle: system
|
||||
2025-06-12 10:02:11 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 3, Status: unreachable, Quelle: system
|
||||
2025-06-12 10:02:14 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 4, Status: unreachable, Quelle: system
|
||||
2025-06-12 10:02:16 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 5, Status: unreachable, Quelle: system
|
||||
2025-06-12 10:02:18 - [app] app - [INFO] INFO - Steckdosen-Status geloggt: Drucker 6, Status: unreachable, Quelle: system
|
||||
|
@ -80,3 +80,7 @@
|
||||
2025-06-12 09:42:56 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)
|
||||
2025-06-12 09:42:58 - [core_system] core_system - [INFO] INFO - ✅ Core System Management Module erfolgreich initialisiert
|
||||
2025-06-12 09:42:58 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)
|
||||
2025-06-12 09:59:23 - [core_system] core_system - [INFO] INFO - ✅ Core System Management Module erfolgreich initialisiert
|
||||
2025-06-12 09:59:23 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)
|
||||
2025-06-12 10:02:04 - [core_system] core_system - [INFO] INFO - ✅ Core System Management Module erfolgreich initialisiert
|
||||
2025-06-12 10:02:04 - [core_system] core_system - [INFO] INFO - 📊 Massive Konsolidierung: 6 Dateien → 1 Datei (88% Reduktion)
|
||||
|
@ -80,3 +80,7 @@
|
||||
2025-06-12 09:42:56 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 09:42:58 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert
|
||||
2025-06-12 09:42:58 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 09:59:23 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert
|
||||
2025-06-12 09:59:23 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 10:02:04 - [data_management] data_management - [INFO] INFO - ✅ Data Management Module initialisiert
|
||||
2025-06-12 10:02:04 - [data_management] data_management - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
|
@ -166,3 +166,11 @@
|
||||
2025-06-12 09:42:58 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Printer Monitor initialisiert
|
||||
2025-06-12 09:42:58 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Hardware Integration Module initialisiert
|
||||
2025-06-12 09:42:58 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Massive Konsolidierung: 2 Dateien → 1 Datei (50% Reduktion)
|
||||
2025-06-12 09:59:23 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ PyP100 (TP-Link Tapo) verfügbar
|
||||
2025-06-12 09:59:23 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Printer Monitor initialisiert
|
||||
2025-06-12 09:59:23 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Hardware Integration Module initialisiert
|
||||
2025-06-12 09:59:23 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Massive Konsolidierung: 2 Dateien → 1 Datei (50% Reduktion)
|
||||
2025-06-12 10:02:04 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ PyP100 (TP-Link Tapo) verfügbar
|
||||
2025-06-12 10:02:04 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Printer Monitor initialisiert
|
||||
2025-06-12 10:02:04 - [hardware_integration] hardware_integration - [INFO] INFO - ✅ Hardware Integration Module initialisiert
|
||||
2025-06-12 10:02:04 - [hardware_integration] hardware_integration - [INFO] INFO - 📊 Massive Konsolidierung: 2 Dateien → 1 Datei (50% Reduktion)
|
||||
|
@ -159,3 +159,7 @@
|
||||
2025-06-12 09:43:00 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestartet (Legacy-Kompatibilität)
|
||||
2025-06-12 09:43:57 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestoppt (Legacy-Kompatibilität)
|
||||
2025-06-12 09:43:57 - [job_queue_system] job_queue_system - [INFO] INFO - Queue Manager gestoppt (Legacy-Kompatibilität)
|
||||
2025-06-12 09:59:23 - [job_queue_system] job_queue_system - [INFO] INFO - ✅ Job & Queue System Module initialisiert
|
||||
2025-06-12 09:59:23 - [job_queue_system] job_queue_system - [INFO] INFO - 📊 MASSIVE Konsolidierung: 4 Dateien → 1 Datei (75% Reduktion)
|
||||
2025-06-12 10:02:04 - [job_queue_system] job_queue_system - [INFO] INFO - ✅ Job & Queue System Module initialisiert
|
||||
2025-06-12 10:02:04 - [job_queue_system] job_queue_system - [INFO] INFO - 📊 MASSIVE Konsolidierung: 4 Dateien → 1 Datei (75% Reduktion)
|
||||
|
@ -80,3 +80,7 @@
|
||||
2025-06-12 09:42:57 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 09:43:00 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - ✅ Monitoring & Analytics Module initialisiert
|
||||
2025-06-12 09:43:00 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 09:59:24 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - ✅ Monitoring & Analytics Module initialisiert
|
||||
2025-06-12 09:59:24 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 10:02:05 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - ✅ Monitoring & Analytics Module initialisiert
|
||||
2025-06-12 10:02:05 - [monitoring_analytics] monitoring_analytics - [INFO] INFO - 📊 MASSIVE Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
|
@ -119,3 +119,5 @@
|
||||
2025-06-12 09:42:58 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
2025-06-12 09:43:00 - [scheduler] scheduler - [INFO] INFO - Scheduler-Thread gestartet
|
||||
2025-06-12 09:43:00 - [scheduler] scheduler - [INFO] INFO - Scheduler gestartet
|
||||
2025-06-12 09:59:23 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
2025-06-12 10:02:04 - [scheduler] scheduler - [INFO] INFO - Task check_jobs registriert: Intervall 30s, Enabled: True
|
||||
|
@ -121,3 +121,9 @@
|
||||
2025-06-12 09:42:58 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert
|
||||
2025-06-12 09:42:58 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 09:43:00 - [security_suite] security_suite - [INFO] INFO - 🔒 Security Suite initialisiert
|
||||
2025-06-12 09:59:23 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert
|
||||
2025-06-12 09:59:23 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 09:59:24 - [security_suite] security_suite - [INFO] INFO - 🔒 Security Suite initialisiert
|
||||
2025-06-12 10:02:04 - [security_suite] security_suite - [INFO] INFO - ✅ Security Suite Module initialisiert
|
||||
2025-06-12 10:02:04 - [security_suite] security_suite - [INFO] INFO - 📊 Massive Konsolidierung: 3 Dateien → 1 Datei (67% Reduktion)
|
||||
2025-06-12 10:02:05 - [security_suite] security_suite - [INFO] INFO - 🔒 Security Suite initialisiert
|
||||
|
@ -367,3 +367,21 @@
|
||||
2025-06-12 09:43:00 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
|
||||
2025-06-12 09:43:00 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
|
||||
2025-06-12 09:43:00 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - [START] MYP Platform Backend wird gestartet...
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)]
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32)
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-12T09:59:24.769254
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
|
||||
2025-06-12 09:59:24 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - ==================================================
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - [START] MYP Platform Backend wird gestartet...
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - 🐍 Python Version: 3.13.3 (tags/v3.13.3:6280bb5, Apr 8 2025, 14:47:33) [MSC v.1943 64 bit (AMD64)]
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - 💻 Betriebssystem: nt (win32)
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - 📁 Arbeitsverzeichnis: C:\Users\TTOMCZA.EMEA\Dev\Projektarbeit-MYP\backend
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - ⏰ Startzeit: 2025-06-12T10:02:05.540570
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - 🪟 Windows-Modus: Aktiviert
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - 🔒 Windows-sichere Log-Rotation: Aktiviert
|
||||
2025-06-12 10:02:05 - [startup] startup - [INFO] INFO - ==================================================
|
||||
|
@ -40,3 +40,5 @@
|
||||
2025-06-12 09:37:27 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
|
||||
2025-06-12 09:42:56 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
|
||||
2025-06-12 09:42:58 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
|
||||
2025-06-12 09:59:23 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
|
||||
2025-06-12 10:02:04 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert
|
||||
|
2
backend/logs/tapo_status_manager/tapo_status_manager.log
Normal file
2
backend/logs/tapo_status_manager/tapo_status_manager.log
Normal file
@ -0,0 +1,2 @@
|
||||
2025-06-12 09:59:23 - [tapo_status_manager] tapo_status_manager - [INFO] INFO - TapoStatusManager initialisiert
|
||||
2025-06-12 10:02:04 - [tapo_status_manager] tapo_status_manager - [INFO] INFO - TapoStatusManager initialisiert
|
64
backend/logs/test_printer_setup/test_printer_setup.log
Normal file
64
backend/logs/test_printer_setup/test_printer_setup.log
Normal file
@ -0,0 +1,64 @@
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - ============================================================
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🏭 Mercedes-Benz 3D-Druck-Management - Drucker-Setup & Test
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - ============================================================
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🚀 Starte Drucker-Setup...
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📋 Erstelle Drucker 1/6: 3D-Drucker 1 - Halle A
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📋 Erstelle Drucker 2/6: 3D-Drucker 2 - Halle A
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📋 Erstelle Drucker 3/6: 3D-Drucker 3 - Halle B
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📋 Erstelle Drucker 4/6: 3D-Drucker 4 - Halle B
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📋 Erstelle Drucker 5/6: 3D-Drucker 5 - Labor
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📋 Erstelle Drucker 6/6: 3D-Drucker 6 - Werkstatt
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO - ✅ Alle 6 Drucker erfolgreich erstellt!
|
||||
2025-06-12 10:02:05 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
🔍 Teste Drucker-Status...
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📊 Status für 6 Drucker abgerufen:
|
||||
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🖨️ 3D-Drucker 1 - Halle A
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📍 Standort: Halle A - Arbeitsplatz 1
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🔌 Steckdosen-IP: 192.168.1.201
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚡ Status: Nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❌ Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚠️ Fehler: Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🖨️ 3D-Drucker 2 - Halle A
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📍 Standort: Halle A - Arbeitsplatz 2
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🔌 Steckdosen-IP: 192.168.1.202
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚡ Status: Nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❌ Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚠️ Fehler: Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🖨️ 3D-Drucker 3 - Halle B
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📍 Standort: Halle B - Arbeitsplatz 1
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🔌 Steckdosen-IP: 192.168.1.203
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚡ Status: Nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❌ Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚠️ Fehler: Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🖨️ 3D-Drucker 4 - Halle B
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📍 Standort: Halle B - Arbeitsplatz 2
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🔌 Steckdosen-IP: 192.168.1.204
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚡ Status: Nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❌ Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚠️ Fehler: Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🖨️ 3D-Drucker 5 - Labor
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📍 Standort: Labor - SLA-Bereich
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🔌 Steckdosen-IP: 192.168.1.205
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚡ Status: Nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❌ Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚠️ Fehler: Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🖨️ 3D-Drucker 6 - Werkstatt
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📍 Standort: Werkstatt - Spezialbereich
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 🔌 Steckdosen-IP: 192.168.1.206
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚡ Status: Nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❌ Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⚠️ Fehler: Steckdose nicht erreichbar
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - 📈 Status-Zusammenfassung:
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ✅ Eingeschaltet: 0
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ⭕ Ausgeschaltet: 0
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❌ Nicht erreichbar: 6
|
||||
2025-06-12 10:02:18 - [test_printer_setup] test_printer_setup - [INFO] INFO - ❓ Unbekannt: 0
|
||||
2025-06-12 10:02:37 - [test_printer_setup] test_printer_setup - [INFO] INFO -
|
||||
✅ Test abgeschlossen!
|
@ -84,3 +84,7 @@
|
||||
2025-06-12 09:42:56 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)
|
||||
2025-06-12 09:42:58 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert
|
||||
2025-06-12 09:42:58 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)
|
||||
2025-06-12 09:59:23 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert
|
||||
2025-06-12 09:59:23 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)
|
||||
2025-06-12 10:02:04 - [utilities_collection] utilities_collection - [INFO] INFO - ✅ Utilities Collection initialisiert
|
||||
2025-06-12 10:02:04 - [utilities_collection] utilities_collection - [INFO] INFO - 🚨 ALLERLETZTE MEGA-Konsolidierung: 12+ Dateien → 1 Datei (90%+ Reduktion)
|
||||
|
@ -83,3 +83,7 @@
|
||||
2025-06-12 09:42:56 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
|
||||
2025-06-12 09:42:58 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
|
||||
2025-06-12 09:42:58 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
|
||||
2025-06-12 09:59:23 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
|
||||
2025-06-12 09:59:23 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
|
||||
2025-06-12 10:02:04 - [windows_fixes] windows_fixes - [INFO] INFO - 🔧 Wende Windows-spezifische Fixes an...
|
||||
2025-06-12 10:02:04 - [windows_fixes] windows_fixes - [INFO] INFO - ✅ Alle Windows-Fixes erfolgreich angewendet
|
||||
|
258
backend/scripts/test_printer_setup.py
Normal file
258
backend/scripts/test_printer_setup.py
Normal file
@ -0,0 +1,258 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Skript zum Einrichten und Testen der 6 Standard-Drucker
|
||||
"""
|
||||
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime
|
||||
|
||||
# Pfad zum Backend hinzufügen
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from app import app
|
||||
from models import init_database, Printer, User, get_db_session
|
||||
from utils.tapo_status_manager import tapo_status_manager
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
logger = get_logger("test_printer_setup")
|
||||
|
||||
# Die 6 Standard-Drucker für Mercedes-Benz
|
||||
STANDARD_PRINTERS = [
|
||||
{
|
||||
"name": "3D-Drucker 1 - Halle A",
|
||||
"model": "Prusa MK3S+",
|
||||
"location": "Halle A - Arbeitsplatz 1",
|
||||
"ip_address": "192.168.1.101",
|
||||
"plug_ip": "192.168.1.201",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "tapo_password"
|
||||
},
|
||||
{
|
||||
"name": "3D-Drucker 2 - Halle A",
|
||||
"model": "Prusa MK3S+",
|
||||
"location": "Halle A - Arbeitsplatz 2",
|
||||
"ip_address": "192.168.1.102",
|
||||
"plug_ip": "192.168.1.202",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "tapo_password"
|
||||
},
|
||||
{
|
||||
"name": "3D-Drucker 3 - Halle B",
|
||||
"model": "Ultimaker S5",
|
||||
"location": "Halle B - Arbeitsplatz 1",
|
||||
"ip_address": "192.168.1.103",
|
||||
"plug_ip": "192.168.1.203",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "tapo_password"
|
||||
},
|
||||
{
|
||||
"name": "3D-Drucker 4 - Halle B",
|
||||
"model": "Ultimaker S5",
|
||||
"location": "Halle B - Arbeitsplatz 2",
|
||||
"ip_address": "192.168.1.104",
|
||||
"plug_ip": "192.168.1.204",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "tapo_password"
|
||||
},
|
||||
{
|
||||
"name": "3D-Drucker 5 - Labor",
|
||||
"model": "Formlabs Form 3",
|
||||
"location": "Labor - SLA-Bereich",
|
||||
"ip_address": "192.168.1.105",
|
||||
"plug_ip": "192.168.1.205",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "tapo_password"
|
||||
},
|
||||
{
|
||||
"name": "3D-Drucker 6 - Werkstatt",
|
||||
"model": "Markforged X7",
|
||||
"location": "Werkstatt - Spezialbereich",
|
||||
"ip_address": "192.168.1.106",
|
||||
"plug_ip": "192.168.1.206",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "tapo_password"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
def setup_printers():
|
||||
"""Richtet die 6 Standard-Drucker ein"""
|
||||
logger.info("🚀 Starte Drucker-Setup...")
|
||||
|
||||
with app.app_context():
|
||||
db_session = get_db_session()
|
||||
|
||||
# Prüfen, ob schon Drucker existieren
|
||||
existing_count = db_session.query(Printer).count()
|
||||
if existing_count > 0:
|
||||
logger.info(f"ℹ️ Es existieren bereits {existing_count} Drucker in der Datenbank")
|
||||
response = input("Möchten Sie diese löschen und neu anlegen? (j/n): ")
|
||||
if response.lower() == 'j':
|
||||
logger.info("🗑️ Lösche existierende Drucker...")
|
||||
db_session.query(Printer).delete()
|
||||
db_session.commit()
|
||||
else:
|
||||
logger.info("⏩ Überspringe Drucker-Setup")
|
||||
return
|
||||
|
||||
# Drucker erstellen
|
||||
for i, printer_data in enumerate(STANDARD_PRINTERS, 1):
|
||||
logger.info(f"📋 Erstelle Drucker {i}/6: {printer_data['name']}")
|
||||
|
||||
printer = Printer()
|
||||
printer.name = printer_data["name"]
|
||||
printer.model = printer_data["model"]
|
||||
printer.location = printer_data["location"]
|
||||
printer.ip_address = printer_data["ip_address"]
|
||||
printer.plug_ip = printer_data["plug_ip"]
|
||||
printer.plug_username = printer_data["plug_username"]
|
||||
printer.plug_password = printer_data["plug_password"]
|
||||
printer.status = "offline"
|
||||
printer.active = True
|
||||
printer.created_at = datetime.now()
|
||||
|
||||
db_session.add(printer)
|
||||
|
||||
db_session.commit()
|
||||
logger.info("✅ Alle 6 Drucker erfolgreich erstellt!")
|
||||
|
||||
db_session.close()
|
||||
|
||||
|
||||
def test_printer_status():
|
||||
"""Testet den Status aller Drucker"""
|
||||
logger.info("\n🔍 Teste Drucker-Status...")
|
||||
|
||||
with app.app_context():
|
||||
# Status aller Drucker abrufen
|
||||
all_status = tapo_status_manager.get_all_printer_status()
|
||||
|
||||
logger.info(f"📊 Status für {len(all_status)} Drucker abgerufen:\n")
|
||||
|
||||
# Status-Zusammenfassung
|
||||
status_summary = {
|
||||
"on": 0,
|
||||
"off": 0,
|
||||
"unreachable": 0,
|
||||
"unknown": 0
|
||||
}
|
||||
|
||||
for status in all_status:
|
||||
plug_status = status.get("plug_status", "unknown")
|
||||
status_summary[plug_status] = status_summary.get(plug_status, 0) + 1
|
||||
|
||||
# Detaillierte Ausgabe
|
||||
logger.info(f"🖨️ {status['name']}")
|
||||
logger.info(f" 📍 Standort: {status['location']}")
|
||||
logger.info(f" 🔌 Steckdosen-IP: {status.get('plug_ip', 'Keine')}")
|
||||
|
||||
if status.get("has_plug"):
|
||||
status_text = tapo_status_manager.STATUS_DISPLAY.get(plug_status, {}).get("text", "Unbekannt")
|
||||
logger.info(f" ⚡ Status: {status_text}")
|
||||
|
||||
if status.get("plug_reachable"):
|
||||
logger.info(f" ✅ Steckdose erreichbar")
|
||||
else:
|
||||
logger.info(f" ❌ Steckdose nicht erreichbar")
|
||||
if status.get("error"):
|
||||
logger.info(f" ⚠️ Fehler: {status['error']}")
|
||||
else:
|
||||
logger.info(f" ℹ️ Keine Steckdose konfiguriert")
|
||||
|
||||
if status.get("current_job"):
|
||||
job = status["current_job"]
|
||||
logger.info(f" 🏃 Aktiver Job: {job['name']} (von {job['user']})")
|
||||
|
||||
if status.get("next_job"):
|
||||
job = status["next_job"]
|
||||
logger.info(f" ⏰ Nächster Job: {job['name']} (in {job['starts_in_minutes']} Minuten)")
|
||||
|
||||
logger.info("")
|
||||
|
||||
# Zusammenfassung
|
||||
logger.info("📈 Status-Zusammenfassung:")
|
||||
logger.info(f" ✅ Eingeschaltet: {status_summary.get('on', 0)}")
|
||||
logger.info(f" ⭕ Ausgeschaltet: {status_summary.get('off', 0)}")
|
||||
logger.info(f" ❌ Nicht erreichbar: {status_summary.get('unreachable', 0)}")
|
||||
logger.info(f" ❓ Unbekannt: {status_summary.get('unknown', 0)}")
|
||||
|
||||
|
||||
def test_plug_control():
|
||||
"""Testet die Steckdosen-Steuerung"""
|
||||
logger.info("\n🎮 Teste Steckdosen-Steuerung...")
|
||||
|
||||
with app.app_context():
|
||||
db_session = get_db_session()
|
||||
|
||||
# Ersten Drucker für Test auswählen
|
||||
printer = db_session.query(Printer).first()
|
||||
if not printer:
|
||||
logger.error("❌ Keine Drucker gefunden!")
|
||||
return
|
||||
|
||||
logger.info(f"🎯 Teste Steuerung für: {printer.name}")
|
||||
|
||||
# Aktuellen Status abrufen
|
||||
current_status = tapo_status_manager.get_printer_status(printer.id)
|
||||
logger.info(f"📊 Aktueller Status: {current_status.get('plug_status', 'unknown')}")
|
||||
|
||||
if not current_status.get("can_control"):
|
||||
logger.warning("⚠️ Steckdose kann nicht gesteuert werden")
|
||||
if current_status.get("error"):
|
||||
logger.warning(f" Grund: {current_status['error']}")
|
||||
return
|
||||
|
||||
# Versuche ein/auszuschalten
|
||||
response = input("\nMöchten Sie versuchen, die Steckdose zu steuern? (j/n): ")
|
||||
if response.lower() == 'j':
|
||||
action = "on" if current_status.get("plug_status") == "off" else "off"
|
||||
logger.info(f"🔄 Versuche Steckdose zu '{action}' zu schalten...")
|
||||
|
||||
success, msg = tapo_status_manager.control_plug(printer.id, action)
|
||||
|
||||
if success:
|
||||
logger.info(f"✅ {msg}")
|
||||
|
||||
# Neuen Status abrufen
|
||||
new_status = tapo_status_manager.get_printer_status(printer.id)
|
||||
logger.info(f"📊 Neuer Status: {new_status.get('plug_status', 'unknown')}")
|
||||
else:
|
||||
logger.error(f"❌ Fehler: {msg}")
|
||||
|
||||
db_session.close()
|
||||
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion"""
|
||||
logger.info("=" * 60)
|
||||
logger.info("🏭 Mercedes-Benz 3D-Druck-Management - Drucker-Setup & Test")
|
||||
logger.info("=" * 60)
|
||||
|
||||
try:
|
||||
# Datenbank initialisieren
|
||||
with app.app_context():
|
||||
init_database()
|
||||
|
||||
# Drucker einrichten
|
||||
setup_printers()
|
||||
|
||||
# Status testen
|
||||
test_printer_status()
|
||||
|
||||
# Optional: Steuerung testen
|
||||
response = input("\n🎮 Möchten Sie die Steckdosen-Steuerung testen? (j/n): ")
|
||||
if response.lower() == 'j':
|
||||
test_plug_control()
|
||||
|
||||
logger.info("\n✅ Test abgeschlossen!")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler aufgetreten: {str(e)}", exc_info=True)
|
||||
return 1
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
Binary file not shown.
481
backend/tests/test_tapo_integration.py
Normal file
481
backend/tests/test_tapo_integration.py
Normal file
@ -0,0 +1,481 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Umfassende Tests für Tapo-Steckdosen-Integration und Drucker-Verwaltung
|
||||
|
||||
Diese Tests stellen sicher, dass:
|
||||
1. Alle 6 Drucker/Steckdosen immer angezeigt werden
|
||||
2. Die 3 Status-Zustände korrekt funktionieren: an, aus, nicht erreichbar
|
||||
3. Das automatische An-/Ausschalten basierend auf Reservierungen funktioniert
|
||||
4. Die Kalender-Integration für Admins funktioniert
|
||||
"""
|
||||
|
||||
import pytest
|
||||
import json
|
||||
import sys
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from time import sleep
|
||||
|
||||
# Pfad zum Backend hinzufügen
|
||||
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
|
||||
|
||||
from app import app
|
||||
from models import init_database, User, Printer, Job, get_db_session
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
logger = get_logger("test_tapo_integration")
|
||||
|
||||
# Test-Konfiguration
|
||||
TEST_USER = {
|
||||
"username": "testuser",
|
||||
"email": "test@mercedes-benz.com",
|
||||
"password": "TestPassword123!",
|
||||
"name": "Test User"
|
||||
}
|
||||
|
||||
TEST_ADMIN = {
|
||||
"username": "testadmin",
|
||||
"email": "admin.test@mercedes-benz.com",
|
||||
"password": "AdminPassword123!",
|
||||
"name": "Test Admin"
|
||||
}
|
||||
|
||||
# Die 6 Standard-Drucker mit Tapo-Steckdosen
|
||||
STANDARD_PRINTERS = [
|
||||
{
|
||||
"name": "Drucker 1 - Halle A",
|
||||
"model": "Prusa MK3S+",
|
||||
"location": "Halle A - Arbeitsplatz 1",
|
||||
"ip_address": "192.168.1.101",
|
||||
"plug_ip": "192.168.1.201",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "secure_password"
|
||||
},
|
||||
{
|
||||
"name": "Drucker 2 - Halle A",
|
||||
"model": "Prusa MK3S+",
|
||||
"location": "Halle A - Arbeitsplatz 2",
|
||||
"ip_address": "192.168.1.102",
|
||||
"plug_ip": "192.168.1.202",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "secure_password"
|
||||
},
|
||||
{
|
||||
"name": "Drucker 3 - Halle B",
|
||||
"model": "Ultimaker S5",
|
||||
"location": "Halle B - Arbeitsplatz 1",
|
||||
"ip_address": "192.168.1.103",
|
||||
"plug_ip": "192.168.1.203",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "secure_password"
|
||||
},
|
||||
{
|
||||
"name": "Drucker 4 - Halle B",
|
||||
"model": "Ultimaker S5",
|
||||
"location": "Halle B - Arbeitsplatz 2",
|
||||
"ip_address": "192.168.1.104",
|
||||
"plug_ip": "192.168.1.204",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "secure_password"
|
||||
},
|
||||
{
|
||||
"name": "Drucker 5 - Labor",
|
||||
"model": "Formlabs Form 3",
|
||||
"location": "Labor - SLA-Bereich",
|
||||
"ip_address": "192.168.1.105",
|
||||
"plug_ip": "192.168.1.205",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "secure_password"
|
||||
},
|
||||
{
|
||||
"name": "Drucker 6 - Werkstatt",
|
||||
"model": "Markforged X7",
|
||||
"location": "Werkstatt - Spezialbereich",
|
||||
"ip_address": "192.168.1.106",
|
||||
"plug_ip": "192.168.1.206",
|
||||
"plug_username": "admin",
|
||||
"plug_password": "secure_password"
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
class TestTapoIntegration:
|
||||
"""Test-Suite für Tapo-Steckdosen-Integration"""
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def setup(self):
|
||||
"""Setup für jeden Test"""
|
||||
self.app = app
|
||||
self.app.config['TESTING'] = True
|
||||
self.app.config['WTF_CSRF_ENABLED'] = False
|
||||
self.client = self.app.test_client()
|
||||
|
||||
with self.app.app_context():
|
||||
# Datenbank initialisieren
|
||||
init_database()
|
||||
|
||||
# Test-Benutzer erstellen
|
||||
db_session = get_db_session()
|
||||
|
||||
# Normaler Benutzer
|
||||
self.user = User()
|
||||
self.user.username = TEST_USER["username"]
|
||||
self.user.email = TEST_USER["email"]
|
||||
self.user.set_password(TEST_USER["password"])
|
||||
self.user.name = TEST_USER["name"]
|
||||
self.user.role = "user"
|
||||
db_session.add(self.user)
|
||||
|
||||
# Admin-Benutzer
|
||||
self.admin = User()
|
||||
self.admin.username = TEST_ADMIN["username"]
|
||||
self.admin.email = TEST_ADMIN["email"]
|
||||
self.admin.set_password(TEST_ADMIN["password"])
|
||||
self.admin.name = TEST_ADMIN["name"]
|
||||
self.admin.role = "admin"
|
||||
db_session.add(self.admin)
|
||||
|
||||
db_session.commit()
|
||||
|
||||
# Standard-Drucker erstellen
|
||||
self._create_standard_printers()
|
||||
|
||||
db_session.close()
|
||||
|
||||
yield
|
||||
|
||||
# Cleanup nach Test
|
||||
with self.app.app_context():
|
||||
db_session = get_db_session()
|
||||
db_session.query(Job).delete()
|
||||
db_session.query(Printer).delete()
|
||||
db_session.query(User).delete()
|
||||
db_session.commit()
|
||||
db_session.close()
|
||||
|
||||
def _create_standard_printers(self):
|
||||
"""Erstellt die 6 Standard-Drucker"""
|
||||
db_session = get_db_session()
|
||||
|
||||
for printer_data in STANDARD_PRINTERS:
|
||||
printer = Printer()
|
||||
printer.name = printer_data["name"]
|
||||
printer.model = printer_data["model"]
|
||||
printer.location = printer_data["location"]
|
||||
printer.ip_address = printer_data["ip_address"]
|
||||
printer.plug_ip = printer_data["plug_ip"]
|
||||
printer.plug_username = printer_data["plug_username"]
|
||||
printer.plug_password = printer_data["plug_password"]
|
||||
printer.status = "offline" # Initial-Status
|
||||
printer.active = True
|
||||
db_session.add(printer)
|
||||
|
||||
db_session.commit()
|
||||
|
||||
def _login_as_admin(self):
|
||||
"""Login als Admin"""
|
||||
response = self.client.post('/api/login', json={
|
||||
'username': TEST_ADMIN['username'],
|
||||
'password': TEST_ADMIN['password']
|
||||
})
|
||||
assert response.status_code == 200
|
||||
return response.get_json()
|
||||
|
||||
def _login_as_user(self):
|
||||
"""Login als normaler Benutzer"""
|
||||
response = self.client.post('/api/login', json={
|
||||
'username': TEST_USER['username'],
|
||||
'password': TEST_USER['password']
|
||||
})
|
||||
assert response.status_code == 200
|
||||
return response.get_json()
|
||||
|
||||
def test_all_printers_always_visible(self):
|
||||
"""Test: Alle 6 Drucker werden immer angezeigt"""
|
||||
# Als Benutzer einloggen
|
||||
self._login_as_user()
|
||||
|
||||
# Drucker abrufen
|
||||
response = self.client.get('/api/printers')
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.get_json()
|
||||
assert data['success'] is True
|
||||
assert data['count'] == 6
|
||||
assert len(data['printers']) == 6
|
||||
|
||||
# Prüfen, dass alle Drucker vorhanden sind
|
||||
printer_names = [p['name'] for p in data['printers']]
|
||||
for expected_printer in STANDARD_PRINTERS:
|
||||
assert expected_printer['name'] in printer_names
|
||||
|
||||
def test_printer_status_types(self):
|
||||
"""Test: Die 3 Status-Typen (an, aus, nicht erreichbar)"""
|
||||
# Als Admin einloggen
|
||||
self._login_as_admin()
|
||||
|
||||
# Status abrufen
|
||||
response = self.client.get('/api/printers/status')
|
||||
assert response.status_code == 200
|
||||
|
||||
data = response.get_json()
|
||||
assert data['success'] is True
|
||||
assert len(data['printers']) == 6
|
||||
|
||||
# Prüfen, dass alle Drucker einen gültigen Status haben
|
||||
for printer in data['printers']:
|
||||
assert printer['has_plug'] is True
|
||||
assert printer['plug_ip'] is not None
|
||||
# Status sollte einer der erwarteten sein
|
||||
assert printer['plug_status'] in ['on', 'off', 'unreachable', 'error', 'unavailable']
|
||||
|
||||
def test_crud_operations_printers(self):
|
||||
"""Test: CRUD-Operationen für Drucker"""
|
||||
# Als Admin einloggen
|
||||
self._login_as_admin()
|
||||
|
||||
# CREATE: Neuen Drucker erstellen
|
||||
new_printer_data = {
|
||||
"name": "Test-Drucker 7",
|
||||
"model": "Test Model",
|
||||
"location": "Test Location",
|
||||
"ip_address": "192.168.1.107",
|
||||
"plug_ip": "192.168.1.207",
|
||||
"plug_username": "test",
|
||||
"plug_password": "test123"
|
||||
}
|
||||
|
||||
response = self.client.post('/api/admin/printers', json=new_printer_data)
|
||||
assert response.status_code == 201
|
||||
data = response.get_json()
|
||||
assert data['success'] is True
|
||||
printer_id = data['printer']['id']
|
||||
|
||||
# READ: Einzelnen Drucker abrufen
|
||||
response = self.client.get(f'/api/admin/printers/{printer_id}')
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert data['printer']['name'] == new_printer_data['name']
|
||||
|
||||
# UPDATE: Drucker aktualisieren
|
||||
update_data = {
|
||||
"name": "Test-Drucker 7 Updated",
|
||||
"location": "Updated Location"
|
||||
}
|
||||
response = self.client.put(f'/api/admin/printers/{printer_id}', json=update_data)
|
||||
assert response.status_code == 200
|
||||
data = response.get_json()
|
||||
assert data['printer']['name'] == update_data['name']
|
||||
assert data['printer']['location'] == update_data['location']
|
||||
|
||||
# DELETE: Drucker löschen
|
||||
response = self.client.delete(f'/api/admin/printers/{printer_id}')
|
||||
assert response.status_code == 200
|
||||
|
||||
# Verifizieren, dass gelöscht wurde
|
||||
response = self.client.get(f'/api/admin/printers/{printer_id}')
|
||||
assert response.status_code == 404
|
||||
|
||||
def test_automatic_plug_control_with_jobs(self):
|
||||
"""Test: Automatisches An-/Ausschalten der Steckdosen basierend auf Jobs"""
|
||||
# Als Benutzer einloggen
|
||||
self._login_as_user()
|
||||
|
||||
# Job für in 5 Minuten erstellen
|
||||
start_time = datetime.now() + timedelta(minutes=5)
|
||||
end_time = start_time + timedelta(hours=2)
|
||||
|
||||
job_data = {
|
||||
"name": "Test-Druckauftrag",
|
||||
"description": "Automatischer Steuerungstest",
|
||||
"printer_id": 1,
|
||||
"start_at": start_time.isoformat(),
|
||||
"end_at": end_time.isoformat(),
|
||||
"duration_minutes": 120
|
||||
}
|
||||
|
||||
response = self.client.post('/api/jobs', json=job_data)
|
||||
assert response.status_code in [200, 201]
|
||||
job_id = response.get_json()['id']
|
||||
|
||||
# Job-Details abrufen
|
||||
response = self.client.get(f'/api/jobs/{job_id}')
|
||||
assert response.status_code == 200
|
||||
job = response.get_json()
|
||||
assert job['status'] == 'scheduled'
|
||||
|
||||
# Simulieren: Zeit vergeht, Job sollte starten
|
||||
# (In der Realität würde der Scheduler dies automatisch tun)
|
||||
|
||||
# Job manuell starten (simuliert Scheduler)
|
||||
response = self.client.post(f'/api/jobs/{job_id}/start')
|
||||
assert response.status_code == 200
|
||||
|
||||
# Prüfen, dass Job läuft
|
||||
response = self.client.get(f'/api/jobs/{job_id}')
|
||||
assert response.status_code == 200
|
||||
job = response.get_json()
|
||||
assert job['status'] == 'running'
|
||||
|
||||
def test_calendar_shows_printer_status_for_admin(self):
|
||||
"""Test: Kalender zeigt Drucker-Status für Admin"""
|
||||
# Als Admin einloggen
|
||||
self._login_as_admin()
|
||||
|
||||
# Jobs für verschiedene Zeiträume erstellen
|
||||
now = datetime.now()
|
||||
|
||||
# Job 1: Läuft gerade
|
||||
running_job = {
|
||||
"name": "Laufender Druck",
|
||||
"printer_id": 1,
|
||||
"start_at": (now - timedelta(hours=1)).isoformat(),
|
||||
"end_at": (now + timedelta(hours=1)).isoformat(),
|
||||
"duration_minutes": 120,
|
||||
"status": "running"
|
||||
}
|
||||
|
||||
# Job 2: Geplant für später
|
||||
scheduled_job = {
|
||||
"name": "Geplanter Druck",
|
||||
"printer_id": 2,
|
||||
"start_at": (now + timedelta(hours=2)).isoformat(),
|
||||
"end_at": (now + timedelta(hours=4)).isoformat(),
|
||||
"duration_minutes": 120,
|
||||
"status": "scheduled"
|
||||
}
|
||||
|
||||
# Jobs erstellen
|
||||
for job_data in [running_job, scheduled_job]:
|
||||
response = self.client.post('/api/jobs', json=job_data)
|
||||
assert response.status_code in [200, 201]
|
||||
|
||||
# Kalender-Events abrufen
|
||||
response = self.client.get('/api/calendar/events')
|
||||
assert response.status_code == 200
|
||||
|
||||
events = response.get_json()
|
||||
assert len(events) >= 2
|
||||
|
||||
# Prüfen, dass Events Drucker-Status enthalten
|
||||
for event in events:
|
||||
if 'extendedProps' in event:
|
||||
assert 'printer_id' in event['extendedProps']
|
||||
assert 'status' in event['extendedProps']
|
||||
|
||||
def test_printer_status_persistence(self):
|
||||
"""Test: Drucker-Status wird korrekt gespeichert und abgerufen"""
|
||||
# Als Admin einloggen
|
||||
self._login_as_admin()
|
||||
|
||||
# Status für alle Drucker abrufen
|
||||
response = self.client.get('/api/printers/status')
|
||||
assert response.status_code == 200
|
||||
initial_status = response.get_json()
|
||||
|
||||
# Manuell einen Drucker "einschalten" (simuliert)
|
||||
response = self.client.post('/api/tapo/control', json={
|
||||
'printer_id': 1,
|
||||
'action': 'on'
|
||||
})
|
||||
# Kann fehlschlagen wenn Steckdose nicht erreichbar
|
||||
# assert response.status_code == 200
|
||||
|
||||
# Status erneut abrufen
|
||||
response = self.client.get('/api/printers/status')
|
||||
assert response.status_code == 200
|
||||
updated_status = response.get_json()
|
||||
|
||||
# Mindestens die Anzahl sollte gleich bleiben
|
||||
assert len(updated_status['printers']) == len(initial_status['printers'])
|
||||
|
||||
def test_error_handling_unreachable_plugs(self):
|
||||
"""Test: Fehlerbehandlung für nicht erreichbare Steckdosen"""
|
||||
# Als Admin einloggen
|
||||
self._login_as_admin()
|
||||
|
||||
# Versuchen, eine nicht erreichbare Steckdose zu steuern
|
||||
response = self.client.post('/api/tapo/control', json={
|
||||
'printer_id': 1,
|
||||
'action': 'on'
|
||||
})
|
||||
|
||||
# Sollte entweder erfolgreich sein oder einen kontrollierten Fehler zurückgeben
|
||||
assert response.status_code in [200, 400, 500]
|
||||
|
||||
if response.status_code != 200:
|
||||
data = response.get_json()
|
||||
assert 'error' in data or 'message' in data
|
||||
|
||||
def test_concurrent_job_scheduling(self):
|
||||
"""Test: Mehrere Jobs gleichzeitig planen"""
|
||||
# Als Benutzer einloggen
|
||||
self._login_as_user()
|
||||
|
||||
# Mehrere Jobs für verschiedene Drucker erstellen
|
||||
start_time = datetime.now() + timedelta(hours=1)
|
||||
|
||||
jobs = []
|
||||
for i in range(3):
|
||||
job_data = {
|
||||
"name": f"Concurrent Job {i+1}",
|
||||
"printer_id": i+1,
|
||||
"start_at": start_time.isoformat(),
|
||||
"end_at": (start_time + timedelta(hours=2)).isoformat(),
|
||||
"duration_minutes": 120
|
||||
}
|
||||
|
||||
response = self.client.post('/api/jobs', json=job_data)
|
||||
assert response.status_code in [200, 201]
|
||||
jobs.append(response.get_json())
|
||||
|
||||
# Prüfen, dass alle Jobs erstellt wurden
|
||||
assert len(jobs) == 3
|
||||
|
||||
# Alle Jobs abrufen
|
||||
response = self.client.get('/api/jobs')
|
||||
assert response.status_code == 200
|
||||
all_jobs = response.get_json()
|
||||
assert len(all_jobs) >= 3
|
||||
|
||||
def test_admin_dashboard_printer_overview(self):
|
||||
"""Test: Admin-Dashboard zeigt Drucker-Übersicht"""
|
||||
# Als Admin einloggen
|
||||
self._login_as_admin()
|
||||
|
||||
# Dashboard-Daten abrufen
|
||||
response = self.client.get('/api/admin/dashboard')
|
||||
if response.status_code == 404:
|
||||
# Alternative: Stats abrufen
|
||||
response = self.client.get('/api/stats')
|
||||
|
||||
if response.status_code == 200:
|
||||
data = response.get_json()
|
||||
# Prüfen auf relevante Daten
|
||||
assert isinstance(data, dict)
|
||||
|
||||
|
||||
def run_tests():
|
||||
"""Führt die Tests aus"""
|
||||
logger.info("Starte Tapo-Integrationstests...")
|
||||
|
||||
# Pytest ausführen
|
||||
pytest_args = [
|
||||
__file__,
|
||||
'-v', # Verbose
|
||||
'-s', # Keine Capture, zeige print-Ausgaben
|
||||
'--tb=short' # Kurze Traceback-Ausgabe
|
||||
]
|
||||
|
||||
result = pytest.main(pytest_args)
|
||||
|
||||
if result == 0:
|
||||
logger.info("✅ Alle Tests erfolgreich bestanden!")
|
||||
else:
|
||||
logger.error("❌ Tests fehlgeschlagen!")
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(run_tests())
|
Binary file not shown.
BIN
backend/utils/__pycache__/tapo_status_manager.cpython-313.pyc
Normal file
BIN
backend/utils/__pycache__/tapo_status_manager.cpython-313.pyc
Normal file
Binary file not shown.
@ -11,6 +11,7 @@ from utils.logging_config import get_logger
|
||||
from models import Job, Printer, get_db_session
|
||||
from utils.utilities_collection import TAPO_USERNAME, TAPO_PASSWORD
|
||||
from utils.hardware_integration import tapo_controller
|
||||
from utils.tapo_status_manager import tapo_status_manager
|
||||
# Legacy function - use tapo_controller.test_connection instead
|
||||
def test_tapo_connection(*args, **kwargs):
|
||||
return tapo_controller.test_connection(*args, **kwargs)
|
||||
@ -620,6 +621,92 @@ class BackgroundTaskScheduler:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _check_and_start_jobs(self):
|
||||
"""
|
||||
Prüft anstehende Jobs und startet sie automatisch.
|
||||
"""
|
||||
try:
|
||||
from models import get_db_session, Job
|
||||
from utils.tapo_status_manager import tapo_status_manager
|
||||
|
||||
db_session = get_db_session()
|
||||
now = datetime.now()
|
||||
|
||||
# Jobs die starten sollten
|
||||
jobs_to_start = db_session.query(Job).filter(
|
||||
Job.status == "scheduled",
|
||||
Job.start_at <= now
|
||||
).all()
|
||||
|
||||
for job in jobs_to_start:
|
||||
try:
|
||||
self.logger.info(f"Starte geplanten Job {job.id} für Drucker {job.printer_id}")
|
||||
|
||||
# Steckdose einschalten
|
||||
success, msg = tapo_status_manager.control_plug(job.printer_id, "on")
|
||||
|
||||
if success:
|
||||
job.status = "running"
|
||||
job.actual_start_time = now
|
||||
self.logger.info(f"✅ Job {job.id} gestartet, Steckdose eingeschaltet")
|
||||
else:
|
||||
self.logger.error(f"❌ Fehler beim Starten von Job {job.id}: {msg}")
|
||||
# Job trotzdem starten, aber mit Warnung
|
||||
job.status = "running"
|
||||
job.notes = f"Warnung: Steckdose konnte nicht eingeschaltet werden: {msg}"
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fehler beim Starten von Job {job.id}: {str(e)}")
|
||||
job.status = "error"
|
||||
job.notes = f"Fehler beim Start: {str(e)}"
|
||||
|
||||
# Jobs die enden sollten
|
||||
jobs_to_end = db_session.query(Job).filter(
|
||||
Job.status == "running",
|
||||
Job.end_at <= now
|
||||
).all()
|
||||
|
||||
for job in jobs_to_end:
|
||||
try:
|
||||
self.logger.info(f"Beende Job {job.id} für Drucker {job.printer_id}")
|
||||
|
||||
# Steckdose ausschalten
|
||||
success, msg = tapo_status_manager.control_plug(job.printer_id, "off")
|
||||
|
||||
if success:
|
||||
job.status = "finished"
|
||||
job.actual_end_time = now
|
||||
self.logger.info(f"✅ Job {job.id} beendet, Steckdose ausgeschaltet")
|
||||
else:
|
||||
self.logger.error(f"❌ Fehler beim Beenden von Job {job.id}: {msg}")
|
||||
# Job trotzdem beenden, aber mit Warnung
|
||||
job.status = "finished"
|
||||
job.actual_end_time = now
|
||||
if job.notes:
|
||||
job.notes += f"\nWarnung: Steckdose konnte nicht ausgeschaltet werden: {msg}"
|
||||
else:
|
||||
job.notes = f"Warnung: Steckdose konnte nicht ausgeschaltet werden: {msg}"
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fehler beim Beenden von Job {job.id}: {str(e)}")
|
||||
job.status = "error"
|
||||
if job.notes:
|
||||
job.notes += f"\nFehler beim Beenden: {str(e)}"
|
||||
else:
|
||||
job.notes = f"Fehler beim Beenden: {str(e)}"
|
||||
|
||||
db_session.commit()
|
||||
db_session.close()
|
||||
|
||||
# Statistiken aktualisieren
|
||||
self.job_check_count += len(jobs_to_start) + len(jobs_to_end)
|
||||
|
||||
except Exception as e:
|
||||
self.logger.error(f"Fehler bei der Job-Überprüfung: {str(e)}", exc_info=True)
|
||||
if 'db_session' in locals():
|
||||
db_session.rollback()
|
||||
db_session.close()
|
||||
|
||||
|
||||
# Scheduler-Instanz erzeugen
|
||||
scheduler = BackgroundTaskScheduler()
|
||||
|
461
backend/utils/tapo_status_manager.py
Normal file
461
backend/utils/tapo_status_manager.py
Normal file
@ -0,0 +1,461 @@
|
||||
"""
|
||||
Tapo Status Manager - Verwaltung der 3 Steckdosen-Status
|
||||
|
||||
Dieser Manager stellt sicher, dass:
|
||||
1. Alle 6 Drucker/Steckdosen immer angezeigt werden
|
||||
2. Die 3 Status korrekt verwaltet werden: an, aus, nicht erreichbar
|
||||
3. Der Status persistent gespeichert wird
|
||||
4. Die automatische Steuerung basierend auf Jobs funktioniert
|
||||
"""
|
||||
|
||||
from typing import Dict, Tuple, Optional, List
|
||||
from datetime import datetime, timedelta
|
||||
import asyncio
|
||||
from concurrent.futures import ThreadPoolExecutor
|
||||
import threading
|
||||
|
||||
from models import Printer, Job, PlugStatusLog, get_db_session
|
||||
from utils.logging_config import get_logger
|
||||
|
||||
logger = get_logger("tapo_status_manager")
|
||||
|
||||
|
||||
class TapoStatusManager:
|
||||
"""
|
||||
Zentraler Manager für Tapo-Steckdosen-Status
|
||||
"""
|
||||
|
||||
# Die 3 möglichen Status-Zustände
|
||||
STATUS_ON = "on"
|
||||
STATUS_OFF = "off"
|
||||
STATUS_UNREACHABLE = "unreachable"
|
||||
|
||||
# Status-Mapping für UI
|
||||
STATUS_DISPLAY = {
|
||||
STATUS_ON: {"text": "An", "color": "green", "icon": "power"},
|
||||
STATUS_OFF: {"text": "Aus", "color": "gray", "icon": "power-off"},
|
||||
STATUS_UNREACHABLE: {"text": "Nicht erreichbar", "color": "red", "icon": "exclamation-triangle"}
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
"""Initialisiert den Status-Manager"""
|
||||
self._status_cache = {}
|
||||
self._cache_lock = threading.RLock()
|
||||
self._last_check = {}
|
||||
self.check_interval = 30 # Sekunden zwischen Status-Checks
|
||||
|
||||
# Thread-Pool für asynchrone Operationen
|
||||
self._executor = ThreadPoolExecutor(max_workers=6)
|
||||
|
||||
logger.info("TapoStatusManager initialisiert")
|
||||
|
||||
def get_printer_status(self, printer_id: int) -> Dict[str, any]:
|
||||
"""
|
||||
Gibt den aktuellen Status eines Druckers zurück
|
||||
|
||||
Args:
|
||||
printer_id: ID des Druckers
|
||||
|
||||
Returns:
|
||||
Dict mit Status-Informationen
|
||||
"""
|
||||
with self._cache_lock:
|
||||
# Aus Cache holen wenn vorhanden und aktuell
|
||||
if printer_id in self._status_cache:
|
||||
cache_data = self._status_cache[printer_id]
|
||||
if self._is_cache_valid(printer_id):
|
||||
return cache_data
|
||||
|
||||
# Neuen Status abrufen
|
||||
return self._fetch_printer_status(printer_id)
|
||||
|
||||
def get_all_printer_status(self) -> List[Dict[str, any]]:
|
||||
"""
|
||||
Gibt den Status aller Drucker zurück
|
||||
|
||||
Returns:
|
||||
Liste mit Status-Informationen aller Drucker
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
printers = db_session.query(Printer).all()
|
||||
|
||||
status_list = []
|
||||
|
||||
# Status für jeden Drucker abrufen
|
||||
for printer in printers:
|
||||
status = self.get_printer_status(printer.id)
|
||||
status_list.append(status)
|
||||
|
||||
db_session.close()
|
||||
|
||||
return status_list
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen aller Drucker-Status: {str(e)}")
|
||||
return []
|
||||
|
||||
def _fetch_printer_status(self, printer_id: int) -> Dict[str, any]:
|
||||
"""
|
||||
Holt den aktuellen Status eines Druckers
|
||||
|
||||
Args:
|
||||
printer_id: ID des Druckers
|
||||
|
||||
Returns:
|
||||
Dict mit Status-Informationen
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
|
||||
|
||||
if not printer:
|
||||
logger.warning(f"Drucker {printer_id} nicht gefunden")
|
||||
return self._create_error_status(printer_id, "Drucker nicht gefunden")
|
||||
|
||||
# Basis-Status erstellen
|
||||
status_info = {
|
||||
"id": printer.id,
|
||||
"name": printer.name,
|
||||
"model": printer.model,
|
||||
"location": printer.location,
|
||||
"ip_address": printer.ip_address,
|
||||
"has_plug": bool(printer.plug_ip),
|
||||
"plug_ip": printer.plug_ip,
|
||||
"active": printer.active,
|
||||
"last_checked": datetime.now()
|
||||
}
|
||||
|
||||
# Wenn keine Steckdose konfiguriert
|
||||
if not printer.plug_ip:
|
||||
status_info.update({
|
||||
"plug_status": "no_plug",
|
||||
"plug_reachable": False,
|
||||
"power_status": None,
|
||||
"can_control": False
|
||||
})
|
||||
else:
|
||||
# Tapo-Status abrufen
|
||||
plug_status = self._check_tapo_status(printer)
|
||||
status_info.update(plug_status)
|
||||
|
||||
# Aktuelle Jobs prüfen
|
||||
active_job = self._get_active_job(printer_id, db_session)
|
||||
if active_job:
|
||||
status_info["current_job"] = {
|
||||
"id": active_job.id,
|
||||
"name": active_job.name,
|
||||
"user": active_job.user.name,
|
||||
"start_at": active_job.start_at.isoformat(),
|
||||
"end_at": active_job.end_at.isoformat(),
|
||||
"status": active_job.status
|
||||
}
|
||||
else:
|
||||
status_info["current_job"] = None
|
||||
|
||||
# Nächster geplanter Job
|
||||
next_job = self._get_next_job(printer_id, db_session)
|
||||
if next_job:
|
||||
status_info["next_job"] = {
|
||||
"id": next_job.id,
|
||||
"name": next_job.name,
|
||||
"user": next_job.user.name,
|
||||
"start_at": next_job.start_at.isoformat(),
|
||||
"starts_in_minutes": int((next_job.start_at - datetime.now()).total_seconds() / 60)
|
||||
}
|
||||
else:
|
||||
status_info["next_job"] = None
|
||||
|
||||
# Status in Cache speichern
|
||||
with self._cache_lock:
|
||||
self._status_cache[printer_id] = status_info
|
||||
self._last_check[printer_id] = datetime.now()
|
||||
|
||||
# Status in Datenbank loggen
|
||||
self._log_status(printer, status_info.get("plug_status", "unknown"))
|
||||
|
||||
db_session.close()
|
||||
|
||||
return status_info
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen des Status für Drucker {printer_id}: {str(e)}")
|
||||
return self._create_error_status(printer_id, str(e))
|
||||
|
||||
def _check_tapo_status(self, printer: Printer) -> Dict[str, any]:
|
||||
"""
|
||||
Prüft den Tapo-Steckdosen-Status
|
||||
|
||||
Args:
|
||||
printer: Printer-Objekt
|
||||
|
||||
Returns:
|
||||
Dict mit Tapo-Status
|
||||
"""
|
||||
try:
|
||||
# Tapo-Controller importieren
|
||||
from utils.hardware_integration import tapo_controller
|
||||
|
||||
if not tapo_controller:
|
||||
return {
|
||||
"plug_status": self.STATUS_UNREACHABLE,
|
||||
"plug_reachable": False,
|
||||
"power_status": None,
|
||||
"can_control": False,
|
||||
"error": "Tapo-Controller nicht verfügbar"
|
||||
}
|
||||
|
||||
# Status abrufen
|
||||
reachable, plug_status = tapo_controller.check_outlet_status(
|
||||
printer.plug_ip,
|
||||
printer_id=printer.id
|
||||
)
|
||||
|
||||
if reachable:
|
||||
# Erfolgreiche Verbindung
|
||||
return {
|
||||
"plug_status": self.STATUS_ON if plug_status == "on" else self.STATUS_OFF,
|
||||
"plug_reachable": True,
|
||||
"power_status": plug_status,
|
||||
"can_control": True
|
||||
}
|
||||
else:
|
||||
# Steckdose nicht erreichbar
|
||||
return {
|
||||
"plug_status": self.STATUS_UNREACHABLE,
|
||||
"plug_reachable": False,
|
||||
"power_status": None,
|
||||
"can_control": False,
|
||||
"error": "Steckdose nicht erreichbar"
|
||||
}
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Prüfen des Tapo-Status für {printer.name}: {str(e)}")
|
||||
return {
|
||||
"plug_status": self.STATUS_UNREACHABLE,
|
||||
"plug_reachable": False,
|
||||
"power_status": None,
|
||||
"can_control": False,
|
||||
"error": str(e)
|
||||
}
|
||||
|
||||
def control_plug(self, printer_id: int, action: str) -> Tuple[bool, str]:
|
||||
"""
|
||||
Steuert eine Tapo-Steckdose
|
||||
|
||||
Args:
|
||||
printer_id: ID des Druckers
|
||||
action: "on" oder "off"
|
||||
|
||||
Returns:
|
||||
Tuple (Erfolg, Nachricht)
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
printer = db_session.query(Printer).filter(Printer.id == printer_id).first()
|
||||
|
||||
if not printer:
|
||||
return False, "Drucker nicht gefunden"
|
||||
|
||||
if not printer.plug_ip:
|
||||
return False, "Keine Steckdose konfiguriert"
|
||||
|
||||
# Tapo-Controller verwenden
|
||||
from utils.hardware_integration import tapo_controller
|
||||
|
||||
if not tapo_controller:
|
||||
return False, "Tapo-Controller nicht verfügbar"
|
||||
|
||||
# Aktion ausführen
|
||||
success = False
|
||||
if action == "on":
|
||||
success = tapo_controller.turn_on_outlet(printer.plug_ip, printer_id)
|
||||
elif action == "off":
|
||||
success = tapo_controller.turn_off_outlet(printer.plug_ip, printer_id)
|
||||
else:
|
||||
return False, f"Ungültige Aktion: {action}"
|
||||
|
||||
if success:
|
||||
# Cache invalidieren
|
||||
with self._cache_lock:
|
||||
if printer_id in self._status_cache:
|
||||
del self._status_cache[printer_id]
|
||||
|
||||
# Status loggen
|
||||
self._log_status(printer, action, source="manual")
|
||||
|
||||
db_session.close()
|
||||
return True, f"Steckdose erfolgreich {action}"
|
||||
else:
|
||||
db_session.close()
|
||||
return False, "Steckdose konnte nicht gesteuert werden"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Steuern der Steckdose für Drucker {printer_id}: {str(e)}")
|
||||
return False, str(e)
|
||||
|
||||
def check_and_control_for_jobs(self):
|
||||
"""
|
||||
Prüft alle Jobs und steuert Steckdosen entsprechend
|
||||
|
||||
Diese Methode sollte regelmäßig vom Scheduler aufgerufen werden
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
now = datetime.now()
|
||||
|
||||
# Jobs die starten sollten
|
||||
jobs_to_start = db_session.query(Job).filter(
|
||||
Job.status == "scheduled",
|
||||
Job.start_at <= now
|
||||
).all()
|
||||
|
||||
for job in jobs_to_start:
|
||||
logger.info(f"Starte Job {job.id} für Drucker {job.printer_id}")
|
||||
success, msg = self.control_plug(job.printer_id, "on")
|
||||
if success:
|
||||
job.status = "running"
|
||||
logger.info(f"Steckdose für Job {job.id} eingeschaltet")
|
||||
else:
|
||||
logger.error(f"Fehler beim Einschalten für Job {job.id}: {msg}")
|
||||
|
||||
# Jobs die enden sollten
|
||||
jobs_to_end = db_session.query(Job).filter(
|
||||
Job.status == "running",
|
||||
Job.end_at <= now
|
||||
).all()
|
||||
|
||||
for job in jobs_to_end:
|
||||
logger.info(f"Beende Job {job.id} für Drucker {job.printer_id}")
|
||||
success, msg = self.control_plug(job.printer_id, "off")
|
||||
if success:
|
||||
job.status = "finished"
|
||||
job.actual_end_time = now
|
||||
logger.info(f"Steckdose für Job {job.id} ausgeschaltet")
|
||||
else:
|
||||
logger.error(f"Fehler beim Ausschalten für Job {job.id}: {msg}")
|
||||
|
||||
db_session.commit()
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler bei der automatischen Job-Steuerung: {str(e)}")
|
||||
|
||||
def _get_active_job(self, printer_id: int, db_session) -> Optional[Job]:
|
||||
"""Gibt den aktuell aktiven Job für einen Drucker zurück"""
|
||||
return db_session.query(Job).filter(
|
||||
Job.printer_id == printer_id,
|
||||
Job.status == "running"
|
||||
).first()
|
||||
|
||||
def _get_next_job(self, printer_id: int, db_session) -> Optional[Job]:
|
||||
"""Gibt den nächsten geplanten Job für einen Drucker zurück"""
|
||||
return db_session.query(Job).filter(
|
||||
Job.printer_id == printer_id,
|
||||
Job.status == "scheduled",
|
||||
Job.start_at > datetime.now()
|
||||
).order_by(Job.start_at).first()
|
||||
|
||||
def _is_cache_valid(self, printer_id: int) -> bool:
|
||||
"""Prüft ob der Cache noch gültig ist"""
|
||||
if printer_id not in self._last_check:
|
||||
return False
|
||||
|
||||
age = (datetime.now() - self._last_check[printer_id]).total_seconds()
|
||||
return age < self.check_interval
|
||||
|
||||
def _create_error_status(self, printer_id: int, error: str) -> Dict[str, any]:
|
||||
"""Erstellt einen Fehler-Status"""
|
||||
return {
|
||||
"id": printer_id,
|
||||
"name": f"Drucker {printer_id}",
|
||||
"plug_status": self.STATUS_UNREACHABLE,
|
||||
"plug_reachable": False,
|
||||
"error": error,
|
||||
"last_checked": datetime.now()
|
||||
}
|
||||
|
||||
def _log_status(self, printer: Printer, status: str, source: str = "system"):
|
||||
"""Loggt einen Status in die Datenbank"""
|
||||
try:
|
||||
PlugStatusLog.log_status_change(
|
||||
printer_id=printer.id,
|
||||
status=status,
|
||||
source=source,
|
||||
ip_address=printer.plug_ip
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Loggen des Status: {str(e)}")
|
||||
|
||||
def get_status_for_calendar(self, start_date: datetime, end_date: datetime) -> List[Dict]:
|
||||
"""
|
||||
Gibt Status-Informationen für die Kalender-Ansicht zurück
|
||||
|
||||
Args:
|
||||
start_date: Start-Datum
|
||||
end_date: End-Datum
|
||||
|
||||
Returns:
|
||||
Liste mit Status-Events für den Kalender
|
||||
"""
|
||||
try:
|
||||
db_session = get_db_session()
|
||||
|
||||
# Jobs im Zeitraum abrufen
|
||||
jobs = db_session.query(Job).filter(
|
||||
Job.start_at <= end_date,
|
||||
Job.end_at >= start_date
|
||||
).all()
|
||||
|
||||
events = []
|
||||
|
||||
for job in jobs:
|
||||
# Drucker-Status für Job
|
||||
printer = job.printer
|
||||
status = self.get_printer_status(printer.id)
|
||||
|
||||
event = {
|
||||
"id": f"job_{job.id}",
|
||||
"title": f"{printer.name}: {job.name}",
|
||||
"start": job.start_at.isoformat(),
|
||||
"end": job.end_at.isoformat(),
|
||||
"backgroundColor": self._get_status_color(job.status),
|
||||
"extendedProps": {
|
||||
"job_id": job.id,
|
||||
"printer_id": printer.id,
|
||||
"printer_name": printer.name,
|
||||
"printer_status": status.get("plug_status", "unknown"),
|
||||
"job_status": job.status,
|
||||
"user": job.user.name,
|
||||
"plug_reachable": status.get("plug_reachable", False)
|
||||
}
|
||||
}
|
||||
|
||||
events.append(event)
|
||||
|
||||
db_session.close()
|
||||
|
||||
return events
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Fehler beim Abrufen der Kalender-Status: {str(e)}")
|
||||
return []
|
||||
|
||||
def _get_status_color(self, status: str) -> str:
|
||||
"""Gibt die Farbe für einen Status zurück"""
|
||||
colors = {
|
||||
"scheduled": "#3788d8",
|
||||
"running": "#28a745",
|
||||
"finished": "#6c757d",
|
||||
"aborted": "#dc3545"
|
||||
}
|
||||
return colors.get(status, "#6c757d")
|
||||
|
||||
|
||||
# Globale Instanz
|
||||
tapo_status_manager = TapoStatusManager()
|
||||
|
||||
|
||||
def get_tapo_status_manager() -> TapoStatusManager:
|
||||
"""Gibt die globale TapoStatusManager-Instanz zurück"""
|
||||
return tapo_status_manager
|
Reference in New Issue
Block a user