806 lines
32 KiB
Markdown
806 lines
32 KiB
Markdown
# 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)
|
|
|