diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md index fcbce1c..0f4b10d 100644 --- a/DEPLOYMENT.md +++ b/DEPLOYMENT.md @@ -1,130 +1,57 @@ -# Deployment Guide +# Deployment Guide (Kompakt) -This guide covers production deployment of the IoT Bridge and its integration with Odoo. +**Stand:** 2026-02-19 -## Prerequisites +## 1) Zielbild -- Docker and Docker Compose -- Odoo 18 with `open_workshop_mqtt` installed -- Mosquitto (or any MQTT broker) +Produktiver Betrieb mit: +- Odoo 18 + `open_workshop_mqtt` +- IoT Bridge (`iot_bridge`) +- MQTT Broker (z. B. Mosquitto) +- Persistenz für Bridge-Daten (`/data/config-active.yaml`) -## Recommended Topology +## 2) Start (Dev/Integration) -- `odoo-dev` (Odoo 18) -- `iot_bridge` (this service) -- `mosquitto` (MQTT broker) -- Persistent volume for `/data` in the bridge container +Compose-Datei: `odoo/docker-compose.dev.yaml` -## Configuration - -### Required Environment Variables - -``` -MQTT_BROKER=mosquitto -MQTT_PORT=1883 -BRIDGE_PORT=8080 -ODOO_BASE_URL=http://odoo-dev:8069 -ODOO_DATABASE=odoo -ODOO_USERNAME=admin +Beispiel: +```bash +cd odoo +docker compose --env-file .env -f docker-compose.dev.yaml up -d ``` -### Optional Environment Variables +## 3) Pflicht-Konfiguration -``` -MQTT_USERNAME= -MQTT_PASSWORD= -MQTT_CLIENT_ID=iot_bridge -MQTT_USE_TLS=false -BRIDGE_API_TOKEN= -LOG_LEVEL=INFO -LOG_FORMAT=json -LOG_FILE= -``` +### Odoo → Bridge +- Systemparameter in Odoo: + - Key: `open_workshop_mqtt.bridge_url` + - Value: z. B. `http://iot-bridge:8080` -## Docker Compose Example +### API-Schutz (optional) +- Bridge-Token über `BRIDGE_API_TOKEN` +- Geschützte Aufrufe mit `Authorization: Bearer ` -``` -version: '3.8' +## 4) Betriebs-Checks -services: - iot-bridge: - build: ./iot_bridge - container_name: iot_bridge - restart: unless-stopped - ports: - - "8080:8080" - volumes: - - bridge-data:/data - environment: - - MQTT_BROKER=mosquitto - - MQTT_PORT=1883 - - BRIDGE_PORT=8080 - - ODOO_BASE_URL=http://odoo-dev:8069 - - ODOO_DATABASE=odoo - - ODOO_USERNAME=admin - networks: - - odoo18-nw - depends_on: - - mosquitto - - odoo-dev - healthcheck: - test: ["CMD", "curl", "-f", "http://localhost:8080/health"] - interval: 30s - timeout: 10s - retries: 3 - start_period: 10s +### Bridge +- Health: `GET http://:8080/health` +- Active Config: `GET http://:8080/config` +- API-Doku: `/docs`, `/redoc`, `/openapi.json` - mosquitto: - image: eclipse-mosquitto:2 - ports: - - "1883:1883" - volumes: - - ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf - networks: - - odoo18-nw +### End-to-End +1. Device in Odoo ändern/erstellen +2. Odoo pusht Config an Bridge (`POST /config`) +3. Bridge verarbeitet MQTT und sendet Events an Odoo (`POST /ows/iot/event`) -volumes: - bridge-data: +## 5) Häufige Fehlerbilder -networks: - odoo18-nw: - driver: bridge -``` +- **Bridge nicht erreichbar:** `bridge_url`/Netzwerk prüfen +- **MQTT-Verbindung instabil:** Broker-Host/Port/TLS prüfen +- **Keine Events in Odoo:** Bridge-Logs + Odoo-Controller-Logs prüfen +- **Session-Anomalien:** aktuelle Event-Historie in `ows.iot.event` gegen `ows.mqtt.session` prüfen -## Odoo Configuration +## 6) Referenzen -Set the bridge URL in Odoo (System Parameters): - -- Key: `open_workshop_mqtt.bridge_url` -- Value: `http://iot-bridge:8080` - -When devices are created/updated/deleted, Odoo will push the config to the bridge. - -## Persistence - -Bridge config is stored in `/data/config-active.yaml` inside the container. -Make sure the `/data` volume is persisted to survive restarts. - -## Health Checks - -- `GET /health` exposes current status. -- Use Docker health checks to monitor readiness. - -## Security Notes - -- Set `BRIDGE_API_TOKEN` to protect POST /config. -- Run on an internal network; expose the port only if necessary. -- Use TLS for MQTT if broker supports it. - -## Rollback - -- Stop the bridge container. -- Restore the previous image tag and restart. -- The config file remains in `/data/config-active.yaml`. - -## Validation Checklist - -- `GET /health` returns `status: ok`. -- `GET /config` returns the current device list. -- Odoo device changes trigger POST /config. -- MQTT subscriptions match configured devices. +- Bridge Betrieb & Debugging: `iot_bridge/DEVELOPMENT.md` +- Bridge Architektur: `iot_bridge/ARCHITECTURE.md` +- Odoo Addon API: `extra-addons/open_workshop/open_workshop_mqtt/API.md` diff --git a/DOCUMENTATION_STRATEGY.md b/DOCUMENTATION_STRATEGY.md index 4784141..50f559a 100644 --- a/DOCUMENTATION_STRATEGY.md +++ b/DOCUMENTATION_STRATEGY.md @@ -1,819 +1,45 @@ -# API Documentation Strategy -## Best Practices für IoT Bridge ↔ Odoo Schnittstellen +# Dokumentationsstrategie (Root) -**Stand:** 2026-02-15 -**Status:** ✅ Beide APIs funktionstüchtig, Dokumentation verbesserungswürdig +**Stand:** 2026-02-19 +**Ziel:** Root-Dokumente schlank halten, fachliche Details in die Modul-Dokumentation verlagern. ---- +## 1) Source of Truth -## 📋 Übersicht der Schnittstellen +### IoT Bridge (Runtime) +- `iot_bridge/README.md` – Gesamtüberblick, Betriebslogik, Konfiguration +- `iot_bridge/ARCHITECTURE.md` – Architektur und Datenfluss +- `iot_bridge/DEVELOPMENT.md` – Setup, Tests, Debugging +- `iot_bridge/API.md` – Einstieg + Verweis auf OpenAPI (`/docs`, `/redoc`, `/openapi.json`) -``` -┌──────────────┐ ┌──────────────────┐ -│ Odoo │ POST /config │ IoT Bridge │ -│ │ ────────────────────────> │ │ -│ │ (Device Config Push) │ │ -│ │ │ │ -│ │ GET /health │ │ -│ │ ────────────────────────> │ │ -│ │ (Health Check) │ │ -└──────────────┘ └──────────────────┘ +### Odoo Addon (`open_workshop_mqtt`) +- `extra-addons/open_workshop/open_workshop_mqtt/README.md` – Funktionsumfang und Bedienung +- `extra-addons/open_workshop/open_workshop_mqtt/API.md` – Event-API und Odoo-spezifisches Verhalten -┌──────────────┐ ┌──────────────────┐ -│ IoT Bridge │ POST /ows/iot/event │ Odoo │ -│ │ ────────────────────────> │ │ -│ │ (Event Submission) │ │ -│ │ JSON-RPC 2.0 │ │ -└──────────────┘ └──────────────────┘ -``` +## 2) Rolle der Root-Dokumente -### API 1: **IoT Bridge Config API** (Odoo → Bridge) -- **Technologie:** FastAPI (Python) -- **Dokumentation:** ✅ Automatisch via OpenAPI/Swagger -- **Endpunkte:** `POST /config`, `GET /config`, `GET /health` -- **Zugriff:** http://localhost:8080/docs +Root-Dateien enthalten nur noch: +- Projektweite Orientierung +- Deployment-Einstieg +- Historische Entscheidungskontexte (klar als „historisch“ markiert) -### API 2: **Odoo IoT Event API** (Bridge → Odoo) -- **Technologie:** Odoo HTTP Controller (JSON-RPC) -- **Dokumentation:** ⚠️ Manuell zu erstellen -- **Endpunkt:** `POST /ows/iot/event` -- **Format:** JSON-RPC 2.0 +Root-Dateien enthalten **nicht mehr**: +- Vollständige API-Schemata +- Detaillierte Implementierungsanweisungen auf Dateiebene +- Duplicate Content aus `iot_bridge/*` oder Addon-README/API ---- +## 3) Aktueller Status der Root-Dateien -## 🎯 Empfohlene Dokumentationsstrategie +- `DEPLOYMENT.md` → **aktiv** (kurzer operativer Leitfaden) +- `IMPLEMENTATION_PLAN.md` → **aktiv** (kompakte Statusübersicht + nächste Schritte) -### Prinzipien +Historische Request-/Teilplan-Dokumente liegen unter `docs/history/`: +- `docs/history/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md` +- `docs/history/FEATURE_REQUEST_DEVICE_STATUS.md` +- `docs/history/IMPLEMENTATION_PLAN_DEVICE_STATUS.md` -1. **Single Source of Truth** - Code ist die Quelle, Dokumentation wird daraus generiert -2. **Living Documentation** - Tests als ausführbare Beispiele -3. **Developer-First** - Fokus auf Nutzbarkeit für Entwickler -4. **Automatisierung** - Swagger/OpenAPI wo möglich -5. **Versionierung** - API-Versionen klar kennzeichnen +## 4) Pflege-Regeln -### Werkzeuge - -| Aspekt | Bridge API | Odoo API | Tool | -|--------|-----------|----------|------| -| Schema Definition | ✅ Automatisch | ⚠️ Manuell | Pydantic / Python Docstrings | -| Interactive Docs | ✅ Swagger UI | ❌ Fehlt | FastAPI built-in / Postman Collection | -| Code Examples | ✅ Tests | ✅ Tests | pytest / test_e2e_push_architecture.py | -| Changelog | ⚠️ Git only | ⚠️ Git only | CHANGELOG.md | -| Type Safety | ✅ Pydantic | ⚠️ Basic | -/- | - ---- - -## 🔧 Implementierungsplan - -### 1. IoT Bridge API (FastAPI) - ✅ Gut, kleinere Verbesserungen - -**Bereits vorhanden:** -- ✅ OpenAPI Schema automatisch generiert -- ✅ Swagger UI: http://localhost:8080/docs -- ✅ ReDoc: http://localhost:8080/redoc -- ✅ Pydantic Models für Validation -- ✅ API.md mit Beispielen - -**Verbesserungen:** - -#### a) Erweitere FastAPI Endpoint-Dokumentation mit response_model und Examples - -```python -# In config_server.py - -@self.app.post( - "/config", - response_model=ConfigResponse, - summary="Receive device configuration from Odoo", - description=""" - Receives full device configuration push from Odoo. - - **Triggered automatically by Odoo when:** - - Device is created - - Device is updated (relevant fields changed) - - Device is deleted - - Manual push button clicked - - **Side effects:** - - Validates config schema - - Computes device diff (added/updated/removed) - - Updates MQTT subscriptions - - Persists config to /data/config-active.yaml - """, - responses={ - 200: { - "description": "Configuration successfully applied", - "content": { - "application/json": { - "example": { - "status": "success", - "message": "Configuration applied", - "devices_configured": 2, - "added": ["device-123"], - "updated": ["device-456"], - "removed": [], - "timestamp": "2026-02-15T10:30:45.123456" - } - } - } - }, - 422: { - "description": "Validation error", - "content": { - "application/json": { - "example": { - "detail": [ - { - "loc": ["body", "devices", 0, "session_config", "working_threshold_w"], - "msg": "working_threshold_w must be greater than standby_threshold_w", - "type": "value_error" - } - ] - } - } - } - } - }, - tags=["Configuration"] -) -async def receive_config(...): - ... -``` - -#### b) Füge OpenAPI Metadata hinzu - -```python -# In config_server.py __init__ - -self.app = FastAPI( - title="IoT Bridge Config API", - description=""" - **Autonomous MQTT-to-Odoo Integration Service** - - This API receives device configuration from Odoo and manages MQTT subscriptions. - - ## Features - - Push-based configuration (no polling) - - Dynamic MQTT subscription management - - Config persistence to /data/config-active.yaml - - Pydantic validation for type safety - - ## Workflow - 1. Odoo pushes config via POST /config - 2. Bridge validates and applies config - 3. Bridge subscribes to MQTT topics - 4. Bridge persists config for restart survival - - ## Authentication - Optional Bearer token via `BRIDGE_API_TOKEN` environment variable. - """, - version="1.0.0", - contact={ - "name": "Open Workshop Team", - "email": "lotz.matthias@gmail.com" - }, - license_info={ - "name": "MIT", - "url": "https://opensource.org/licenses/MIT" - }, - openapi_tags=[ - { - "name": "Configuration", - "description": "Device configuration management" - }, - { - "name": "Health", - "description": "Service health and monitoring" - } - ] -) -``` - -#### c) Exportiere OpenAPI Schema in CI/CD - -```bash -# In iot_bridge/Makefile oder CI-Pipeline - -docs-export: - python3 -c "from config_server import ConfigServer; import json; \ - server = ConfigServer(lambda x: None); \ - print(json.dumps(server.app.openapi(), indent=2))" \ - > docs/openapi.json - -docs-serve: - cd docs && python3 -m http.server 8888 -``` - ---- - -### 2. Odoo IoT Event API (JSON-RPC) - ⚠️ Braucht Dokumentation - -**Status:** Funktioniert, aber keine strukturierte Dokumentation außer Docstrings - -**Empfohlener Ansatz:** - -#### a) Erstelle OpenAPI-ähnliches Dokument - -```markdown -# Odoo IoT Event API Reference - -**Base URL:** `http://localhost:9018` -**Endpoint:** `POST /ows/iot/event` -**Protocol:** JSON-RPC 2.0 -**Authentication:** Odoo Session (Cookie) oder API Key - -## Request Format (JSON-RPC 2.0) - -All requests must use JSON-RPC 2.0 format: - -{ - "jsonrpc": "2.0", - "params": { - "event_uid": "550e8400-e29b-41d4-a716-446655440000", - "event_type": "session_started", - "device_id": "shellypmminig3-48f6eeb73a1c", - "session_id": "optional-session-uuid", - "timestamp": "2026-02-15T10:30:45.123456Z", - "payload": { - "power_w": 125.5, - "state": "working" - } - } -} - -## Parameters - -| Field | Type | Required | Description | -|-------|------|----------|-------------| -| event_uid | String (UUID) | ✅ Yes | Unique event identifier (prevents duplicates) | -| event_type | String (Enum) | ✅ Yes | Event type (see below) | -| device_id | String | ✅ Yes | Device identifier (must exist in mqtt.device) | -| session_id | String | ❌ Optional | Session identifier (for session events) | -| timestamp | String (ISO 8601) | ✅ Yes | Event timestamp (UTC) | -| payload | Object | ❌ Optional | Device-specific data | - -## Event Types - -### device_online -Device came online (MQTT connection established). - -**Effect:** Sets device.state = 'idle' - -**Example:** -{ - "jsonrpc": "2.0", - "params": { - "event_uid": "a1b2c3d4-...", - "event_type": "device_online", - "device_id": "shelly-pm-001", - "timestamp": "2026-02-15T10:00:00Z", - "payload": {} - } -} - -### device_offline -Device went offline (MQTT timeout or disconnect). - -**Effect:** Sets device.state = 'offline' - -**Example:** -{ - "jsonrpc": "2.0", - "params": { - "event_uid": "e5f6g7h8-...", - "event_type": "device_offline", - "device_id": "shelly-pm-001", - "timestamp": "2026-02-15T10:05:00Z", - "payload": {} - } -} - -### session_started -Work session started (power crossed working threshold). - -**Effect:** -- Sets device.state = 'active' -- Creates mqtt.session record - -**Example:** -{ - "jsonrpc": "2.0", - "params": { - "event_uid": "i9j0k1l2-...", - "event_type": "session_started", - "device_id": "shelly-pm-001", - "session_id": "session-abc123", - "timestamp": "2026-02-15T10:15:00Z", - "payload": { - "power_w": 125.5, - "state": "working" - } - } -} - -### session_stopped -Work session ended (power dropped below standby threshold). - -**Effect:** -- Sets device.state = 'idle' -- Updates mqtt.session.end_time -- Calculates session.duration - -**Example:** -{ - "jsonrpc": "2.0", - "params": { - "event_uid": "m3n4o5p6-...", - "event_type": "session_stopped", - "device_id": "shelly-pm-001", - "session_id": "session-abc123", - "timestamp": "2026-02-15T11:30:00Z", - "payload": { - "power_w": 3.2, - "total_duration_s": 4500 - } - } -} - -### session_heartbeat -Periodic update during active session. - -**Effect:** Updates session statistics (avg_power, peak_power, etc.) - -**Example:** -{ - "jsonrpc": "2.0", - "params": { - "event_uid": "q7r8s9t0-...", - "event_type": "session_heartbeat", - "device_id": "shelly-pm-001", - "session_id": "session-abc123", - "timestamp": "2026-02-15T10:20:00Z", - "payload": { - "power_w": 110.3, - "avg_power_w": 115.2, - "peak_power_w": 130.1, - "state": "working" - } - } -} - -## Response Format - -### Success (201 Created) -{ - "status": "success", - "event_id": 123, - "event_uid": "550e8400-e29b-41d4-a716-446655440000", - "timestamp": "2026-02-15T10:30:45.234567" -} - -### Duplicate Event (409 Conflict) -{ - "status": "duplicate", - "event_id": 122, - "event_uid": "550e8400-e29b-41d4-a716-446655440000", - "message": "Event already processed" -} - -### Validation Error (400 Bad Request) -{ - "status": "error", - "error": "Missing required fields: event_uid, device_id", - "code": 400 -} - -### Device Not Found (404 Not Found) -{ - "status": "error", - "error": "Device not found: unknown-device-123", - "code": 404 -} - -## Idempotency - -The API is **idempotent** via `event_uid`: -- Same `event_uid` submitted twice → 409 Conflict (second request ignored) -- Prevents duplicate event processing -- Bridge can safely retry failed submissions - -## Rate Limiting - -Currently **no rate limiting** implemented. - -## Testing - -### Manual Test (curl) -```bash -curl -X POST http://localhost:9018/ows/iot/event \ - -H "Content-Type: application/json" \ - -d '{ - "jsonrpc": "2.0", - "params": { - "event_uid": "550e8400-e29b-41d4-a716-446655440000", - "event_type": "device_online", - "device_id": "testshelly-simulator", - "timestamp": "2026-02-15T10:30:45Z", - "payload": {} - } - }' -``` - -### Automated Tests -```bash -cd iot_bridge/tests -python3 test_e2e_push_architecture.py -``` - -## Changelog - -| Version | Date | Changes | -|---------|------|---------| -| 1.0.0 | 2026-02-15 | Initial implementation with JSON-RPC 2.0, event_uid for idempotency | -``` - -#### b) Füge Docstrings für IDEs hinzu - -```python -# In extra-addons/open_workshop/open_workshop_mqtt/controllers/iot_api.py - -@route('/ows/iot/event', type='json', auth='none', methods=['POST'], csrf=False, save_session=False) -def receive_iot_event(self, event_uid=None, event_type=None, device_id=None, - session_id=None, timestamp=None, payload=None, **kw): - """ - Receive IoT event from Bridge (JSON-RPC 2.0). - - Args: - event_uid (str): Unique event identifier (UUID, required for idempotency) - event_type (str): Event type - one of: - - 'device_online': Device came online → state='idle' - - 'device_offline': Device went offline → state='offline' - - 'session_started': Session started → state='active', creates mqtt.session - - 'session_stopped': Session ended → state='idle', finalizes mqtt.session - - 'session_heartbeat': Periodic update during session - device_id (str): Device identifier (must exist in mqtt.device, required) - session_id (str, optional): Session identifier (required for session events) - timestamp (str): ISO 8601 timestamp (required) - payload (dict, optional): Device-specific data - - Returns: - dict: Response with status, event_id, timestamp - - Example Request (JSON-RPC 2.0): - { - "jsonrpc": "2.0", - "params": { - "event_uid": "550e8400-e29b-41d4-a716-446655440000", - "event_type": "session_started", - "device_id": "shellypmminig3-48f6eeb73a1c", - "session_id": "abc-123", - "timestamp": "2026-02-15T10:30:45.123456Z", - "payload": {"power_w": 125.5} - } - } - - Example Response (Success): - { - "status": "success", - "event_id": 123, - "event_uid": "550e8400-e29b-41d4-a716-446655440000", - "timestamp": "2026-02-15T10:30:45.234567" - } - - Example Response (Duplicate): - { - "status": "duplicate", - "event_id": 122, - "event_uid": "550e8400-e29b-41d4-a716-446655440000", - "message": "Event already processed" - } - - HTTP Status Codes: - 201: Event created successfully - 409: Duplicate event_uid (idempotent retry) - 400: Validation error (missing fields, invalid data) - 404: Device not found - 500: Internal server error - - Side Effects: - - Creates iot.event record - - May update mqtt.device.state (online/offline/idle/active) - - May create/update mqtt.session record (for session events) - - Idempotent via event_uid (duplicate submissions return 409) - """ - # ... implementation -``` - ---- - -### 3. Postman Collection - Für beide APIs - -**Vorteil:** Interaktive Dokumentation + Testing Tool - -Erstelle Postman Collection mit: -- ✅ Alle Endpoints (Bridge + Odoo) -- ✅ Beispiel-Requests mit Variablen -- ✅ Pre-request Scripts (UUID Generation für event_uid) -- ✅ Tests (Assertions für Status Codes) - -```json -{ - "info": { - "name": "IoT Bridge Odoo Integration", - "description": "Complete API collection for IoT Bridge ↔ Odoo communication", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json" - }, - "variable": [ - { - "key": "bridge_url", - "value": "http://localhost:8080" - }, - { - "key": "odoo_url", - "value": "http://localhost:9018" - } - ], - "item": [ - { - "name": "Bridge API", - "item": [ - { - "name": "Health Check", - "request": { - "method": "GET", - "url": "{{bridge_url}}/health" - } - }, - { - "name": "Get Config", - "request": { - "method": "GET", - "url": "{{bridge_url}}/config" - } - }, - { - "name": "Push Config (from Odoo)", - "request": { - "method": "POST", - "url": "{{bridge_url}}/config", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"devices\": [\n {\n \"device_id\": \"testshelly-simulator\",\n \"machine_name\": \"Test Machine\",\n \"mqtt_topic\": \"testshelly-simulator/status/pm1:0\",\n \"parser_type\": \"shelly_pm\",\n \"session_config\": {\n \"standby_threshold_w\": 5.0,\n \"working_threshold_w\": 50.0,\n \"start_debounce_s\": 3.0,\n \"stop_debounce_s\": 15.0,\n \"message_timeout_s\": 20.0,\n \"heartbeat_interval_s\": 300.0\n }\n }\n ]\n}" - } - } - } - ] - }, - { - "name": "Odoo API", - "item": [ - { - "name": "Submit Device Online Event", - "request": { - "method": "POST", - "url": "{{odoo_url}}/ows/iot/event", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"jsonrpc\": \"2.0\",\n \"params\": {\n \"event_uid\": \"{{$guid}}\",\n \"event_type\": \"device_online\",\n \"device_id\": \"testshelly-simulator\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"payload\": {}\n }\n}" - } - } - }, - { - "name": "Submit Session Started Event", - "request": { - "method": "POST", - "url": "{{odoo_url}}/ows/iot/event", - "header": [ - { - "key": "Content-Type", - "value": "application/json" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"jsonrpc\": \"2.0\",\n \"params\": {\n \"event_uid\": \"{{$guid}}\",\n \"event_type\": \"session_started\",\n \"device_id\": \"testshelly-simulator\",\n \"session_id\": \"session-{{$timestamp}}\",\n \"timestamp\": \"{{$isoTimestamp}}\",\n \"payload\": {\n \"power_w\": 125.5,\n \"state\": \"working\"\n }\n }\n}" - } - } - } - ] - } - ] -} -``` - -**Nutzung:** -1. Importiere in Postman -2. Setze Environment Variables (bridge_url, odoo_url) -3. Führe Requests aus -4. Teile Collection mit Team (Export als JSON) - ---- - -### 4. Tests als Living Documentation - -**Bereits vorhanden:** `test_e2e_push_architecture.py` - -**Verbesserungen:** - -#### a) Füge docstrings zu Test-Funktionen hinzu - -```python -def test_device_status_monitoring(odoo: OdooAPI, bridge: BridgeAPI): - """ - Test: Device Status Lifecycle (offline → idle → active → idle → offline) - - Scenario: - 1. Send device_online event → Device state = 'idle' - 2. Send session_started event → Device state = 'active' - 3. Send session_stopped event → Device state = 'idle' - 4. Send device_offline event → Device state = 'offline' - - Validates: - - JSON-RPC 2.0 format for Odoo event API - - event_uid requirement for idempotency - - State transitions via device.write() in controllers/iot_api.py - - Proper timestamp handling (ISO 8601) - - Related: - - controllers/iot_api.py::receive_iot_event() - - models/mqtt_device.py::state field - - DOCUMENTATION_STRATEGY.md::Odoo IoT Event API - """ - # ... test implementation -``` - -#### b) Generiere HTML Test Report mit Beispielen - -```bash -# In iot_bridge/tests/ - -pytest test_e2e_push_architecture.py \ - --html=report.html \ - --self-contained-html \ - --capture=no -``` - -Zeigt: -- ✅ Welche Tests liefen -- ✅ Request/Response Examples aus den Tests -- ✅ Failure Details mit Stacktraces - ---- - -## 📦 Deliverables (Was konkret erstellen?) - -### Sofort (heute): - -1. **✅ DOCUMENTATION_STRATEGY.md** (dieses Dokument) - - Übersicht der APIs - - Werkzeuge & Best Practices - - Implementierungsplan - -2. **🔧 Erweitere config_server.py** (15 min) - - Füge OpenAPI Metadata hinzu (contact, license, tags) - - Erweitere endpoint docstrings mit examples - - Siehe Code-Snippet oben - -3. **📝 ODOO_EVENT_API.md** erstellen (30 min) - - Vollständige Referenz für /ows/iot/event - - Alle Event Types dokumentiert - - Beispiele für jeden Event-Typ - - Siehe Template oben - -### Kurzfristig (diese Woche): - -4. **🧪 Test Docstrings erweitern** (20 min) - - Alle test_*() Funktionen dokumentieren - - Verlinke zu relevanten Code-Stellen - - Siehe Beispiel oben - -5. **📮 Postman Collection erstellen** (45 min) - - Alle Endpoints beider APIs - - Environment Variables - - Pre-request Scripts für UUID/Timestamp - - Export als JSON im Repo - -6. **📊 OpenAPI Export im CI/CD** (15 min) - - Script zum Exportieren von openapi.json - - Commit in Repo für Versionskontrolle - - Siehe Makefile-Snippet oben - -### Mittelfristig (nächster Sprint): - -7. **🔄 API Changelog einführen** - - CHANGELOG.md für Breaking Changes - - Semantic Versioning (1.0.0 → 1.1.0 → 2.0.0) - - Git Tags für API-Versionen - -8. **🏷️ API Versioning Strategy** - - Entscheide: URL-basiert (/v1/event) oder Header-basiert? - - Implementiere Version Header: `X-API-Version: 1.0` - - Plane Deprecation Policy (wie lange Support für alte Version?) - -9. **📈 API Monitoring Dashboard** - - Prometheus Metrics für beide APIs - - Grafana Dashboard mit: - - Request Rate (req/s) - - Error Rate (4xx/5xx) - - Latency (p50, p95, p99) - - Active Devices - ---- - -## 🎨 Empfohlene Verzeichnisstruktur - -``` -iot_bridge/ -├── API.md # ✅ Bereits vorhanden (Bridge API) -├── docs/ -│ ├── openapi.json # 🆕 Exportiertes OpenAPI Schema -│ └── postman/ -│ └── collection.json # 🆕 Postman Collection -└── tests/ - └── test_e2e_push_architecture.py # ✅ Mit erweiterten Docstrings - -extra-addons/open_workshop/open_workshop_mqtt/ -├── API.md # 🆕 Odoo Event API Referenz -├── controllers/ -│ └── iot_api.py # 🔧 Erweiterte Docstrings -└── docs/ - └── postman/ - └── collection.json # 🆕 Odoo API Beispiele -``` - ---- - -## ✅ Vorteile dieser Strategie - -### Für Entwickler: -- ✅ **Automatische Swagger Docs** - kein manuelles Update nötig -- ✅ **Type Safety** - Pydantic Models verhindern Fehler -- ✅ **Executable Examples** - Tests zeigen echte Nutzung -- ✅ **IDE Support** - Docstrings → Autocomplete - -### Für DevOps: -- ✅ **OpenAPI Schema** - kann in API Gateway integriert werden -- ✅ **Health Endpoint** - für Load Balancer / Monitoring -- ✅ **Postman Collection** - für Smoke Tests nach Deployment - -### Für QA: -- ✅ **E2E Tests** - validieren komplette Workflows -- ✅ **Postman Collection** - für manuelle Explorative Tests -- ✅ **Test Reports** - HTML Report mit Beispielen - -### Für neue Teammitglieder: -- ✅ **Swagger UI** - interaktiv API ausprobieren -- ✅ **Markdown Docs** - leicht zu lesen -- ✅ **Tests** - zeigen "How to use" Patterns - ---- - -## 🚀 Quick Wins (Start hier!) - -### 30-Minuten-Sprint: - -```bash -# 1. Erstelle Odoo API Dokumentation -cd extra-addons/open_workshop/open_workshop_mqtt -cat > API.md << 'EOF' -[Inhalt von oben: "# Odoo IoT Event API Reference" ...] -EOF - -# 2. Teste Swagger UI -curl http://localhost:8080/docs - -# 3. Exportiere OpenAPI Schema -curl http://localhost:8080/openapi.json > iot_bridge/docs/openapi.json - -# 4. Commit -git add . -git commit -m "docs: Add comprehensive API documentation for Bridge and Odoo APIs" -``` - ---- - -## 📚 Weiterführende Ressourcen - -- [FastAPI Docs - Metadata and Doc URLs](https://fastapi.tiangolo.com/tutorial/metadata/) -- [OpenAPI Specification](https://swagger.io/specification/) -- [Postman Collections](https://learning.postman.com/docs/getting-started/creating-your-first-collection/) -- [Semantic Versioning](https://semver.org/) -- [API Versioning Best Practices](https://www.freecodecamp.org/news/rest-api-best-practices-rest-endpoint-design-examples/) - ---- - -**Autor:** Open Workshop Team -**Letzte Aktualisierung:** 2026-02-15 -**Status:** 🟢 Aktiv implementiert +1. Änderungen zuerst in Code + tests + Modul-Doku. +2. Root-Doku nur aktualisieren, wenn sich Prozess/Status/Navigation ändert. +3. Jede Root-Datei mit Datum und Status versehen. +4. Keine langen Codebeispiele im Root, stattdessen Link auf das zuständige Modul-Dokument. diff --git a/FEATURE_REQUEST_DEVICE_STATUS.md b/FEATURE_REQUEST_DEVICE_STATUS.md deleted file mode 100644 index 5de2234..0000000 --- a/FEATURE_REQUEST_DEVICE_STATUS.md +++ /dev/null @@ -1,46 +0,0 @@ -# Feature Request: Device Online/Offline Status - -## Summary -The device status in Odoo is always shown as Offline. We need a reliable Online/Offline indicator that reflects whether a device is connected to the broker or actively sending MQTT messages. - -## Goals -- Show Online when the device is connected or has sent a recent message. -- Show Offline when the device is no longer connected or stopped sending messages. -- Provide a clear reason for Offline where possible (timeout, broker disconnect, bridge offline). -- Keep the system robust if Odoo or the bridge restarts. - -## Non-Goals -- We do not need device-side firmware changes. -- We do not need exact broker connection state for every device if not supported by the device. - -## User Stories -- As a workshop operator, I want to see whether a machine is online at a glance. -- As a technician, I want to know when a device went offline and why. -- As an admin, I want the status to survive restarts and recover quickly. - -## Proposed Behavior -- Online if a device has sent a message within a configurable timeout (e.g., 30s). -- Offline if no message is received within the timeout. -- Optionally use MQTT LWT if available to improve accuracy. -- Odoo shows: - - status: online/offline - - last_seen_at - - offline_reason (timeout, broker_disconnect, bridge_offline) - -## Data Needed -- Per device: last_seen_at (timestamp), online_state (bool), offline_reason (string), last_online_at (timestamp) - -## Acceptance Criteria -- Device status changes to Online within 1 message after device starts sending. -- Device status changes to Offline within timeout + 1 interval when messages stop. -- Status is visible in Odoo list and form views. -- Restarting the bridge keeps or quickly restores status. - -## Risks -- Devices that publish very infrequently may appear Offline unless timeout is tuned. -- Some devices might not support MQTT LWT; fallback to last_seen is required. - -## Open Questions -- What timeout should be the default (30s, 60s, 120s)? -- Do we want a separate heartbeat topic or use existing power messages? -- Should Odoo be updated by push (events) or poll from the bridge? diff --git a/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md b/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md deleted file mode 100644 index 69d75b8..0000000 --- a/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md +++ /dev/null @@ -1,733 +0,0 @@ -# Projektplan: MQTT-basierte IoT-Events in Odoo 18 Community - -Ziel: **Odoo-18-Community (self-hosted)** soll **Geräte-Events** (Maschinenlaufzeit) über **MQTT** aufnehmen und in Odoo als **saubere, nachvollziehbare Sessions/Events** verarbeiten. -Die Entwicklung startet mit **shelly_simulator.py** aus ./iot_bridge/tests/tools/shelly_simulator.py , das bereits im MQTT Broker integriert ist. -Es wurde bereits eine iot_bridge in ./iot_bridge erstellt. Diese muss jedoch überarbeitet werden. - ---- - -## 1. Ziele und Nicht-Ziele - -### 1.1 Ziele (MVP) -[x] **shelly Simulator**: als primäres Test-Device -[x] **Session-Logik** für Maschinenlaufzeit basierend auf Power-Schwellenwerten (Start/Stop mit Hysterese) -[x]**Python-Prototyp** (Standalone) der später in Odoo-Bridge übernommen wird: - - MQTT Client für Shelly-Integration - - Event-Normalisierung (Shelly-Format → Unified Event Schema) - - Session-Detection Engine - - Konfigurierbare Device-Mappings und Schwellenwerte -[x] **Odoo-Integration** (Phase 2): - - [x] Einheitliche **Device-Event-Schnittstelle** (REST/Webhook) inkl. Authentifizierung - - [x] **Event-Log** in Odoo (persistente Rohereignisse + Normalisierung) - - [x] Session-Verwaltung mit Maschinenzuordnung - - [x] Device Verwaltung in Odoo mit benutzerfreundlicher GUI - - [x] **Odoo pusht Config aktiv an Bridge** (POST /config) - Bridge läuft autark mit lokaler Config - - [x] Benutzerfreundliche Konfigurationsfelder statt JSON-Eingabe - - [x] Automatischer Config-Push bei Änderungen (write/create/delete hooks) -[x] Reproduzierbare Tests und eindeutige Fehlerdiagnostik (Logging) - -### 1.2 Nicht-Ziele (für Phase 1) -- Keine Enterprise-IoT-Box, keine Enterprise-Module -- Kein POS-Frontend-Live-Widget (optional erst in späterer Phase) -- Keine Abrechnungslogik/Preisregeln (kann vorbereitet, aber nicht umgesetzt werden) -- Das open_workshop_modul soll komplett neu erstellt werden. der aktuelle stand darf verworfern werden. ---- - -## 2. Zielarchitektur (Docker-First, Sidecar-Pattern) - -### 2.1 Komponenten -1. **MQTT Broker**: Mosquitto in Docker (bereits vorhanden) -2. **IoT Bridge Service**: ✅ Vollständig implementiert - [x] Image: `iot_bridge` siehe `./odoo/docker-compose.dev.yaml` - [x] Source: `iot_bridge/` Unterverzeichnis - [x] MQTT Client (subscribed auf Topics) - [x] Session Detection Engine (State Machine) - [x] Event-Normalisierung & Parsing - [x] Sendet Events an Odoo via REST API: `POST /ows/iot/event` - [x] **REST API Server in Bridge**: `POST /config` - empfängt Config-Updates von Odoo - [x] **Config-Persistence**: Speichert empfangene Config lokal zu `/data/config-active.yaml` - [x] **Dynamic Subscription**: Updated MQTT-Subscriptions bei Config-Änderungen -3. **Odoo Container**: ✅ Business Logic & Konfiguration - [x] Models: ows.mqtt.device, ows.mqtt.session, ows.iot.event (mit ows.* prefix) - [x] REST API für Event-Empfang: `POST /ows/iot/event` (mit Idempotenz) - [x] Admin UI für Device-Management mit benutzerfreundlichen Feldern - [x] **Bridge-Config-Push-Logik**: Bei Device-Änderung automatisch `POST http://iot-bridge:8080/config` - [x] **HTTP Client** für Bridge-API (requests library mit Retry-Logic) - [x] **Config-Build-Funktion**: Konvertiert Odoo Models → Bridge Config JSON - [x] **Benutzerfreundliche GUI**: Abrechnungs-Intervall, Schwellenwerte, Verzögerungen mit Icons & Hilfe-Texten - [x] **Computed Fields**: strategy_config automatisch aus benutzerfreundlichen Feldern generiert -4. **Docker Compose**: Orchestrierung aller Services -> siehe `./odoo/docker-compose.dev.yaml` - [x] Shared Network - [x] Shared Volumes (optional) - [x] Services starten zusammen: `docker compose up -d` - -### 2.2 Datenfluss - -**Konfigurations-Flow (Odoo → Bridge):** -``` -Odoo Admin UI → Device Model (create/update) → Trigger → HTTP POST - ↓ - Bridge:8080/config (REST API) - ↓ - Update Subscriptions + Session Detector -``` - -**Event-Flow (Bridge → Odoo):** -``` -Shelly PM → MQTT Broker ← Bridge Service → REST API → Odoo - (Subscribe) (POST) (Event Storage) -``` - -1. **Admin konfiguriert Device in Odoo** (UI: mqtt.device) -2. **Odoo pusht Config an Bridge** via `POST http://iot-bridge:8080/config` -3. **Bridge updated laufende Config**: Subscribed neue Topics, erstellt/updated Session Detectors -4. **Bridge empfängt MQTT Messages** von Shelly PM -5. **Bridge normalisiert & erkennt Sessions** (State Machine) -6. **Bridge sendet Events** via `POST /ows/iot/event` an Odoo -7. **Odoo speichert Events + Sessions** in Datenbank - -### 2.3 Vorteile Sidecar-Pattern -- ✅ **Klare Trennung**: Odoo = Business Logic, Bridge = MQTT/Retry/Queue -- ✅ **Unabhängige Prozesse**: Bridge restart ohne Odoo-Downtime -- ✅ **Docker Best Practice**: Ein Prozess pro Container -- ✅ **Einfaches Setup**: `docker compose up -d` startet alles -- ✅ **Kein Overhead**: Gleiche Network/Volumes, kein Extra-Komplexität - ---- - -## 3. Schnittstellen zwischen Bridge und Odoo - -### 3.1 Odoo → Bridge: Konfiguration pushen - -**Endpoint:** `POST /bridge/config` - -**Authentifizierung:** -- **Optional:** Bearer Token (später für Produktiv-Umgebung) -- Header: `Authorization: Bearer ` -- Token wird Odoo als ENV-Variable übergeben: `BRIDGE_API_TOKEN=...` -- Token in Bridge: Validierung gegen ENV-Variable oder Config - -**Request Body:** -```json -{ - "devices": [ - { - "device_id": "shellypmminig3-48f6eeb73a1c", - "mqtt_topic": "shaperorigin/status/pm1:0", - "parser_type": "shelly_pm_mini_g3", - "machine_name": "Shaper Origin", - "session_config": { - "strategy": "power_threshold", - "standby_threshold_w": 20, # Einschalt-Schwelle: Ab dieser Leistung ist Gerät AN - "working_threshold_w": 100, # Arbeits-Schwelle: Ab dieser Leistung arbeitet Gerät aktiv - "start_debounce_s": 3, # Anlauf-Verzögerung: Gerät muss 3s an sein bis Session startet - "stop_debounce_s": 15, # Abschalt-Verzögerung: Gerät muss 15s aus sein bis Session endet - "message_timeout_s": 20, # Timeout: Nach 20s ohne MQTT-Nachricht = Session-Ende - "heartbeat_interval_s": 300 # Abrechnungs-Intervall: Events alle 5 Minuten (300s) - } - } - ], - "timestamp": "2026-02-12T10:30:00Z" -} -``` - -**Response:** -- `200 OK`: `{ "status": "ok", "devices_configured": 1, "subscriptions_updated": true }` -- `400 Bad Request`: Schema-Fehler -- `401 Unauthorized`: Token fehlt/ungültig -- `500 Internal Server Error`: Bridge-interner Fehler - -**Bridge-Verhalten:** -1. Empfängt Config von Odoo -2. Validiert JSON Schema -3. Persisted Config lokal zu `/data/config-active.yaml` (für Restart) -4. Updated laufende MQTT Subscriptions (neue Topics subscriben, alte entfernen) -5. Erstellt/Updated Session Detectors für alle Devices -6. Loggt Config-Change Event - ---- - -### 3.2 Bridge → Odoo: Event-Empfang - -**Endpoint:** `POST /ows/iot/event` - -**Authentifizierung (optional):** -- **Empfohlen für**: Produktiv-Umgebung, Multi-Host-Setup, externe Zugriffe -- **Nicht nötig für**: Docker-Compose-Setup (isoliertes Netzwerk) -- Header: `Authorization: Bearer ` (falls aktiviert) -- Token wird Bridge als ENV-Variable übergeben: `ODOO_TOKEN=...` -- Token in Odoo: `ir.config_parameter` → `ows_iot.bridge_token` -- Aktivierung: `ir.config_parameter` → `ows_iot.require_token = true` - -**Request Body:** - -**Variante 1: Session Lifecycle Events** -```json -{ - "schema_version": "v1", - "event_uid": "uuid", - "ts": "2026-01-31T10:30:15Z", - "device_id": "shellypmminig3-48f6eeb73a1c", - "event_type": "session_started", - "payload": { "session_id": "sess-abc123" } -} -``` - -**Variante 2: Session Heartbeat (periodisch, aggregiert)** -```json -{ - "schema_version": "v1", - "event_uid": "uuid", - "ts": "2026-01-31T10:35:00Z", - "device_id": "shellypmminig3-48f6eeb73a1c", - "event_type": "session_heartbeat", - "payload": { - "session_id": "sess-abc123", - "interval_start": "2026-01-31T10:30:00Z", - "interval_end": "2026-01-31T10:35:00Z", - "interval_working_s": 200, - "interval_standby_s": 100, - "current_state": "WORKING", - "avg_power_w": 142.3 - } -} -``` - -**Response:** -- `200 OK`: `{ "status": "ok", "event_id": 123, "session_id": 456 }` -- `400 Bad Request`: Schema-Fehler -- `401 Unauthorized`: Token fehlt/ungültig (nur wenn Auth aktiviert) -- `409 Conflict`: Duplikat (wenn `event_uid` bereits existiert) -- `500 Internal Server Error`: Odoo-Fehler - -**Idempotenz:** -- `event_uid` hat Unique-Constraint in Odoo -- Wiederholte Events → `409 Conflict` (Bridge ignoriert) - ---- - -### 3.3 Startup & Fallback-Verhalten - -**Bridge Startup:** -1. Bridge startet → lädt optionale lokale Config `/data/config-active.yaml` (letzte von Odoo gepushte Config) -2. Wenn vorhanden: Startet mit bekannten Devices (autark lauffähig) -3. Wenn nicht vorhanden: Wartet auf ersten Config-Push von Odoo (loggt Warnung) -4. HTTP Server lauscht auf Port 8080 für Config-Updates - -**Odoo Startup/Device-Änderung:** -1. Admin erstellt/ändert Device in Odoo UI -2. Model-Hook (`create()`, `write()`) triggert Config-Push -3. Odoo baut Config-JSON aus allen aktiven Devices -4. Odoo sendet `POST http://iot-bridge:8080/config` -5. Bei Fehler (Bridge nicht erreichbar): Retry mit Exponential Backoff (3 Versuche) - ---- - -## 4. Ereignis- und Topic-Standard (Versioniert) - -### 4.1 MQTT Topics (v1) -- Maschinenzustand: - `hobbyhimmel/machines//state` -- Maschinenereignisse: - `hobbyhimmel/machines//event` -- Waage: - `hobbyhimmel/scales//event` -- Geräte-Status (optional): - `hobbyhimmel/devices//status` - -### 4.2 Gemeinsames JSON Event Schema (v1) -Pflichtfelder: -- `schema_version`: `"v1"` -- `event_uid`: UUID/string -- `ts`: ISO-8601 UTC (z. B. `"2026-01-10T12:34:56Z"`) -- `source`: `"simulator" | "device" | "gateway"` -- `device_id`: string -- `entity_type`: `"machine" | "scale" | "sensor"` -- `entity_id`: string (z. B. machine_id) -- `event_type`: string (siehe unten) -- `payload`: object -- `confidence`: `"high" | "medium" | "low"` (für Sensorfusion) - -### 4.3 Event-Typen (v1) -**Maschine/Timer** -- `session_started` (Session-Start, Bridge erkannt Aktivität) -- `session_heartbeat` (periodische Aggregation während laufender Session) -- `session_ended` (Session-Ende nach Timeout oder manuell) -- `session_timeout` (Session automatisch beendet wegen Inaktivität) -- `state_changed` (optional, Echtzeit-State-Change: IDLE/STANDBY/WORKING) -- `fault` (Fehler mit Code/Severity) - - - ---- - -## 5. Odoo Datenmodell (Vorschlag) - -### 5.1 `ows.iot.device` -- `name` -- `device_id` (unique) -- `token_hash` (oder Token in separater Tabelle) -- `device_type` (machine/scale/...) -- `active` -- `last_seen` -- `notes` - -### 5.2 `ows.iot.event` -- `event_uid` (unique) -- `device_id` (m2o -> device) -- `entity_type`, `entity_id` -- `event_type` -- `timestamp` -- `payload_json` (Text/JSON) -- `confidence` -- `processing_state` (new/processed/error) -- `session_id` (m2o optional) - -### 5.3 `ows.machine.session` (Timer-Sessions) -- `machine_id` (Char oder m2o auf bestehendes Maschinenmodell) -- `session_id` (external, von Bridge generiert) -- `start_ts`, `stop_ts` -- `duration_s` (computed: stop_ts - start_ts) -- `total_working_time_s` (Summe aller WORKING-Intervalle) -- `total_standby_time_s` (Summe aller STANDBY-Intervalle) -- `state` (running/stopped/aborted/timeout) -- `origin` (sensor/manual/sim) -- `billing_units` (computed: ceil(total_working_time_s / billing_unit_seconds)) -- `event_ids` (o2m → session_heartbeat Events) - -> Hinweis: Wenn du bereits `ows.machine` aus deinem open_workshop nutzt, referenziert `machine_id` direkt dieses Modell. - ---- - -## 6. Verarbeitungslogik (Bridge & Odoo) - -### 6.1 Bridge: State Tracking & Aggregation - -**State Machine (5 Zustände):** -- `IDLE` (< Einschalt-Schwelle): Maschine aus/Leerlauf - - Beispiel: < 20W = Gerät ist AUS -- `STARTING` (Anlauf-Verzögerung): Power > Einschalt-Schwelle, aber noch nicht lange genug - - Beispiel: 25W für < 3 Sekunden = Wartet auf Start-Bestätigung -- `STANDBY` (Einschalt-Schwelle bis Arbeits-Schwelle): Maschine an, aber nicht aktiv arbeitend - - Beispiel: 20-100W = Gerät ist AN, aber arbeitet nicht -- `WORKING` (> Arbeits-Schwelle): Maschine arbeitet aktiv - - Beispiel: > 100W = Gerät arbeitet aktiv -- `STOPPING` (Abschalt-Verzögerung): Power < Einschalt-Schwelle, aber noch nicht lange genug - - Beispiel: 5W für < 15 Sekunden = Wartet auf Stop-Bestätigung - -**Schwellenwerte (Power Thresholds):** -- **Einschalt-Schwelle** (`standby_threshold_w`): Ab welcher Leistung gilt das Gerät als "eingeschaltet"? - - Beispiel: 20W - darunter ist Gerät AUS, darüber ist Gerät AN -- **Arbeits-Schwelle** (`working_threshold_w`): Ab welcher Leistung arbeitet das Gerät aktiv? - - Beispiel: 100W - darüber arbeitet das Gerät, wird abgerechnet - -**Verzögerungen (Debounce/Hysterese gegen Flackern):** -- **Anlauf-Verzögerung** (`start_debounce_s`): Wie lange muss Gerät eingeschaltet sein, bevor Session startet? - - Beispiel: 3 Sekunden - verhindert Fehlalarme bei kurzen Stromspitzen -- **Abschalt-Verzögerung** (`stop_debounce_s`): Wie lange muss Gerät ausgeschaltet sein, bevor Session endet? - - Beispiel: 15 Sekunden - berücksichtigt kurze Arbeitspausen (Material-Wechsel) - -**Aggregation Logic:** -- Bridge trackt intern State-Wechsel (1 msg/s von Shelly) -- Alle `heartbeat_interval_s` (z.B. 300s = 5 Min) = **Abrechnungs-Intervall**: - - Berechne `interval_working_s` (Summe aller WORKING-Zeiten) - - Berechne `interval_standby_s` (Summe aller STANDBY-Zeiten) - - Sende `session_heartbeat` an Odoo -- Bei Session-Start: `session_started` Event -- Bei Session-Ende: `session_ended` Event mit Totals - -### 6.2 Odoo: Session-Aggregation & Billing - -**Event-Frequenz wird durch Bridge bestimmt:** -- Bridge sendet alle `heartbeat_interval_s` (z.B. 300s = 5 Min) ein aggregiertes Event -- `heartbeat_interval_s` = **Abrechnungseinheit** (konfiguriert in Bridge pro Device) -- Odoo empfängt nur diese periodischen Events (nicht jeden Power-Wert!) - -**Odoo verarbeitet Heartbeat-Events:** -- Summiert `working_duration_s` und `standby_duration_s` aller empfangenen Heartbeats -- Speichert Totals in Session-Model (`total_duration_min`, `working_duration_min`, etc.) -- Timeout-Regel: wenn kein Heartbeat für `2 * heartbeat_interval_s` → Session `timeout` - -**Abrechnungsbeispiel:** - -Szenario: Gerät läuft 2x 4 Minuten mit 10 Minuten Pause in 15 Minuten -- **Abrechnungs-Intervall:** 5 Minuten (`billing_interval_min = 5` in Odoo → `heartbeat_interval_s = 300` in Bridge) -- Bridge sendet 3 Events: t=5min, t=10min, t=15min - -``` -Event 1 (t=5min): working=4min, standby=1min -Event 2 (t=10min): working=0min, standby=5min -Event 3 (t=15min): working=4min, standby=1min - -Session Total: working=8min, standby=7min → Kunde zahlt für 8 Minuten Arbeitszeit -``` - -**Wo wird das konfiguriert?** -- In Odoo: Device-Formular → Abschnitt "💰 Abrechnung" → Feld "Abrechnungs-Intervall (Minuten)" -- Standard: 5 Minuten -- Empfohlen: 5-15 Minuten für normale Werkstatt-Abrechnung - -**Abrechnungslogik (optional, nicht im MVP):** -```python -# Beispiel: Aufrundung auf volle Abrechnungseinheiten -billing_interval_min = 5 -billable_minutes = ceil(working_duration_min / billing_interval_min) * billing_interval_min -# Beispiel: 8min → ceil(8/5) * 5 = 2 * 5 = 10min -``` - ---- - -## 7. Simulation (Software statt Hardware) - -### 7.1 Device Simulator: Maschine -- Konfigurierbar: - - Muster: random, fixed schedule, manuell per CLI - - Zustände: idle/running/fault - - Optional: „power_w“ und „vibration“ als Felder im Payload -- Publiziert MQTT in realistischen Intervallen - -### 7.2 Device Simulator: Waage -- Modi: - - stream weight (mehrfach pro Sekunde) - - stable_weight nur auf „stabil“ - - tare/zero Events per CLI - -### 7.3 Bridge Simulator (MQTT → Odoo) -- Abonniert alle relevanten Topics -- Validiert Schema v1 -- POSTet Events an Odoo -- Retry-Queue (lokal) bei Odoo-Ausfall -- Metriken/Logs: - - gesendete Events, Fehlerquoten, Latenz - ---- - -## 8. Milestones & Deliverables (NEU: Hardware-First Approach) - -### Phase 1: Python-Prototyp (Standalone, ohne Odoo) - -#### M0 – [x] Projekt Setup & MQTT Verbindung (0.5 Tag) [x] -**Deliverables** -- Python Projekt Struktur -- Requirements.txt (paho-mqtt, pyyaml, etc.) -- Config-Datei (YAML): MQTT Broker Settings -- MQTT Client Basis-Verbindung testen - -**Test**: Verbindung zum MQTT Broker herstellen, Topics anzeigen - ---- - -#### M1 – [x] Shelly PM Mini G3 Integration (1 Tag) [x] -**Deliverables** -- MQTT Subscriber für Shelly-Topics -- Parser für Shelly JSON-Payloads (beide Formate): - - Status-Updates (full data mit voltage, current, apower) - - NotifyStatus Events (einzelne Werte) -- Datenextraktion: `apower`, `timestamp`, `device_id` -- Logging aller empfangenen Shelly-Messages - -**Test**: Shelly-Daten empfangen und parsen, Console-Output - ---- - -#### M2 – [x] Event-Normalisierung & Unified Schema (1 Tag) -**Deliverables** -- Unified Event Schema v1 Implementation -- Shelly-to-Unified Konverter: - - `apower` Updates → `power_measurement` Events - - Enrichment mit `device_id`, `machine_id`, `timestamp` -- Event-UID Generierung -- JSON-Export der normalisierten Events - -**Test**: Shelly-Daten werden zu Schema-v1-konformen Events konvertiert - ---- - -#### M3 – [x] Session Detection Engine (1-2 Tage) -**Deliverables** -- State Machine für run_start/run_stop Detection -- Konfigurierbare Parameter pro Device: - - `power_threshold` (z.B. 50W) - - `start_debounce_s` (z.B. 3s) - - `stop_debounce_s` (z.B. 15s) -- Session-Objekte: - - `session_id`, `machine_id`, `start_ts`, `stop_ts`, `duration_s` - - `events` (Liste der zugehörigen Events) -- Session-Export (JSON/CSV) - -**Test**: -- Maschine anschalten → `run_start` Event -- Maschine ausschalten → `run_stop` Event -- Sessions werden korrekt erkannt und gespeichert - ---- - -#### M4 – [x] Multi-Device Support & Config (0.5-1 Tag) -**Deliverables** -- Device-Registry in Config: - ```yaml - devices: - - shelly_id: "shellypmminig3-48f6eeb73a1c" - machine_name: "Shaper Origin" - power_threshold: 50 - start_debounce_s: 3 - stop_debounce_s: 15 - ``` -- Dynamisches Laden mehrerer Devices -- Parallele Session-Tracking pro Device - -**Test**: Mehrere Shelly-Devices in Config, jedes wird separat getrackt - ---- - -#### M5 – [x] Monitoring & Robustheit (0.5 Tag) -**Deliverables** -- Reconnect-Logik bei MQTT-Disconnect -- Error-Handling bei ungültigen Payloads -- Statistiken: empfangene Events, aktive Sessions, Fehler -- Strukturiertes Logging (Logger-Konfiguration) - -**Test**: MQTT Broker Neustart → Client reconnected automatisch - ---- - -### Phase 2: Docker-Container-Architektur - -#### M6 – [x] IoT Bridge Docker Container (2-3 Tage) -**Deliverables:** -- Verzeichnis: `iot_bridge/` - - `main.py` - Bridge Hauptprogramm - - `mqtt_client.py` - MQTT Client (von Odoo portiert) - - `session_detector.py` - State Machine (von Odoo portiert) - - `parsers/` - Shelly, Tasmota, Generic - - `odoo_client.py` - REST API Client für Odoo - - `config.py` - Config Management (ENV + Odoo) - - `requirements.txt` - Python Dependencies - - `Dockerfile` - Multi-stage Build -- Docker Image: `iot_mqtt_bridge_for_odoo` -- ENV-Variablen: - - `ODOO_URL` (z.B. `http://odoo:8069`) - **Pflicht** - - `MQTT_URL` (z.B. `mqtt://mosquitto:1883`) - **Pflicht** - - `ODOO_TOKEN` (API Token für Bridge) - **Optional**, nur wenn Auth in Odoo aktiviert - - `LOG_LEVEL`, `CONFIG_REFRESH_INTERVAL` - Optional - -**Test:** -- Bridge startet via `docker run` -- Verbindet zu MQTT Broker -- Holt Config von Odoo via `GET /ows/iot/config` -- Subscribed auf Topics - ---- - -#### M7 – [x] Odoo REST API Endpoints & GUI (KOMPLETT) -**Deliverables:** -- Controller: `controllers/iot_api.py` - [x] `POST /ows/iot/event` - Event-Empfang mit Idempotenz (event_uid unique constraint) - [x] Auto-Verlinkung: Device & Session werden automatisch zugeordnet -- **Benutzerfreundliche Device-Konfiguration:** - [x] Abrechnungs-Intervall (Minuten) - statt heartbeat_interval_s - [x] Einschalt-Schwelle / Arbeits-Schwelle (Watt) - statt standby/working_threshold_w - [x] Anlauf-/Abschalt-Verzögerung (Sekunden) - statt start/stop_debounce_s - [x] Icons, Hilfe-Texte, Beispiele direkt in der GUI - [x] Automatische JSON-Generierung (strategy_config ist computed & readonly) -- **Auto-Config-Push an Bridge:** - [x] Bei create/write/delete automatisch POST /config an Bridge - [x] Retry-Logic (3 Versuche, exponential backoff) - [x] Manueller Push-Button: "🔄 Push Config to Bridge" -- Session-Aggregation: - [x] Total/Working/Standby/Idle Duration (transparent breakdown) - [x] Alle Dauer-Felder in Minuten (billing-friendly) -- Event-Validation gegen Schema v1 -- Event → Session Mapping - ---- - -#### M8 – [x] Docker Compose Integration (KOMPLETT) -**Deliverables:** -- [x] Multi-Container Setup: Odoo + Bridge + MQTT + DB + pgAdmin -- [x] Shared Network: Alle Services kommunizieren über `hobbyhimmel_odoo_18-dev_network` -- [x] Volume Mounts: Bridge Config, Odoo addons, DB persistence -- [x] Dev Script: `./odoo/dev.sh` für schnelles Starten/Stoppen -- Siehe: `./odoo/docker-compose.dev.yaml` - ---- - -#### M9 – [x] End-to-End Tests & Dokumentation (KOMPLETT) -**Deliverables:** -- [x] Integration Tests: Shelly Simulator → MQTT → Bridge → Odoo -- [x] Session Tests: run_start/run_stop Detection funktioniert -- [x] Container Restart: Bridge läuft autark mit persisted config -- [x] Config-Push: Odoo Änderungen triggern automatisch Bridge-Update -- Dokumentation: - - [x] `iot_bridge/README.md` - Bridge Architektur & API - - [x] Module README - Benutzer-Dokumentation mit API-Link - - [x] Feature Request mit aktuellen Begriffen & Beispielen - -**Tests bestätigt:** -- ✅ Shelly Simulator → Session erstellt in Odoo -- ✅ mqtt_device_id, power_w, state werden korrekt befüllt -- ✅ Events kommen im konfigurierten Abrechnungs-Intervall -- ✅ Config-Änderung in GUI → Bridge empfängt Update sofort -- ✅ Duration-Breakdown: Total = Working + Standby + Idle (transparent) - ---- - -#### M10 – Tests & Dokumentation (1 Tag) -**Deliverables** -- Unit Tests: Event-Parsing, Session-Detection -- Integration Tests: MQTT → Odoo End-to-End -- Dokumentation: Setup-Anleitung, Config-Beispiele, API-Docs - ---- - -### Optional M11 – POS/Anzeige (später) -- Realtime Anzeige im POS oder auf Display -- Live Power-Consumption in Odoo - ---- - -## 9. Testplan (Simulation-first) - -### 9.1 Unit Tests (Odoo) -- Auth: gültiger Token → 200 -- ungültig/fehlend → 401/403 -- Schema-Validation → 400 -- Idempotenz: duplicate `event_uid` → 409 oder duplicate-flag - -### 9.2 Integration Tests -- Sequenz: start → heartbeat → stop → Session duration plausibel -- stop ohne start → kein Crash, Event loggt Fehlerzustand -- Timeout: start → keine heartbeat → Session aborted - -### 9.3 Last-/Stabilitätstest (Simulator) -- 20 Maschinen, je 1 Event/s, 1h Lauf -- Ziel: Odoo bleibt stabil, Event-Insert performant, Queue läuft nicht über - ---- - -## 10. Betriebs- und Sicherheitskonzept - -- Token-Rotation möglich (neues Token, altes deaktivieren) -- Reverse Proxy: - - HTTPS - - Rate limiting auf `/ows/iot/event` - - optional Basic WAF Regeln -- Payload-Größenlimit (z. B. 16–64 KB) -- Bridge persistiert Retry-Queue auf Disk - ---- - -## 11. Offene Entscheidungen (später, nicht blocker für MVP) - -- Event-Consumer in Odoo vs. Bridge-only Webhook (empfohlen: Webhook) -- Genaues Mapping zu bestehenden open_workshop Modellen (`ows.machine`) -- User-Zuordnung zu Sessions (manuell über UI oder zukünftig via RFID/NFC) -- Abrechnung: Preisregeln, Rundungen, POS-Integration -- Realtime: Odoo Bus vs. eigener WebSocket Service - ---- - -## 12. Implementierungsstatus (Stand: 17. Februar 2026) - -### ✅ Phase 1: Python-Prototyp - **KOMPLETT** -- [x] M0-M5: Alle Milestones abgeschlossen -- [x] Shelly PM Mini G3 Integration funktioniert -- [x] Session Detection Engine mit State Machine -- [x] Multi-Device Support mit Config-Management - -### ✅ Phase 2: Docker-Integration - **KOMPLETT** -- [x] M6: IoT Bridge als Docker Container -- [x] M7: Odoo REST API + Benutzerfreundliche GUI -- [x] M8: Docker Compose Multi-Container Setup -- [x] M9: End-to-End Tests erfolgreich - -### 🎯 Highlights der Implementierung - -**Benutzerfreundliche Konfiguration:** -- Keine JSON-Eingabe mehr - Stattdessen klare deutsche Feldnamen -- Icons und Hilfe-Texte direkt am Feld -- Beispiele und Empfehlungen in der GUI -- Automatische Validierung mit verständlichen Fehlermeldungen - -**Transparente Abrechnung:** -- Abrechnungs-Intervall in Minuten konfigurierbar -- Duration-Breakdown: Total = Working + Standby + Idle -- Alle Zeiten in Minuten (billing-friendly) -- Idle-Zeit zeigt Overhead (Debounce/Timeout) transparent - -**Automatische Integration:** -- Config-Push: Odoo → Bridge automatisch bei Änderungen -- Event-Flow: Bridge → Odoo mit Retry-Logic & Idempotenz -- Auto-Verlinkung: Events → Device & Session -- Container-Orchestrierung via Docker Compose - -**Produktionsreife Features:** -- Bridge läuft autark (persisted config in /data/config-active.yaml) -- Retry-Logic: 3 Versuche mit exponential backoff -- Health Checks & Status Monitoring -- Strukturiertes Logging (JSON) - -### 📊 Aktuelle Konfiguration (Beispiel) -```yaml -# Odoo GUI Felder: -Abrechnungs-Intervall: 1 Minute (anpassbar) -Einschalt-Schwelle: 20 Watt -Arbeits-Schwelle: 100 Watt -Anlauf-Verzögerung: 3 Sekunden -Abschalt-Verzögerung: 15 Sekunden -Timeout: 20 Sekunden - -# Automatisch generiert für Bridge: -{ - "standby_threshold_w": 20, - "working_threshold_w": 100, - "start_debounce_s": 3, - "stop_debounce_s": 15, - "message_timeout_s": 20, - "heartbeat_interval_s": 60 # = 1 Minute -} -``` - -### 🚀 Nächste Schritte (Optional) - -**Produktiv-Deployment:** -- [ ] Token-Authentifizierung aktivieren (BRIDGE_API_TOKEN) -- [ ] HTTPS mit Reverse Proxy (nginx/traefik) -- [ ] Rate Limiting auf Event-Endpunkt -- [ ] Monitoring & Alerting (Prometheus/Grafana) - -**Feature-Erweiterungen:** -- [ ] Multi-Tenant Support (mehrere Werkstätten) -- [ ] RFID/NFC für User-Zuordnung -- [ ] Preis-Regeln & Abrechnung -- [ ] POS-Integration für Live-Display -- [ ] Mobile App für Maschinenstatus - -**Code-Qualität:** -- [ ] Erweiterte Unit Tests (pytest) -- [ ] Integration Tests automatisieren (CI/CD) -- [ ] Load Tests (20+ Devices, 1h Laufzeit) -- [ ] API-Dokumentation generieren (OpenAPI/Swagger) - ---- - -## 13. Lessons Learned - -**Was gut funktioniert hat:** -- ✅ Sidecar-Pattern: Bridge & Odoo als separate Container -- ✅ Config-Push statt Config-Pull: Odoo pusht, Bridge läuft autark -- ✅ Benutzerfreundliche GUI statt JSON-Eingabe -- ✅ Computed Fields: strategy_config automatisch generiert -- ✅ Verständliche deutsche Begriffe statt technischer Jargon - -**Was verbessert wurde:** -- 🔄 model naming: `mqtt.*` → `ows.mqtt.*` für Konsistenz -- 🔄 duration units: hours → minutes (billing-friendly) -- 🔄 transparent breakdown: idle_duration zeigt Overhead -- 🔄 auto-push: relevant_fields erweitert um neue GUI-Felder - -**Architektur-Entscheidungen:** -- Bridge = Technisches System (Sekunden, Power-Werte) -- Odoo = Business System (Minuten, Abrechnungseinheiten) -- Separation of Concerns funktioniert hervorragend - diff --git a/IMPLEMENTATION_PLAN.md b/IMPLEMENTATION_PLAN.md index 73535bf..d6c5fc8 100644 --- a/IMPLEMENTATION_PLAN.md +++ b/IMPLEMENTATION_PLAN.md @@ -1,805 +1,35 @@ -# Implementation Plan: IoT Bridge & Odoo Integration +# Implementation Plan (Konsolidiert) -**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 +**Stand:** 2026-02-19 +**Status:** In Betrieb, laufende Optimierung ---- +## 1) Erreichte Ergebnisse -## 📦 Bestandsaufnahme +### Architektur +- IoT-Bridge als eigenständiger Service (`iot_bridge/`) +- Odoo-Addon als fachliche Integrationsschicht (`open_workshop_mqtt`) +- Push-Config Odoo → Bridge über `POST /config` +- Event-Flow Bridge → Odoo über `POST /ows/iot/event` (JSON-RPC) -### 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) +### Qualität & Stabilität +- Type-Safety und Logging-Harmonisierung umgesetzt +- Retry-/Fehlerbehandlung für Odoo-Delivery ausgebaut +- Unit- und Integration-Tests erweitert +- Bridge-Dokumentation (`ARCHITECTURE.md`, `DEVELOPMENT.md`, API-Referenzen) konsolidiert -### 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 +### Fachlicher Fix (aktuell) +- Offene Sessions werden robust geschlossen, wenn für dieselbe `device_id` neue Start-/Online-Ereignisse eintreffen +- Ziel: Keine hängenden Parallel-Sessions pro Gerät ---- +## 2) Offene Punkte (kurz) -## 🎯 Phase 1: Bridge Container (Standalone) +1. Optional: weitere Härtung der Odoo-Event-Tests rund um Restart-/Reconnect-Szenarien +2. Optional: Performance-/Batching-Themen (siehe `iot_bridge/OPTIMIZATION_PLAN.md`, Phase 5) +3. Kontinuierliche Doku-Pflege nach „Source-of-Truth“-Prinzip -**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) +## 3) Referenzen +- Detail-Roadmap: `iot_bridge/OPTIMIZATION_PLAN.md` +- Bridge-Architektur: `iot_bridge/ARCHITECTURE.md` +- Bridge-Entwicklung: `iot_bridge/DEVELOPMENT.md` +- Odoo-Addon API: `extra-addons/open_workshop/open_workshop_mqtt/API.md` diff --git a/IMPLEMENTATION_PLAN_DEVICE_STATUS.md b/IMPLEMENTATION_PLAN_DEVICE_STATUS.md deleted file mode 100644 index f212f60..0000000 --- a/IMPLEMENTATION_PLAN_DEVICE_STATUS.md +++ /dev/null @@ -1,102 +0,0 @@ -# Implementation Plan: Device Online/Offline Status - -## Overview -Wir leiten den Device Online/Offline Status von MQTT Message Activity (last_seen) ab. Die Bridge überwacht die Aktivität und informiert Odoo via Events. Odoo aktualisiert die bestehenden Status-Felder und zeigt sie in der UI an. - -**Wichtig:** Die benötigten Felder existieren bereits in `mqtt.device`: -- `state` (selection: offline/idle/active) -- `last_message_time` (datetime) - -## Implementierung: Event-basierte Status-Updates -### Bridge -1. Track last_seen per device on every MQTT message. -2. Add a background monitor that marks devices Offline after timeout. -3. Emit status events to Odoo: - - event_type: device_online, device_offline - - payload: device_id, last_seen_at, reason -4. Add optional MQTT LWT handling if available from devices. -5. Persist last_seen state in /data to survive restart. - -### Odoo -1. **Bestehende Felder nutzen** (mqtt.device): - - `state` (offline/idle/active) - - `last_message_time` (datetime) -2. **Event Controller erweitern** (/ows/iot/event): - - Event-Type `device_online` verarbeiten: state → 'idle', last_message_time aktualisieren - - Event-Type `device_offline` verarbeiten: state → 'offline' -3. **Views aktualisieren**: - - Status-Anzeige mit Farb-Kodierung (grün=idle/active, grau=offline) - - Last_message_time in List und Form View - - Filter in List View: Online (idle/active), Offline - -## Data Model -**Bestehende Felder** (keine Änderungen nötig): -- `state`: selection [offline, idle, active] - Current device status -- `last_message_time`: datetime - Timestamp of last MQTT message - -## Configuration -- BRIDGE_DEVICE_TIMEOUT_S (default 30) -- BRIDGE_STATUS_CHECK_INTERVAL_S (default 5) - -## Implementation Steps - -### Phase 1: Bridge - Status Monitoring -1. **mqtt_client.py**: Track last_seen timestamp pro device bei jedem MQTT Message -2. **Status Monitor Thread**: - - Background Task, läuft alle BRIDGE_STATUS_CHECK_INTERVAL_S (default: 5s) - - Prüft jedes device: if (now - last_seen) > BRIDGE_DEVICE_TIMEOUT_S → Offline - - State Transitions: - - first_message → device_online event - - timeout → device_offline event -3. **Persistenz**: last_seen state in /data/device_status.json speichern (Restart-Recovery) -4. **Event Emission**: device_online/device_offline via odoo_client.send_event() - -### Phase 2: Odoo - Event Processing -1. **iot_api.py** (Event Controller): - - Erweitere `_process_event()` für event_type='device_online': - ```python - device.write({'state': 'idle', 'last_message_time': event_data['timestamp']}) - ``` - - Erweitere für event_type='device_offline': - ```python - device.write({'state': 'offline'}) - ``` -2. **Views aktualisieren**: - - mqtt_device_views.xml: Status-Badge mit Farbe (idle/active=success, offline=secondary) - - List View: last_message_time column hinzufügen - - Filter: "Online" (state in ['idle','active']), "Offline" (state='offline') - -### Phase 3: Testing -1. **Unit Tests (Bridge)**: - - Test timeout detection logic - - Test state persistence (save/load) - - Test event emission -2. **Integration Tests**: - - iot_bridge/tests/tools/shelly_simulator.py erweitern: - - `scenario_online_test()`: Kontinuierliches Senden - - `scenario_offline_test()`: Stop-Senden für X Sekunden - - Odoo Test: Prüfe state-Updates nach Online/Offline Events -3. **Test Cases**: - - Device wird Online: state='idle', last_message_time gesetzt - - Device timeout: state='offline' nach TIMEOUT_S - - Bridge restart: last_seen State recovered - -### Phase 4: Documentation -1. README.md: Status Monitoring section -2. API.md: device_online/device_offline event schema -3. config.example.yaml: BRIDGE_DEVICE_TIMEOUT_S, BRIDGE_STATUS_CHECK_INTERVAL_S - -## Acceptance Tests -- ✅ Start shelly_simulator: status wird 'idle' within 1 message -- ✅ Stop simulator: status wird 'offline' nach TIMEOUT_S (default: 30s) -- ✅ Restart bridge: last_seen state wird recovered, status korrekt innerhalb 1 timeout window -- ✅ Multiple devices: Status-Updates unabhängig voneinander - -## Rollback Plan -- Config Flag: `BRIDGE_STATUS_MONITORING_ENABLED=false` deaktiviert Status Monitor -- Odoo: Ignoriere device_online/device_offline events (graceful degradation) - -## Notes -- **Event-basiert**: Near real-time Status ohne Polling overhead -- **Bestehende Felder**: Keine DB-Migration nötig -- **Backward Compatible**: Alte Events funktionieren weiterhin diff --git a/docs/history/FEATURE_REQUEST_DEVICE_STATUS.md b/docs/history/FEATURE_REQUEST_DEVICE_STATUS.md new file mode 100644 index 0000000..ae53aaa --- /dev/null +++ b/docs/history/FEATURE_REQUEST_DEVICE_STATUS.md @@ -0,0 +1,24 @@ +# Feature Request (Historisch): Device Online/Offline Status + +**Stand:** 2026-02-19 +**Status:** Umgesetzt + +## Ergebnis + +Der Device-Status wird eventbasiert gepflegt: +- `device_online` / `device_offline` Events aus der Bridge +- Aktualisierung von `ows.mqtt.device.state` und `last_message_time` in Odoo +- Timeout-basierte Offline-Erkennung über Bridge-Statusmonitor + +Zusätzlich wurde die Session-Konsistenz gehärtet: +- bei neuen Start-/Online-Ereignissen für dieselbe `device_id` werden stale laufende Sessions abgeschlossen + +## Relevante aktuelle Referenzen + +- Odoo Event-Controller: `extra-addons/open_workshop/open_workshop_mqtt/controllers/iot_api.py` +- Odoo API-Doku: `extra-addons/open_workshop/open_workshop_mqtt/API.md` +- Bridge Betrieb/Debugging: `iot_bridge/DEVELOPMENT.md` + +## Hinweis + +Diese Datei dient nur noch als historischer Request-Container. Aktuelle Details stehen in den Modul-Dokumentationen. diff --git a/docs/history/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md b/docs/history/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md new file mode 100644 index 0000000..edf188e --- /dev/null +++ b/docs/history/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md @@ -0,0 +1,23 @@ +# Feature Request (Historisch): Open Workshop MQTT IoT + +**Stand:** 2026-02-19 +**Status:** Weitgehend umgesetzt + +## Kurzfazit + +Die ursprüngliche Anforderung (MQTT-basierte IoT-Events in Odoo über eine Bridge) ist umgesetzt: +- eigenständige IoT-Bridge +- Odoo-Config-Push an Bridge +- Event-Verarbeitung Bridge → Odoo +- Session-/Gerätestatus-Verarbeitung + +## Wo der aktuelle Stand dokumentiert ist + +- Gesamtstatus: `IMPLEMENTATION_PLAN.md` +- Detaillierte technische Roadmap: `iot_bridge/OPTIMIZATION_PLAN.md` +- Bridge Architektur: `iot_bridge/ARCHITECTURE.md` +- Odoo Addon API: `extra-addons/open_workshop/open_workshop_mqtt/API.md` + +## Hinweis + +Diese Datei bleibt nur als historischer Kontext erhalten. Für aktuelle Implementierungsdetails bitte die oben genannten Dokumente verwenden. diff --git a/docs/history/IMPLEMENTATION_PLAN_DEVICE_STATUS.md b/docs/history/IMPLEMENTATION_PLAN_DEVICE_STATUS.md new file mode 100644 index 0000000..ca60951 --- /dev/null +++ b/docs/history/IMPLEMENTATION_PLAN_DEVICE_STATUS.md @@ -0,0 +1,22 @@ +# Implementation Plan (Historisch): Device Status + +**Stand:** 2026-02-19 +**Status:** Abgeschlossen + +## Umgesetzte Punkte + +- Last-seen/Timeout-basierte Online/Offline-Erkennung in der Bridge +- Event-Übertragung an Odoo (`device_online`, `device_offline`) +- Odoo-Verarbeitung im Event-Controller (`/ows/iot/event`) +- Sichtbarkeit des Device-Status in Odoo-Modellen/Views +- Session-Härtung gegen hängende parallele Running-Sessions pro `device_id` + +## Aktuelle technische Referenzen + +- Bridge Statusmonitor: `iot_bridge/utils/status_monitor.py` +- Bridge Service-Verkabelung: `iot_bridge/core/service_manager.py` +- Odoo Event-Controller: `extra-addons/open_workshop/open_workshop_mqtt/controllers/iot_api.py` + +## Hinweis + +Diese Datei ist als historischer Abschlussvermerk gedacht. Für laufende Arbeit bitte `IMPLEMENTATION_PLAN.md` und `iot_bridge/OPTIMIZATION_PLAN.md` nutzen. diff --git a/docs/history/README.md b/docs/history/README.md new file mode 100644 index 0000000..6080e98 --- /dev/null +++ b/docs/history/README.md @@ -0,0 +1,10 @@ +# History Archive + +Dieses Verzeichnis enthält historische Anforderungen und Teilpläne, die nicht mehr als aktive Arbeitsdokumente im Root geführt werden. + +Aktuelle operative und technische Dokumentation: +- `DOCUMENTATION_STRATEGY.md` +- `IMPLEMENTATION_PLAN.md` +- `DEPLOYMENT.md` +- `iot_bridge/*` +- `extra-addons/open_workshop/open_workshop_mqtt/*`