Update Docker configuration, enhance error handling, and improve logging

- Added health check to the camera management API service in docker-compose.yml for better container reliability.
- Updated installation scripts in Dockerfile to check for existing dependencies before installation, improving efficiency.
- Enhanced error handling in the USDAVisionSystem class to allow partial operation if some components fail to start, preventing immediate shutdown.
- Improved logging throughout the application, including more detailed error messages and critical error handling in the main loop.
- Refactored WebSocketManager and CameraMonitor classes to use debug logging for connection events, reducing log noise.
This commit is contained in:
salirezav
2025-12-03 17:23:31 -05:00
parent 2bce817b4e
commit 933d4417a5
30 changed files with 4314 additions and 220 deletions

View File

@@ -151,42 +151,42 @@ class CameraMonitor:
# ALWAYS check our streamer state first, before doing any camera availability tests
streamer = self.camera_manager.camera_streamers.get(camera_name)
self.logger.info(f"Checking streamer for {camera_name}: {streamer}")
self.logger.debug(f"Checking streamer for {camera_name}: {streamer}")
if streamer and streamer.is_streaming():
self.logger.info(f"Camera {camera_name} is streaming - setting status to streaming")
self.logger.debug(f"Camera {camera_name} is streaming - setting status to streaming")
return "streaming", "Camera streaming (live preview)", self._get_device_info_dict(device_info)
# Also check if our recorder is active
recorder = self.camera_manager.camera_recorders.get(camera_name)
if recorder and recorder.hCamera and recorder.recording:
self.logger.info(f"Camera {camera_name} is recording - setting status to available")
self.logger.debug(f"Camera {camera_name} is recording - setting status to available")
return "available", "Camera recording (in use by system)", self._get_device_info_dict(device_info)
# Check if camera is already opened by another process
try:
self.logger.info(f"Checking if camera {camera_name} is opened...")
self.logger.debug(f"Checking if camera {camera_name} is opened...")
is_opened = mvsdk.CameraIsOpened(device_info)
self.logger.info(f"CameraIsOpened result for {camera_name}: {is_opened}")
self.logger.debug(f"CameraIsOpened result for {camera_name}: {is_opened}")
if is_opened:
self.logger.info(f"Camera {camera_name} is opened by another process - setting status to busy")
self.logger.debug(f"Camera {camera_name} is opened by another process - setting status to busy")
return "busy", "Camera opened by another process", self._get_device_info_dict(device_info)
else:
self.logger.info(f"Camera {camera_name} is not opened, will try initialization")
self.logger.debug(f"Camera {camera_name} is not opened, will try initialization")
# Camera is not opened, so we can try to initialize it
pass
except Exception as e:
self.logger.warning(f"CameraIsOpened failed for {camera_name}: {e}")
# If we can't determine the status, try to initialize to see what happens
self.logger.info(f"CameraIsOpened failed for {camera_name}, will try initialization: {e}")
self.logger.debug(f"CameraIsOpened failed for {camera_name}, will try initialization: {e}")
# Try to initialize camera briefly to test availability
try:
# Ensure SDK is initialized
ensure_sdk_initialized()
self.logger.info(f"Attempting to initialize camera {camera_name} for availability test...")
self.logger.debug(f"Attempting to initialize camera {camera_name} for availability test...")
# Check if camera is already in use by recorder or streamer before trying to initialize
recorder = self.camera_manager.camera_recorders.get(camera_name) if self.camera_manager else None
@@ -198,7 +198,7 @@ class CameraMonitor:
# Check if recorder has camera open
if mvsdk.CameraIsOpened(recorder.hCamera):
camera_in_use = True
self.logger.info(f"Camera {camera_name} is already in use by recorder (handle: {recorder.hCamera})")
self.logger.debug(f"Camera {camera_name} is already in use by recorder (handle: {recorder.hCamera})")
except:
pass
@@ -207,13 +207,13 @@ class CameraMonitor:
# Check if streamer has camera open
if mvsdk.CameraIsOpened(streamer.hCamera):
camera_in_use = True
self.logger.info(f"Camera {camera_name} is already in use by streamer (handle: {streamer.hCamera})")
self.logger.debug(f"Camera {camera_name} is already in use by streamer (handle: {streamer.hCamera})")
except:
pass
# If camera is already in use, mark as available (since it's working, just occupied)
if camera_in_use:
self.logger.info(f"Camera {camera_name} is in use by system components - marking as available")
self.logger.debug(f"Camera {camera_name} is in use by system components - marking as available")
return "available", "Camera is in use by system", self._get_device_info_dict(device_info)
# Suppress output to avoid MVCAMAPI error messages during camera testing
@@ -221,7 +221,7 @@ class CameraMonitor:
try:
with suppress_camera_errors():
hCamera = mvsdk.CameraInit(device_info, -1, -1)
self.logger.info(f"Camera {camera_name} initialized successfully, starting test capture...")
self.logger.debug(f"Camera {camera_name} initialized successfully, starting test capture...")
except mvsdk.CameraException as init_e:
error_msg = f"CameraInit failed for {camera_name}: {init_e.message} (error_code: {init_e.error_code})"
@@ -256,14 +256,14 @@ class CameraMonitor:
mvsdk.CameraSetTriggerMode(hCamera, 0)
mvsdk.CameraPlay(hCamera)
self.logger.info(f"Camera {camera_name} test: Attempting to capture frame with {CAMERA_TEST_CAPTURE_TIMEOUT}ms timeout...")
self.logger.debug(f"Camera {camera_name} test: Attempting to capture frame with {CAMERA_TEST_CAPTURE_TIMEOUT}ms timeout...")
# Try to capture with short timeout
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(hCamera, CAMERA_TEST_CAPTURE_TIMEOUT)
mvsdk.CameraReleaseImageBuffer(hCamera, pRawData)
# Success - camera is available
mvsdk.CameraUnInit(hCamera)
self.logger.info(f"Camera {camera_name} test successful - camera is available")
self.logger.debug(f"Camera {camera_name} test successful - camera is available")
return "available", "Camera test successful", self._get_device_info_dict(device_info)
except mvsdk.CameraException as capture_e:

View File

@@ -90,8 +90,20 @@ class CameraStreamer:
"""Start streaming preview frames"""
with self._lock:
if self.streaming:
self.logger.warning("Streaming already active")
return True
self.logger.warning("Streaming already active - checking if thread is alive")
# Check if thread is actually running
if self._streaming_thread and self._streaming_thread.is_alive():
self.logger.info("Streaming thread is alive, returning success")
return True
else:
# Thread died but flag wasn't reset - clean up and restart
self.logger.warning("Streaming flag set but thread is dead - cleaning up and restarting")
self.streaming = False
if self.hCamera is not None:
try:
self._cleanup_camera()
except Exception as e:
self.logger.error(f"Error cleaning up stale camera handle: {e}")
try:
# Initialize camera for streaming
@@ -249,6 +261,15 @@ class CameraStreamer:
try:
self.logger.info(f"Initializing camera for streaming: {self.camera_config.name}")
# Safety check: ensure no stale camera handle exists
if self.hCamera is not None and not self._using_shared_camera:
self.logger.warning("Stale camera handle detected during initialization - cleaning up first")
try:
mvsdk.CameraUnInit(self.hCamera)
except Exception as e:
self.logger.warning(f"Error cleaning up stale handle: {e}")
self.hCamera = None
# Check if recorder is active and has camera open - if so, share it
if self.recorder and self.recorder.hCamera and self.recorder.recording:
self.logger.info("Recorder is active with camera open - will share recorder's camera connection")
@@ -423,11 +444,41 @@ class CameraStreamer:
time.sleep(STREAMING_LOOP_SLEEP) # Just wait, recorder populates queues
continue
# Safety check: ensure camera handle is valid before use
if self.hCamera is None:
self.logger.error("Camera handle is None in streaming loop - stopping")
break
# Capture frame with timeout
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, CAMERA_GET_BUFFER_TIMEOUT)
try:
pRawData, FrameHead = mvsdk.CameraGetImageBuffer(self.hCamera, CAMERA_GET_BUFFER_TIMEOUT)
except mvsdk.CameraException as e:
if e.error_code == mvsdk.CAMERA_STATUS_TIME_OUT:
continue # Timeout is normal, continue
else:
self.logger.error(f"CameraGetImageBuffer failed: {e.message} (error_code: {e.error_code})")
# If camera is invalid, break to prevent segfault
if e.error_code in [mvsdk.CAMERA_STATUS_INVALID_HANDLE, mvsdk.CAMERA_STATUS_INVALID_PARAM]:
self.logger.error("Invalid camera handle detected - stopping streaming loop")
break
time.sleep(BRIEF_PAUSE_SLEEP)
continue
# Process frame
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
try:
mvsdk.CameraImageProcess(self.hCamera, pRawData, self.frame_buffer, FrameHead)
except mvsdk.CameraException as e:
self.logger.error(f"CameraImageProcess failed: {e.message} (error_code: {e.error_code})")
# Release buffer before continuing
try:
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
except:
pass
if e.error_code in [mvsdk.CAMERA_STATUS_INVALID_HANDLE, mvsdk.CAMERA_STATUS_INVALID_PARAM]:
self.logger.error("Invalid camera handle in image process - stopping streaming loop")
break
time.sleep(BRIEF_PAUSE_SLEEP)
continue
# Convert to OpenCV format
frame = self._convert_frame_to_opencv(FrameHead)
@@ -477,7 +528,14 @@ class CameraStreamer:
pass
# Release buffer
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
try:
mvsdk.CameraReleaseImageBuffer(self.hCamera, pRawData)
except mvsdk.CameraException as e:
self.logger.error(f"CameraReleaseImageBuffer failed: {e.message} (error_code: {e.error_code})")
# If handle is invalid, break to prevent further issues
if e.error_code in [mvsdk.CAMERA_STATUS_INVALID_HANDLE, mvsdk.CAMERA_STATUS_INVALID_PARAM]:
self.logger.error("Invalid camera handle when releasing buffer - stopping streaming loop")
break
# Control frame rate
time.sleep(1.0 / self.preview_fps)
@@ -491,6 +549,15 @@ class CameraStreamer:
self.logger.error(f"Fatal error in streaming loop: {e}")
finally:
self.logger.info("Streaming loop ended")
# Reset streaming flag when loop ends
with self._lock:
self.streaming = False
# Cleanup camera resources if not already done
if self.hCamera is not None and not self._using_shared_camera:
try:
self._cleanup_camera()
except Exception as cleanup_e:
self.logger.error(f"Error during cleanup after loop ended: {cleanup_e}")
def _convert_frame_to_opencv(self, FrameHead) -> Optional[np.ndarray]:
"""Convert camera frame to OpenCV format"""