fix: Deprecated force_sync Feature entfernt und wiki_doku_id bei Reset erhalten
Änderungen: - maintenance_equipment.py: wiki_doku_id wird bei action_reset_wiki_sync_status() nicht mehr zurückgesetzt (ist manuell editierbar) - equipment_wiki_sync_wizard.py: Deprecated force_sync Feld und zugehörige Logik entfernt - equipment_wiki_sync_wizard_views.xml: force_sync UI-Element entfernt - FEATURE_REQUEST/Odoo18_MQTT_IoT.md: Projektplan für MQTT-basierte IoT-Events hinzugefügt Grund: - force_sync Feature referenzierte nicht existierende Methoden (_generate_wiki_doku_page_content, _get_wiki_doku_page_id) - wiki_doku_id ist jetzt manuell editierbar und sollte nicht automatisch zurückgesetzt werden - Cleanup von veralteter/fehlerhafter Funktionalität
This commit is contained in:
parent
4684d33241
commit
77cd3f4595
324
FEATURE_REQUEST/Odoo18_MQTT_IoT.md
Normal file
324
FEATURE_REQUEST/Odoo18_MQTT_IoT.md
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
# Projektplan: MQTT-basierte IoT-Events in Odoo 18 Community (Simulation-first)
|
||||
|
||||
Stand: 2026-01-10 : https://chatgpt.com/share/696e559d-1640-800f-9d53-f7a9d1e784bd
|
||||
|
||||
Ziel: **Odoo-18-Community (self-hosted)** soll **Geräte-Events** (Timer/Maschinenlaufzeit, Waage, Zustandswechsel) über **MQTT** aufnehmen und in Odoo als **saubere, nachvollziehbare Sessions/Events** verarbeiten.
|
||||
Wichtig: **Hardware wird zunächst vollständig simuliert** (Software-Simulatoren), damit die Odoo-Schnittstelle stabil steht, bevor echte Geräte angebunden werden.
|
||||
|
||||
---
|
||||
|
||||
## 1. Ziele und Nicht-Ziele
|
||||
|
||||
### 1.1 Ziele (MVP)
|
||||
- Einheitliche **Device-Event-Schnittstelle** in Odoo (REST/Webhook) inkl. Authentifizierung
|
||||
- **Event-Log** in Odoo (persistente Rohereignisse + Normalisierung)
|
||||
- **Session-Logik** für Timer/Maschinenlaufzeit (Start/Stop/Heartbeat)
|
||||
- **Simulation** von:
|
||||
- Maschinenzustand (running/idle/fault)
|
||||
- Timer-Events (run_start/run_stop)
|
||||
- Waage (stable_weight/tare)
|
||||
- Reproduzierbare Tests (Unit/Integration) und eindeutige Fehlerdiagnostik (Logging)
|
||||
|
||||
### 1.2 Nicht-Ziele (für Phase 1)
|
||||
- Keine Enterprise-IoT-Box, keine Enterprise-Module
|
||||
- Keine echte Hardware-Treiberentwicklung in Phase 1
|
||||
- Kein POS-Frontend-Live-Widget (optional erst in späterer Phase)
|
||||
- Keine Abrechnungslogik/Preisregeln (kann vorbereitet, aber nicht umgesetzt werden)
|
||||
|
||||
---
|
||||
|
||||
## 2. Zielarchitektur (Simulation-first)
|
||||
|
||||
### 2.1 Komponenten
|
||||
1. **MQTT Broker** : Mosquitto in Docker vorhanden
|
||||
2. **Device Simulator(s)** (Python/Node)
|
||||
- veröffentlicht MQTT Topics wie echte Geräte
|
||||
3. **Gateway/Bridge (Software)**
|
||||
- abonniert MQTT Topics
|
||||
- validiert/normalisiert Payload
|
||||
- sendet Events via HTTPS an Odoo (Webhook)
|
||||
4. **Odoo Modul** `ows_iot_bridge`
|
||||
- REST Controller `/ows/iot/event`
|
||||
- Modelle für Devices, Events, Sessions
|
||||
- Business-Regeln für Session-Start/Stop/Hysterese (softwareseitig)
|
||||
5. Optional später: **Realtime-Feed** (WebSocket) für POS/Display
|
||||
|
||||
### 2.2 Datenfluss
|
||||
1. Simulator publiziert MQTT → `hobbyhimmel/machines/<machine_id>/state`
|
||||
2. Bridge konsumiert MQTT, mappt auf Event-Format
|
||||
3. Bridge POSTet JSON an Odoo Endpoint
|
||||
4. Odoo speichert Roh-Event + erzeugt ggf. Session-Updates
|
||||
|
||||
---
|
||||
|
||||
## 3. Schnittstelle zu Odoo (Kern des Projekts)
|
||||
|
||||
### 3.1 Authentifizierung
|
||||
- Pro Device oder pro Bridge ein **API-Token** (Bearer)
|
||||
- Header: `Authorization: Bearer <token>`
|
||||
- Token in Odoo in `ows.iot.device` oder `ir.config_parameter` hinterlegt
|
||||
- Optional: IP-Allowlist / Rate-Limit (in Reverse Proxy)
|
||||
|
||||
### 3.2 Endpoint (REST/Webhook)
|
||||
- `POST /ows/iot/event`
|
||||
- Content-Type: `application/json`
|
||||
- Antwort:
|
||||
- `200 OK` mit `{ "status": "ok", "event_id": "...", "session_id": "..." }`
|
||||
- Fehler: `400` (Schema), `401/403` (Auth), `409` (Duplikat), `500` (Server)
|
||||
|
||||
### 3.3 Idempotenz / Duplikaterkennung
|
||||
- Jedes Event enthält eine eindeutige `event_uid`
|
||||
- Odoo legt Unique-Constraint auf `event_uid` (pro device)
|
||||
- Wiederholte Events liefern `409 Conflict` oder `200 OK (duplicate=true)` (Designentscheidung)
|
||||
|
||||
---
|
||||
|
||||
## 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**
|
||||
- `run_start` (Start einer Laufphase)
|
||||
- `run_stop` (Ende einer Laufphase)
|
||||
- `heartbeat` (optional, laufend während running)
|
||||
- `state_changed` (idle/running/fault)
|
||||
- `fault` (Fehler mit Code/Severity)
|
||||
|
||||
**Waage**
|
||||
- `stable_weight` (stabiler Messwert)
|
||||
- `weight` (laufend)
|
||||
- `tare`
|
||||
- `zero`
|
||||
- `error`
|
||||
|
||||
---
|
||||
|
||||
## 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)
|
||||
- `start_ts`, `stop_ts`
|
||||
- `duration_s` (computed)
|
||||
- `state` (running/stopped/aborted)
|
||||
- `origin` (sensor/manual/sim)
|
||||
- `confidence_summary`
|
||||
- `event_ids` (o2m)
|
||||
|
||||
> Hinweis: Wenn du bereits `ows.machine` aus deinem open_workshop nutzt, referenziert `machine_id` direkt dieses Modell.
|
||||
|
||||
---
|
||||
|
||||
## 6. Verarbeitungslogik (Phase 1: minimal, robust)
|
||||
|
||||
### 6.1 Session-Automat (State Machine)
|
||||
- Eingang: Events `run_start`, `run_stop`, optional `heartbeat`
|
||||
- Regeln:
|
||||
- `run_start` erstellt neue Session, wenn keine läuft
|
||||
- `run_start` während laufender Session → nur Log, keine neue Session
|
||||
- `run_stop` schließt laufende Session
|
||||
- Timeout-Regel: wenn `heartbeat` ausbleibt (z. B. 10 min) → Session `aborted`
|
||||
|
||||
### 6.2 Hysterese (Simulation als Stellvertreter für Sensorik)
|
||||
In Simulation definierst du:
|
||||
- Start, wenn „running“ mindestens **2s stabil**
|
||||
- Stop, wenn „idle“ mindestens **15s stabil**
|
||||
Diese Parameter als Odoo Systemparameter, z. B.:
|
||||
- `ows_iot.start_debounce_s`
|
||||
- `ows_iot.stop_debounce_s`
|
||||
|
||||
---
|
||||
|
||||
## 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
|
||||
|
||||
### M0 – Repo/Grundgerüst (0.5–1 Tag)
|
||||
**Deliverables**
|
||||
- Git-Repo Struktur
|
||||
- Docker Compose: mosquitto + simulator + bridge
|
||||
- Odoo Modul Skeleton `ows_iot_bridge`
|
||||
|
||||
### M1 – Odoo Endpoint & Modelle (1–2 Tage)
|
||||
**Deliverables**
|
||||
- `/ows/iot/event` Controller inkl. Token-Auth
|
||||
- Modelle `ows.iot.device`, `ows.iot.event`
|
||||
- Admin UI: Geräte anlegen, Token setzen, letzte Events anzeigen
|
||||
|
||||
### M2 – Session-Engine (1–2 Tage)
|
||||
**Deliverables**
|
||||
- Modell `ows.machine.session`
|
||||
- Event → Session Zuordnung
|
||||
- Timeout/Abbruch-Logik
|
||||
- Parameter für Debounce/Timeout
|
||||
|
||||
### M3 – Simulatoren & End-to-End Test (1–2 Tage)
|
||||
**Deliverables**
|
||||
- Machine Simulator + Scale Simulator
|
||||
- Bridge mit Retry-Queue
|
||||
- End-to-End: Simulator → MQTT → Bridge → Odoo → Session entsteht
|
||||
|
||||
### M4 – Qualität & Betrieb (1 Tag)
|
||||
**Deliverables**
|
||||
- Logging-Konzept (Odoo + Bridge)
|
||||
- Tests (Unit: Controller/Auth, Integration: Session Engine)
|
||||
- Doku: Event-Schema v1, Topics, Beispielpayloads
|
||||
|
||||
### Optional M5 – POS/Anzeige (später)
|
||||
- Realtime Anzeige im POS oder auf Display
|
||||
- Live-Weight in POS
|
||||
|
||||
---
|
||||
|
||||
## 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`, Nutzer/RFID)
|
||||
- Abrechnung: Preisregeln, Rundungen, POS-Integration
|
||||
- Realtime: Odoo Bus vs. eigener WebSocket Service
|
||||
|
||||
---
|
||||
|
||||
## 12. Nächste Schritte (konkret)
|
||||
|
||||
1. `ows_iot_bridge` Modul: Endpoint + Device/Event Modelle implementieren
|
||||
2. Docker Compose: mosquitto + bridge + simulator bereitstellen
|
||||
3. Simulatoren produzieren v1-Events; Bridge sendet an Odoo
|
||||
4. Session-Engine anschließen und End-to-End testen
|
||||
|
||||
---
|
||||
|
||||
## Anhang A: Beispiel-Event (Maschine run_start)
|
||||
```json
|
||||
{
|
||||
"schema_version": "v1",
|
||||
"event_uid": "c2a7d6f1-7d8d-4a63-9a7f-4e6d7b0d9e2a",
|
||||
"ts": "2026-01-10T12:34:56Z",
|
||||
"source": "simulator",
|
||||
"device_id": "esp32-fraser-01",
|
||||
"entity_type": "machine",
|
||||
"entity_id": "formatkreissaege",
|
||||
"event_type": "run_start",
|
||||
"confidence": "high",
|
||||
"payload": {
|
||||
"power_w": 820,
|
||||
"vibration": 0.73,
|
||||
"reason": "power_threshold"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Anhang B: Beispiel-Event (Waage stable_weight)
|
||||
```json
|
||||
{
|
||||
"schema_version": "v1",
|
||||
"event_uid": "b6d0a2c5-9b1f-4a0b-8b19-7f2e1b8f3d11",
|
||||
"ts": "2026-01-10T12:40:12Z",
|
||||
"source": "simulator",
|
||||
"device_id": "scale-sim-01",
|
||||
"entity_type": "scale",
|
||||
"entity_id": "waage-01",
|
||||
"event_type": "stable_weight",
|
||||
"confidence": "high",
|
||||
"payload": {
|
||||
"weight_g": 1532.4,
|
||||
"unit": "g",
|
||||
"stable_ms": 1200
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -915,7 +915,7 @@ Diese Seite wird automatisch aktualisiert.
|
|||
'wiki_synced': False,
|
||||
'wiki_last_sync': False,
|
||||
'wiki_status_id': False, # Status-ID neu generieren
|
||||
'wiki_doku_id': False, # Doku-ID neu generieren
|
||||
# wiki_doku_id wird NICHT zurückgesetzt - ist manuell editierbar!
|
||||
})
|
||||
|
||||
_logger.info(f"Wiki-Sync-Status für {count} Equipment zurückgesetzt")
|
||||
|
|
|
|||
|
|
@ -24,12 +24,6 @@ class EquipmentWikiSyncWizard(models.TransientModel):
|
|||
help='Nur Equipment aus diesem Bereich synchronisieren'
|
||||
)
|
||||
|
||||
force_sync = fields.Boolean(
|
||||
string='Zentrale Doku-Seiten überschreiben',
|
||||
default=False,
|
||||
help='Auch bereits existierende zentrale Dokumentationsseiten neu generieren (VORSICHT: Überschreibt manuell erstellten Inhalt!)'
|
||||
)
|
||||
|
||||
# Statistik-Felder (nach Sync)
|
||||
total_count = fields.Integer(string='Gefunden', readonly=True)
|
||||
success_count = fields.Integer(string='Erfolgreich', readonly=True)
|
||||
|
|
@ -91,19 +85,7 @@ class EquipmentWikiSyncWizard(models.TransientModel):
|
|||
_logger.warning(error_msg)
|
||||
continue
|
||||
|
||||
# Force-Sync: Zentrale Doku-Seite überschreiben
|
||||
if self.force_sync and equipment.wiki_doku_id:
|
||||
doku_page_id = equipment._get_wiki_doku_page_id()
|
||||
doku_content = equipment._generate_wiki_doku_page_content()
|
||||
dokuwiki_client = self.env['dokuwiki.client']
|
||||
dokuwiki_client.create_page(
|
||||
doku_page_id,
|
||||
doku_content,
|
||||
f"Überschrieben von Massen-Sync: {equipment.name}"
|
||||
)
|
||||
_logger.info(f"Zentrale Doku-Seite überschrieben: {doku_page_id}")
|
||||
|
||||
# Standard-Synchronisation
|
||||
# Standard-Synchronisation (erstellt nur Odoo-Status-Seiten)
|
||||
equipment.sync_to_dokuwiki()
|
||||
success += 1
|
||||
_logger.info(f"✓ {equipment.name} synchronisiert")
|
||||
|
|
|
|||
|
|
@ -11,9 +11,6 @@
|
|||
<field name="sync_mode" widget="radio"/>
|
||||
<field name="ows_area_id" invisible="sync_mode != 'area'" required="sync_mode == 'area'"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="force_sync" invisible="sync_mode == 'overview_table'"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- Ergebnis-Anzeige -->
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user