# Implementation Plan: IoT Bridge & Odoo Integration **Ziel:** Sidecar-Container-Architektur mit periodischer Session-Aggregation **Stand:** 12.02.2026 (aktualisiert nach Autonomie-Fix) **Strategie:** Bridge zuerst (standalone testbar), dann Odoo API, dann Integration --- ## 📦 Bestandsaufnahme ### Vorhanden (wiederverwendbar) - ✅ `python_prototype/` - Standalone MQTT Client & Session Detector (Basis für Bridge) - ✅ `services/mqtt_client.py` - Odoo-integrierter MQTT Client (Referenz) - ✅ `services/session_detector.py` - State Machine Logik (portierbar) - ✅ `services/parsers/shelly_parser.py` - Shelly PM Parser (direkt übernehmen) - ✅ `models/` - Odoo Models (anzupassen) - ✅ Mosquitto MQTT Broker (läuft) ### Neu zu erstellen (Architektur-Änderung) - ✅ `iot_bridge/` Container-Source (Skeleton existiert) - ✅ Odoo REST API Controller für Event-Empfang (bereits vorhanden) - ✅ **Bridge HTTP Server** für Config-Empfang (`POST /config`) - ✅ **Odoo Config-Push-Logik** (Model-Hooks für device create/write) - ✅ **Dynamic Subscription Management** in Bridge (Topics zur Laufzeit ändern) - ✅ Session-Aggregation in Bridge - ✅ Odoo Session-Model mit Billing-Logik - ✅ Docker Compose Integration --- ## 🎯 Phase 1: Bridge Container (Standalone) **Ziel:** Bridge läuft unabhängig, loggt auf Console, nutzt YAML-Config ### 1.1 Bridge Grundstruktur ✅ - [x] `iot_bridge/config.yaml` erstellen (Device-Registry, MQTT Settings) - [x] `iot_bridge/config.py` - Config Loader (YAML → Dataclass) - [x] Mock-OdooClient (gibt hardcoded Config zurück, kein HTTP) - [x] Logger Setup (structlog mit JSON output) **Test:** ✅ Bridge startet, lädt Config, loggt Status (JSON), Graceful Shutdown funktioniert --- --- ### 1.2 MQTT Client portieren ✅ - [x] `python_prototype/mqtt_client.py` → `iot_bridge/mqtt_client.py` - [x] Shelly Parser integrieren (copy from `services/parsers/`) - [x] Connection Handling (reconnect, error handling) - [x] Message-Callback registrieren - [x] TLS/SSL Support (Port 8883) **Test:** ✅ Bridge empfängt Shelly-Messages (40-55W), parst apower, loggt auf Console (JSON), TLS funktioniert **Reconnect Test:** ✅ Broker neugestartet → Bridge disconnected (rc=7) → Auto-Reconnect → Re-Subscribe → Messages wieder empfangen --- ### 1.3 Session Detector mit Aggregation ✅ - [x] `session_detector.py` portieren (5-State Machine) - [x] Aggregation-Logik hinzufügen: - Interner State-Tracker (1s Updates) - Timer für `heartbeat_interval_s` - Berechnung: `interval_working_s`, `interval_standby_s` - [x] Session-IDs generieren (UUID) - [x] Events sammeln (event_callback) - [x] Unit Tests (9 Tests, alle PASSED) - [x] Shelly Simulator für Testing - [x] Integration Tests (7 Tests, alle PASSED) - [x] Lokaler Mosquitto Container (docker-compose.dev.yaml) **Test:** ✅ Session gestartet (34.5W > 20W threshold), State-Machine funktioniert, Heartbeat-Events nach 10s **Unit Tests:** ✅ 9/9 passed (test_session_detector.py) **Integration Tests:** ✅ 7/7 passed (test_bridge_integration.py) - Session Start, Heartbeat, Multi-Device, Timeout --- ### 1.4 Event Queue & Retry Logic ✅ - [x] `event_queue.py`: Event-Queue mit Retry-Logic - Exponential Backoff (1s → 2s → 4s → ... → max 60s) - Max 10 Retries - Background-Thread für Queue-Processing - Thread-safe mit `collections.deque` und `threading.Lock` - [x] `event_uid` zu allen Events hinzufügen (UUID) - [x] Queue-Integration in `main.py` - `on_event_generated()` nutzt Queue statt direktem send - Queue-Start/Stop im Lifecycle - [x] Mock-Odoo Failure-Simulation - `mock_failure_rate` in config.yaml (0.0-1.0) - MockOdooClient wirft Exceptions bei failures - [x] Unit Tests: `test_event_queue.py` (13 Tests, alle PASSED) - Queue Operations (enqueue, statistics) - Exponential Backoff Berechnung - Retry Logic mit Mock-Callback - Max Retries exceeded - [x] Integration Tests: `test_retry_logic.py` (2 Tests, PASSED in 48.29s) - test_retry_on_odoo_failure: Events werden enqueued - test_eventual_success_after_retries: 50% failure rate → eventual success **Test:** ✅ Mock-Odoo-Client gibt 500 → Events in Queue → Retry mit Backoff → Success **Unit Tests:** ✅ 13/13 passed **Integration Tests:** ✅ 2/2 passed in 48.29s --- ### 1.5 Docker Container Build - [x] Multi-stage `Dockerfile` finalisiert - Builder Stage: gcc + pip install dependencies - Runtime Stage: minimal image, non-root user (bridge:1000) - Python packages korrekt nach /usr/local/lib/python3.11/site-packages kopiert - [x] `.dockerignore` erstellt (venv/, tests/, __pycache__, logs/, data/) - [x] `requirements.txt` erweitert mit PyYAML>=6.0 - [x] `docker build -t iot_mqtt_bridge:latest` - 3 Build-Iterationen (Package-Path-Fix erforderlich) - Finale Image: 8b690d20b5f7 - [x] `docker run` erfolgreich getestet - Volume mount: config.yaml (read-only) - Network: host (für MQTT-Zugriff) - Container verbindet sich mit mqtt.majufilo.eu:8883 - Sessions werden erkannt und Events verarbeitet - [x] Development Setup - `config.yaml.dev` für lokale Tests (Mosquitto + Odoo) - `docker-compose.dev.yaml` erweitert mit iot-bridge service **Test:** ✅ Container läuft produktiv, empfängt MQTT, erkennt Sessions **Docker:** ✅ Multi-stage build, 71MB final image, non-root user --- ## 🎯 Phase 2: Odoo REST API **Ziel:** Odoo REST API für Bridge-Config und Event-Empfang ### 2.1 Models anpassen - [x] **ows.iot.event** Model erstellt (`models/iot_event.py`) - `event_uid` (unique constraint) - `event_type` (selection: session_started/updated/stopped/timeout/heartbeat/power_change) - `payload_json` (JSON Field) - `device_id`, `session_id` (Char fields) - Auto-Linking zu `mqtt.device` und `mqtt.session` - Payload-Extraktion: `power_w`, `state` - `processed` flag, `processing_error` für Error-Tracking - [x] **mqtt.device** erweitert: - `device_id` (External ID für Bridge API) - Bestehende Felder: `strategy_config` (JSON), session_strategy, parser_type, topic_pattern - [x] **mqtt.session** - bereits vorhanden: - `session_id` (external UUID) - Duration fields: `total_duration_s`, `standby_duration_s`, `working_duration_s` - State tracking: `status` (running/completed), `current_state` - Power tracking: `start_power_w`, `end_power_w`, `current_power_w` **Test:** ✅ Models erstellt, Security Rules aktualisiert --- ### 2.2 REST API Controller - [x] `controllers/iot_api.py` erstellt - [x] **GET /ows/iot/config**: - Returns: Alle aktiven Devices mit `session_config` als JSON - Auth: public (später API-Key möglich) - Response Format: ```json { "status": "success", "devices": [{ "device_id": "...", "mqtt_topic": "...", "parser_type": "...", "machine_name": "...", "session_config": { strategy, thresholds, ... } }], "timestamp": "ISO8601" } ``` - [x] **POST /ows/iot/event**: - Schema-Validation (event_type, device_id, event_uid, timestamp required) - Event-UID Duplikat-Check → 409 Conflict (idempotent) - Event speichern in `ows.iot.event` - Auto-Processing: Session erstellen/updaten/beenden - Response Codes: 201 Created, 409 Duplicate, 400 Bad Request, 500 Error - JSON-RPC Format (Odoo type='json') **Test:** ✅ Controller erstellt, bereit für manuellen Test ```bash curl http://localhost:8069/ows/iot/config curl -X POST http://localhost:8069/ows/iot/event -d '{...}' ``` --- ### 2.3 Bridge Client - OdooClient - [x] **OdooClient** implementiert (`iot_bridge/odoo_client.py`) - ~~`get_config()`: GET /ows/iot/config~~ → **ENTFERNEN (nicht mehr verwendet)** - `send_event()`: POST /ows/iot/event (JSON-RPC) → **BEHALTEN** - Retry-Logic über EventQueue (bereits in Phase 1.4) - Duplicate Handling: 409 wird als Success behandelt - Error Handling: Exceptions für 4xx/5xx - HTTP Session mit requests library - [x] **config.py** erweitert: - `OdooConfig`: `base_url`, `database`, `username`, `api_key` - Entfernt: alte `url`, `token` Felder - [x] **main.py** angepasst: - OdooClient Initialisierung mit neuen Parametern - Conditional: MockOdooClient (use_mock=true) oder OdooClient (use_mock=false) **Test:** ✅ Code fertig, bereit für Integration Test --- ### 2.4 Integration Testing (Alt-Status, vor Architektur-Änderung) - [x] Odoo Modul upgraden (Models + Controller laden) - ✅ Module installed in odoo database - ✅ Deprecation warnings (non-blocking, documented) - [x] mqtt.device angelegt: "Shaper Origin" (device_id: shellypmminig3-48f6eeb73a1c) - ✅ Widget fix: CodeEditor 'ace' → 'text' (Odoo 18 compatibility) - ✅ connection_id made optional (deprecated legacy field) - [x] API manuell getestet - ✅ GET /ows/iot/config → HTTP 200, returns device list - ✅ POST /ows/iot/event → HTTP 200, creates event records - [x] `config.yaml.dev`: `use_mock=false` gesetzt - [x] Docker Compose gestartet: Odoo + Mosquitto + Bridge - [x] End-to-End Tests: - ✅ MQTT → Bridge → Odoo event flow working - ✅ Bridge fetches config from Odoo (1 device) - ✅ Events stored in ows_iot_event table - ✅ session_started creates mqtt.session automatically - ✅ session_heartbeat updates session (controller fix applied) - [x] Odoo 18 Compatibility Fixes: - ✅ type='json' routes: Use function parameters instead of request.jsonrequest - ✅ event_type Selection: Added 'session_heartbeat' - ✅ Timestamp parsing: ISO format → Odoo datetime format - ✅ Multiple databases issue: Deleted extra DBs (hh18, odoo18) for db_monodb() - ✅ auth='none' working with single database **Outstanding Issues:** - [x] UI Cleanup - MQTT Connection (deprecated) view removal - ✅ Menu items hidden (commented out in mqtt_menus.xml) - ✅ mqtt.message menu hidden (replaced by ows.iot.event) - ✅ New "IoT Events" menu added - [x] UI Cleanup - mqtt.device form: - ✅ connection_id field hidden (invisible="1") - ✅ device_id field prominent ("External Device ID") - ✅ Parser Type + Session Strategy fields clarified - ✅ Help text: "Configuration is sent to IoT Bridge via REST API" - ✅ Strategy config placeholder updated with all required fields - [x] ows.iot.event Views created: - ✅ List view with filters (event type, processed status) - ✅ Form view with payload display - ✅ Search/filters: event type, device, session, date grouping - ✅ Security rules configured - [x] Create Test Device in Odoo: - ✅ "Test Device - Manual Simulation" (device_id: test-device-manual) - ✅ Low thresholds for easy testing (10W standby, 50W working) - ✅ Test script created: tests/send_test_event.sh - [x] Verify session auto-create/update workflow: - ✅ session_started creates mqtt.session - ✅ session_heartbeat updates durations + power - ✅ total_duration_s = total_working_s + total_standby_s (calculated in controller) **Next Steps:** - [ ] UI Testing: Login to Odoo and verify: - [ ] MQTT -> Devices: See "Shaper Origin" + "Test Device" - [ ] MQTT -> Sessions: Check duration calculations (Total = Working + Standby) - [ ] MQTT -> IoT Events: Verify event list, filters work - [ ] No "Connections" or "Messages" menus visible - [ ] Duration Field Validation: - [ ] Check if Total Hours displays correctly (computed from _s fields) - [ ] Verify Standby Hours + Working Hours sum equals Total Hours **Test:** ⏳ Core functionality working, UI cleanup needed --- ## 🎯 Phase 2.6: **KRITISCHE FIXES** - Autonomie & Event-Type-Kompatibilität ✅ **Ziel:** Bridge resilient machen (läuft ohne Odoo) und Session-Closing fixen ### 2.6.1 Bridge Autonomie-Fix (CRITICAL) ✅ **Problem:** Bridge crashed in Endlosschleife wenn Odoo nicht erreichbar war **Ursache:** ```python # iot_bridge/main.py:113 try: device_config = odoo_client.get_config() except Exception as e: logger.error("config_load_failed", error=str(e)) sys.exit(1) # ← Container crash → Docker restart → Endlosschleife! ``` **Lösung:** ✅ - [x] `sys.exit(1)` entfernt - [x] Odoo-Config-Check wird zu optionalem Warning (nicht blocker) - [x] Bridge läuft autark mit lokaler `config.yaml` - [x] Loggt: `bridge_autonomous_mode` + `odoo_not_reachable` (warning) **Test:** ✅ - [x] Bridge startet ohne Odoo → läuft stabil (kein Crash) - [x] Session-Detection funktioniert autark - [x] Events werden in Queue gesammelt - [x] Bei Odoo-Reconnect: Queue wird automatisch geleert --- ### 2.6.2 Event-Type Kompatibilität Fix ✅ **Problem:** Sessions blieben offen - `session_ended` Events wurden ignoriert **Ursache:** - Bridge sendet: `session_ended` - Controller erwartet: `session_stopped` - Mismatch → Event wird nicht verarbeitet → Session bleibt `status='running'` **Lösung:** ✅ - [x] `iot_event.py`: `session_ended` zu Selection hinzugefügt - [x] `iot_api.py`: Controller akzeptiert beide Event-Types ```python elif event.event_type in ['session_stopped', 'session_ended', 'session_timeout']: ``` **Test:** ✅ - [x] 3 offene Sessions manuell geschlossen via Re-Send - [x] Neue Sessions werden korrekt geschlossen - [x] Keine 500 Errors mehr bei `session_ended` Events --- ### 2.6.3 Ergebnis: Resiliente Architektur ✅ **Vorher:** - ❌ Bridge crashed ohne Odoo (Endlosschleife) - ❌ Sessions blieben offen (Event-Type-Mismatch) - ❌ Keine Autarkie **Nachher:** - ✅ Bridge läuft autark mit lokaler `config.yaml` - ✅ Events werden in Queue gesammelt bei Odoo-Ausfall - ✅ Automatic Retry mit Exponential Backoff (bis zu 10 Versuche) - ✅ Bei Odoo-Wiederverbindung: Queue wird automatisch geleert - ✅ Sessions werden korrekt geschlossen (`session_ended` wird verarbeitet) **Commit:** ✅ `18cac26 - fix: Make IoT Bridge autonomous and fix session closing` --- ## 🎯 Phase 3: **ARCHITEKTUR-ÄNDERUNG** - Odoo konfiguriert Bridge (PUSH statt PULL) **Ziel:** Umkehrung der Config-Flow-Richtung: Odoo pusht Config an Bridge (statt Bridge holt Config) ### 3.1 Bridge: HTTP Server für Config-Empfang ✅ **Status:** ✅ ABGESCHLOSSEN (12.02.2026) **Implementiert:** - [x] **HTTP Server implementieren** (FastAPI) - Port 8080 (konfigurierbar via ENV `BRIDGE_PORT`) - Endpoint: `POST /config` - Config von Odoo empfangen - Endpoint: `GET /config` - Aktuelle Config abfragen - Endpoint: `GET /health` - Health Check - Authentifizierung: Bearer Token (optional, ENV `BRIDGE_API_TOKEN`) - [x] **Config-Validation & Processing**: - Pydantic Models für JSON Schema Validation (BridgeConfig, DeviceConfig, SessionConfig) - Validierung: unique device_id, unique mqtt_topic, working_threshold_w > standby_threshold_w - DeviceManager: Device diff (add/remove/update detection) - MQTT Subscriptions dynamisch updaten (subscribe/unsubscribe) - Session Detectors erstellen/updaten/entfernen - [x] **Config-Persistence**: - Schreibe empfangene Config nach `/data/config-active.yaml` - Beim Bridge-Restart: Lade `/data/config-active.yaml` (Fallback vor config.yaml) - Volume `iot-bridge-data:/data` in docker-compose.dev.yaml - [x] **Health-Endpoint**: `GET /health` → `{ "status": "ok", "devices": 2, "subscriptions": 2, "last_config_update": "..." }` **Neue Dateien:** - `iot_bridge/config_server.py` - FastAPI Server mit Config API - `iot_bridge/device_manager.py` - Dynamisches Device/MQTT Subscription Management - `iot_bridge/tests/test-config-push.sh` - Test-Skript für Config Push - `iot_bridge/tests/test-config-push.json` - Test-Config (2 Devices) **Geänderte Dateien:** - `iot_bridge/main.py` - Integration HTTP Server (Thread), DeviceManager, config-active.yaml Fallback - `iot_bridge/mqtt_client.py` - subscribe()/unsubscribe() Methoden für dynamische Topics - `iot_bridge/requirements.txt` - FastAPI, Uvicorn, Pydantic hinzugefügt - `iot_bridge/Dockerfile` - EXPOSE 8080, HEALTHCHECK via HTTP - `odoo/docker-compose.dev.yaml` - Bridge Port 8080, Volume /data, ENV BRIDGE_PORT **Test-Durchführung:** ```bash # 1. Bridge neu bauen und starten cd iot_bridge && docker build -t iot_mqtt_bridge:latest . cd ../odoo && docker compose -f docker-compose.dev.yaml restart iot-bridge # 2. Health Check curl http://localhost:8080/health # → {"status": "ok", "devices": 2, "subscriptions": 2, "last_config_update": null} # 3. Config Push cd ../iot_bridge/tests ./test-config-push.sh # Testet: # - POST /config mit test-config-push.json (2 Devices) # - Validierung: HTTP 200, Config applied # - Health Check: last_config_update gesetzt # - GET /config: Neue Config wird zurückgegeben # 4. Persistence verifizieren docker exec hobbyhimmel_odoo_18-dev_iot_bridge cat /data/config-active.yaml # → YAML-File mit neuer Config # 5. Bridge-Logs prüfen docker logs hobbyhimmel_odoo_18-dev_iot_bridge | grep -E "(config_received|device_added|device_removed)" # → Logs zeigen: # - config_received (2 devices) # - config_persisted (/data/config-active.yaml) # - device_removed (alte Devices: testshelly, shaperorigin) # - device_added (neue Devices: shaper-origin-pm, test-device-manual) # - mqtt unsubscribe/subscribe für neue Topics ``` **Ergebnis:** - ✅ Config Push funktioniert (HTTP 200, devices_configured: 2) - ✅ Alte Devices werden entfernt (MQTT unsubscribe) - ✅ Neue Devices werden hinzugefügt (MQTT subscribe, SessionDetector erstellt) - ✅ Config wird persistiert nach `/data/config-active.yaml` - ✅ Bridge läuft autark weiter (config-active.yaml wird beim Restart geladen) - ✅ Health Endpoint zeigt last_config_update Timestamp --- ### 3.2 Odoo: Config-Push-System ✅ **Status:** ✅ ABGESCHLOSSEN (12.02.2026) **Implementiert:** - [x] **Model-Hooks in `mqtt.device`**: - Override `create()`: Nach Device-Erstellung → `_push_bridge_config()` ✅ - Override `write()`: Nach Device-Update → `_push_bridge_config()` ✅ - Override `unlink()`: Nach Device-Löschung → `_push_bridge_config()` ✅ - Smart detection: Nur bei relevanten Feldern (active, device_id, topic_pattern, etc.) - [x] **Config-Builder**: `_build_bridge_config()`✅ - Sammelt alle `mqtt.device` mit `active=True` - Konvertiert zu Bridge-JSON-Format (BridgeConfig Schema) - Topic Pattern Transformation (/# → /status/pm1:0) - Session Config Extraktion aus strategy_config JSON - Returns: `{"devices": [...], "timestamp": "...", "version": "1.0"}` - [x] **HTTP-Client**: `_push_bridge_config()` ✅ - `requests.post(f"{bridge_url}/config", json=config, timeout=10)` - Retry-Logic: 3 Versuche mit exponential backoff (1s, 2s, 4s) - Error-Handling: Loggt Fehler, wirft KEINE Exception (non-blocking) - Response: dict mit success/message für UI-Notifications - Unterscheidung: 4xx = kein Retry, 5xx = Retry - [x] **System Parameter**: `open_workshop_mqtt.bridge_url` ✅ - Default: `http://iot-bridge:8080` - Gespeichert in `data/system_parameters.xml` - Abruf via `ir.config_parameter.get_param()` - [x] **Manual Push**: Button in Device-List-View ✅ - "🔄 Push Config to Bridge" Button im List Header - Ruft `action_push_config_to_bridge()` auf - UI Success/Error Notifications (green/red toast) - [x] **External Dependencies** ✅ - `requests` library zu `__manifest__.py` hinzugefügt **Neue/Geänderte Dateien:** - `models/mqtt_device.py` - +250 Zeilen (Config Builder, HTTP Client, Hooks) - `data/system_parameters.xml` - System Parameter für Bridge URL - `views/mqtt_device_views.xml` - Manual Push Button - `__manifest__.py` - requests dependency, data file - `tests/test_config_push_integration.py` - Integration Test Script **Test-Durchführung:** ```bash # Manuelle Tests: # 1. Odoo UI öffnen: http://localhost:9018 # 2. MQTT -> Devices -> "🔄 Push Config to Bridge" Button # 3. Erfolgs-Notification erscheint # 4. Bridge Logs prüfen: docker logs hobbyhimmel_odoo_18-dev_iot_bridge | grep config_received # 5. Device erstellen/ändern/löschen # -> Config wird automatisch gepusht (model hooks) # Bridge Config überprüfen: curl http://localhost:8080/config | jq '.devices[].machine_name' curl http://localhost:8080/health | jq '.' ``` **Ergebnis:** - ✅ Model Hooks funktionieren (create/write/unlink) - ✅ Config Builder erstellt valide Bridge Config - ✅ HTTP Push mit Retry erfolgreich - ✅ Manual Push Button funktioniert - ✅ System Parameter wird geladen - ✅ Non-blocking: Odoo-UI funktioniert auch wenn Bridge offline --- ### 3.3 Refactoring: Config-Removal in Bridge ✅ **Status:** ✅ ABGESCHLOSSEN (12.02.2026) **Implementiert:** - [x] **Entfernt**: `odoo_client.get_config()` Methode (nicht mehr verwendet) - Entfernt aus `MockOdooClient` (LEGACY PULL-Logik) - Entfernt aus `OdooClient` (LEGACY PULL-Logik) - [x] **Verifiziert**: Keine periodische Config-Refresh-Logik in `main.py` - Bridge lädt config-active.yaml beim Start - Config wird nur via POST /config empfangen (von Odoo gepusht) - Keine GET-Requests an Odoo mehr für Config - [x] **Beibehalten**: `odoo_client.send_event()` für Event-Push ✅ - [x] **main.py Startup-Flow geprüft**: 1. Lade lokale `/data/config-active.yaml` (falls vorhanden) ✅ 2. Starte HTTP Server (Port 8080) ✅ 3. Warte auf MQTT + Config-Updates ✅ 4. Laufe autark mit lokaler Config ✅ **Geänderte Dateien:** - `iot_bridge/odoo_client.py` - ~40 Zeilen entfernt (2x get_config() Methoden) **Ergebnis:** - ✅ Bridge hat keine PULL-Logik mehr - ✅ Nur PUSH-Architektur: Odoo → POST /config → Bridge - ✅ Bridge läuft vollständig autark (config-active.yaml) - ✅ Keine periodischen Config-Requests an Odoo - ✅ Sauberer Code ohne Legacy-Logik **Test:** - [x] Bridge startet ohne Odoo → lädt config-active.yaml → subscribed Topics ✅ - [x] Bridge startet ohne config-active.yaml → wartet auf Push (loggt Warning) ✅ --- ### 3.4 Docker Compose Integration - [x] **Bridge Container**: - Volume: `/data` für config-active.yaml Persistence - ENV: `BRIDGE_PORT=8080`, `BRIDGE_API_TOKEN=...` (optional) - Expose Port 8080 (nur zu Odoo, nicht nach außen) - [x] **Odoo Container**: - ENV: `IOT_BRIDGE_URL=http://iot-bridge:8080` - [x] **Network**: Shared Network `odoo18-nw` (bereits vorhanden) **Test:** `docker compose up -d` → alle Services starten → Config-Push funktioniert --- ### 3.5 End-to-End Tests (Neue Architektur) ✅ **Status:** ✅ ABGESCHLOSSEN (12.02.2026) **Implementiert:** - [x] **Automated E2E Test Suite** (`iot_bridge/tests/test_e2e_push_architecture.py`) - Python-basiertes Test-Framework - Vollautomatische Tests ohne manuelle Interaktion - Farbiger Output mit detaillierter Fehlerdiagnose - Docker-Integration für Bridge Restart Tests **Test-Szenarien (alle ✅ PASSED):** - [x] **Test 1: Device Create → Config Push** ✅ - Device in Odoo erstellen via JSON-RPC API - Model Hook triggert automatischen Config Push - Bridge empfängt Config via POST /config - Bridge Device Count erhöht sich (2 → 3) - Device-ID im Bridge Config vorhanden - **Ergebnis:** ✅ PASSED - [x] **Test 2: Device Update → Config Push** ✅ - Device working_threshold_w ändern (50W → 75W) - Model Hook triggert automatischen Config Push - Bridge empf ängt Update - Bridge Session Detector Config aktualisiert - Neue Schwellenwerte in Bridge Config - **Ergebnis:** ✅ PASSED - [x] **Test 3: Manual Push Button** ✅ - Manual Push Button via Odoo API triggern - Bridge empfängt Config - Config Timestamp ändert sich - **Ergebnis:** ✅ PASSED - [x] **Test 4: Device Delete → Config Remove** ✅ - Device in Odoo löschen - Model Hook triggert automatischen Config Push - Bridge entfernt Device - Bridge Device Count verringert sich (3 → 2) - Device-ID NICHT mehr in Bridge Config - **Ergebnis:** ✅ PASSED - [x] **Test 5: Bridge Restart → Config Persistence** ✅ - Bridge Container neu starten (docker restart) - Bridge lädt config-active.yaml automatisch - Device Count bleibt erhalten (2 Devices) - Device IDs bleiben erhalten - Bridge läuft ohne Odoo-Zugriff weiter (autark) - **Ergebnis:** ✅ PASSED **Test-Ausführung:** ```bash cd iot_bridge python3 tests/test_e2e_push_architecture.py # Output: # ====================================================================== # End-to-End Tests: PUSH Architecture (Phase 3.5) # ====================================================================== # Device Create..................................... ✓ PASSED # Device Update..................................... ✓ PASSED # Manual Push....................................... ✓ PASSED # Device Delete..................................... ✓ PASSED # Bridge Restart.................................... ✓ PASSED # # Result: 5/5 tests passed # ====================================================================== # ALL TESTS PASSED ✓ # ====================================================================== ``` **Neue Dateien:** - `iot_bridge/tests/test_e2e_push_architecture.py` - Vollautomatisches E2E Test-Suite **Ergebnis:** - ✅ Alle 5 End-to-End Tests bestanden - ✅ PUSH-Architektur vollständig funktionsfähig - ✅ Model Hooks triggern automatisch Config Push - ✅ Manual Push Button funktioniert - ✅ Bridge Config Persistence funktioniert (config-active.yaml) - ✅ Bridge läuft autark ohne Odoo - ✅ No Volume Deletion required (Tests laufen mit running infrastructure) --- ## 🎯 Phase 4: Polishing & Dokumentation ### 4.1 Error Handling & Monitoring - [x] Bridge: Structured Logging (JSON) - [x] Odoo: Event-Processing Errors loggen - [x] Metriken: Events sent/failed, Session count - [x] Health-Checks für beide Services --- ### 4.2 Dokumentation - [x] `iot_bridge/README.md` aktualisieren (ENV vars, Config) - [x] `DEPLOYMENT.md` - Produktiv-Setup Guide - [x] API Docs - REST Endpoints dokumentieren - [x] Troubleshooting Guide --- ## 📊 Dependency Graph ``` Phase 1.1-1.4 (Bridge Core) ↓ Phase 1.5 (Docker) ↓ Phase 2.1-2.3 (Odoo API) ← kann parallel zu 1.1-1.4 ↓ Phase 2.4 (Integration Testing) ↓ Phase 2.6 (Autonomie-Fix) ✅ ↓ Phase 3.1 (Bridge HTTP Server) ✅ ↓ Phase 3.2 (Odoo Config-Push) ✅ ↓ Phase 3.3 (Legacy Code Cleanup) ✅ ↓ Phase 3.5 (E2E Tests) ✅ ↓ Phase 4 (Polish & Dokumentation) ✅ ``` --- ## 🚀 Quick Start (nächster Schritt) **Aktueller Stand:** ✅ Phase 1 & 2 abgeschlossen, Phase 2.6 (Autonomie-Fix) abgeschlossen, **Phase 3.1 & 3.2 abgeschlossen** (PUSH-Architektur vollständig) **Nächste Schritte:** 1. [x] Phase 3.1: Bridge HTTP Server implementieren (`POST /config`) ✅ 2. [x] Phase 3.2: Odoo Config-Push-System (Model-Hooks) ✅ 3. [x] Phase 3.3: Refactoring (get_config() entfernen) ✅ 4. [x] Phase 3.4: Docker Compose Integration (Volumes für config-active.yaml) ✅ (bereits in 3.1 erledigt) 5. [x] Phase 3.5: End-to-End Tests (neue Architektur) ✅ 6. [x] Phase 4: Polishing & Dokumentation ✅ --- ## ✅ Definition of Done **Phase 1 Done:** ✅ ABGESCHLOSSEN - [x] Bridge läuft als Docker Container ✅ - [x] Empfängt Shelly-Messages ✅ - [x] State-Detection + Aggregation funktioniert ✅ - [x] Loggt aggregierte Events auf Console ✅ **Phase 2 Done:** ✅ ABGESCHLOSSEN - [x] Odoo REST API antwortet ✅ - [x] Events werden in DB gespeichert ✅ - [x] Sessions werden erstellt/aktualisiert ✅ - [x] Billing Units werden berechnet ✅ **Phase 2.6 Done:** ✅ ABGESCHLOSSEN (12.02.2026) - [x] Bridge läuft autark ohne Odoo ✅ - [x] Event-Queue mit Retry-Mechanismus ✅ - [x] Session-Closing funktioniert (session_ended Support) ✅ - [x] Keine Endlosschleife bei Odoo-Ausfall ✅ **Phase 3.1 Done:** ✅ ABGESCHLOSSEN (12.02.2026) - [x] Bridge HTTP Server (`POST /config`, `GET /config`, `GET /health`) ✅ - [x] FastAPI mit Pydantic Validation ✅ - [x] Dynamic Subscription Management (DeviceManager) ✅ - [x] Config-Persistence in Bridge (`/data/config-active.yaml`) ✅ - [x] Device Add/Update/Remove mit MQTT Subscribe/Unsubscribe ✅ **Phase 3.2 Done:** ✅ ABGESCHLOSSEN (12.02.2026) - [x] Model Hooks (create/write/unlink) ✅ - [x] Config Builder (_build_bridge_config) ✅ - [x] HTTP Client mit Retry-Logic ✅ - [x] System Parameter (bridge_url) ✅ - [x] Manual Push Button in UI ✅ **Phase 3.3 Done:** ✅ ABGESCHLOSSEN (12.02.2026) - [x] get_config() aus MockOdooClient entfernt ✅ - [x] get_config() aus OdooClient entfernt ✅ - [x] Keine periodische Config-Refresh Logik ✅ - [x] Bridge läuft vollständig mit PUSH-only Architektur ✅ **Phase 3.5 Done:** ✅ ABGESCHLOSSEN (12.02.2026) - [x] Automated E2E Test Suite (5 Tests) ✅ - [x] Test 1: Device Create → Config Push ✅ - [x] Test 2: Device Update → Config Push ✅ - [x] Test 3: Manual Push Button ✅ - [x] Test 4: Device Delete → Config Remove ✅ - [x] Test 5: Bridge Restart → Config Persistence ✅ - [x] All tests PASSED (5/5) ✅ **Phase 3 Done:** ✅ ABGESCHLOSSEN (12.02.2026) - [x] Bridge HTTP Server (POST /config) ✅ - [x] Odoo Config-Push System ✅ - [x] Dynamic Subscription Management ✅ - [x] Config-Persistence in Bridge (`/data/config-active.yaml`) ✅ - [x] Legacy Code Cleanup (get_config() entfernt) ✅ - [x] End-to-End Tests (5/5 passed) ✅ **Phase 4 Done:** ✅ ABGESCHLOSSEN (12.02.2026) - [x] Dokumentation vollständig - [x] Error Handling robust - [x] Produktiv-Ready --- ## 📊 Aktueller Gesamtstatus (12.02.2026) ### Funktioniert ✅ - ✅ Docker Compose Setup (Odoo, PostgreSQL, Mosquitto, Bridge, pgAdmin) - ✅ Bridge läuft autark mit lokaler config.yaml oder config-active.yaml - ✅ **HTTP Config API (Port 8080): POST /config, GET /config, GET /health** - ✅ **Dynamic Device Management (DeviceManager mit Add/Update/Remove)** - ✅ **Config Persistence nach /data/config-active.yaml** - ✅ **Odoo Config-Push System (Model Hooks: create/write/unlink)** - ✅ **Manual Push Button in Odoo UI ("🔄 Push Config to Bridge")** - ✅ **Config Builder (sammelt alle active devices, konvertiert zu Bridge-JSON)** - ✅ **HTTP Client mit Retry (3 Versuche, exponential backoff)** - ✅ **System Parameter: open_workshop_mqtt.bridge_url** - ✅ **Legacy Code Cleanup (get_config() vollständig entfernt)** - ✅ **End-to-End Tests (5/5 passed):** - ✅ Device Create → Config Push - ✅ Device Update → Config Push - ✅ Manual Push Button - ✅ Device Delete → Config Remove - ✅ Bridge Restart → Config Persistence - ✅ **IoT Bridge Status Monitoring:** - ✅ `ows.mqtt.bridge` Model mit Health-Status-Feldern - ✅ Bridge List/Form Views mit Status-Ampel - ✅ Scheduled Action für Health-Checks (alle 2 Min) - ✅ Status: Online ✅ / Offline ❌ / Unknown ⚠️ - ✅ Health-Metriken: Devices Count, Subscriptions, Last Seen - ✅ Manual Health-Check Button "Check Health Now" - ✅ Smart Button: Broker → Bridge Relation - ✅ **Dynamic MQTT Reconnection (ohne Bridge-Restart):** - ✅ Automatische Reconnection bei Broker-Änderungen (host/port/auth) - ✅ TLS-Änderungen erfordern manuellen Restart (paho-mqtt Limitation) - ✅ Robuster Startup (Bridge startet auch bei MQTT-Fehler) - ✅ MQTT Subscription (dynamisch, automatisches Subscribe/Unsubscribe) - ✅ Session Detection Engine (5-State Machine) - ✅ Event-Aggregation (30s Heartbeat-Intervall) - ✅ Event-Queue mit Retry-Logic (Exponential Backoff, max 10 retries) - ✅ Odoo REST API: `POST /ows/iot/event` - ✅ Sessions werden korrekt erstellt/aktualisiert/geschlossen - ✅ Shelly Simulator für Testing - ✅ Test-Skript für Config Push (`iot_bridge/tests/test-config-push.sh`) - ✅ Integration Test Script (`extra-addons/.../tests/test_config_push_integration.py`) - ✅ **Automated End-to-End Test Suite (Phase 3.5 - 5/5 tests passed)** ### Nächste Aufgabe 🎯 - ✅ Phase 4 abgeschlossen ### Architektur-Status ✅ - ✅ Bridge ist autark-fähig (läuft ohne Odoo) - ✅ Bridge kann Config via HTTP empfangen (POST /config) - ✅ Odoo pusht Config automatisch (Model Hooks) - ✅ Manual Push Button in UI verfügbar - ✅ Legacy get_config() vollständig entfernt (nur PUSH-Architektur) - ✅ End-to-End Tests validieren gesamte PUSH-Architektur (5/5 passed)