Update camera management and MQTT logging for improved functionality

- 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.
This commit is contained in:
salirezav
2025-11-03 16:56:53 -05:00
parent 868aa3f036
commit 4acad772f9
17 changed files with 1074 additions and 83 deletions

View File

@@ -30,13 +30,13 @@ sys.path.insert(0, str(Path(__file__).parent.parent.parent))
from usda_vision_system.core.config import Config
from usda_vision_system.camera.recorder import CameraRecorder
from usda_vision_system.core.state_manager import StateManager
from usda_vision_system.core.events import EventSystem
from usda_vision_system.core.events import EventSystem, EventType, Event
class StandaloneAutoRecorder:
"""Standalone auto-recording system that monitors MQTT and controls cameras directly"""
def __init__(self, config_path: str = "config.json", config: Optional[Config] = None):
def __init__(self, config_path: str = "config.json", config: Optional[Config] = None, camera_manager=None, state_manager=None, event_system=None):
# Load configuration
if config:
self.config = config
@@ -45,9 +45,9 @@ class StandaloneAutoRecorder:
# Setup logging (only if not already configured)
if not logging.getLogger().handlers:
# Use WARNING level by default to reduce INFO log noise
log_level = getattr(self.config.system, 'log_level', 'WARNING')
log_level_num = getattr(logging, log_level.upper(), logging.WARNING)
# Use configured log level
log_level = getattr(self.config.system, 'log_level', 'INFO')
log_level_num = getattr(logging, log_level.upper(), logging.INFO)
logging.basicConfig(
level=log_level_num,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
@@ -59,16 +59,17 @@ class StandaloneAutoRecorder:
self.logger = logging.getLogger(__name__)
# Ensure this logger respects the configured log level
if hasattr(self.config, 'system') and hasattr(self.config.system, 'log_level'):
self.logger.setLevel(getattr(logging, self.config.system.log_level.upper(), logging.WARNING))
self.logger.setLevel(getattr(logging, self.config.system.log_level.upper(), logging.INFO))
# Initialize components
self.state_manager = StateManager()
self.event_system = EventSystem()
# Use provided components or create new ones
self.state_manager = state_manager if state_manager else StateManager()
self.event_system = event_system if event_system else EventSystem()
self.camera_manager = camera_manager
# MQTT client
# MQTT client (only if not using event system)
self.mqtt_client: Optional[mqtt.Client] = None
# Camera recorders
# Camera recorders (only if not using camera_manager)
self.camera_recorders: Dict[str, CameraRecorder] = {}
self.active_recordings: Dict[str, str] = {} # camera_name -> filename
@@ -82,8 +83,17 @@ class StandaloneAutoRecorder:
self.running = False
self._stop_event = threading.Event()
# Subscribe to machine state change events if using event system
if self.event_system and self.camera_manager:
self.event_system.subscribe(EventType.MACHINE_STATE_CHANGED, self._on_machine_state_changed)
self.logger.info("Subscribed to MACHINE_STATE_CHANGED events")
self.logger.info("Standalone Auto-Recorder initialized")
self.logger.info(f"Machine-Camera mapping: {self.machine_camera_map}")
if self.camera_manager:
self.logger.info("Using provided camera_manager for recording")
else:
self.logger.info("Will create own camera recorders (standalone mode)")
def _build_machine_camera_map(self) -> Dict[str, str]:
"""Build mapping from machine topics to camera names"""
@@ -162,80 +172,137 @@ class StandaloneAutoRecorder:
except Exception as e:
self.logger.error(f"Error processing MQTT message: {e}")
def _on_machine_state_changed(self, event: Event):
"""Handle machine state change event from event system"""
try:
machine_name = event.data.get("machine_name")
state = event.data.get("state", "").lower()
source = event.source
self.logger.info(f"📡 AUTO-RECORDER: Received MACHINE_STATE_CHANGED event from {source}")
self.logger.info(f"📡 AUTO-RECORDER: Event data - machine_name: {machine_name}, state: {state}")
if not machine_name or not state:
self.logger.warning(f"❌ AUTO-RECORDER: Invalid event data - machine_name: {machine_name}, state: {state}")
return
self._handle_machine_state_change(machine_name, state)
except Exception as e:
self.logger.error(f"❌ AUTO-RECORDER: Error handling machine state change event: {e}", exc_info=True)
def _handle_machine_state_change(self, machine_name: str, state: str):
"""Handle machine state change"""
try:
# Check if we have a camera for this machine
camera_name = self.machine_camera_map.get(machine_name)
if not camera_name:
self.logger.debug(f"No camera mapped to machine: {machine_name}")
return
self.logger.info(f"Handling state change: {machine_name} ({camera_name}) -> {state}")
self.logger.info(f"📡 MQTT: Machine {machine_name} ({camera_name}) -> {state}")
if state == "on":
self._start_recording(camera_name, machine_name)
elif state == "off":
self._stop_recording(camera_name, machine_name)
else:
self.logger.debug(f"Ignoring state '{state}' for machine {machine_name}")
except Exception as e:
self.logger.error(f"Error handling machine state change: {e}")
self.logger.error(f"Error handling machine state change: {e}", exc_info=True)
def _start_recording(self, camera_name: str, machine_name: str):
"""Start recording for a camera"""
try:
# Check if already recording
if camera_name in self.active_recordings:
self.logger.warning(f"Camera {camera_name} is already recording")
camera_info = self.state_manager.get_camera_status(camera_name) if self.state_manager else None
if camera_info and camera_info.is_recording:
self.logger.info(f"Camera {camera_name} is already recording, skipping")
return
# Get or create camera recorder
recorder = self._get_camera_recorder(camera_name)
if not recorder:
self.logger.error(f"Failed to get recorder for camera {camera_name}")
return
# Use camera_manager if available, otherwise use standalone recorder
if self.camera_manager:
# Generate filename with timestamp and machine info
from ..core.timezone_utils import format_filename_timestamp
timestamp = format_filename_timestamp()
camera_config = self.config.get_camera_by_name(camera_name)
video_format = camera_config.video_format if camera_config else "mp4"
filename = f"{camera_name}_auto_{machine_name}_{timestamp}.{video_format}"
# Generate filename with timestamp and machine info
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
camera_config = self.config.get_camera_by_name(camera_name)
video_format = camera_config.video_format if camera_config else "mp4"
filename = f"{camera_name}_auto_{machine_name}_{timestamp}.{video_format}"
# Use camera manager to start recording with camera's default settings
success = self.camera_manager.manual_start_recording(
camera_name=camera_name,
filename=filename,
exposure_ms=camera_config.exposure_ms if camera_config else None,
gain=camera_config.gain if camera_config else None,
fps=camera_config.target_fps if camera_config else None
)
# Start recording
success = recorder.start_recording(filename)
if success:
self.active_recordings[camera_name] = filename
self.logger.info(f"✅ Started recording: {camera_name} -> {filename}")
if success:
self.logger.info(f"✅ Started auto-recording: {camera_name} -> {filename}")
self.active_recordings[camera_name] = filename
else:
self.logger.error(f"❌ Failed to start auto-recording for camera {camera_name}")
else:
self.logger.error(f"❌ Failed to start recording for camera {camera_name}")
# Standalone mode - use own recorder
recorder = self._get_camera_recorder(camera_name)
if not recorder:
self.logger.error(f"Failed to get recorder for camera {camera_name}")
return
# Generate filename with timestamp and machine info
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
camera_config = self.config.get_camera_by_name(camera_name)
video_format = camera_config.video_format if camera_config else "mp4"
filename = f"{camera_name}_auto_{machine_name}_{timestamp}.{video_format}"
# Start recording
success = recorder.start_recording(filename)
if success:
self.active_recordings[camera_name] = filename
self.logger.info(f"✅ Started recording: {camera_name} -> {filename}")
else:
self.logger.error(f"❌ Failed to start recording for camera {camera_name}")
except Exception as e:
self.logger.error(f"Error starting recording for {camera_name}: {e}")
self.logger.error(f"Error starting recording for {camera_name}: {e}", exc_info=True)
def _stop_recording(self, camera_name: str, machine_name: str):
"""Stop recording for a camera"""
try:
# Check if recording
if camera_name not in self.active_recordings:
self.logger.warning(f"Camera {camera_name} is not recording")
return
# Get recorder
recorder = self._get_camera_recorder(camera_name)
if not recorder:
self.logger.error(f"Failed to get recorder for camera {camera_name}")
return
# Stop recording
filename = self.active_recordings.pop(camera_name)
success = recorder.stop_recording()
if success:
self.logger.info(f"✅ Stopped recording: {camera_name} -> {filename}")
# Use camera_manager if available
if self.camera_manager:
success = self.camera_manager.manual_stop_recording(camera_name)
if success:
self.logger.info(f"✅ Stopped auto-recording: {camera_name}")
if camera_name in self.active_recordings:
filename = self.active_recordings.pop(camera_name)
self.logger.debug(f"Recording filename was: {filename}")
else:
self.logger.warning(f"Camera {camera_name} may not have been recording")
else:
self.logger.error(f"❌ Failed to stop recording for camera {camera_name}")
# Standalone mode - use own recorder
if camera_name not in self.active_recordings:
self.logger.warning(f"Camera {camera_name} is not recording")
return
recorder = self._get_camera_recorder(camera_name)
if not recorder:
self.logger.error(f"Failed to get recorder for camera {camera_name}")
return
# Stop recording
filename = self.active_recordings.pop(camera_name)
success = recorder.stop_recording()
if success:
self.logger.info(f"✅ Stopped recording: {camera_name} -> {filename}")
else:
self.logger.error(f"❌ Failed to stop recording for camera {camera_name}")
except Exception as e:
self.logger.error(f"Error stopping recording for {camera_name}: {e}")
self.logger.error(f"Error stopping recording for {camera_name}: {e}", exc_info=True)
def _get_camera_recorder(self, camera_name: str) -> Optional[CameraRecorder]:
"""Get or create camera recorder"""
@@ -356,19 +423,27 @@ class StandaloneAutoRecorder:
try:
self.logger.info("Starting Standalone Auto-Recorder...")
# Setup MQTT
# If using event system and camera_manager, we don't need our own MQTT client
if self.event_system and self.camera_manager:
self.logger.info("Using event system - no need for separate MQTT client")
self.running = True
self.logger.info("✅ Standalone Auto-Recorder started successfully (event-based mode)")
return True
# Otherwise, setup MQTT client for standalone mode
if not self._setup_mqtt():
self.logger.error("Failed to setup MQTT client")
return False
# Wait for MQTT connection
time.sleep(2)
self.running = True
self.logger.info("✅ Standalone Auto-Recorder started successfully")
self.logger.info("✅ Standalone Auto-Recorder started successfully (standalone MQTT mode)")
return True
except Exception as e:
self.logger.error(f"Failed to start auto-recorder: {e}")
self.logger.error(f"Failed to start auto-recorder: {e}", exc_info=True)
return False
def stop(self) -> bool: