Commit Graph

34 Commits

Author SHA1 Message Date
0bce1e1bed fix(device-availability): fix timeout monitor and bridge-restart race conditions
- status_monitor: add availability_managed set; _monitor_loop skips devices
  in this set so the LWT/availability topic is the sole online/offline source
- device_manager: register device with status_monitor.set_availability_managed()
  so the monitor actually skips them (previously the monitor had no knowledge
  of DeviceManager.availability_managed)
- mqtt_bridge: remove blanket 'reset all devices to offline' on bridge restart;
  this was causing a race condition where the cron reset state AFTER the bridge
  had already sent device_online events via retained MQTT messages;
  stale running session cleanup is kept (still needed)
2026-03-19 19:11:03 +01:00
2fb45a6582 feat(device-manager): LWT-based availability monitoring for direct_session devices
- direct_session devices now use availability_topic (LWT) exclusively
  for online/offline state - timeout monitor no longer interferes
- Added availability_managed set: devices in this set bypass
  update_last_seen() and are ignored by the timeout monitor
- Added heartbeat_topics set: heartbeat messages return early before
  the session parser, eliminating direct_session_missing_fields warnings
- Added mark_online_silent() to DeviceStatusMonitor: updates state
  without emitting a duplicate device_online event
- registry.py: added availability_topic + status_topic params for
  direct_session parser type
- server.py: set last_config_update from file mtime on load_persisted_config
- mqtt_bridge.py: auto push config + reset device states when bridge
  comes back from offline (prevents stale state in Odoo after restart)
2026-03-19 18:58:55 +01:00
0293256c76 fix(iot_api): use savepoint to prevent InFailedSqlTransaction
When _process_event raised a DB exception (e.g. constraint violation),
PostgreSQL put the whole transaction into ABORTED state.  Any subsequent
ORM call (event.mark_processed, Odoo's own flush) then raised
psycopg2.errors.InFailedSqlTransaction, masking the real error.

Fix: wrap _process_event in a database savepoint inside receive_iot_event.
A processing failure now only rolls back the session/device side-effects;
the ows.iot.event record stays committed and the error is stored in
processing_error.  The transaction itself remains valid for Odoo's flush.
2026-03-19 17:03:04 +01:00
ffd1c2d10e fix: __manifest__.py data view order
Some checks failed
IoT Bridge - Tests & Quality Checks / Type Check (mypy) (push) Failing after 5m24s
IoT Bridge - Tests & Quality Checks / Tests (Python ${{ matrix.python-version }}) (3.10) (push) Failing after 55s
IoT Bridge - Tests & Quality Checks / Tests (Python ${{ matrix.python-version }}) (3.11) (push) Successful in 2m13s
IoT Bridge - Tests & Quality Checks / Lint & Format Check (push) Failing after 10m28s
IoT Bridge - Tests & Quality Checks / Integration Tests (push) Failing after 6s
IoT Bridge - Tests & Quality Checks / Tests (Python ${{ matrix.python-version }}) (3.12) (push) Successful in 2m23s
IoT Bridge - Tests & Quality Checks / Docker Build Test (push) Failing after 5m43s
IoT Bridge - Tests & Quality Checks / Quality Gate (push) Failing after 2s
2026-03-18 18:20:47 +01:00
3a1b442baa feat(open_workshop_pos_mqtt): Maschinenzeit-Popup verbessert
- Zeitfenster-Navigation mit 3 Schrittgrößen (±1h, ±4h, ±1 Tag)
- Sticky Navigationsheader: Zeitfenster-Steuerung bleibt beim Scrollen sichtbar
- Session-Liste scrollbar (max-height: 60vh)
- Alle/Keine-Button zum Selektieren aller Sessions
- Karten-Layout: Beginn/Ende/Total/Aktivzeit in 4-Spalten-Tabelle
- Datumsanzeige mit Sekunden
- Durationsformat M:SS (unter 60 min) / H:MM:SS h (ab 60 min)
- AlertDialog-Blocker entfernt: Popup öffnet sich auch bei leerer Session-Liste
- UTC-Fix in order_summary für korrekte Zeitberechnung
- README.md erstellt
- seed_workshop_sessions.py hinzugefügt (Testdaten Shaper Origin + Lasercutter)
2026-03-17 21:20:43 +01:00
0972124efe fix: NameError 'fields' in session_complete + fields/timedelta import
NameError: name 'fields' is not defined (ab 20:12 Uhr in den Logs)
Ursache: iot_api.py ist ein Controller, kein Model – 'fields' war
niemals importiert. Der UTC-Fix verwendete fälschlicherweise
fields.Datetime.to_datetime() und fields.Datetime.to_string().

Fix:
- 'fields' und 'timedelta' zu den top-level Imports ergänzt
- fields.Datetime.to_datetime(event.timestamp) → event.timestamp
  (ORM-Datetime-Felder sind im Controller bereits datetime-Objekte)
- fields.Datetime.to_string(dt) → dt.strftime('%Y-%m-%d %H:%M:%S')
- Inline 'from datetime import timedelta as _td' entfernt
2026-03-11 21:26:29 +01:00
5b62ef2224 fix: _logger.info() in session_complete mit Standard-Python-Logger-Syntax
_logger.info('msg', session_id=...) ist structlog-Syntax.
Python-Standard-Logger wirft dabei TypeError → Odoo rollback →
session_complete-Events wurden niemals committed und landeten
in der Bridge-Retry-Queue (Endlosschleife).

Fix: Auf _logger.info('msg %s %s', val1, val2)-Format umgestellt.
2026-03-11 21:21:04 +01:00
21ac8641cc fix: session_complete Zeitstempel UTC-korrekt + PoS attendanceEnd in UTC
Problem: Lasercutter-Sessions wurden in get_pos_session_suggestions
übersprungen, weil start_time > end_time.

Bug 1 – iot_api.py (session_complete):
session_start_time im Gerät-Payload ist Lokalzeit ohne TZ-Info.
Bisher naiv als UTC gespeichert → start_time lag 1h nach end_time.
Fix: start_time = end_time - session_seconds (komplett UTC-basiert).
session_start_time aus dem Payload wird nicht mehr verwendet.

Bug 2 – machine_time_control_button.js:
toServerDatetime(new Date()) verwendete getHours() (Lokalzeit).
Odoo erwartet UTC-Strings → attendanceEnd war 1h zu groß.
Fix: Auf getUTCHours() / getUTCDate() etc. umgestellt.
2026-03-11 21:04:28 +01:00
4fb98200b4 docs: README und API.md auf Stand 2026-03-11 aktualisiert
iot_bridge/README.md:
- Project Structure vervollständigt (bridge_types.py, dependencies.py,
  exceptions.py, pyproject.toml, requirements-dev.txt, core/bootstrap.py,
  core/service_manager.py, tests/conftest.py, tests/helpers.py,
  tests/fixtures/, tests/integration/ vollständig)

open_workshop_mqtt/README.md:
- Session Detection: Strategy A + B (Direct Session / session_complete)
- Device erstellen: parser_config JSON statt einzelner Threshold-Felder
- Event Types: session_complete, session_updated, heartbeat, power_change ergänzt
- Architecture: Availability-Topic + direktes Session-Event aufgeführt

open_workshop_mqtt/API.md:
- Neu generiert via build_docs.py (bisher 3, jetzt 5 Modelldateien:
  mqtt_bridge.py und mqtt_broker.py neu dazu)

open_workshop_mqtt/static/description/index.html:
- Neu generiert aus aktualisierter README.md
2026-03-11 20:06:57 +01:00
b9485237f1 feat: end-to-end direct_session pipeline + auto hint view
Bridge → Odoo pipeline für direct_session parser:
- device_manager.py: route session_complete in event_queue (+ import uuid)
- iot_api.py: _process_event handler für session_complete
- iot_event.py: event_type += session_complete
- mqtt_session.py: end_reason += direct_session

View auto-hint aus Bridge-Registry:
- mqtt_device.py: parser_topic_hint + parser_type_description fields
- mqtt_device.py: _onchange_parser_type setzt beide Felder via GET /parsers
- mqtt_device_views.xml: einziger generischer alert-div (kein hardcoding mehr)
2026-03-11 15:16:28 +01:00
fcca3272ae feat: add DirectSessionParser for devices delivering complete sessions
Device payload (e.g. lasercutter/session):
  {session_id, session_minutes, session_seconds, session_start_time, freetime_s, ip}

- parsers/direct_session_parser.py: new parser returning message_type='session_complete'
- parsers/registry.py: register 'direct_session' with freetime_s parameter (default 0)
- mqtt_device.py: add ('direct_session', 'Direct Session (Lasercutter)') to _PARSER_SELECTION
- tests/unit/test_direct_session_parser.py: 24 tests (happy-path, missing fields, defaults, registry)
2026-03-11 14:29:22 +01:00
ea565775b2 feat: parser_config als Wire-Format durchgehend (Phase 3 komplett)
session_config wird nicht mehr über die API/YAML gesendet.
parser_config ist jetzt das einzige Config-Format zwischen Odoo und Bridge.

Änderungen:
- api/models.py: DeviceConfig.session_config → parser_config: dict
  SessionConfig bleibt als internes Modell für SessionDetector
  DeviceConfig.to_session_config() extrahiert Werte mit Defaults
- config/schema.py: DeviceConfig gleich umgestellt + to_session_config()
- config/loader.py: liest parser_config aus YAML, Fallback für legacy
  session_config (Rückwärtskompatibilität für bestehende config-active.yaml)
- core/device_manager.py: device.session_config → device.to_session_config()
- core/service_manager.py: session_config Referenzen entfernt
- Odoo _build_bridge_config: sendet parser_config direkt (+ heartbeat)
- Odoo iot_api.py: gleich umgestellt
- Tests: alle SessionConfig-Fixtures → parser_config dicts
  63/63 passing
2026-03-11 13:05:54 +01:00
907fce37da fix: ORM-Cache vor Bridge-Config-Push invalidieren
write() rief _build_bridge_config() auf bevor der ORM-Cache
geleert war – self.search() las noch die alten (gecachten) Werte.
Resultat: parser_type-Änderung wurde in die config-active.yaml
nicht übernommen.

Fix: flush_all() + invalidate_all() vor dem Push erzwingt,
dass _build_bridge_config() die soeben geschriebenen Werte liest.
2026-03-11 12:22:24 +01:00
c90fd5a6a8 revert: parser_config immer mit Defaults überschreiben bei Typwechsel
Der vorherige Fix war falsch: 'nur bei leerem Feld befüllen' führt
dazu, dass nach shelly_pm → dummy → shelly_pm die dummy-Config übrig
bleibt. Jeder Parser hat eigene Parameter, eine fremde Config ist
für den neuen Parser wertlos.

Korrekt: Typwechsel = immer frische Defaults des neuen Parsers.
2026-03-11 11:50:58 +01:00
b36eb491c2 fix: parser_config nur bei leerem Feld mit Defaults befüllen
Bisher wurden bei jedem parser_type-Wechsel die Registry-Defaults
überschrieben – angepasste Werte gingen verloren.

Neu: Defaults werden nur eingetragen wenn parser_config leer ('{}')
ist, z. B. bei neu angelegten Geräten oder nach manuellem Leeren.
2026-03-11 11:36:37 +01:00
8e7298fcbe fix: parser_config fields.Json → fields.Text für ace-Widget
Das ace-Widget erwartet einen String, aber fields.Json liefert ein
Python-Dict zurück. JS ruft .toString() darauf auf → '[object Object]'.

Änderungen:
- mqtt_device.py: fields.Json → fields.Text(default='{}')
- _onchange_parser_type: json.dumps(defaults, indent=2) statt dict
- get_parser_config_dict: json.loads() mit Fehlerbehandlung
- iot_api.py: json.loads(device.parser_config or '{}')
- DB: parser_config jsonb → text (USING parser_config::text)
2026-03-11 11:22:03 +01:00
d0ca48b3ef refactor(odoo): Registry-driven JSON Config (Phase 3)
Ersetzt alle parser-spezifischen Einzelfelder durch ein einziges
parser_config (fields.Json / jsonb) – neuer Parser = Bridge + 1 Zeile.

Entfernt (Odoo, DB):
  power_on_threshold_w, active_work_threshold_w
  startup_delay_s, shutdown_delay_s, message_timeout_s
  dummy_pulse_count, dummy_pulse_debounce_ms, dummy_reset_interval_min
  strategy_config (computed Text)
  session_strategy

Neu (Odoo, DB):
  parser_config (fields.Json / jsonb)

Geändert:
  _onchange_parser_type holt Defaults via GET /parsers aus
  Bridge-Registry, kein parser-spezifischer if/elif mehr
  _compute_strategy_config entfällt komplett
  _build_bridge_config liest parser_config direkt
  View: alle parser-spezifischen Gruppen weg, ein ace-Widget
  iot_api.py: strategy_config -> parser_config
  IMPLEMENTATION_PLAN.md: Phase 3 dokumentiert

Bridge: unverändert (session_config API bleibt, 63/63 Tests grün)
DB: parser_config jsonb-Spalte, alle alten Spalten entfernt
2026-03-11 10:56:32 +01:00
0111c5db24 feat(dummy_generic): Puls-Zähler-Parser als Referenz-Workflow
Demonstriert den vollständigen Ablauf für einen neuen Parser mit
ANDEREN Parametern als shelly_pm (Flat Wide Table, Bridge SoT).

Bridge (iot_bridge):
- parsers/dummy_parser.py: Komplett neu – liest 'pulses' (int) statt
  'value', gibt apower=float(pulses) für SessionDetector zurück
- parsers/registry.py: dummy_generic-Parameter ersetzt:
  standby_threshold_w/working_threshold_w → pulse_count, pulse_debounce_ms,
  reset_interval_min (je mit odoo_field-Mapping)

Odoo (open_workshop_mqtt):
- mqtt_device.py: 3 neue Felder dummy_pulse_count, dummy_pulse_debounce_ms,
  dummy_reset_interval_min (Flat Wide Table, NULL für andere Parser)
- mqtt_device.py: _compute_strategy_config depends + elif für dummy_generic
  erzeugt jetzt pulse-spezifischen JSON-Config-Dict
- mqtt_device.py: _onchange_parser_type setzt Puls-Defaults statt
  shelly-Schwellenwerte
- mqtt_device_views.xml: invisible-Bedingungen auf parser_type not in [...]
  umgestellt (korrekt skalierend bei 3+ Parsern)
- mqtt_device_views.xml: Inline-Hint-Divs aus Leistungs-Schwellenwerte
  entfernt (unnötig)
- mqtt_device_views.xml: Neue Gruppe '🔢 Puls-Konfiguration' mit 3 Feldern

DB: 3 neue Spalten in ows_mqtt_device angelegt (odoo -u Erfolg)
Tests: 63/63 grün
2026-03-10 19:30:43 +01:00
ab9ba5d270 test: Dummy-Parser als Erweiterungsvalidierung (Phase 2 Smoke-Test)
- parsers/dummy_parser.py: DummyParser – liest 'value' aus JSON-Payload,
  mappt auf apower; akzeptiert beliebige Topics
- parsers/registry.py: dummy_generic hinzugefügt (topic_hint: <device>/data)
- parsers/__init__.py: DummyParser exportiert
- mqtt_device.py: _PARSER_SELECTION um dummy_generic erweitert
- mqtt_device_views.xml: Warn-Block für dummy_generic (mit topic hint +
  Payload-Beispiel), nur sichtbar wenn parser_type == dummy_generic
- Validiert: GET /parsers liefert beide Typen, 63/63 Tests grün,
  Odoo Modul-Update ohne Fehler
2026-03-10 18:15:02 +01:00
c7915d0ba1 feat(odoo): Phase 2 – parser_type aus Bridge Registry ableiten
- mqtt_device.py: _PARSER_SELECTION-Konstante eingeführt; tasmota und
  generic entfernt (hatten keine Bridge-Implementierung); depends in
  _compute_strategy_config um parser_type erweitert; _onchange nutzt
  jetzt topic_hint aus Registry (<device>/status/pm1:0)
- mqtt_device_views.xml: Parser-spezifischer Info-Block für shelly_pm
  (topic_hint, Beschreibung) mit invisible='parser_type != shelly_pm'
- Bestehende DB-Geräte: alle 3 bereits auf shelly_pm → keine Migration
- GET /parsers: läuft und liefert vollständige Schema-Antwort
2026-03-10 18:03:12 +01:00
8d7be4b9a7 feat(pos-mqtt): produktgefilterte Sessions + sichtbares Orderline-Icon
- Maschinenzeit-Icon nur bei gemappten Produkten (OrderSummary Patch)

- Session-Suggestions auf gewählte Produkt-ID gefiltert

- Debug-Dialog bei leerem Zeitfenster inkl. Produkt/Von/Bis

- Asset-Switch auf order_summary Implementierung

- Implementierungsplan Schritt-4 Review ergänzt
2026-02-21 17:35:23 +01:00
f0881c3c2c feat: MVP Step 4 - PoS Maschinenzeit-Übernahme produktiv
- ControlButtons-Extension mit stabiler Template-Inheritance (hasclass XPath)
- MachineTimeSelectionPopup Komponente für Session-Auswahl und Minuten-Bearbeitung
- Backend Service get_pos_session_suggestions mit Overlap + Rounding
- Seed-Tool für reproducible Test-Sessions
- PoS-Mapping-Backend für Device-to-Product-Zuordnung
- Entfernt: fehleranfällige ProductScreen-Extension (Owl-Crash behoben)

Workflow im PoS:
1. Orderline auswählen
2. 'Maschinenzeit'-Button klicken (ControlButtons)
3. Session-Popup mit Vorschlägen + editierbaren Minuten
4. Auswahl bestätigen → Quantity in Orderline durch Summe ersetzen

Akzeptanzkriterien Step 4 erfüllt:
✓ Button sichtbar und erreichbar
✓ Popup öffnet und schließt korrekt
✓ Vorschläge sichtbar und auswählbar
✓ Übernahme schreibt in Auftrag
✓ 'Keine Sessions'-Hinweis bei Leerfall
✓ Keine Crashes mehr
2026-02-21 16:08:01 +01:00
fb0710c680 fix(odoo): close stale running sessions on new starts 2026-02-19 20:42:52 +01:00
f7b5a28f9a Fix: sync device status timeout with Odoo message timeout
- Include device_status_timeout_s in Odoo bridge payload
- Resolve status monitor timeout robustly with backward-compatible fallbacks
- Update running status monitor timeout on POST /config without restart
- Keep compatibility for legacy/local configs without device_status_timeout_s

Result: shaperorigin uses configured 90s timeout for online/offline monitor, preventing 30s flapping.
2026-02-19 18:40:30 +01:00
e723315e35 Fix: Unify device_status timeout with message_timeout_s
Problem: Device Status Monitor was using a hardcoded 30-second global timeout
for marking devices offline, independent of the configurable message_timeout_s.
This caused alternating offline/online events for devices with power=0 that
don't send frequent MQTT messages.

Solution: Use the same timeout value (message_timeout_s) for both:
1. Session Detection (message_timeout_s)
2. Device Status Monitoring (device_status_timeout_s)

Implementation:
- Add device_status_timeout_s field to api/models.py DeviceConfig (default: 120s)
- Update Odoo iot_api.py to include device_status_timeout_s in config response
  (synchronized with message_timeout_s from device strategy config)
- Update Bridge service_manager.py to use device_status_timeout_s when
  initializing DeviceStatusMonitor (fallback to global config if not provided)

Result:
- Single configurable timeout per device in Odoo
- Both checks (session + device status) use same value
- Backward compatible (defaults to 120s if not provided)
- Solves alternating offline/online events for low-power/idle devices

Validation:
- mypy: 0 errors across 47 files
- API model test: device_status_timeout_s field functional
2026-02-19 00:09:22 +01:00
ca31e3f4a6 Update README.md 2026-02-18 23:29:36 +01:00
3eaf2e933d feat: IoT Bridge Health Monitoring & Dynamic MQTT Reconnection
Features:
- Added ows.mqtt.bridge model with health status monitoring
- Bridge list/form views with status indicators (Online/Offline/Unknown)
- Scheduled action for periodic health checks (every 2 minutes)
- Health metrics: devices count, subscriptions, last seen timestamp
- Manual health check button in UI
- Smart button linking brokers to bridge instance

Infrastructure:
- Added ows.mqtt.broker model for MQTT broker configurations
- Bridge-Broker relation via Many2one field
- Dynamic MQTT reconnection without container restart
- Robust startup: Bridge starts even when MQTT fails
- TLS reconfiguration limitation documented (paho-mqtt)

Technical Changes:
- Updated models to use @api.model_create_multi for Odoo 18
- Fixed view definitions: tree → list for Odoo 18 compatibility
- Removed mail.thread dependency (unnecessary chatter)
- Added ir.cron for automated health monitoring
- Security/ACL rules for bridge and broker models
- Menu integration under IoT/MQTT section

Documentation:
- Updated IMPLEMENTATION_PLAN.md with new features
- Updated Odoo module README with bridge/broker models
- iot_bridge/README.md already documents dynamic reconnection

Testing:
- All changes tested via Odoo module upgrade
- Health check endpoint verified: GET /health
- Bridge reconnection tested with broker config changes
2026-02-18 20:36:11 +01:00
d3a28fddb6 feat: benutzerfreundliche GUI für Device-Konfiguration mit Auto-Config-Push
Hauptänderungen:
─────────────
 Benutzerfreundliche GUI statt JSON-Eingabe
  • Abrechnungs-Intervall (Minuten) - klar verständlich statt heartbeat_interval_s
  • Einschalt-/Arbeits-Schwellen (Watt) - statt technischer Begriffe
  • Anlauf-/Abschalt-Verzögerung (Sekunden) - erklärt was es tut
  • Icons, Hilfe-Texte, Beispiele direkt am Feld

🔄 Auto-Config-Push an Bridge
  • write() Hook: Automatischer Push bei Feldänderungen
  • create() Hook: Push bei neuem Device
  • unlink() Hook: Push bei Device-Löschung
  • Retry-Logic: 3 Versuche mit exponential backoff
  • Manueller Button: 🔄 Push Config to Bridge

📊 Transparente Abrechnung
  • Duration-Breakdown: Total = Working + Standby + Idle
  • Alle Zeiten in Minuten (billing-friendly)
  • Idle-Zeit zeigt Overhead (Debounce/Timeout) transparent

🧹 Code-Cleanup
  • Deprecated write() Hook entfernt
  • Überflüssige _onchange_session_strategy() entfernt
  • Computed Field: strategy_config automatisch generiert
  • Validation mit verständlichen deutschen Fehlermeldungen

Technische Details:
──────────────────
Models (mqtt_device.py):
  • billing_interval_min, power_on_threshold_w, active_work_threshold_w,
    startup_delay_s, shutdown_delay_s, message_timeout_s
  • Computed: strategy_config = f(alle benutzerfreundlichen Felder)
  • relevant_fields erweitert für Auto-Push Trigger

Views (mqtt_device_views.xml):
  •  Leistungs-Schwellenwerte
  • ⏱️ Zeitsteuerung
  • 💰 Abrechnung mit Info-Box
  • Generierte Config (readonly) für Debugging

Sessions (mqtt_session.py):
  • idle_duration_s = max(0, total - working - standby)
  • *_duration_min Felder für alle Zeiten
  • Displays konvertiert: hours → minutes

Bridge (session_detector.py):
  • Payload standardisiert: power_w, state (konsistent in allen Events)
  • Heartbeat-Intervall aus Odoo billing_interval_min

Feature Request:
  • Implementierungsstatus aktualisiert (Phase 1+2 komplett)
  • Lessons Learned dokumentiert
  • Nächste Schritte definiert

 Tests bestätigt:
  • Events kommen im konfigurierten Abrechnungs-Intervall (1 Min)
  • Config-Push funktioniert automatisch
  • Duration-Breakdown ist transparent
  • GUI ist verständlich und hilfreich
2026-02-17 00:09:51 +01:00
0b6e41d207 refactor: standardize model naming to ows.* prefix for consistency
BREAKING CHANGE: Renamed models for consistent naming convention
- mqtt.device → ows.mqtt.device (table: ows_mqtt_device)
- mqtt.session → ows.mqtt.session (table: ows_mqtt_session)
- ows.iot.event unchanged (table: ows_iot_event)

Changes:
- Updated all Many2one/One2many relations to use new model names
- Updated all env references in controllers and tests
- Updated security CSV file with new model IDs
- Updated all view records (list/form/kanban/pivot/graph/search)
- Fixed controller reference that was still using old mqtt.session

Documentation:
- Added README.md for user-facing module documentation
- Regenerated API.md from updated docstrings
- Regenerated index.html from README.md

Cleanup:
- Removed debug/test files (check_routes.py, test-*.sh/txt)
- Removed obsolete python_proto_type directory

Note: This requires database migration or fresh setup.
Database was reset and module reinstalled successfully.
E2E test with Shelly Simulator passed.
2026-02-15 13:56:52 +01:00
78b9befc26 Changed Odoo Test Database from OWS_MQTT to odoo 2026-02-15 11:07:21 +01:00
ba588842ad feat: Add automatic API documentation generation and device status monitoring
- Implement build script (build_docs.py) with AST parser to auto-generate HTML docs from docstrings
- Add comprehensive Google-style docstrings to all controllers and models
- Create static/description/index.html for Odoo Apps UI with module overview
- Generate api_reference.html (20.5 KB) from source code, linked from Odoo UI
- Add DOCUMENTATION_STRATEGY.md with comparison of 5 documentation approaches
- Create API.md with complete REST API documentation

Device Status Monitoring:
- Implement device_status_monitor.py with health checks and offline detection
- Add /status endpoint for device health overview
- Automatic offline detection after message_timeout_s

Config Push Architecture:
- Add POST /config endpoint to IoT Bridge for dynamic device management
- Auto-push device config from Odoo on create/write/unlink
- Implement device_manager.py for runtime device updates

E2E Tests:
- All 6 E2E tests passing (Create, Update, Push, Delete, Restart, Status Monitor)
- Test coverage for device lifecycle and config synchronization

Documentation is auto-generated via: ./build_docs.sh
View in Odoo: Settings → Apps → Open Workshop MQTT → API Reference
2026-02-15 11:03:22 +01:00
7337e57b40 feat: Implement Odoo Config-Push System (Phase 3.2)
Completes PUSH architecture - Odoo automatically pushes config to Bridge

NEW FEATURES:
- Model Hooks in mqtt.device (create/write/unlink)
  - Automatically push config after device create/update/delete
  - Smart detection: only relevant fields trigger push
  - Non-blocking: UI works even if bridge is offline

- Config Builder (_build_bridge_config)
  - Collects all active mqtt.device records
  - Converts to Bridge-compatible JSON format
  - Topic pattern transformation (/#→/status/pm1:0)
  - Session config extraction from strategy_config JSON
  - Returns BridgeConfig schema with timestamp + version

- HTTP Client with Retry Logic (_push_bridge_config)
  - POST to bridge_url/config with JSON payload
  - 3 retries with exponential backoff (1s, 2s, 4s)
  - Timeout: 10 seconds per request
  - Error handling: 4xx no retry, 5xx retry
  - Returns success/message dict for UI notifications

- System Parameter for Bridge URL
  - Configurable via ir.config_parameter
  - Key: open_workshop_mqtt.bridge_url
  - Default: http://iot-bridge:8080
  - Stored in data/system_parameters.xml

- Manual Push Button in UI
  - '🔄 Push Config to Bridge' button in device list header
  - Calls action_push_config_to_bridge()
  - Shows success/error notification toast
  - Allows manual sync when needed

NEW FILES:
- data/system_parameters.xml                          - Bridge URL param
- tests/test_config_push_integration.py              - Python integration test

MODIFIED FILES:
- models/mqtt_device.py                              - +250 lines (hooks, builder, client)
- views/mqtt_device_views.xml                        - Manual push button
- __manifest__.py                                    - requests dependency, data file

ARCHITECTURE CHANGE:
Before: Bridge pulls config from Odoo via GET /api/config
After:  Odoo pushes config to Bridge via POST /config
        - Triggered automatically on device changes (create/write/unlink)
        - Manual trigger via UI button
        - Bridge receives validated config via FastAPI endpoint

TESTING:
 Model hooks fire on create/write/unlink
 Config builder generates valid JSON
 HTTP client successfully pushes to bridge
 Retry logic works (tested with bridge down→up)
 Manual button displays notifications
 Bridge receives and applies config
 Device add/update/remove reflected in bridge

NEXT STEP:
Phase 3.3 - Remove legacy get_config() from bridge (no longer needed)
2026-02-12 20:18:13 +01:00
18cac263a2 fix: Make IoT Bridge autonomous and fix session closing
Major architectural improvements to make Bridge resilient:

1. Bridge Autonomy (CRITICAL FIX):
   - Remove sys.exit(1) when Odoo config fails (iot_bridge/main.py)
   - Bridge now runs autonomously with local config.yaml
   - No longer crashes in endless restart loop when Odoo is down
   - Odoo connection check becomes optional warning, not blocker

2. Event Type Compatibility:
   - Add 'session_ended' to controller event processing (iot_api.py)
   - Bridge sends 'session_ended', controller expected 'session_stopped'
   - Now accepts both event types for closing sessions

3. Event Type Support:
   - Add 'session_ended' to iot_event model selection (iot_event.py)
   - Fixes 500 errors when Bridge sends session_ended events

4. Architecture Documentation:
   - Update FEATURE_REQUEST with new PUSH architecture (Odoo -> Bridge)
   - Update IMPLEMENTATION_PLAN with Phase 3 refactoring plan
   - Document autonomous mode and config-push design
   - Remove obsolete documentation files

Tested Scenarios:
-  Bridge starts and runs without Odoo
-  Session detection works autonomously
-  Events queue when Odoo is down
-  Queue automatically processes when Odoo returns
-  Sessions close correctly with session_ended events

This enables the next phase: Odoo pushing config to Bridge via HTTP API.
2026-02-12 19:28:25 +01:00
8c27373697 initial MQTT Development 2026-02-11 21:07:32 +01:00