211 lines
7.0 KiB
Python
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()
|