🎉 Improved documentation and code organization in Backend 🌐
This commit is contained in:
65
README.md
65
README.md
@@ -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
|
||||||
|
|
||||||
|
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"])
|
@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():
|
||||||
|
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 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)
|
||||||
|
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 - 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
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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)
|
||||||
|
@@ -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
|
||||||
|
@@ -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
|
||||||
|
@@ -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 - ==================================================
|
||||||
|
@@ -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
|
||||||
|
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: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)
|
||||||
|
@@ -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
|
||||||
|
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 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()
|
||||||
|
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