odoo_mqtt/iot_bridge/main.py
2026-02-10 20:00:27 +01:00

211 lines
7.0 KiB
Python

#!/usr/bin/env python3
"""
IoT Bridge Main Entry Point
"""
import sys
import signal
import os
import argparse
from pathlib import Path
from config import load_config
from logger_setup import setup_logging
from odoo_client import MockOdooClient, OdooClient
from mqtt_client import MQTTClient
from shelly_parser import ShellyParser
from session_detector import SessionDetector
from event_queue import EventQueue
# Global flag for graceful shutdown
shutdown_flag = False
mqtt_client = None
session_detectors = {}
event_queue = None
def signal_handler(signum, frame):
"""Handle shutdown signals."""
global shutdown_flag
print(f"\nReceived signal {signum}, shutting down gracefully...")
shutdown_flag = True
def on_event_generated(event, logger, event_queue):
"""Handle events from session detector - enqueue for retry logic."""
logger.info(f"event_generated type={event['event_type']} device={event['device_id']} session_id={event.get('session_id', 'N/A')[:8]}")
# Enqueue event (will be sent with retry logic)
event_queue.enqueue(event)
def on_mqtt_message(topic: str, payload: dict, logger, parser, device_id, detector):
"""Handle incoming MQTT messages."""
try:
# Parse message
parsed = parser.parse_message(topic, payload)
if parsed and parsed.get('apower') is not None:
power_w = parsed['apower']
# Process in session detector
from datetime import datetime
detector.process_power_measurement(power_w, datetime.utcnow())
# logger.debug(f"power_measurement device={device_id} power={power_w}W state={detector.state}")
except Exception as e:
logger.error(f"on_mqtt_message_error error={str(e)}")
def main():
"""Main entry point for IoT Bridge."""
global shutdown_flag, mqtt_client, session_detectors, event_queue
# Parse command line arguments
parser = argparse.ArgumentParser(description='IoT MQTT Bridge for Odoo')
parser.add_argument('--config', type=str, default='config.yaml',
help='Path to configuration file (default: config.yaml)')
args = parser.parse_args()
# Register signal handlers
signal.signal(signal.SIGINT, signal_handler)
signal.signal(signal.SIGTERM, signal_handler)
# Load configuration (--config argument overrides BRIDGE_CONFIG env var)
config_path = args.config if args.config != 'config.yaml' else os.getenv("BRIDGE_CONFIG", "config.yaml")
print(f"Loading configuration from {config_path}")
try:
config = load_config(config_path)
except FileNotFoundError as e:
print(f"ERROR: {e}")
sys.exit(1)
except Exception as e:
print(f"ERROR: Failed to load config: {e}")
sys.exit(1)
# Setup logging
logger = setup_logging(config.logging)
logger.info("bridge_started", config_file=config_path, devices=len(config.devices))
# Initialize Odoo client
if config.odoo.use_mock:
failure_rate = getattr(config.odoo, 'mock_failure_rate', 0.0)
logger.info("using_mock_odoo_client", failure_rate=failure_rate)
odoo_client = MockOdooClient(config.devices, failure_rate=failure_rate)
else:
logger.info("using_real_odoo_client",
base_url=config.odoo.base_url,
database=config.odoo.database)
odoo_client = OdooClient(
base_url=config.odoo.base_url,
database=config.odoo.database,
username=config.odoo.username,
api_key=config.odoo.api_key
)
# Test config loading
try:
device_config = odoo_client.get_config()
logger.info("config_loaded", device_count=len(device_config.get("devices", [])))
except Exception as e:
logger.error("config_load_failed", error=str(e))
sys.exit(1)
# Initialize Event Queue with retry logic
def send_to_odoo(event):
"""Send event to Odoo, returns True on success."""
try:
response = odoo_client.send_event(event)
return True
except Exception as e:
logger.error(f"odoo_send_failed error={str(e)}")
return False
event_queue = EventQueue(send_callback=send_to_odoo, logger=logger)
event_queue.start()
logger.info("event_queue_started")
# Initialize Session Detectors (one per device)
parser = ShellyParser()
for device in config.devices:
session_cfg = device.session_config
detector = SessionDetector(
device_id=device.device_id,
machine_name=device.machine_name,
standby_threshold_w=session_cfg.standby_threshold_w,
working_threshold_w=session_cfg.working_threshold_w,
start_debounce_s=session_cfg.start_debounce_s,
stop_debounce_s=session_cfg.stop_debounce_s,
message_timeout_s=session_cfg.message_timeout_s,
heartbeat_interval_s=session_cfg.heartbeat_interval_s,
event_callback=lambda evt: on_event_generated(evt, logger, event_queue)
)
session_detectors[device.device_id] = detector
logger.info(f"session_detector_initialized device={device.device_id} machine={device.machine_name}")
# Initialize MQTT client
topics = [device.mqtt_topic for device in config.devices]
# Create device mapping for message routing
device_map = {device.mqtt_topic: device.device_id for device in config.devices}
def mqtt_callback(topic, payload):
device_id = device_map.get(topic)
if device_id and device_id in session_detectors:
on_mqtt_message(topic, payload, logger, parser, device_id, session_detectors[device_id])
mqtt_client = MQTTClient(
broker=config.mqtt.broker,
port=config.mqtt.port,
topics=topics,
message_callback=mqtt_callback,
username=config.mqtt.username,
password=config.mqtt.password,
client_id=config.mqtt.client_id,
use_tls=getattr(config.mqtt, 'use_tls', False)
)
# Connect to MQTT
if not mqtt_client.connect():
logger.error("mqtt_connection_failed")
sys.exit(1)
mqtt_client.start()
if not mqtt_client.wait_for_connection(timeout=10):
logger.error("mqtt_connection_timeout")
sys.exit(1)
logger.info("bridge_ready", status="running")
print("Bridge is running. Press Ctrl+C to stop.")
# Main loop
while not shutdown_flag:
import time
time.sleep(1)
# Check for timeouts
from datetime import datetime
current_time = datetime.utcnow()
for detector in session_detectors.values():
detector.check_timeout(current_time)
# Shutdown
logger.info("bridge_shutdown", status="stopping")
# Stop event queue first (process remaining events)
if event_queue:
event_queue.stop()
mqtt_client.stop()
logger.info("bridge_shutdown", status="stopped")
print("Bridge stopped.")
if __name__ == "__main__":
main()