🎉 Improved documentation and code organization in Backend 🌐

This commit is contained in:
2025-06-12 10:06:34 +02:00
parent c356111149
commit 2bf4e0e6c0
30 changed files with 1596 additions and 94 deletions

View File

@@ -1,6 +1,6 @@
# MYP Druckerverwaltungssystem # MYP Druckerverwaltungssystem
**Manage Your Printer** - Mercedes-Benz Werk 040 Berlin **Manage Your Printer** - Mercedes-Benz Werk 040 Berlin
Vollständige 3D-Drucker Verwaltungsplattform mit Smart-Plug-Technologie Vollständige 3D-Drucker Verwaltungsplattform mit Smart-Plug-Technologie
## 🎯 System-Übersicht ## 🎯 System-Übersicht
@@ -10,22 +10,26 @@ Vollständige 3D-Drucker Verwaltungsplattform mit Smart-Plug-Technologie
### 🔑 Kernfunktionen ### 🔑 Kernfunktionen
#### **Benutzer- und Rechteverwaltung** #### **Benutzer- und Rechteverwaltung**
- **Registrierung, Login und Rollenkonzept** (Admin/Benutzer) - **Registrierung, Login und Rollenkonzept** (Admin/Benutzer)
- **Administrierende** können Drucker und Nutzer verwalten - **Administrierende** können Drucker und Nutzer verwalten
- **Standard-Benutzer** können Reservierungen anlegen und Druckjobs verwalten - **Standard-Benutzer** können Reservierungen anlegen und Druckjobs verwalten
#### **Drucker- und Auftragsmanagement** #### **Drucker- und Auftragsmanagement**
- **Zentrales Reservierungssystem** für Zeitfenster-Buchungen - **Zentrales Reservierungssystem** für Zeitfenster-Buchungen
- **Automatische Drucker-Schaltung**: Einschalten zum Reservierungsstart, Ausschalten nach Ende - **Automatische Drucker-Schaltung**: Einschalten zum Reservierungsstart, Ausschalten nach Ende
- **Herstellerunabhängig**: Keine direkte Kommunikation mit 3D-Druckern - ausschließlich Stromsteuerung über Smart-Plug-Steckdosen - **Herstellerunabhängig**: Keine direkte Kommunikation mit 3D-Druckern - ausschließlich Stromsteuerung über Smart-Plug-Steckdosen
- **Einfache Integration**: Keine Eingriffe in die Druckerhardware erforderlich - **Einfache Integration**: Keine Eingriffe in die Druckerhardware erforderlich
#### **Statistikerfassung** #### **Statistikerfassung**
- **Protokollierung** von Nutzungszeiten und abgeschlossenen Druckaufträgen - **Protokollierung** von Nutzungszeiten und abgeschlossenen Druckaufträgen
- **Auswertungen** (z.B. Gesamtdruckzeit pro Zeitraum) - **Auswertungen** (z.B. Gesamtdruckzeit pro Zeitraum)
- **Analytics-Dashboard** für Effizienzanalysen - **Analytics-Dashboard** für Effizienzanalysen
#### **Offline-Fähigkeit & Kiosk-Modus** #### **Offline-Fähigkeit & Kiosk-Modus**
- **Autonomer Betrieb** ohne Internetzugang nach Installation - **Autonomer Betrieb** ohne Internetzugang nach Installation
- **Raspberry Pi Kiosk-Modus**: Vollbild-Dashboard vor Ort - **Raspberry Pi Kiosk-Modus**: Vollbild-Dashboard vor Ort
- **Touch-Interface** für aktuelle Druckerbelegungen und Systemstatus - **Touch-Interface** für aktuelle Druckerbelegungen und Systemstatus
@@ -35,12 +39,14 @@ Vollständige 3D-Drucker Verwaltungsplattform mit Smart-Plug-Technologie
Dieses Repository enthält **zwei sich ergänzende Projektarbeiten** für die IHK-Abschlussprüfung: Dieses Repository enthält **zwei sich ergänzende Projektarbeiten** für die IHK-Abschlussprüfung:
### 🏗️ **Backend-System** (Till Tomczak) - **KERN-INFRASTRUKTUR** ### 🏗️ **Backend-System** (Till Tomczak) - **KERN-INFRASTRUKTUR**
- **Entwickler**: Till Tomczak - **Entwickler**: Till Tomczak
- **Fachrichtung**: Fachinformatiker für digitale Vernetzung - **Fachrichtung**: Fachinformatiker für digitale Vernetzung
- **Technologie**: **Flask-basiertes Backend in Python** mit **SQLite-Datenbank** - **Technologie**: **Flask-basiertes Backend in Python** mit **SQLite-Datenbank**
- **Verantwortung**: Hardware-Integration, REST-APIs und cyber-physische Vernetzung - **Verantwortung**: Hardware-Integration, REST-APIs und cyber-physische Vernetzung
### 📊 **Frontend-System** (Torben Haack) - **BENUTZEROBERFLÄCHE & ANALYTICS** - LEGACY ### 📊 **Frontend-System** (Torben Haack) - **BENUTZEROBERFLÄCHE & ANALYTICS** - LEGACY
- **Entwickler**: Torben Haack - **Entwickler**: Torben Haack
- **Fachrichtung**: Fachinformatiker für Daten- und Prozessanalyse - **Fachrichtung**: Fachinformatiker für Daten- und Prozessanalyse
- **Technologie**: **Next.js-basierte Webanwendung** mit erweiterten Analytics (Legacy-Version) - **Technologie**: **Next.js-basierte Webanwendung** mit erweiterten Analytics (Legacy-Version)
@@ -49,6 +55,7 @@ Dieses Repository enthält **zwei sich ergänzende Projektarbeiten** für die IH
## 🏗️ Technische Architektur ## 🏗️ Technische Architektur
### Cyber-Physische Lösung ### Cyber-Physische Lösung
``` ```
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Frontend-Server │◄──►│ Backend-Server │◄──►│ Raspberry Pi │ │ Frontend-Server │◄──►│ Backend-Server │◄──►│ Raspberry Pi │
@@ -64,6 +71,7 @@ Dieses Repository enthält **zwei sich ergänzende Projektarbeiten** für die IH
``` ```
### Kommunikations-Architektur ### Kommunikations-Architektur
- **RESTful API**: Backend kommuniziert mit Frontend und externen Diensten - **RESTful API**: Backend kommuniziert mit Frontend und externen Diensten
- **HTTPS-Verschlüsselung**: Selbstsignierte Zertifikate für sichere Übertragung - **HTTPS-Verschlüsselung**: Selbstsignierte Zertifikate für sichere Übertragung
- **Progressive Web App (PWA)**: Offline-Funktionalität im Browser - **Progressive Web App (PWA)**: Offline-Funktionalität im Browser
@@ -72,6 +80,7 @@ Dieses Repository enthält **zwei sich ergänzende Projektarbeiten** für die IH
## 🚀 Schnellstart ## 🚀 Schnellstart
### Backend-System (Hardware & APIs) ### Backend-System (Hardware & APIs)
```bash ```bash
# Backend-Server automatisch installieren (Till Tomczaks System) # Backend-Server automatisch installieren (Till Tomczaks System)
cd backend cd backend
@@ -82,6 +91,7 @@ python app.py
``` ```
### Frontend-System (Web-Interface) - NEU: Automatische Installation ### Frontend-System (Web-Interface) - NEU: Automatische Installation
```bash ```bash
# Frontend-Server automatisch installieren (Torben Haacks System) # Frontend-Server automatisch installieren (Torben Haacks System)
cd frontend cd frontend
@@ -94,6 +104,7 @@ pnpm dev # Development-Server
``` ```
### Vollständiges System ### Vollständiges System
```bash ```bash
# Backend (API-Server) # Backend (API-Server)
cd backend && sudo ./setup.sh cd backend && sudo ./setup.sh
@@ -105,21 +116,26 @@ cd frontend && sudo ./setup.sh
## 🌐 Systemzugriff ## 🌐 Systemzugriff
### Produktions-URLs (Nach Setup-Skript Installation) ### Produktions-URLs (Nach Setup-Skript Installation)
- **Frontend (HTTPS)**: `https://m040tbaraspi001.de040.corpintra.net` (Torben Haacks Frontend) - **Frontend (HTTPS)**: `https://m040tbaraspi001.de040.corpintra.net` (Torben Haacks Frontend)
- **Frontend (Lokal)**: `https://localhost` (Fallback-Zugang) - **Frontend (Lokal)**: `https://localhost` (Fallback-Zugang)
- **API-Backend**: `https://192.168.0.105:443/api` (Till Tomczaks APIs) - **API-Backend**: `https://192.168.0.105:443/api` (Till Tomczaks APIs)
- **Kiosk-Modus**: `https://192.168.0.105:443` (Lokales Touch-Interface) - **Kiosk-Modus**: `https://192.168.0.105:443` (Lokales Touch-Interface)
### Development-URLs ### Development-URLs
- **Frontend (Dev)**: `http://localhost:3000` (Development-Server) - **Frontend (Dev)**: `http://localhost:3000` (Development-Server)
- **Backend (Dev)**: `http://localhost:5000` (Development-API) - **Backend (Dev)**: `http://localhost:5000` (Development-API)
### Standard-Anmeldedaten ### Standard-Anmeldedaten
- **Benutzername**: `admin` - **Benutzername**: `admin`
- **Passwort**: `admin123` - **Passwort**: `744563017196A`
### SSL-Zertifikate (Mercedes) ### SSL-Zertifikate (Mercedes)
Nach der automatischen Installation sind selbstsignierte Mercedes-Zertifikate verfügbar: Nach der automatischen Installation sind selbstsignierte Mercedes-Zertifikate verfügbar:
- **Domain**: `m040tbaraspi001.de040.corpintra.net` - **Domain**: `m040tbaraspi001.de040.corpintra.net`
- **Organisation**: Mercedes-Benz AG - **Organisation**: Mercedes-Benz AG
- **Abteilung**: IT-Abteilung - **Abteilung**: IT-Abteilung
@@ -170,6 +186,7 @@ Projektarbeit-MYP/
## 🎯 Funktions-Aufgabenteilung ## 🎯 Funktions-Aufgabenteilung
### Backend-Verantwortlichkeiten (Till Tomczak) ### Backend-Verantwortlichkeiten (Till Tomczak)
-**Smart-Plug-Steuerung**: TP-Link Tapo P110 WLAN-Steckdosen -**Smart-Plug-Steuerung**: TP-Link Tapo P110 WLAN-Steckdosen
-**Automatische Drucker-Schaltung**: Zeitgesteuerte Ein-/Ausschaltung -**Automatische Drucker-Schaltung**: Zeitgesteuerte Ein-/Ausschaltung
-**REST-API-Bereitstellung**: Vollständige API für alle Drucker-Operationen -**REST-API-Bereitstellung**: Vollständige API für alle Drucker-Operationen
@@ -180,6 +197,7 @@ Projektarbeit-MYP/
-**Offline-Fähigkeit**: Autonomer Betrieb ohne Internet -**Offline-Fähigkeit**: Autonomer Betrieb ohne Internet
### Frontend-Verantwortlichkeiten (Torben Haack) ### Frontend-Verantwortlichkeiten (Torben Haack)
-**Moderne Web-UI**: React-basierte Benutzeroberfläche -**Moderne Web-UI**: React-basierte Benutzeroberfläche
-**Progressive Web App**: Offline-Funktionalität im Browser -**Progressive Web App**: Offline-Funktionalität im Browser
-**Advanced Analytics**: Interaktive Charts und Datenvisualisierung -**Advanced Analytics**: Interaktive Charts und Datenvisualisierung
@@ -192,6 +210,7 @@ Projektarbeit-MYP/
## 🔗 API-Integration & Kommunikation ## 🔗 API-Integration & Kommunikation
### Backend-REST-Endpunkte (Till Tomczak) ### Backend-REST-Endpunkte (Till Tomczak)
```typescript ```typescript
// Drucker-Management // Drucker-Management
GET /api/printers // Alle Drucker abrufen GET /api/printers // Alle Drucker abrufen
@@ -216,6 +235,7 @@ GET /api/reports // Report-Daten für Analytics
``` ```
### Frontend-Integration (Torben Haack) ### Frontend-Integration (Torben Haack)
```typescript ```typescript
// Backend-API Client - Konfiguriert für separaten Server // Backend-API Client - Konfiguriert für separaten Server
export class MYPApiClient { export class MYPApiClient {
@@ -249,6 +269,7 @@ export const API_BASE_URL = {
## 🖥️ Deployment-Szenarien ## 🖥️ Deployment-Szenarien
### Szenario 1: Automatische Produktions-Installation (Neu - Empfohlen) ### Szenario 1: Automatische Produktions-Installation (Neu - Empfohlen)
```bash ```bash
# Backend-Server (Raspberry Pi oder Linux-Server) # Backend-Server (Raspberry Pi oder Linux-Server)
cd backend cd backend
@@ -260,6 +281,7 @@ sudo ./setup.sh # Automatische Installation mit HTTPS auf Port 443
``` ```
### Szenario 2: Separate Server (Manuell) ### Szenario 2: Separate Server (Manuell)
```bash ```bash
# Backend-Server (z.B. Raspberry Pi oder Linux-Server) # Backend-Server (z.B. Raspberry Pi oder Linux-Server)
cd backend cd backend
@@ -271,13 +293,14 @@ npm run build && npm start
``` ```
### Szenario 3: Docker-Deployment ### Szenario 3: Docker-Deployment
```yaml ```yaml
# docker-compose.yml # docker-compose.yml
services: services:
backend: backend:
build: ./backend build: ./backend
ports: ["5000:5000", "443:443"] ports: ["5000:5000", "443:443"]
frontend: frontend:
build: ./frontend build: ./frontend
ports: ["80:80", "443:443"] ports: ["80:80", "443:443"]
@@ -286,6 +309,7 @@ services:
``` ```
### Szenario 4: Raspberry Pi Kiosk (Lokal) ### Szenario 4: Raspberry Pi Kiosk (Lokal)
```bash ```bash
# Vollständige Kiosk-Installation (Backend + Frontend) # Vollständige Kiosk-Installation (Backend + Frontend)
cd backend && sudo ./setup.sh # Backend mit Kiosk-Interface cd backend && sudo ./setup.sh # Backend mit Kiosk-Interface
@@ -295,6 +319,7 @@ cd frontend && sudo ./setup.sh # Frontend mit HTTPS-Server
## 🔧 Konfiguration & Environment ## 🔧 Konfiguration & Environment
### Backend-Konfiguration (.env) ### Backend-Konfiguration (.env)
```env ```env
# Flask-Server Einstellungen # Flask-Server Einstellungen
FLASK_HOST=0.0.0.0 FLASK_HOST=0.0.0.0
@@ -312,6 +337,7 @@ OFFLINE_MODE=true
``` ```
### Frontend-Konfiguration (.env.local) ### Frontend-Konfiguration (.env.local)
```env ```env
# Frontend-Server Einstellungen - HTTPS mit Mercedes SSL # Frontend-Server Einstellungen - HTTPS mit Mercedes SSL
NEXT_PUBLIC_API_URL=https://192.168.0.105:443 NEXT_PUBLIC_API_URL=https://192.168.0.105:443
@@ -333,6 +359,7 @@ SSL_KEY_PATH=/etc/ssl/certs/myp/frontend.key
## 📊 Features im Überblick ## 📊 Features im Überblick
### Backend-Features (Till Tomczak) - Cyber-Physische Integration ### Backend-Features (Till Tomczak) - Cyber-Physische Integration
- **TP-Link Tapo P110 Integration**: Lokale WLAN-Steckdosen-Steuerung - **TP-Link Tapo P110 Integration**: Lokale WLAN-Steckdosen-Steuerung
- **Automatische Zeitsteuerung**: Drucker Ein-/Ausschaltung nach Reservierung - **Automatische Zeitsteuerung**: Drucker Ein-/Ausschaltung nach Reservierung
- **Herstellerunabhängigkeit**: Keine direkten Drucker-Eingriffe erforderlich - **Herstellerunabhängigkeit**: Keine direkten Drucker-Eingriffe erforderlich
@@ -343,6 +370,7 @@ SSL_KEY_PATH=/etc/ssl/certs/myp/frontend.key
- **Raspberry Pi Kiosk**: Touch-optimiertes Dashboard vor Ort - **Raspberry Pi Kiosk**: Touch-optimiertes Dashboard vor Ort
### Frontend-Features (Torben Haack) - Moderne Web-Oberfläche ### Frontend-Features (Torben Haack) - Moderne Web-Oberfläche
- **Progressive Web App**: Offline-Funktionalität im Browser - **Progressive Web App**: Offline-Funktionalität im Browser
- **React 18 + Next.js 14**: Moderne, performante Web-Technologien - **React 18 + Next.js 14**: Moderne, performante Web-Technologien
- **Analytics-Dashboard**: Recharts-Visualisierungen für Nutzungsstatistiken - **Analytics-Dashboard**: Recharts-Visualisierungen für Nutzungsstatistiken
@@ -354,6 +382,7 @@ SSL_KEY_PATH=/etc/ssl/certs/myp/frontend.key
## 🛠️ Entwicklung ## 🛠️ Entwicklung
### Backend-Entwicklung (Till Tomczak) ### Backend-Entwicklung (Till Tomczak)
```bash ```bash
cd backend cd backend
python -m venv venv python -m venv venv
@@ -365,12 +394,14 @@ python app.py --debug
### Frontend-Entwicklung (Torben Haack) ### Frontend-Entwicklung (Torben Haack)
#### Automatische Installation (Empfohlen) #### Automatische Installation (Empfohlen)
```bash ```bash
cd frontend cd frontend
sudo ./setup.sh # Interaktives Setup-Menü sudo ./setup.sh # Interaktives Setup-Menü
``` ```
#### Manuelle Entwicklung #### Manuelle Entwicklung
```bash ```bash
cd frontend cd frontend
pnpm install pnpm install
@@ -379,18 +410,21 @@ pnpm dev
``` ```
#### Frontend-Setup-Skript Features #### Frontend-Setup-Skript Features
Das neue `frontend/setup.sh` bietet: Das neue `frontend/setup.sh` bietet:
- **Vollständige Installation**: Docker, SSL-Zertifikate, Caddy Reverse Proxy - **Vollständige Installation**: Docker, SSL-Zertifikate, Caddy Reverse Proxy
- **Mercedes SSL-Zertifikate**: Selbstsignierte Zertifikate für `m040tbaraspi001.de040.corpintra.net` - **Mercedes SSL-Zertifikate**: Selbstsignierte Zertifikate für `m040tbaraspi001.de040.corpintra.net`
- **Automatischer HTTPS-Server**: Verfügbar auf Port 443 (nicht 3000) - **Automatischer HTTPS-Server**: Verfügbar auf Port 443 (nicht 3000)
- **Systemd-Integration**: Automatischer Start beim Boot - **Systemd-Integration**: Automatischer Start beim Boot
- **Interaktives Menü**: - **Interaktives Menü**:
1. Vollständige Frontend-Installation 1. Vollständige Frontend-Installation
2. SSL-Zertifikate neu generieren 2. SSL-Zertifikate neu generieren
3. Service-Status prüfen 3. Service-Status prüfen
4. Beenden 4. Beenden
### Integration testen ### Integration testen
```bash ```bash
# Backend-APIs testen # Backend-APIs testen
curl http://localhost:5000/api/printers curl http://localhost:5000/api/printers
@@ -403,27 +437,33 @@ curl -k https://localhost/health
## 📚 Dokumentation ## 📚 Dokumentation
### Backend-Dokumentation (Till Tomczak) ### Backend-Dokumentation (Till Tomczak)
- [`backend/README.md`](backend/README.md) - Hardware-Setup & API-Dokumentation - [`backend/README.md`](backend/README.md) - Hardware-Setup & API-Dokumentation
- [`backend/docs/`](backend/docs/) - Raspberry Pi Konfiguration & Smart-Plug-Integration - [`backend/docs/`](backend/docs/) - Raspberry Pi Konfiguration & Smart-Plug-Integration
### Frontend-Dokumentation (Torben Haack) ### Frontend-Dokumentation (Torben Haack)
- [`frontend/README.md`](frontend/README.md) - UI-Entwicklung & Analytics - [`frontend/README.md`](frontend/README.md) - UI-Entwicklung & Analytics
- [`frontend/docs/`](frontend/docs/) - Component-Library & PWA-Features - [`frontend/docs/`](frontend/docs/) - Component-Library & PWA-Features
### Gemeinsame Dokumentation ### Gemeinsame Dokumentation
- [`docs/myp_documentation.md`](docs/myp_documentation.md) - Vollständige Projektdokumentation - [`docs/myp_documentation.md`](docs/myp_documentation.md) - Vollständige Projektdokumentation
- [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) - Production-Deployment-Guide - [`docs/DEPLOYMENT.md`](docs/DEPLOYMENT.md) - Production-Deployment-Guide
## 🤝 Projektphilosophie ## 🤝 Projektphilosophie
### Cyber-Physische Vernetzung ### Cyber-Physische Vernetzung
MYP stellt eine **cyber-physische Lösung** dar, die **IT-System (Reservierungsplattform) und Hardware (Smart-Plugs und Drucker) eng vernetzt**. Das System überbrückt die digitale und physische Welt durch intelligente Automatisierung. MYP stellt eine **cyber-physische Lösung** dar, die **IT-System (Reservierungsplattform) und Hardware (Smart-Plugs und Drucker) eng vernetzt**. Das System überbrückt die digitale und physische Welt durch intelligente Automatisierung.
### Komplementäre Expertisen ### Komplementäre Expertisen
- **Till Tomczak**: Spezialist für Hardware-Integration und cyber-physische Vernetzung - **Till Tomczak**: Spezialist für Hardware-Integration und cyber-physische Vernetzung
- **Torben Haack**: Spezialist für Frontend-Entwicklung und Datenanalyse - **Torben Haack**: Spezialist für Frontend-Entwicklung und Datenanalyse
### Gemeinsame Ziele ### Gemeinsame Ziele
- **Digitalisierung**: Modernisierung des Reservierungsprozesses - **Digitalisierung**: Modernisierung des Reservierungsprozesses
- **Automatisierung**: Zeitgesteuerte Hardware-Steuerung ohne manuelle Eingriffe - **Automatisierung**: Zeitgesteuerte Hardware-Steuerung ohne manuelle Eingriffe
- **Benutzerfreundlichkeit**: Intuitive Bedienung für alle Stakeholder - **Benutzerfreundlichkeit**: Intuitive Bedienung für alle Stakeholder
@@ -432,12 +472,14 @@ MYP stellt eine **cyber-physische Lösung** dar, die **IT-System (Reservierungsp
## 👥 Entwicklerteam ## 👥 Entwicklerteam
### Till Tomczak - **Backend-Infrastruktur & Hardware-Integration** ### Till Tomczak - **Backend-Infrastruktur & Hardware-Integration**
- **Cyber-Physische Systeme**: Smart-Plug-Integration und Hardware-Steuerung - **Cyber-Physische Systeme**: Smart-Plug-Integration und Hardware-Steuerung
- **System-Architektur**: Flask-APIs und SQLite-Datenbank-Design - **System-Architektur**: Flask-APIs und SQLite-Datenbank-Design
- **DevOps**: Raspberry Pi Services und Produktions-Deployment - **DevOps**: Raspberry Pi Services und Produktions-Deployment
- **Offline-Systeme**: Autonomer Betrieb ohne Internet-Abhängigkeiten - **Offline-Systeme**: Autonomer Betrieb ohne Internet-Abhängigkeiten
### Torben Haack - **Frontend-Entwicklung & Analytics** ### Torben Haack - **Frontend-Entwicklung & Analytics**
- **Progressive Web Apps**: Moderne Browser-Technologien und Offline-Features - **Progressive Web Apps**: Moderne Browser-Technologien und Offline-Features
- **User Interface**: React-Komponenten und responsive Design - **User Interface**: React-Komponenten und responsive Design
- **Datenvisualisierung**: Charts, Dashboards und Analytics - **Datenvisualisierung**: Charts, Dashboards und Analytics
@@ -449,10 +491,9 @@ Dieses Projekt wurde für den internen Gebrauch bei Mercedes-Benz entwickelt.
--- ---
**Backend-System**: Till Tomczak (Cyber-Physische Vernetzung & Hardware-Integration) **Backend-System**: Till Tomczak (Cyber-Physische Vernetzung & Hardware-Integration)
**Frontend-System**: Torben Haack (Progressive Web App & Analytics) **Frontend-System**: Torben Haack (Progressive Web App & Analytics)
**Architektur**: Microservices mit REST-API-Integration **Architektur**: Microservices mit REST-API-Integration
**Technologie**: Flask + SQLite (Backend) + Next.js + React (Frontend) **Technologie**: Flask + SQLite (Backend) + Next.js + React (Frontend)
**Hardware**: Raspberry Pi + TP-Link Tapo P110 Smart-Plugs **Hardware**: Raspberry Pi + TP-Link Tapo P110 Smart-Plugs
**Entwickelt für**: Mercedes-Benz Werk 040 Berlin MYP **Entwickelt für**: Mercedes-Benz Werk 040 Berlin MYP

View File

@@ -943,85 +943,26 @@ def api_get_printers():
@app.route("/api/printers/status", methods=["GET"]) @app.route("/api/printers/status", methods=["GET"])
@login_required @login_required
def api_get_printer_status(): def api_get_printer_status():
"""API-Endpunkt für Drucker-Status""" """API-Endpunkt für Drucker-Status mit verbessertem Status-Management"""
try: 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() # Status für alle Drucker abrufen
# Alle Drucker für Status-Abfragen anzeigen (unabhängig von active-Status) status_list = tapo_status_manager.get_all_printer_status()
printers = db_session.query(Printer).all()
status_list = [] # Erweitere Status mit UI-freundlichen Informationen
for status in status_list:
# Tapo-Controller nur importieren, wenn benötigt # Status-Display-Informationen hinzufügen
tapo_controller = None plug_status = status.get("plug_status", "unknown")
has_tapo_printers = any(printer.plug_ip for printer in printers) if plug_status in tapo_status_manager.STATUS_DISPLAY:
status["status_display"] = tapo_status_manager.STATUS_DISPLAY[plug_status]
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"
})
else: else:
# Kein Smart-Plug konfiguriert status["status_display"] = {
status_dict.update({ "text": "Unbekannt",
"plug_status": "no_plug", "color": "gray",
"plug_reachable": False, "icon": "question"
"plug_ip": None, }
"has_plug": False
})
status_list.append(status_dict)
db_session.close()
app_logger.info(f"✅ API: Status für {len(status_list)} Drucker abgerufen") 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: except Exception as e:
app_logger.error(f"❌ API-Fehler beim Abrufen des Drucker-Status: {str(e)}", exc_info=True) app_logger.error(f"❌ API-Fehler beim Abrufen des Drucker-Status: {str(e)}", exc_info=True)
return jsonify({
"success": False, # Fallback: Mindestens die Drucker-Grunddaten zurückgeben
"error": "Fehler beim Laden des Drucker-Status", try:
"details": str(e), from models import get_db_session, Printer
"printers": [], db_session = get_db_session()
"count": 0 printers = db_session.query(Printer).all()
}), 500
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"]) @app.route("/api/health", methods=["GET"])
def api_health_check(): def api_health_check():

Binary file not shown.

Binary file not shown.

View File

@@ -7,6 +7,7 @@ from sqlalchemy import and_, or_, func
from models import Job, Printer, User, UserPermission, get_cached_session from models import Job, Printer, User, UserPermission, get_cached_session
from utils.logging_config import get_logger from utils.logging_config import get_logger
from utils.job_queue_system import conflict_manager, ConflictType, ConflictSeverity from utils.job_queue_system import conflict_manager, ConflictType, ConflictSeverity
from utils.tapo_status_manager import tapo_status_manager
calendar_blueprint = Blueprint('calendar', __name__) calendar_blueprint = Blueprint('calendar', __name__)
logger = get_logger("calendar") 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) events.append(event)
# Steckdosen-Status-Events hinzufügen (falls gewünscht) # Steckdosen-Status-Events hinzufügen (falls gewünscht)

View File

@@ -0,0 +1 @@

View File

@@ -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 - 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: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

View File

@@ -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: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 - ✅ 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: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)

View File

@@ -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: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 - ✅ 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: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)

View File

@@ -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 - ✅ 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 - ✅ 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: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)

View File

@@ -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: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: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)

View File

@@ -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: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 - ✅ 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: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)

View File

@@ -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: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-Thread gestartet
2025-06-12 09:43:00 - [scheduler] scheduler - [INFO] INFO - Scheduler 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

View File

@@ -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 - ✅ 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: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: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

View File

@@ -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-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 - 🔒 Windows-sichere Log-Rotation: Aktiviert
2025-06-12 09:43:00 - [startup] startup - [INFO] INFO - ================================================== 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 - ==================================================

View File

@@ -40,3 +40,5 @@
2025-06-12 09:37:27 - [tapo_controller] tapo_controller - [INFO] INFO - ✅ tapo controller initialisiert 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: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: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

View 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

View 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!

View File

@@ -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: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 - ✅ 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: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)

View File

@@ -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: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 - 🔧 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: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

View 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())

View 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())

View File

@@ -11,6 +11,7 @@ from utils.logging_config import get_logger
from models import Job, Printer, get_db_session from models import Job, Printer, get_db_session
from utils.utilities_collection import TAPO_USERNAME, TAPO_PASSWORD from utils.utilities_collection import TAPO_USERNAME, TAPO_PASSWORD
from utils.hardware_integration import tapo_controller from utils.hardware_integration import tapo_controller
from utils.tapo_status_manager import tapo_status_manager
# Legacy function - use tapo_controller.test_connection instead # Legacy function - use tapo_controller.test_connection instead
def test_tapo_connection(*args, **kwargs): def test_tapo_connection(*args, **kwargs):
return tapo_controller.test_connection(*args, **kwargs) return tapo_controller.test_connection(*args, **kwargs)
@@ -620,6 +621,92 @@ class BackgroundTaskScheduler:
pass pass
return False 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-Instanz erzeugen
scheduler = BackgroundTaskScheduler() scheduler = BackgroundTaskScheduler()

View 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