| .. | ||
| api | ||
| clients | ||
| config | ||
| core | ||
| parsers | ||
| tests | ||
| utils | ||
| .dockerignore | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| API.md | ||
| ARCHITECTURE.md | ||
| bridge_types.py | ||
| config.dev.yaml | ||
| config.example.yaml | ||
| config.yaml | ||
| dependencies.py | ||
| DEVELOPMENT.md | ||
| Dockerfile | ||
| exceptions.py | ||
| main.py | ||
| OPTIMIZATION_PLAN.md | ||
| pyproject.toml | ||
| README.md | ||
| requirements-dev.txt | ||
| requirements.txt | ||
| test_push_config_with_mqtt.sh | ||
IoT MQTT Bridge for Odoo
Autonomous MQTT-to-Odoo Integration Service with PUSH Configuration Architecture
🎯 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
- Device → Bridge: IoT devices publish MQTT messages (e.g.,
shellypmminig3-xxx/status/pm1:0) - 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
- Bridge → Odoo: POST events to
/ows/iot/eventREST endpoint - 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:
- Detects the broker change (compares old vs new config)
- Disconnects from the current broker
- Reconfigures host, port, and authentication
- Connects to the new broker
- 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_mqttmodule installed
1. Configuration
The bridge supports two config sources (in priority order):
- Persisted Config (
/data/config-active.yaml) - Auto-created by config push - 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:
mqttsection 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
- IDLE: No power draw, machine off
- STARTING: Power increasing, debounce active
- STANDBY: Power above standby threshold but below working
- WORKING: Power above working threshold, active machining
- 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 debouncesession_stopped: Session ended after stop debouncesession_heartbeat: Periodic aggregated update (every 300s)session_timeout: No messages received formessage_timeout_s
🧪 Testing
Automated End-to-End Tests
cd iot_bridge
python3 tests/test_e2e_push_architecture.py
Tests:
- Device Create → Config Push
- Device Update → Config Push
- Manual Push Button
- Device Delete → Config Remove
- 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 detectionoffline_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:
-
Check Odoo → Bridge connectivity:
docker exec odoo-dev curl -v http://iot-bridge:8080/health -
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 -
Check Odoo logs for push errors:
docker logs odoo-dev | grep "push_bridge_config" -
Manually trigger config push from Odoo UI (🔄 button)
MQTT Connection Issues
Symptoms: Logs show mqtt_connection_failed
Solutions:
-
Verify Mosquitto is running:
docker ps | grep mosquitto -
Check MQTT broker logs:
docker logs mosquitto -
Test MQTT connectivity:
docker exec iot_bridge mosquitto_sub -h mosquitto -t '#' -v -
Verify MQTT env vars:
docker exec iot_bridge env | grep MQTT
Events Not Reaching Odoo
Symptoms: Sessions not created in Odoo
Solutions:
-
Check event queue status in logs:
docker logs iot_bridge | grep '"event":"event_queue"' -
Verify Odoo REST API is accessible:
docker exec iot_bridge curl -v http://odoo-dev:8069/ows/iot/event -
Check Odoo API logs:
docker logs odoo-dev | grep "/ows/iot/event" -
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:
-
Check volume mount:
docker inspect iot_bridge | jq '.[0].Mounts' -
Verify
/datadirectory permissions:docker exec iot_bridge ls -la /data/ -
Check for config file:
docker exec iot_bridge cat /data/config-active.yaml -
Review logs for persistence errors:
docker logs iot_bridge | grep '"event":"config_persist"'
Session Detection Not Working
Symptoms: Sessions not starting/stopping correctly
Solutions:
-
Check threshold configuration:
curl http://localhost:8080/config | jq '.devices[].session_config' -
Review session detector logs:
docker logs iot_bridge | grep '"event":"state_transition"' -
Verify power readings:
docker logs iot_bridge | grep '"device_id":"your-device"' | tail -20 -
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
📚 Related Documentation
- IMPLEMENTATION_PLAN.md - Project roadmap and architecture decisions
- DEPLOYMENT.md - Production deployment guide
- API.md - HTTP API reference
- ARCHITECTURE.md - Component architecture and data flow
- DEVELOPMENT.md - Local setup, testing and debugging
- Odoo Module Documentation - Odoo side of the integration
- API Models - Pydantic models for config validation
⭐ 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