docs: consolidate root docs and archive historical requests

This commit is contained in:
Matthias Lotz 2026-02-19 21:57:07 +01:00
parent fb0710c680
commit 2d483e08ad
10 changed files with 178 additions and 2597 deletions

View File

@ -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 <token>`
```
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://<bridge-host>:8080/health`
- Active Config: `GET http://<bridge-host>: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`

View File

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

View File

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

View File

@ -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 <BRIDGE_TOKEN>`
- 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 <BRIDGE_API_TOKEN>` (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/<machine_id>/state`
- Maschinenereignisse:
`hobbyhimmel/machines/<machine_id>/event`
- Waage:
`hobbyhimmel/scales/<scale_id>/event`
- Geräte-Status (optional):
`hobbyhimmel/devices/<device_id>/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. 1664 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

View File

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

View File

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

View File

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

View File

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

View File

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

10
docs/history/README.md Normal file
View File

@ -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/*`