327 lines
11 KiB
Python
327 lines
11 KiB
Python
|
|
"""
|
||
|
|
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))
|