2025-01-27 11:26:45 +02:00

190 lines
5.6 KiB
Python
Executable File

#!/usr/bin/python3
import os
import sys
import signal
import threading
import logging
from http.server import HTTPServer
from multiprocessing import Queue
import subprocess
# Import our custom modules
from alarm_api import AlertApi, run as run_api
from alarm_storage import AlarmStorage
from alarm_siren import AlarmSiren
from ncurses_ui import UI
from logging_config import setup_logging
# Set up logging
logger = setup_logging()
class AlarmSystemManager:
def __init__(self,
api_port: int = 8000,
storage_path: str = "data/alerts.json",
log_level: int = logging.DEBUG):
"""
Initialize and manage the entire alarm system
Args:
api_port (int): Port for the HTTP API server
storage_path (str): Path to the JSON storage file
log_level (int): Logging level
"""
# Configure logging
logging.getLogger().setLevel(log_level)
# Ensure storage directory exists
os.makedirs(os.path.dirname(os.path.abspath(storage_path)), exist_ok=True)
# Initialize components
self.siren = AlarmSiren()
self.storage = AlarmStorage(storage_path, siren=self.siren)
# API server setup
self.api_port = api_port
self.api_server = None
self.api_thread = None
# Synchronization and lifecycle management
self.stop_event = threading.Event()
# Signal handling setup
self._setup_signal_handlers()
# Alarm synchronization
self._sync_alarms()
# UI..
self.ui = UI(self, control_queue=self.siren.control_queue)
def _setup_signal_handlers(self):
"""Set up signal handlers for graceful shutdown"""
signal.signal(signal.SIGINT, self._handle_shutdown)
signal.signal(signal.SIGTERM, self._handle_shutdown)
def _handle_shutdown(self, signum, frame):
"""
Handle system shutdown signals
Args:
signum (int): Signal number
frame (frame): Current stack frame
"""
logger.info(f"Received shutdown signal {signum}. Initiating graceful shutdown...")
self.stop_event.set()
def _sync_alarms(self):
"""
Synchronize stored alarms with the siren
This ensures any saved alarms are loaded and scheduled
"""
try:
saved_alarms = self.storage.get_saved_alerts()
logger.info(f"Synchronizing {len(saved_alarms)} saved alarms")
for alarm in saved_alarms:
if alarm.get('enabled', True):
self.siren.schedule_alarm(alarm)
except Exception as e:
logger.error(f"Error synchronizing alarms: {e}")
def _start_api_server(self):
"""
Start the HTTP API server in a separate thread
"""
try:
def run_server():
server_address = ("", self.api_port)
self.api_server = HTTPServer(server_address, AlertApi)
logger.info(f"API Server started on port {self.api_port}")
# Run until stopped
while not self.stop_event.is_set():
self.api_server.handle_request()
self.api_server.server_close()
logger.info("API Server stopped")
# Start API server in a thread
self.api_thread = threading.Thread(target=run_server, daemon=True)
self.api_thread.start()
except Exception as e:
logger.error(f"Failed to start API server: {e}")
self.stop_event.set()
def run(self):
"""
Main run method to start all system components
"""
try:
# Start API server
self._start_api_server()
# Start UI
ui_thread = self.ui.run()
# Log system startup
logger.info("Alarm System started successfully")
# Wait for shutdown signal
while not self.stop_event.is_set():
self.stop_event.wait(timeout=1)
# Graceful shutdown
logger.info("Initiating system shutdown...")
except Exception as e:
logger.error(f"System startup error: {e}")
finally:
self._cleanup()
def _cleanup(self):
"""
Perform cleanup operations during shutdown
"""
# Close API server if running
if self.api_server:
try:
self.api_server.server_close()
except Exception as e:
logger.error(f"Error closing API server: {e}")
# Stop threads
if self.api_thread and self.api_thread.is_alive():
self.api_thread.join(timeout=2)
# Kill any remaining mpg123 processes
try:
subprocess.run(['pkill', 'mpg123'], check=False)
except Exception as e:
logger.error(f"Error killing mpg123 processes: {e}")
logger.info("Alarm System shutdown complete")
def main():
"""
Entry point for the Alarm System
Parse command-line arguments and start the system
"""
# Default configurations
default_port = 8000
default_storage_path = "data/alerts.json"
# Parse port from command line if provided
port = int(sys.argv[1]) if len(sys.argv) > 1 else default_port
# Create and run the Alarm System
try:
alarm_system = AlarmSystemManager(
api_port=port,
storage_path=default_storage_path
)
alarm_system.run()
except Exception as e:
logger.error(f"Fatal error starting Alarm System: {e}", exc_info=True)
sys.exit(1)
if __name__ == "__main__":
main()