- Changed log level in configuration from WARNING to INFO for better visibility of system operations. - Enhanced StandaloneAutoRecorder initialization to accept camera manager, state manager, and event system for improved modularity. - Updated recording routes to handle optional request bodies and improved error logging for better debugging. - Added checks in CameraMonitor to determine if a camera is already in use before initialization, enhancing resource management. - Improved MQTT client logging to provide more detailed connection and message handling information. - Added new MQTT event handling capabilities to the VisionApiClient for better tracking of machine states.
280 lines
11 KiB
Python
280 lines
11 KiB
Python
"""
|
|
Main Application Coordinator for the USDA Vision Camera System.
|
|
|
|
This module coordinates all system components and provides graceful startup/shutdown.
|
|
"""
|
|
|
|
import signal
|
|
import time
|
|
import logging
|
|
import sys
|
|
from typing import Optional
|
|
from datetime import datetime
|
|
|
|
from .core.config import Config
|
|
from .core.state_manager import StateManager
|
|
from .core.events import EventSystem, EventType
|
|
from .core.logging_config import setup_logging, get_error_tracker, get_performance_logger
|
|
from .core.timezone_utils import log_time_info, check_time_sync
|
|
from .mqtt.client import MQTTClient
|
|
from .camera.manager import CameraManager
|
|
from .storage.manager import StorageManager
|
|
from .recording.standalone_auto_recorder import StandaloneAutoRecorder
|
|
from .api.server import APIServer
|
|
|
|
|
|
class USDAVisionSystem:
|
|
"""Main application coordinator for the USDA Vision Camera System"""
|
|
|
|
def __init__(self, config_file: Optional[str] = None):
|
|
# Load configuration first (basic logging will be used initially)
|
|
self.config = Config(config_file)
|
|
|
|
# Setup comprehensive logging
|
|
self.logger_setup = setup_logging(log_level=self.config.system.log_level, log_file=self.config.system.log_file)
|
|
self.logger = logging.getLogger(__name__)
|
|
|
|
# Setup error tracking and performance monitoring
|
|
self.error_tracker = get_error_tracker("main_system")
|
|
self.performance_logger = get_performance_logger("main_system")
|
|
|
|
# Initialize core components
|
|
self.state_manager = StateManager()
|
|
self.event_system = EventSystem()
|
|
|
|
# Initialize system components
|
|
self.storage_manager = StorageManager(self.config, self.state_manager, self.event_system)
|
|
self.mqtt_client = MQTTClient(self.config, self.state_manager, self.event_system)
|
|
self.camera_manager = CameraManager(self.config, self.state_manager, self.event_system)
|
|
self.auto_recording_manager = StandaloneAutoRecorder(
|
|
config=self.config,
|
|
camera_manager=self.camera_manager,
|
|
state_manager=self.state_manager,
|
|
event_system=self.event_system
|
|
)
|
|
self.api_server = APIServer(self.config, self.state_manager, self.event_system, self.camera_manager, self.mqtt_client, self.storage_manager, self.auto_recording_manager)
|
|
|
|
# System state
|
|
self.running = False
|
|
self.start_time: Optional[datetime] = None
|
|
|
|
# Setup signal handlers for graceful shutdown
|
|
self._setup_signal_handlers()
|
|
|
|
self.logger.info("USDA Vision Camera System initialized")
|
|
|
|
def _setup_signal_handlers(self) -> None:
|
|
"""Setup signal handlers for graceful shutdown"""
|
|
|
|
def signal_handler(signum, frame):
|
|
self.logger.info(f"Received signal {signum}, initiating graceful shutdown...")
|
|
self.stop()
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
def start(self) -> bool:
|
|
"""Start the entire system"""
|
|
if self.running:
|
|
self.logger.warning("System is already running")
|
|
return True
|
|
|
|
self.logger.info("Starting USDA Vision Camera System...")
|
|
self.performance_logger.start_timer("system_startup")
|
|
self.start_time = datetime.now()
|
|
|
|
# Check time synchronization
|
|
self.logger.info("Checking time synchronization...")
|
|
log_time_info(self.logger)
|
|
sync_info = check_time_sync()
|
|
if sync_info["sync_status"] == "out_of_sync":
|
|
self.error_tracker.log_warning(f"System time may be out of sync (difference: {sync_info.get('time_diff_seconds', 'unknown')}s)", "time_sync_check")
|
|
elif sync_info["sync_status"] == "synchronized":
|
|
self.logger.info("✅ System time is synchronized")
|
|
|
|
try:
|
|
# Start storage manager (no background tasks)
|
|
self.logger.info("Initializing storage manager...")
|
|
try:
|
|
# Verify storage integrity
|
|
integrity_report = self.storage_manager.verify_storage_integrity()
|
|
if integrity_report.get("fixed_issues", 0) > 0:
|
|
self.logger.info(f"Fixed {integrity_report['fixed_issues']} storage integrity issues")
|
|
self.logger.info("Storage manager ready")
|
|
except Exception as e:
|
|
self.error_tracker.log_error(e, "storage_manager_init")
|
|
self.logger.error("Failed to initialize storage manager")
|
|
return False
|
|
|
|
# Start MQTT client
|
|
self.logger.info("Starting MQTT client...")
|
|
try:
|
|
if not self.mqtt_client.start():
|
|
self.error_tracker.log_error(Exception("MQTT client failed to start"), "mqtt_startup")
|
|
return False
|
|
self.logger.info("MQTT client started successfully")
|
|
except Exception as e:
|
|
self.error_tracker.log_error(e, "mqtt_startup")
|
|
return False
|
|
|
|
# Start camera manager
|
|
self.logger.info("Starting camera manager...")
|
|
try:
|
|
if not self.camera_manager.start():
|
|
self.error_tracker.log_error(Exception("Camera manager failed to start"), "camera_startup")
|
|
self.mqtt_client.stop()
|
|
return False
|
|
self.logger.info("Camera manager started successfully")
|
|
except Exception as e:
|
|
self.error_tracker.log_error(e, "camera_startup")
|
|
self.mqtt_client.stop()
|
|
return False
|
|
|
|
# Start auto-recording manager
|
|
self.logger.info("Starting auto-recording manager...")
|
|
try:
|
|
if not self.auto_recording_manager.start():
|
|
self.error_tracker.log_warning("Failed to start auto-recording manager", "auto_recording_startup")
|
|
else:
|
|
self.logger.info("Auto-recording manager started successfully")
|
|
except Exception as e:
|
|
self.error_tracker.log_error(e, "auto_recording_startup")
|
|
self.logger.warning("Auto-recording manager failed to start (continuing without auto-recording)")
|
|
|
|
# Start API server
|
|
self.logger.info("Starting API server...")
|
|
try:
|
|
if not self.api_server.start():
|
|
self.error_tracker.log_warning("Failed to start API server", "api_startup")
|
|
else:
|
|
self.logger.info("API server started successfully")
|
|
except Exception as e:
|
|
self.error_tracker.log_error(e, "api_startup")
|
|
self.logger.warning("API server failed to start (continuing without API)")
|
|
|
|
# Update system state
|
|
self.running = True
|
|
self.state_manager.set_system_started(True)
|
|
|
|
# Publish system started event
|
|
self.event_system.publish(EventType.SYSTEM_SHUTDOWN, "main_system", {"action": "started", "timestamp": self.start_time.isoformat()}) # We don't have SYSTEM_STARTED, using closest
|
|
|
|
startup_time = self.performance_logger.end_timer("system_startup")
|
|
self.logger.info(f"USDA Vision Camera System started successfully in {startup_time:.2f}s")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.error_tracker.log_error(e, "system_startup")
|
|
self.stop()
|
|
return False
|
|
|
|
def stop(self) -> None:
|
|
"""Stop the entire system gracefully"""
|
|
if not self.running:
|
|
return
|
|
|
|
self.logger.info("Stopping USDA Vision Camera System...")
|
|
self.running = False
|
|
|
|
try:
|
|
# Update system state
|
|
self.state_manager.set_system_started(False)
|
|
|
|
# Publish system shutdown event
|
|
self.event_system.publish(EventType.SYSTEM_SHUTDOWN, "main_system", {"action": "stopping", "timestamp": datetime.now().isoformat()})
|
|
|
|
# Stop API server
|
|
self.api_server.stop()
|
|
|
|
# Stop auto-recording manager
|
|
self.auto_recording_manager.stop()
|
|
|
|
# Stop camera manager (this will stop all recordings)
|
|
self.camera_manager.stop()
|
|
|
|
# Stop MQTT client
|
|
self.mqtt_client.stop()
|
|
|
|
# Final cleanup
|
|
if self.start_time:
|
|
uptime = (datetime.now() - self.start_time).total_seconds()
|
|
self.logger.info(f"System uptime: {uptime:.1f} seconds")
|
|
|
|
self.logger.info("USDA Vision Camera System stopped")
|
|
|
|
except Exception as e:
|
|
self.logger.error(f"Error during system shutdown: {e}")
|
|
|
|
def run(self) -> None:
|
|
"""Run the system (blocking call)"""
|
|
if not self.start():
|
|
self.logger.error("Failed to start system")
|
|
return
|
|
|
|
try:
|
|
self.logger.info("System running... Press Ctrl+C to stop")
|
|
|
|
# Main loop - just keep the system alive
|
|
while self.running:
|
|
time.sleep(1)
|
|
|
|
# Periodic maintenance tasks could go here
|
|
# For example: cleanup old recordings, health checks, etc.
|
|
|
|
except KeyboardInterrupt:
|
|
self.logger.info("Keyboard interrupt received")
|
|
except Exception as e:
|
|
self.logger.error(f"Unexpected error in main loop: {e}")
|
|
finally:
|
|
self.stop()
|
|
|
|
def get_system_status(self) -> dict:
|
|
"""Get comprehensive system status"""
|
|
return {
|
|
"running": self.running,
|
|
"start_time": self.start_time.isoformat() if self.start_time else None,
|
|
"uptime_seconds": (datetime.now() - self.start_time).total_seconds() if self.start_time else 0,
|
|
"components": {"mqtt_client": {"running": self.mqtt_client.is_running(), "connected": self.mqtt_client.is_connected()}, "camera_manager": {"running": self.camera_manager.is_running()}, "api_server": {"running": self.api_server.is_running()}},
|
|
"state_summary": self.state_manager.get_system_summary(),
|
|
}
|
|
|
|
def is_running(self) -> bool:
|
|
"""Check if system is running"""
|
|
return self.running
|
|
|
|
|
|
def main():
|
|
"""Main entry point for the application"""
|
|
import argparse
|
|
|
|
parser = argparse.ArgumentParser(description="USDA Vision Camera System")
|
|
parser.add_argument("--config", type=str, help="Path to configuration file", default="config.json")
|
|
parser.add_argument("--log-level", type=str, choices=["DEBUG", "INFO", "WARNING", "ERROR"], help="Override log level", default=None)
|
|
parser.add_argument("--debug", action="store_true", help="Enable debug mode (sets log level to DEBUG)")
|
|
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging (sets log level to DEBUG and enables additional debug output)")
|
|
|
|
args = parser.parse_args()
|
|
|
|
# Create and run system
|
|
system = USDAVisionSystem(args.config)
|
|
|
|
# Override log level if specified
|
|
if args.debug or args.verbose:
|
|
logging.getLogger().setLevel(logging.DEBUG)
|
|
# Enable debug mode in the system
|
|
if hasattr(system.config.system, 'debug_mode'):
|
|
system.config.system.debug_mode = True
|
|
print("🐛 Debug mode enabled - verbose logging active")
|
|
elif args.log_level:
|
|
logging.getLogger().setLevel(getattr(logging, args.log_level))
|
|
|
|
try:
|
|
system.run()
|
|
except Exception as e:
|
|
logging.error(f"Fatal error: {e}")
|
|
sys.exit(1)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|