2025-10-26 18:19:37 +00:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""
|
|
|
|
|
Calejo Control Adapter - Main Application
|
|
|
|
|
|
|
|
|
|
Multi-protocol integration adapter for municipal wastewater pump stations
|
|
|
|
|
with comprehensive safety and security framework.
|
2025-10-27 13:11:17 +00:00
|
|
|
|
|
|
|
|
Features:
|
|
|
|
|
- Phase 1: Core infrastructure (Database, Auto-discovery, Safety)
|
|
|
|
|
- Phase 2: Multi-protocol servers (OPC UA, Modbus, REST API)
|
|
|
|
|
- Phase 3: Setpoint Manager and Optimization
|
2025-10-26 18:19:37 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import asyncio
|
|
|
|
|
import signal
|
|
|
|
|
import structlog
|
2025-10-27 13:11:17 +00:00
|
|
|
from typing import Dict, Any, List
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
from config.settings import Settings
|
2025-10-27 13:11:17 +00:00
|
|
|
from src.database.flexible_client import FlexibleDatabaseClient
|
2025-10-26 18:19:37 +00:00
|
|
|
from src.core.auto_discovery import AutoDiscovery
|
|
|
|
|
from src.core.safety import SafetyLimitEnforcer
|
2025-10-27 13:11:17 +00:00
|
|
|
from src.core.emergency_stop import EmergencyStopManager
|
|
|
|
|
from src.core.optimization_manager import OptimizationPlanManager
|
|
|
|
|
from src.core.setpoint_manager import SetpointManager
|
2025-10-28 15:10:53 +00:00
|
|
|
from src.core.security import SecurityManager
|
|
|
|
|
from src.core.compliance_audit import ComplianceAuditLogger
|
2025-10-26 18:19:37 +00:00
|
|
|
from src.monitoring.watchdog import DatabaseWatchdog
|
|
|
|
|
from src.monitoring.alerts import AlertManager
|
2025-10-30 07:22:00 +00:00
|
|
|
from src.monitoring.health_monitor import HealthMonitor
|
2025-10-27 13:11:17 +00:00
|
|
|
from src.protocols.opcua_server import OPCUAServer
|
2025-10-26 18:19:37 +00:00
|
|
|
from src.protocols.modbus_server import ModbusServer
|
|
|
|
|
from src.protocols.rest_api import RESTAPIServer
|
|
|
|
|
|
|
|
|
|
logger = structlog.get_logger()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class CalejoControlAdapter:
|
|
|
|
|
"""Main application class for Calejo Control Adapter."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, settings: Settings):
|
|
|
|
|
self.settings = settings
|
|
|
|
|
self.running = False
|
2025-10-27 13:11:17 +00:00
|
|
|
self.components: List[Any] = []
|
|
|
|
|
|
|
|
|
|
# Initialize core components (Phase 1)
|
|
|
|
|
self.db_client = FlexibleDatabaseClient(
|
|
|
|
|
database_url=settings.database_url,
|
2025-10-27 13:30:35 +00:00
|
|
|
pool_size=settings.db_min_connections,
|
|
|
|
|
max_overflow=settings.db_max_connections - settings.db_min_connections,
|
|
|
|
|
pool_timeout=30,
|
2025-10-28 15:10:53 +00:00
|
|
|
pool_recycle=3600,
|
|
|
|
|
query_timeout=settings.db_query_timeout
|
2025-10-27 13:11:17 +00:00
|
|
|
)
|
|
|
|
|
self.components.append(self.db_client)
|
|
|
|
|
|
|
|
|
|
self.auto_discovery = AutoDiscovery(
|
|
|
|
|
db_client=self.db_client,
|
|
|
|
|
refresh_interval_minutes=settings.auto_discovery_refresh_minutes
|
|
|
|
|
)
|
|
|
|
|
self.components.append(self.auto_discovery)
|
|
|
|
|
|
|
|
|
|
self.emergency_stop_manager = EmergencyStopManager(self.db_client)
|
|
|
|
|
self.components.append(self.emergency_stop_manager)
|
|
|
|
|
|
|
|
|
|
self.safety_enforcer = SafetyLimitEnforcer(self.db_client, self.emergency_stop_manager)
|
|
|
|
|
self.components.append(self.safety_enforcer)
|
|
|
|
|
|
|
|
|
|
self.optimization_manager = OptimizationPlanManager(
|
|
|
|
|
db_client=self.db_client,
|
|
|
|
|
refresh_interval_seconds=settings.optimization_refresh_seconds
|
|
|
|
|
)
|
|
|
|
|
self.components.append(self.optimization_manager)
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
self.alert_manager = AlertManager(settings)
|
2025-10-27 13:11:17 +00:00
|
|
|
self.components.append(self.alert_manager)
|
|
|
|
|
|
2025-10-26 18:19:37 +00:00
|
|
|
self.watchdog = DatabaseWatchdog(
|
2025-10-27 13:30:35 +00:00
|
|
|
self.db_client, self.alert_manager, settings.watchdog_timeout_seconds
|
2025-10-26 18:19:37 +00:00
|
|
|
)
|
2025-10-27 13:11:17 +00:00
|
|
|
self.components.append(self.watchdog)
|
|
|
|
|
|
2025-10-30 07:22:00 +00:00
|
|
|
# Initialize Health Monitor (Phase 7)
|
|
|
|
|
self.health_monitor = HealthMonitor(port=settings.health_monitor_port)
|
|
|
|
|
self.components.append(self.health_monitor)
|
|
|
|
|
|
2025-10-27 13:11:17 +00:00
|
|
|
# Initialize Setpoint Manager (Phase 3)
|
|
|
|
|
self.setpoint_manager = SetpointManager(
|
|
|
|
|
discovery=self.auto_discovery,
|
|
|
|
|
db_client=self.db_client,
|
|
|
|
|
safety_enforcer=self.safety_enforcer,
|
|
|
|
|
emergency_stop_manager=self.emergency_stop_manager,
|
|
|
|
|
watchdog=self.watchdog
|
|
|
|
|
)
|
|
|
|
|
self.components.append(self.setpoint_manager)
|
|
|
|
|
|
2025-10-28 15:10:53 +00:00
|
|
|
# Initialize security components (Phase 4)
|
|
|
|
|
self.audit_logger = ComplianceAuditLogger(self.db_client)
|
|
|
|
|
self.components.append(self.audit_logger)
|
|
|
|
|
|
|
|
|
|
self.security_manager = SecurityManager(audit_logger=self.audit_logger)
|
|
|
|
|
self.components.append(self.security_manager)
|
|
|
|
|
|
|
|
|
|
# Protocol servers (Phase 2 + Phase 5 enhancements)
|
2025-10-27 13:11:17 +00:00
|
|
|
self.opc_ua_server = OPCUAServer(
|
2025-10-27 13:30:35 +00:00
|
|
|
setpoint_manager=self.setpoint_manager,
|
2025-10-27 13:11:17 +00:00
|
|
|
endpoint=f"opc.tcp://{settings.opcua_host}:{settings.opcua_port}",
|
2025-10-28 15:10:53 +00:00
|
|
|
server_name="Calejo Control OPC UA Server",
|
|
|
|
|
security_manager=self.security_manager,
|
|
|
|
|
audit_logger=self.audit_logger
|
2025-10-27 13:11:17 +00:00
|
|
|
)
|
|
|
|
|
self.components.append(self.opc_ua_server)
|
|
|
|
|
|
|
|
|
|
self.modbus_server = ModbusServer(
|
2025-10-27 13:30:35 +00:00
|
|
|
setpoint_manager=self.setpoint_manager,
|
2025-10-27 13:11:17 +00:00
|
|
|
host=settings.modbus_host,
|
|
|
|
|
port=settings.modbus_port,
|
2025-10-28 15:10:53 +00:00
|
|
|
unit_id=settings.modbus_unit_id,
|
|
|
|
|
security_manager=self.security_manager,
|
|
|
|
|
audit_logger=self.audit_logger
|
2025-10-27 13:11:17 +00:00
|
|
|
)
|
|
|
|
|
self.components.append(self.modbus_server)
|
2025-10-26 18:19:37 +00:00
|
|
|
|
2025-10-27 13:11:17 +00:00
|
|
|
self.rest_api = RESTAPIServer(
|
2025-10-27 13:30:35 +00:00
|
|
|
setpoint_manager=self.setpoint_manager,
|
|
|
|
|
emergency_stop_manager=self.emergency_stop_manager,
|
2025-10-27 13:11:17 +00:00
|
|
|
host=settings.rest_api_host,
|
2025-10-30 07:22:00 +00:00
|
|
|
port=settings.rest_api_port,
|
|
|
|
|
health_monitor=self.health_monitor
|
2025-10-27 13:11:17 +00:00
|
|
|
)
|
|
|
|
|
self.components.append(self.rest_api)
|
2025-10-26 18:19:37 +00:00
|
|
|
|
2025-10-30 07:22:00 +00:00
|
|
|
def _register_health_checks(self):
|
|
|
|
|
"""Register health checks for all components."""
|
|
|
|
|
from src.monitoring.health_monitor import (
|
|
|
|
|
database_health_check, opcua_server_health_check,
|
|
|
|
|
modbus_server_health_check, rest_api_health_check
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Register database health check
|
|
|
|
|
self.health_monitor.register_health_check(
|
|
|
|
|
"database",
|
|
|
|
|
lambda: database_health_check(self.db_client)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Register OPC UA server health check
|
|
|
|
|
self.health_monitor.register_health_check(
|
|
|
|
|
"opcua_server",
|
|
|
|
|
lambda: opcua_server_health_check(self.opc_ua_server)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Register Modbus server health check
|
|
|
|
|
self.health_monitor.register_health_check(
|
|
|
|
|
"modbus_server",
|
|
|
|
|
lambda: modbus_server_health_check(self.modbus_server)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Register REST API server health check
|
|
|
|
|
self.health_monitor.register_health_check(
|
|
|
|
|
"rest_api",
|
|
|
|
|
lambda: rest_api_health_check(self.rest_api)
|
|
|
|
|
)
|
|
|
|
|
|
2025-10-26 18:19:37 +00:00
|
|
|
async def start(self):
|
|
|
|
|
"""Start the Calejo Control Adapter."""
|
|
|
|
|
logger.info("starting_calejo_control_adapter", version="2.0.0")
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
# Connect to database
|
|
|
|
|
await self.db_client.connect()
|
2025-10-27 13:11:17 +00:00
|
|
|
logger.info("database_connected")
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
# Load safety limits
|
|
|
|
|
await self.safety_enforcer.load_safety_limits()
|
2025-10-27 13:11:17 +00:00
|
|
|
logger.info("safety_limits_loaded")
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
# Auto-discover pump stations and pumps
|
|
|
|
|
await self.auto_discovery.discover()
|
2025-10-27 13:11:17 +00:00
|
|
|
logger.info("auto_discovery_completed")
|
|
|
|
|
|
|
|
|
|
# Start optimization manager
|
2025-10-27 13:30:35 +00:00
|
|
|
await self.optimization_manager.start_monitoring()
|
2025-10-27 13:11:17 +00:00
|
|
|
logger.info("optimization_manager_started")
|
|
|
|
|
|
|
|
|
|
# Start monitoring
|
|
|
|
|
await self.watchdog.start()
|
|
|
|
|
logger.info("watchdog_started")
|
|
|
|
|
|
2025-10-30 07:22:00 +00:00
|
|
|
# Start Health Monitor and register health checks
|
|
|
|
|
await self.health_monitor.start_metrics_server()
|
|
|
|
|
self._register_health_checks()
|
|
|
|
|
logger.info("health_monitor_started")
|
|
|
|
|
|
2025-10-27 13:11:17 +00:00
|
|
|
# Start Setpoint Manager
|
|
|
|
|
await self.setpoint_manager.start()
|
|
|
|
|
logger.info("setpoint_manager_started")
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
# Start protocol servers
|
|
|
|
|
await asyncio.gather(
|
|
|
|
|
self.opc_ua_server.start(),
|
|
|
|
|
self.modbus_server.start(),
|
|
|
|
|
self.rest_api.start(),
|
|
|
|
|
)
|
2025-10-27 13:11:17 +00:00
|
|
|
logger.info("protocol_servers_started")
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
self.running = True
|
|
|
|
|
logger.info("calejo_control_adapter_started")
|
|
|
|
|
|
|
|
|
|
# Keep application running
|
|
|
|
|
while self.running:
|
|
|
|
|
await asyncio.sleep(1)
|
|
|
|
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("failed_to_start_adapter", error=str(e))
|
|
|
|
|
await self.stop()
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
async def stop(self):
|
|
|
|
|
"""Stop the Calejo Control Adapter gracefully."""
|
|
|
|
|
logger.info("stopping_calejo_control_adapter")
|
|
|
|
|
self.running = False
|
|
|
|
|
|
2025-10-27 13:11:17 +00:00
|
|
|
# Stop all components in reverse order
|
|
|
|
|
stop_tasks = []
|
|
|
|
|
|
2025-10-26 18:19:37 +00:00
|
|
|
# Stop protocol servers
|
2025-10-27 13:11:17 +00:00
|
|
|
stop_tasks.extend([
|
2025-10-26 18:19:37 +00:00
|
|
|
self.opc_ua_server.stop(),
|
|
|
|
|
self.modbus_server.stop(),
|
|
|
|
|
self.rest_api.stop(),
|
2025-10-27 13:11:17 +00:00
|
|
|
])
|
|
|
|
|
|
|
|
|
|
# Stop Setpoint Manager
|
|
|
|
|
if self.setpoint_manager:
|
|
|
|
|
stop_tasks.append(self.setpoint_manager.stop())
|
|
|
|
|
|
|
|
|
|
# Stop monitoring
|
|
|
|
|
if self.watchdog:
|
|
|
|
|
stop_tasks.append(self.watchdog.stop())
|
|
|
|
|
|
|
|
|
|
# Stop optimization manager
|
|
|
|
|
if self.optimization_manager:
|
2025-10-27 13:30:35 +00:00
|
|
|
stop_tasks.append(self.optimization_manager.stop_monitoring())
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
# Close database connection
|
2025-10-27 13:11:17 +00:00
|
|
|
if self.db_client:
|
|
|
|
|
stop_tasks.append(self.db_client.disconnect())
|
|
|
|
|
|
|
|
|
|
# Execute all stop tasks
|
|
|
|
|
await asyncio.gather(*stop_tasks, return_exceptions=True)
|
2025-10-26 18:19:37 +00:00
|
|
|
|
|
|
|
|
logger.info("calejo_control_adapter_stopped")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def handle_shutdown(signum, frame):
|
|
|
|
|
"""Handle shutdown signals gracefully."""
|
|
|
|
|
logger.info("received_shutdown_signal", signal=signum)
|
|
|
|
|
# Signal handling will be implemented in the async context
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
async def main():
|
|
|
|
|
"""Main application entry point."""
|
|
|
|
|
# Load settings
|
|
|
|
|
settings = Settings()
|
|
|
|
|
|
|
|
|
|
# Set up signal handlers
|
|
|
|
|
signal.signal(signal.SIGINT, handle_shutdown)
|
|
|
|
|
signal.signal(signal.SIGTERM, handle_shutdown)
|
|
|
|
|
|
|
|
|
|
# Create and start adapter
|
|
|
|
|
adapter = CalejoControlAdapter(settings)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
await adapter.start()
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
logger.info("keyboard_interrupt_received")
|
|
|
|
|
except Exception as e:
|
|
|
|
|
logger.error("unexpected_error", error=str(e))
|
|
|
|
|
raise
|
|
|
|
|
finally:
|
|
|
|
|
await adapter.stop()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
asyncio.run(main())
|