odoo_mqtt/iot_bridge
2026-02-19 20:08:47 +01:00
..
api docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
clients docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
config docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
core docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
parsers Phase 3: Complete type safety & logging unification (3.1-3.2) 2026-02-18 23:54:27 +01:00
tests Phase 4.2: Add integration tests for reconnect, config push, and event delivery 2026-02-19 19:17:23 +01:00
utils docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
.dockerignore initial version 2026-02-10 20:00:27 +01:00
.gitignore Phase 1: Foundation & Quality Tools 2026-02-18 22:11:25 +01:00
.pre-commit-config.yaml Phase 1: Foundation & Quality Tools 2026-02-18 22:11:25 +01:00
API.md docs: make API docs source-of-truth explicit 2026-02-19 20:08:47 +01:00
ARCHITECTURE.md Phase 4.3: Update project documentation 2026-02-19 19:19:44 +01:00
bridge_types.py docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
config.dev.yaml feat: Add automatic API documentation generation and device status monitoring 2026-02-15 11:03:22 +01:00
config.example.yaml Changed Odoo Test Database from OWS_MQTT to odoo 2026-02-15 11:07:21 +01:00
config.yaml feat: benutzerfreundliche GUI für Device-Konfiguration mit Auto-Config-Push 2026-02-17 00:09:51 +01:00
dependencies.py docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
DEVELOPMENT.md docs: make API docs source-of-truth explicit 2026-02-19 20:08:47 +01:00
Dockerfile feat: Implement HTTP Config API for Bridge (Phase 3.1) 2026-02-12 20:05:49 +01:00
exceptions.py Phase 3: Complete type safety & logging unification (3.1-3.2) 2026-02-18 23:54:27 +01:00
main.py feat(phase2.4): add dependency injection container and factory wiring 2026-02-18 23:17:22 +01:00
OPTIMIZATION_PLAN.md docs: complete phase 4.4 public API docstrings 2026-02-19 19:27:32 +01:00
pyproject.toml Complete phase 2 rest points: env overrides and unit tests 2026-02-18 23:30:33 +01:00
README.md Phase 4.3: Update project documentation 2026-02-19 19:19:44 +01:00
requirements-dev.txt Phase 1: Foundation & Quality Tools 2026-02-18 22:11:25 +01:00
requirements.txt Phase 4.2: Add integration tests for reconnect, config push, and event delivery 2026-02-19 19:17:23 +01:00
test_push_config_with_mqtt.sh feat: IoT Bridge Health Monitoring & Dynamic MQTT Reconnection 2026-02-18 20:36:11 +01:00

IoT MQTT Bridge for Odoo

Autonomous MQTT-to-Odoo Integration Service with PUSH Configuration Architecture

Python 3.10+ Docker License

🎯 Overview

The IoT Bridge is a standalone Python microservice that bridges MQTT-based IoT devices (e.g., Shelly PM, Tasmota) with Odoo 18. It provides:

  • Real-time MQTT monitoring with auto-reconnect
  • Intelligent session detection via state machine
  • Event normalization for multiple device types
  • Autonomous operation with retry queues and config persistence
  • PUSH-based configuration from Odoo
  • RESTful HTTP API for health checks and config management

🏗️ Architecture

System Overview

┌─────────────┐                      ┌──────────────────┐                  ┌────────────────┐
│  IoT Device │  MQTT Messages       │   IoT Bridge     │  HTTP Events     │   Odoo 18      │
│  (Shelly PM)│ ──────────────────>  │  (Autonomous)    │ ──────────────>  │   (ERP/MES)    │
│             │                      │                  │ <────────────────│                │
│             │                      │  - MQTT Client   │  Config Push     │  - REST API    │
│             │                      │  - Session       │  POST /config    │  - Model Hooks │
│             │                      │    Detector      │                  │  - UI Controls │
│             │                      │  - Event Queue   │                  │                │
│             │                      │  - HTTP Server   │                  │                │
└─────────────┘                      └────────┬─────────┘                  └────────────────┘
                                              │
                                              │ Subscribe
                                              ▼
                                     ┌──────────────────┐
                                     │ Mosquitto Broker │
                                     │  (MQTT Server)   │
                                     └──────────────────┘

Data Flow

  1. Device → Bridge: IoT devices publish MQTT messages (e.g., shellypmminig3-xxx/status/pm1:0)
  2. Bridge Processing:
    • Parse device-specific payload (Shelly, Tasmota, etc.)
    • Run through session detection state machine
    • Aggregate events (heartbeat interval: 300s)
    • Queue for delivery with retry logic
  3. Bridge → Odoo: POST events to /ows/iot/event REST endpoint
  4. Odoo → Bridge: Push device configuration updates via POST /config

PUSH Configuration Architecture

OLD (deprecated): Bridge pulled config from Odoo via GET /ows/iot/config
NEW (current): Odoo pushes config to Bridge via POST /config

Benefits:

  • No periodic polling overhead
  • Instant config updates when devices change
  • Automatic MQTT reconnect when broker settings change
  • Bridge runs autonomously with persisted config
  • Automatic triggers via Odoo model hooks (create/write/unlink)

New: Dynamic MQTT Reconnection

When Odoo pushes a new broker configuration (via the MQTT Broker model), the Bridge automatically:

  1. Detects the broker change (compares old vs new config)
  2. Disconnects from the current broker
  3. Reconfigures host, port, and authentication
  4. Connects to the new broker
  5. Re-subscribes to all device topics

What can be changed without restart:

  • Broker host/hostname
  • Broker port
  • Username/password
  • TLS enable/disable (requires restart due to paho-mqtt library limitation)

No manual container restart required for most changes! The Bridge handles broker switches seamlessly with only 1-2 seconds of downtime during the reconnect.

Robustness: Bridge starts successfully even if MQTT connection fails initially. This prevents deadlock scenarios where Odoo cannot push corrected configuration.


📁 Project Structure

iot_bridge/
├── main.py                    # Main entry point & startup orchestration
│
├── core/                      # Core business logic
│   ├── session_detector.py    #   5-state session detection state machine
│   ├── event_queue.py         #   Retry queue for Odoo events
│   └── device_manager.py      #   Dynamic device & subscription manager
│
├── clients/                   # External service clients
│   ├── mqtt_client.py         #   MQTT client with auto-reconnect
│   └── odoo_client.py         #   HTTP client for Odoo REST API
│
├── parsers/                   # Device message parsers
│   ├── base.py                #   Parser protocol & ABC
│   └── shelly_parser.py       #   Shelly PM parser implementation
│
├── api/                       # HTTP API server
│   ├── server.py              #   FastAPI server for config management
│   └── models.py              #   Pydantic validation models
│
├── config/                    # Configuration management
│   ├── schema.py              #   Config dataclass definitions
│   └── loader.py              #   YAML config loading
│
├── utils/                     # Utilities
│   ├── logging.py             #   Structured logging (JSON)
│   └── status_monitor.py      #   Device status monitoring
│
├── requirements.txt           # Python dependencies
├── Dockerfile                 # Multi-stage production build
├── config.yaml                # Initial config (optional)
├── config.example.yaml        # Config template
│
├── tests/
│   ├── test_e2e_push_architecture.py       # End-to-end automated tests
│   ├── test-config-push.sh                 # Manual config push test
│   ├── integration/
│   │   ├── test_bridge_integration.py      # Bridge integration tests
│   │   └── test_retry_logic.py             # Retry mechanism tests
│   ├── unit/
│   │   ├── test_event_queue.py             # Event queue unit tests
│   │   └── test_session_detector.py        # Session detector unit tests
│   └── tools/
│       └── shelly_simulator.py             # MQTT device simulator
│
└── logs/                      # Log files (if file logging enabled)

🚀 Quick Start

Prerequisites

  • Docker & Docker Compose
  • Python 3.10+ (for local development)
  • Mosquitto MQTT Broker (included in docker-compose)
  • Odoo 18 with open_workshop_mqtt module installed

1. Configuration

The bridge supports two config sources (in priority order):

  1. Persisted Config (/data/config-active.yaml) - Auto-created by config push
  2. Initial Config (config.yaml) - Optional fallback for first startup

Environment Variables:

# MQTT Broker (defaults - can be overridden via Odoo broker config)
MQTT_BROKER=mosquitto                # MQTT broker hostname
MQTT_PORT=1883                       # MQTT broker port
MQTT_USERNAME=                       # Optional MQTT auth
MQTT_PASSWORD=                       # Optional MQTT auth
MQTT_CLIENT_ID=iot_bridge            # MQTT client identifier
MQTT_USE_TLS=false                   # Enable TLS for MQTT

# Bridge HTTP Server
BRIDGE_PORT=8080                     # HTTP API port
BRIDGE_API_TOKEN=                    # Optional Bearer token for auth

# Odoo Connection (for event pushing)
ODOO_BASE_URL=http://odoo-dev:8069   # Odoo base URL
ODOO_DATABASE=odoo               # Odoo database name
ODOO_USERNAME=lotz.matthias@gmail.com # Odoo user
ODOO_API_KEY=                        # Odoo API key (if using API auth)

# Logging
LOG_LEVEL=INFO                       # DEBUG, INFO, WARNING, ERROR
LOG_FORMAT=json                      # json or text
LOG_FILE=                            # Optional log file path

MQTT Config: MQTT broker is configured in Odoo GUI (IoT Bridge → Brokers) and automatically pushed to Bridge. ENV vars are fallback for first startup or standalone mode.

2. Docker Compose Deployment

version: '3.8'

services:
  iot-bridge:
    build: ./iot_bridge
    container_name: iot_bridge
    restart: unless-stopped
    ports:
      - "8080:8080"  # HTTP config API
    volumes:
      - bridge-data:/data  # Config persistence
    environment:
      - MQTT_BROKER=mosquitto
      - MQTT_PORT=1883
      - BRIDGE_PORT=8080
      - ODOO_BASE_URL=http://odoo-dev:8069
      - ODOO_DATABASE=odoo
      - ODOO_USERNAME=lotz.matthias@gmail.com
    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

  mosquitto:
    image: eclipse-mosquitto:2
    ports:
      - "1883:1883"
    volumes:
      - ./mosquitto/mosquitto.conf:/mosquitto/config/mosquitto.conf
    networks:
      - odoo18-nw

volumes:
  bridge-data:  # Persists config-active.yaml

networks:
  odoo18-nw:
    driver: bridge

3. Start Services

docker compose up -d

4. Verify Bridge Health

curl http://localhost:8080/health

# Expected output:
# {
#   "status": "ok",
#   "devices": 2,
#   "subscriptions": 2,
#   "last_config_update": "2026-02-12T19:40:18Z"
# }

🔌 HTTP API

The bridge exposes a FastAPI-based HTTP server for configuration management.

Endpoints

GET /health

Health check endpoint.

Response:

{
  "status": "ok",
  "devices": 2,
  "subscriptions": 2,
  "last_config_update": "2026-02-12T19:40:18.123456"
}

POST /config

Receive configuration push from Odoo. Automatically called by Odoo model hooks (create/write/unlink on mqtt.device).

Authentication: Optional Bearer token via BRIDGE_API_TOKEN env var

Request Body:

{
  "mqtt": {
    "broker": "localhost",
    "port": 1883,
    "username": "",
    "password": "",
    "client_id": "iot_bridge",
    "keepalive": 60,
    "use_tls": false
  },
  "devices": [
    {
      "device_id": "shelly-pm-workshop",
      "machine_name": "CNC Mill (Workshop)",
      "mqtt_topic": "shellypmminig3-48f6eeb73a1c/status/pm1:0",
      "parser_type": "shelly_pm",
      "session_config": {
        "standby_threshold_w": 5.0,
        "working_threshold_w": 50.0,
        "start_debounce_s": 3.0,
        "stop_debounce_s": 15.0,
        "message_timeout_s": 20.0,
        "heartbeat_interval_s": 300.0
      }
    }
  ],
  "timestamp": "2026-02-12T19:40:18Z",
  "version": "1.0"
}

Note: mqtt section is optional. When provided from Odoo (Broker model), Bridge will automatically reconnect to the new broker without requiring a restart. Device config is applied immediately.

Response:

{
  "status": "success",
  "message": "Configuration applied",
  "devices_configured": 1,
  "mqtt_reconnected": true,
  "timestamp": "2026-02-12T19:40:18.456789"
}

Side Effects:

  • Validates config schema (Pydantic)
  • Detects MQTT broker changes and triggers automatic reconnection
  • Computes device diff (added/updated/removed)
  • Updates MQTT subscriptions dynamically
  • Persists config to /data/config-active.yaml
  • Applies new thresholds to session detectors

MQTT Reconnection:

  • Triggered automatically when broker settings change (host, port, auth)
  • Disconnects from old broker gracefully
  • Connects to new broker with updated credentials
  • Re-subscribes to all device topics automatically
  • Typical downtime: 1-2 seconds during switch
  • TLS changes: Requires manual Bridge restart (paho-mqtt limitation)

GET /config

Retrieve current active configuration.

Response: Same structure as POST /config request


⚙️ Configuration Management

Odoo → Bridge Config Push

The bridge receives config automatically from Odoo when devices are created, updated, or deleted.

Odoo Model Hooks:

# In mqtt.device model:

@api.model_create_multi
def create(self, vals_list):
    devices = super().create(vals_list)
    self._push_bridge_config()  # Auto-push after create
    return devices

def write(self, vals):
    result = super().write(vals)
    if any(field in vals for field in RELEVANT_FIELDS):
        self._push_bridge_config()  # Auto-push after update
    return result

def unlink(self):
    result = super().unlink()
    self._push_bridge_config()  # Auto-push after delete
    return result

Manual Push from Odoo UI:

Users can trigger manual config push via the "🔄 Push Config to Bridge" button in the device list view.

Config Persistence

Config is automatically persisted to /data/config-active.yaml when received via POST /config.

Benefits:

  • Bridge survives restarts without Odoo connection
  • Instant startup with last known config
  • Audit trail of config changes (via timestamps)

Config File Location:

  • Container: /data/config-active.yaml
  • Host (with volume): bridge-data:/data/config-active.yaml

🔄 Session Detection

The bridge implements a 5-state state machine for intelligent session detection:

States

IDLE → STARTING → STANDBY/WORKING → STOPPING → IDLE
  1. IDLE: No power draw, machine off
  2. STARTING: Power increasing, debounce active
  3. STANDBY: Power above standby threshold but below working
  4. WORKING: Power above working threshold, active machining
  5. STOPPING: Power decreasing, stop debounce active

Configuration

session_config:
  standby_threshold_w: 5.0      # Min power for session start
  working_threshold_w: 50.0     # Min power for working state
  start_debounce_s: 3.0         # Delay before session confirmed
  stop_debounce_s: 15.0         # Delay before session ends
  message_timeout_s: 20.0       # Max time without MQTT messages
  heartbeat_interval_s: 300.0   # Interval for aggregated events

Event Types

  • session_started: Session confirmed after start debounce
  • session_stopped: Session ended after stop debounce
  • session_heartbeat: Periodic aggregated update (every 300s)
  • session_timeout: No messages received for message_timeout_s

🧪 Testing

Automated End-to-End Tests

cd iot_bridge
python3 tests/test_e2e_push_architecture.py

Tests:

  1. Device Create → Config Push
  2. Device Update → Config Push
  3. Manual Push Button
  4. Device Delete → Config Remove
  5. Bridge Restart → Config Persistence

Manual Config Push Test

cd iot_bridge/tests
./test-config-push.sh

Shelly Device Simulator

cd iot_bridge/tests/tools
python3 shelly_simulator.py

Simulates Shelly PM device sending power measurements via MQTT.

Development Workflow

For complete local setup, debugging and test workflows, see:

Recommended explicit invocations:

# Full session (default scenario)
python3 tests/tools/shelly_simulator.py --broker localhost --port 1883 --no-tls --username "" --password "" --scenario full_session

# Online Status Test - Continuous messaging (verify device stays online)
python3 tests/tools/shelly_simulator.py --broker localhost --port 1883 --no-tls --scenario online_test --duration 120

# Offline Detection Test - Message gap triggers timeout
python3 tests/tools/shelly_simulator.py --broker localhost --port 1883 --no-tls --scenario offline_test --offline-duration 60

# Disable TLS (only if your broker uses plain TCP)
python3 shelly_simulator.py --no-tls

Available Scenarios:

  • full_session: Complete state machine transition (IDLE → STANDBY → WORKING → STOPPING)
  • standby: Continuous low power (20-100W)
  • working: Continuous high power (>100W)
  • online_test: Verify device online status detection
  • offline_test: Test offline timeout detection (message gap)

📊 Monitoring & Observability

Structured Logging

The bridge uses structured JSON logging for easy parsing:

{
  "timestamp": "2026-02-12T19:40:18.123456Z",
  "level": "info",
  "event": "mqtt_message_received",
  "device_id": "shelly-pm-workshop",
  "power_w": 125.5,
  "state": "working"
}

Health Checks

Docker health check via HTTP endpoint:

docker inspect --format='{{json .State.Health}}' iot_bridge | jq

Log Files

# View live logs
docker logs -f iot_bridge

# Search for errors
docker logs iot_bridge | grep '"level":"error"'

# View config changes
docker logs iot_bridge | grep 'config_received'

🐛 Troubleshooting

Bridge Not Receiving Config

Symptoms: Health endpoint shows "devices": 0

Solutions:

  1. Check Odoo → Bridge connectivity:

    docker exec odoo-dev curl -v http://iot-bridge:8080/health
    
  2. Verify system parameter in Odoo:

    SELECT key, value FROM ir_config_parameter 
    WHERE key = 'open_workshop_mqtt.bridge_url';
    -- Should return: http://iot-bridge:8080
    
  3. Check Odoo logs for push errors:

    docker logs odoo-dev | grep "push_bridge_config"
    
  4. Manually trigger config push from Odoo UI (🔄 button)

MQTT Connection Issues

Symptoms: Logs show mqtt_connection_failed

Solutions:

  1. Verify Mosquitto is running:

    docker ps | grep mosquitto
    
  2. Check MQTT broker logs:

    docker logs mosquitto
    
  3. Test MQTT connectivity:

    docker exec iot_bridge mosquitto_sub -h mosquitto -t '#' -v
    
  4. Verify MQTT env vars:

    docker exec iot_bridge env | grep MQTT
    

Events Not Reaching Odoo

Symptoms: Sessions not created in Odoo

Solutions:

  1. Check event queue status in logs:

    docker logs iot_bridge | grep '"event":"event_queue"'
    
  2. Verify Odoo REST API is accessible:

    docker exec iot_bridge curl -v http://odoo-dev:8069/ows/iot/event
    
  3. Check Odoo API logs:

    docker logs odoo-dev | grep "/ows/iot/event"
    
  4. Inspect event queue retries:

    docker logs iot_bridge | grep '"event":"odoo_send_failed"'
    

Config Not Persisting After Restart

Symptoms: Bridge starts with 0 devices after restart

Solutions:

  1. Check volume mount:

    docker inspect iot_bridge | jq '.[0].Mounts'
    
  2. Verify /data directory permissions:

    docker exec iot_bridge ls -la /data/
    
  3. Check for config file:

    docker exec iot_bridge cat /data/config-active.yaml
    
  4. Review logs for persistence errors:

    docker logs iot_bridge | grep '"event":"config_persist"'
    

Session Detection Not Working

Symptoms: Sessions not starting/stopping correctly

Solutions:

  1. Check threshold configuration:

    curl http://localhost:8080/config | jq '.devices[].session_config'
    
  2. Review session detector logs:

    docker logs iot_bridge | grep '"event":"state_transition"'
    
  3. Verify power readings:

    docker logs iot_bridge | grep '"device_id":"your-device"' | tail -20
    
  4. Adjust debounce times if needed (via Odoo UI)


🔧 Development

Local Setup

# Clone repository
git clone <repo-url>
cd iot_bridge

# Create virtual environment
python3 -m venv venv
source venv/bin/activate

# Install production dependencies
pip install -r requirements.txt

# Install development dependencies (testing, linting, etc.)
pip install -r requirements-dev.txt

# Install pre-commit hooks (optional but recommended)
pre-commit install

# Run tests
pytest tests/

# Run bridge locally
python3 main.py --config config.yaml

Code Quality Tools

The project uses several tools to maintain code quality:

# Format code with Black
black .

# Sort imports with isort
isort .

# Lint with Ruff (modern, fast linter)
ruff check .
ruff check --fix .  # Auto-fix issues

# Type check with mypy
mypy . --config-file=pyproject.toml

# Security check with Bandit
bandit -r . -ll

# Run all pre-commit hooks manually
pre-commit run --all-files

Running Tests

# Run all tests
pytest

# Run only unit tests (fast)
pytest tests/unit -v

# Run with coverage report
pytest --cov=. --cov-report=html
open htmlcov/index.html

# Run specific test file
pytest tests/unit/test_session_detector.py -v

# Run tests matching a pattern
pytest -k "test_session" -v

Project Structure

See OPTIMIZATION_PLAN.md for the complete optimization roadmap.

iot_bridge/
├── core/               # Core business logic
│   ├── session_detector.py
│   ├── event_queue.py
│   └── device_manager.py
├── clients/            # External system clients
│   ├── mqtt_client.py
│   └── odoo_client.py
├── parsers/            # Message parsers
│   ├── base.py
│   └── shelly_parser.py
├── api/                # HTTP API
│   ├── server.py
│   └── models.py
├── config/             # Configuration
│   ├── schema.py
│   └── loader.py
├── utils/              # Utilities
│   ├── logging.py
│   └── status_monitor.py
├── tests/              # Test suite
│   ├── conftest.py
│   ├── fixtures/
│   ├── unit/
│   └── integration/
├── main.py             # Entry point
├── pyproject.toml      # Project config & tools
└── requirements.txt    # Dependencies

Environment Variables for Development

export MQTT_BROKER=localhost
export MQTT_PORT=1883
export BRIDGE_PORT=8080
export ODOO_BASE_URL=http://localhost:9018
export ODOO_DATABASE=odoo
export LOG_LEVEL=DEBUG

📜 License

See ../LICENSE



Key Features

  • Autonomous Operation: Runs without Odoo connection after initial config
  • PUSH Configuration: Instant updates from Odoo via model hooks
  • Dynamic MQTT Reconnection: Automatic broker switches without restart (host/port/auth)
  • Robust Startup: Starts successfully even when MQTT connection fails
  • Config Persistence: Survives restarts with /data/config-active.yaml
  • Dynamic Subscriptions: Auto-subscribe/unsubscribe based on devices
  • Retry Queue: Exponential backoff for event delivery (max 10 retries)
  • Session Detection: Intelligent 5-state state machine
  • Multi-Device Support: Handles multiple devices simultaneously
  • Structured Logging: JSON logs for easy parsing & monitoring
  • Health Checks: Docker-compatible health endpoint
  • Automated Tests: 5 E2E tests validating entire PUSH architecture