""" Emergency Stop Manager for Calejo Control Adapter. Implements system-wide and targeted emergency stop functionality with manual clearance and audit trail. """ from typing import Dict, List, Optional, Set, Any from datetime import datetime import structlog from src.database.client import DatabaseClient logger = structlog.get_logger() class EmergencyStopManager: """ Manages emergency stop functionality for pumps and stations. Features: - Single pump emergency stop - Station-wide emergency stop - System-wide emergency stop - Manual clearance with audit trail - Integration with all protocol interfaces """ def __init__(self, db_client: DatabaseClient): self.db_client = db_client self.emergency_stop_pumps: Set[tuple] = set() # (station_id, pump_id) self.emergency_stop_stations: Set[str] = set() self.system_emergency_stop = False self.emergency_stop_history: List[Dict] = [] def emergency_stop_pump(self, station_id: str, pump_id: str, reason: str = "Manual stop", user_id: str = "system") -> bool: """ Emergency stop a specific pump. Args: station_id: Station identifier pump_id: Pump identifier reason: Reason for emergency stop user_id: User who initiated the stop Returns: True if stop was successful """ try: key = (station_id, pump_id) self.emergency_stop_pumps.add(key) # Record emergency stop event self._record_emergency_stop_event(station_id, pump_id, 'PUMP', reason, user_id) logger.critical( "emergency_stop_pump_activated", station_id=station_id, pump_id=pump_id, reason=reason, user_id=user_id ) return True except Exception as e: logger.error( "emergency_stop_pump_failed", station_id=station_id, pump_id=pump_id, error=str(e) ) return False def emergency_stop_station(self, station_id: str, reason: str = "Manual stop", user_id: str = "system") -> bool: """ Emergency stop all pumps in a station. Args: station_id: Station identifier reason: Reason for emergency stop user_id: User who initiated the stop Returns: True if stop was successful """ try: self.emergency_stop_stations.add(station_id) # Record emergency stop event self._record_emergency_stop_event(station_id, None, 'STATION', reason, user_id) logger.critical( "emergency_stop_station_activated", station_id=station_id, reason=reason, user_id=user_id ) return True except Exception as e: logger.error( "emergency_stop_station_failed", station_id=station_id, error=str(e) ) return False def emergency_stop_system(self, reason: str = "Manual stop", user_id: str = "system") -> bool: """ Emergency stop all pumps in the system. Args: reason: Reason for emergency stop user_id: User who initiated the stop Returns: True if stop was successful """ try: self.system_emergency_stop = True # Record emergency stop event self._record_emergency_stop_event(None, None, 'SYSTEM', reason, user_id) logger.critical( "emergency_stop_system_activated", reason=reason, user_id=user_id ) return True except Exception as e: logger.error("emergency_stop_system_failed", error=str(e)) return False def clear_emergency_stop_pump(self, station_id: str, pump_id: str, reason: str = "Manual clearance", user_id: str = "system") -> bool: """ Clear emergency stop for a specific pump. Args: station_id: Station identifier pump_id: Pump identifier reason: Reason for clearance user_id: User who cleared the stop Returns: True if clearance was successful """ try: key = (station_id, pump_id) if key in self.emergency_stop_pumps: self.emergency_stop_pumps.remove(key) # Record clearance event self._record_emergency_stop_clearance(station_id, pump_id, 'PUMP', reason, user_id) logger.info( "emergency_stop_pump_cleared", station_id=station_id, pump_id=pump_id, reason=reason, user_id=user_id ) return True else: logger.warning( "emergency_stop_pump_not_active", station_id=station_id, pump_id=pump_id ) return False except Exception as e: logger.error( "emergency_stop_pump_clearance_failed", station_id=station_id, pump_id=pump_id, error=str(e) ) return False def clear_emergency_stop_station(self, station_id: str, reason: str = "Manual clearance", user_id: str = "system") -> bool: """ Clear emergency stop for all pumps in a station. Args: station_id: Station identifier reason: Reason for clearance user_id: User who cleared the stop Returns: True if clearance was successful """ try: if station_id in self.emergency_stop_stations: self.emergency_stop_stations.remove(station_id) # Record clearance event self._record_emergency_stop_clearance(station_id, None, 'STATION', reason, user_id) logger.info( "emergency_stop_station_cleared", station_id=station_id, reason=reason, user_id=user_id ) return True else: logger.warning( "emergency_stop_station_not_active", station_id=station_id ) return False except Exception as e: logger.error( "emergency_stop_station_clearance_failed", station_id=station_id, error=str(e) ) return False def clear_emergency_stop_system(self, reason: str = "Manual clearance", user_id: str = "system") -> bool: """ Clear system-wide emergency stop. Args: reason: Reason for clearance user_id: User who cleared the stop Returns: True if clearance was successful """ try: if self.system_emergency_stop: self.system_emergency_stop = False # Record clearance event self._record_emergency_stop_clearance(None, None, 'SYSTEM', reason, user_id) logger.info( "emergency_stop_system_cleared", reason=reason, user_id=user_id ) return True else: logger.warning("emergency_stop_system_not_active") return False except Exception as e: logger.error("emergency_stop_system_clearance_failed", error=str(e)) return False def is_emergency_stop_active(self, station_id: str, pump_id: str) -> bool: """ Check if emergency stop is active for a pump. Args: station_id: Station identifier pump_id: Pump identifier Returns: True if emergency stop is active """ # Check system-wide stop if self.system_emergency_stop: return True # Check station-wide stop if station_id in self.emergency_stop_stations: return True # Check pump-specific stop key = (station_id, pump_id) if key in self.emergency_stop_pumps: return True return False def get_emergency_stop_status(self) -> Dict[str, Any]: """Get current emergency stop status.""" return { 'system_emergency_stop': self.system_emergency_stop, 'emergency_stop_stations': list(self.emergency_stop_stations), 'emergency_stop_pumps': [ {'station_id': station_id, 'pump_id': pump_id} for station_id, pump_id in self.emergency_stop_pumps ], 'total_active_stops': ( (1 if self.system_emergency_stop else 0) + len(self.emergency_stop_stations) + len(self.emergency_stop_pumps) ) } def _record_emergency_stop_event(self, station_id: Optional[str], pump_id: Optional[str], stop_type: str, reason: str, user_id: str): """Record emergency stop event in database.""" try: query = """ INSERT INTO emergency_stop_events (station_id, pump_id, stop_type, reason, user_id, timestamp) VALUES (%s, %s, %s, %s, %s, NOW()) """ self.db_client.execute(query, (station_id, pump_id, stop_type, reason, user_id)) except Exception as e: logger.error("failed_to_record_emergency_stop_event", error=str(e)) def _record_emergency_stop_clearance(self, station_id: Optional[str], pump_id: Optional[str], stop_type: str, reason: str, user_id: str): """Record emergency stop clearance event in database.""" try: query = """ INSERT INTO emergency_stop_events (station_id, pump_id, stop_type, event_type, reason, user_id, timestamp) VALUES (%s, %s, %s, 'CLEARED', %s, %s, NOW()) """ self.db_client.execute(query, (station_id, pump_id, stop_type, reason, user_id)) except Exception as e: logger.error("failed_to_record_emergency_stop_clearance", error=str(e))