""" Calejo Control Adapter - Phase 3 Implementation Main application with Setpoint Manager and Multi-Protocol Servers. """ import asyncio import signal import sys from typing import List import structlog from src.config.settings import settings from src.database.client import DatabaseClient from src.core.auto_discovery import AdapterAutoDiscovery from src.core.safety import SafetyLimitEnforcer from src.core.emergency_stop import EmergencyStopManager from src.monitoring.watchdog import DatabaseWatchdog from src.monitoring.alerts import AlertManager from src.core.setpoint_manager import SetpointManager from src.protocols.rest_api import RESTAPIServer from src.protocols.opcua_server import OPCUAServer from src.protocols.modbus_server import ModbusServer logger = structlog.get_logger() class CalejoControlAdapterPhase3: """Calejo Control Adapter with Phase 3 components.""" def __init__(self): self.running = False self.components = [] # Core components self.db_client = None self.discovery = None self.safety_enforcer = None self.emergency_stop_manager = None self.watchdog = None self.alert_manager = None # Phase 3 components self.setpoint_manager = None self.rest_api_server = None self.opcua_server = None self.modbus_server = None async def initialize(self): """Initialize all components.""" logger.info("initializing_calejo_control_adapter_phase3") try: # Initialize database client self.db_client = DatabaseClient(settings.database_url) await self.db_client.connect() self.components.append(self.db_client) logger.info("database_client_initialized") # Initialize auto-discovery self.discovery = AdapterAutoDiscovery(self.db_client) await self.discovery.initialize() self.components.append(self.discovery) logger.info("auto_discovery_initialized") # Initialize safety framework self.safety_enforcer = SafetyLimitEnforcer(self.db_client) self.components.append(self.safety_enforcer) logger.info("safety_enforcer_initialized") # Initialize alert manager self.alert_manager = AlertManager( smtp_host=settings.smtp_host, smtp_port=settings.smtp_port, smtp_username=settings.smtp_username, smtp_password=settings.smtp_password, email_recipients=settings.alert_email_recipients, sms_recipients=settings.alert_sms_recipients, webhook_urls=settings.alert_webhook_urls ) self.components.append(self.alert_manager) logger.info("alert_manager_initialized") # Initialize emergency stop manager self.emergency_stop_manager = EmergencyStopManager(self.db_client) self.components.append(self.emergency_stop_manager) logger.info("emergency_stop_manager_initialized") # Initialize database watchdog self.watchdog = DatabaseWatchdog( self.db_client, timeout_seconds=settings.watchdog_timeout_seconds ) self.components.append(self.watchdog) logger.info("database_watchdog_initialized") # Initialize Setpoint Manager (Phase 3) self.setpoint_manager = SetpointManager( discovery=self.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) logger.info("setpoint_manager_initialized") # Initialize REST API Server (Phase 3) self.rest_api_server = RESTAPIServer( setpoint_manager=self.setpoint_manager, emergency_stop_manager=self.emergency_stop_manager, host=settings.rest_api_host, port=settings.rest_api_port ) self.components.append(self.rest_api_server) logger.info("rest_api_server_initialized") # Initialize OPC UA Server (Phase 3) self.opcua_server = OPCUAServer( setpoint_manager=self.setpoint_manager, endpoint=f"opc.tcp://{settings.opcua_host}:{settings.opcua_port}", server_name="Calejo Control OPC UA Server" ) self.components.append(self.opcua_server) logger.info("opcua_server_initialized") # Initialize Modbus TCP Server (Phase 3) self.modbus_server = ModbusServer( setpoint_manager=self.setpoint_manager, host=settings.modbus_host, port=settings.modbus_port, unit_id=settings.modbus_unit_id ) self.components.append(self.modbus_server) logger.info("modbus_server_initialized") logger.info("all_components_initialized_successfully") except Exception as e: logger.error("failed_to_initialize_components", error=str(e)) await self.shutdown() raise async def start(self): """Start all components.""" logger.info("starting_calejo_control_adapter_phase3") try: self.running = True # Start database watchdog await self.watchdog.start() logger.info("database_watchdog_started") # Start REST API Server rest_api_task = asyncio.create_task(self.rest_api_server.start()) self.components.append(rest_api_task) logger.info("rest_api_server_started") # Start OPC UA Server opcua_task = asyncio.create_task(self.opcua_server.start()) self.components.append(opcua_task) logger.info("opcua_server_started") # Start Modbus TCP Server modbus_task = asyncio.create_task(self.modbus_server.start()) self.components.append(modbus_task) logger.info("modbus_server_started") # Setup signal handlers self._setup_signal_handlers() logger.info("calejo_control_adapter_phase3_started_successfully") # Keep the application running while self.running: await asyncio.sleep(1) # Periodically log status await self._log_status() except Exception as e: logger.error("failed_to_start_components", error=str(e)) await self.shutdown() raise async def shutdown(self): """Shutdown all components gracefully.""" logger.info("shutting_down_calejo_control_adapter_phase3") self.running = False # Stop components in reverse order for component in reversed(self.components): try: if hasattr(component, 'stop') and callable(getattr(component, 'stop')): await component.stop() logger.info("component_stopped", component=type(component).__name__) elif isinstance(component, asyncio.Task): component.cancel() except Exception as e: logger.error( "failed_to_stop_component", component=type(component).__name__, error=str(e) ) # Clear components list self.components.clear() logger.info("calejo_control_adapter_phase3_shutdown_complete") def _setup_signal_handlers(self): """Setup signal handlers for graceful shutdown.""" def signal_handler(signum, frame): logger.info("received_shutdown_signal", signal=signum) asyncio.create_task(self.shutdown()) signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) async def _log_status(self): """Periodically log system status.""" try: # Get current setpoints setpoints = self.setpoint_manager.get_all_current_setpoints() # Count active emergency stops emergency_stops = ( len(self.emergency_stop_manager.emergency_stop_pumps) + len(self.emergency_stop_manager.emergency_stop_stations) + (1 if self.emergency_stop_manager.system_emergency_stop else 0) ) # Count failsafe pumps failsafe_pumps = sum( 1 for (station_id, pump_id) in self.setpoint_manager.discovery.get_all_pumps() if self.watchdog.is_failsafe_active(station_id, pump_id) ) logger.info( "system_status", total_pumps=len(self.setpoint_manager.discovery.get_all_pumps()), active_setpoints=sum(len(pumps) for pumps in setpoints.values()), emergency_stops=emergency_stops, failsafe_pumps=failsafe_pumps, watchdog_running=self.watchdog.running ) except Exception as e: logger.error("failed_to_log_status", error=str(e)) async def main(): """Main entry point for Phase 3.""" adapter = CalejoControlAdapterPhase3() try: await adapter.initialize() await adapter.start() except KeyboardInterrupt: logger.info("keyboard_interrupt_received") except Exception as e: logger.error("unexpected_error", error=str(e)) sys.exit(1) finally: await adapter.shutdown() if __name__ == "__main__": asyncio.run(main())