docs: consolidate root docs and archive historical requests
This commit is contained in:
parent
fb0710c680
commit
2d483e08ad
153
DEPLOYMENT.md
153
DEPLOYMENT.md
|
|
@ -1,130 +1,57 @@
|
|||
# Deployment Guide
|
||||
# Deployment Guide (Kompakt)
|
||||
|
||||
This guide covers production deployment of the IoT Bridge and its integration with Odoo.
|
||||
**Stand:** 2026-02-19
|
||||
|
||||
## Prerequisites
|
||||
## 1) Zielbild
|
||||
|
||||
- Docker and Docker Compose
|
||||
- Odoo 18 with `open_workshop_mqtt` installed
|
||||
- Mosquitto (or any MQTT broker)
|
||||
Produktiver Betrieb mit:
|
||||
- Odoo 18 + `open_workshop_mqtt`
|
||||
- IoT Bridge (`iot_bridge`)
|
||||
- MQTT Broker (z. B. Mosquitto)
|
||||
- Persistenz für Bridge-Daten (`/data/config-active.yaml`)
|
||||
|
||||
## Recommended Topology
|
||||
## 2) Start (Dev/Integration)
|
||||
|
||||
- `odoo-dev` (Odoo 18)
|
||||
- `iot_bridge` (this service)
|
||||
- `mosquitto` (MQTT broker)
|
||||
- Persistent volume for `/data` in the bridge container
|
||||
Compose-Datei: `odoo/docker-compose.dev.yaml`
|
||||
|
||||
## Configuration
|
||||
|
||||
### Required Environment Variables
|
||||
|
||||
```
|
||||
MQTT_BROKER=mosquitto
|
||||
MQTT_PORT=1883
|
||||
BRIDGE_PORT=8080
|
||||
ODOO_BASE_URL=http://odoo-dev:8069
|
||||
ODOO_DATABASE=odoo
|
||||
ODOO_USERNAME=admin
|
||||
Beispiel:
|
||||
```bash
|
||||
cd odoo
|
||||
docker compose --env-file .env -f docker-compose.dev.yaml up -d
|
||||
```
|
||||
|
||||
### Optional Environment Variables
|
||||
## 3) Pflicht-Konfiguration
|
||||
|
||||
```
|
||||
MQTT_USERNAME=
|
||||
MQTT_PASSWORD=
|
||||
MQTT_CLIENT_ID=iot_bridge
|
||||
MQTT_USE_TLS=false
|
||||
BRIDGE_API_TOKEN=
|
||||
LOG_LEVEL=INFO
|
||||
LOG_FORMAT=json
|
||||
LOG_FILE=
|
||||
```
|
||||
### Odoo → Bridge
|
||||
- Systemparameter in Odoo:
|
||||
- Key: `open_workshop_mqtt.bridge_url`
|
||||
- Value: z. B. `http://iot-bridge:8080`
|
||||
|
||||
## Docker Compose Example
|
||||
### API-Schutz (optional)
|
||||
- Bridge-Token über `BRIDGE_API_TOKEN`
|
||||
- Geschützte Aufrufe mit `Authorization: Bearer <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`
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
@ -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. 16–64 KB)
|
||||
- Bridge persistiert Retry-Queue auf Disk
|
||||
|
||||
---
|
||||
|
||||
## 11. Offene Entscheidungen (später, nicht blocker für MVP)
|
||||
|
||||
- Event-Consumer in Odoo vs. Bridge-only Webhook (empfohlen: Webhook)
|
||||
- Genaues Mapping zu bestehenden open_workshop Modellen (`ows.machine`)
|
||||
- User-Zuordnung zu Sessions (manuell über UI oder zukünftig via RFID/NFC)
|
||||
- Abrechnung: Preisregeln, Rundungen, POS-Integration
|
||||
- Realtime: Odoo Bus vs. eigener WebSocket Service
|
||||
|
||||
---
|
||||
|
||||
## 12. Implementierungsstatus (Stand: 17. Februar 2026)
|
||||
|
||||
### ✅ Phase 1: Python-Prototyp - **KOMPLETT**
|
||||
- [x] M0-M5: Alle Milestones abgeschlossen
|
||||
- [x] Shelly PM Mini G3 Integration funktioniert
|
||||
- [x] Session Detection Engine mit State Machine
|
||||
- [x] Multi-Device Support mit Config-Management
|
||||
|
||||
### ✅ Phase 2: Docker-Integration - **KOMPLETT**
|
||||
- [x] M6: IoT Bridge als Docker Container
|
||||
- [x] M7: Odoo REST API + Benutzerfreundliche GUI
|
||||
- [x] M8: Docker Compose Multi-Container Setup
|
||||
- [x] M9: End-to-End Tests erfolgreich
|
||||
|
||||
### 🎯 Highlights der Implementierung
|
||||
|
||||
**Benutzerfreundliche Konfiguration:**
|
||||
- Keine JSON-Eingabe mehr - Stattdessen klare deutsche Feldnamen
|
||||
- Icons und Hilfe-Texte direkt am Feld
|
||||
- Beispiele und Empfehlungen in der GUI
|
||||
- Automatische Validierung mit verständlichen Fehlermeldungen
|
||||
|
||||
**Transparente Abrechnung:**
|
||||
- Abrechnungs-Intervall in Minuten konfigurierbar
|
||||
- Duration-Breakdown: Total = Working + Standby + Idle
|
||||
- Alle Zeiten in Minuten (billing-friendly)
|
||||
- Idle-Zeit zeigt Overhead (Debounce/Timeout) transparent
|
||||
|
||||
**Automatische Integration:**
|
||||
- Config-Push: Odoo → Bridge automatisch bei Änderungen
|
||||
- Event-Flow: Bridge → Odoo mit Retry-Logic & Idempotenz
|
||||
- Auto-Verlinkung: Events → Device & Session
|
||||
- Container-Orchestrierung via Docker Compose
|
||||
|
||||
**Produktionsreife Features:**
|
||||
- Bridge läuft autark (persisted config in /data/config-active.yaml)
|
||||
- Retry-Logic: 3 Versuche mit exponential backoff
|
||||
- Health Checks & Status Monitoring
|
||||
- Strukturiertes Logging (JSON)
|
||||
|
||||
### 📊 Aktuelle Konfiguration (Beispiel)
|
||||
```yaml
|
||||
# Odoo GUI Felder:
|
||||
Abrechnungs-Intervall: 1 Minute (anpassbar)
|
||||
Einschalt-Schwelle: 20 Watt
|
||||
Arbeits-Schwelle: 100 Watt
|
||||
Anlauf-Verzögerung: 3 Sekunden
|
||||
Abschalt-Verzögerung: 15 Sekunden
|
||||
Timeout: 20 Sekunden
|
||||
|
||||
# Automatisch generiert für Bridge:
|
||||
{
|
||||
"standby_threshold_w": 20,
|
||||
"working_threshold_w": 100,
|
||||
"start_debounce_s": 3,
|
||||
"stop_debounce_s": 15,
|
||||
"message_timeout_s": 20,
|
||||
"heartbeat_interval_s": 60 # = 1 Minute
|
||||
}
|
||||
```
|
||||
|
||||
### 🚀 Nächste Schritte (Optional)
|
||||
|
||||
**Produktiv-Deployment:**
|
||||
- [ ] Token-Authentifizierung aktivieren (BRIDGE_API_TOKEN)
|
||||
- [ ] HTTPS mit Reverse Proxy (nginx/traefik)
|
||||
- [ ] Rate Limiting auf Event-Endpunkt
|
||||
- [ ] Monitoring & Alerting (Prometheus/Grafana)
|
||||
|
||||
**Feature-Erweiterungen:**
|
||||
- [ ] Multi-Tenant Support (mehrere Werkstätten)
|
||||
- [ ] RFID/NFC für User-Zuordnung
|
||||
- [ ] Preis-Regeln & Abrechnung
|
||||
- [ ] POS-Integration für Live-Display
|
||||
- [ ] Mobile App für Maschinenstatus
|
||||
|
||||
**Code-Qualität:**
|
||||
- [ ] Erweiterte Unit Tests (pytest)
|
||||
- [ ] Integration Tests automatisieren (CI/CD)
|
||||
- [ ] Load Tests (20+ Devices, 1h Laufzeit)
|
||||
- [ ] API-Dokumentation generieren (OpenAPI/Swagger)
|
||||
|
||||
---
|
||||
|
||||
## 13. Lessons Learned
|
||||
|
||||
**Was gut funktioniert hat:**
|
||||
- ✅ Sidecar-Pattern: Bridge & Odoo als separate Container
|
||||
- ✅ Config-Push statt Config-Pull: Odoo pusht, Bridge läuft autark
|
||||
- ✅ Benutzerfreundliche GUI statt JSON-Eingabe
|
||||
- ✅ Computed Fields: strategy_config automatisch generiert
|
||||
- ✅ Verständliche deutsche Begriffe statt technischer Jargon
|
||||
|
||||
**Was verbessert wurde:**
|
||||
- 🔄 model naming: `mqtt.*` → `ows.mqtt.*` für Konsistenz
|
||||
- 🔄 duration units: hours → minutes (billing-friendly)
|
||||
- 🔄 transparent breakdown: idle_duration zeigt Overhead
|
||||
- 🔄 auto-push: relevant_fields erweitert um neue GUI-Felder
|
||||
|
||||
**Architektur-Entscheidungen:**
|
||||
- Bridge = Technisches System (Sekunden, Power-Werte)
|
||||
- Odoo = Business System (Minuten, Abrechnungseinheiten)
|
||||
- Separation of Concerns funktioniert hervorragend
|
||||
|
||||
|
|
@ -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`
|
||||
|
|
|
|||
|
|
@ -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
|
||||
24
docs/history/FEATURE_REQUEST_DEVICE_STATUS.md
Normal file
24
docs/history/FEATURE_REQUEST_DEVICE_STATUS.md
Normal 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.
|
||||
23
docs/history/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md
Normal file
23
docs/history/FEATURE_REQUEST_OPEN_WORKSHOP_MQTT_IoT.md
Normal 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.
|
||||
22
docs/history/IMPLEMENTATION_PLAN_DEVICE_STATUS.md
Normal file
22
docs/history/IMPLEMENTATION_PLAN_DEVICE_STATUS.md
Normal 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
10
docs/history/README.md
Normal 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/*`
|
||||
Loading…
Reference in New Issue
Block a user