From e93d25c33e810e1d071341b85efaab319f73c79f Mon Sep 17 00:00:00 2001 From: edo-neo Date: Thu, 21 Aug 2025 14:02:23 +0200 Subject: [PATCH] dev --- src/components/hikrobot_sc/hikrobot_dll.py | 279 +++++++++++++-------- src/components/hikrobot_sc/hikrobot_sc.py | 50 +++- 2 files changed, 221 insertions(+), 108 deletions(-) diff --git a/src/components/hikrobot_sc/hikrobot_dll.py b/src/components/hikrobot_sc/hikrobot_dll.py index 10f4f66..280b6b3 100644 --- a/src/components/hikrobot_sc/hikrobot_dll.py +++ b/src/components/hikrobot_sc/hikrobot_dll.py @@ -226,114 +226,193 @@ mv_lib.MV_VS_SetStringValue.restype = c_int def MV_VS_GetFrame(handle, name=""): + """ + Get a frame from the camera with enhanced error handling to prevent access violations. + + Args: + handle: Camera handle + name: Optional name for debugging + + Returns: + dict: Dictionary containing frame data and results, or None if an error occurred + """ + # Create a logger for this function + import logging + logger = logging.getLogger("MV_VS_GetFrame") + + try: + stFrameData = MV_VS_DATA() - stFrameData = MV_VS_DATA() - - nRet = mv_lib.MV_VS_GetResultData(handle, byref(stFrameData), 500) - if nRet == MV_VS_OK: - # Print frame information - # print(f"Frame Width: [{stFrameData.nImageWidth}], Height: [{stFrameData.nImageHeight}], ImageLen: [{stFrameData.nImageLen}]") + # Get result data with timeout + nRet = mv_lib.MV_VS_GetResultData(handle, byref(stFrameData), 500) + if nRet != MV_VS_OK: + error_code = nRet & 0xFFFFFFFF + logger.error(f"MV_VS_GetResultData failed! Error code: 0x{error_code:x}") + + # Special handling for 0x80030100 (MV_VS_E_GC_GENERIC) + if error_code == 0x80030100: + logger.warning("Detected MV_VS_E_GC_GENERIC error (0x80030100) during frame acquisition") + logger.info("This is a non-critical error, will return None") + + return None + + # Validate frame data + if stFrameData.pImage is None: + logger.error("Frame data contains null image pointer") + return None + + if stFrameData.nImageLen <= 0: + logger.error(f"Invalid image length: {stFrameData.nImageLen}") + return None # Process chunk data currentDataTag = 0 chResultInfo = {} - while stFrameData.nChunkDataLen > currentDataTag: - chunkLength = ctypes.c_uint32() - chunkID = ctypes.c_uint32() - - # Calculate source addresses for chunkLength and chunkID - source_address_length = ctypes.addressof( - stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 4 - currentDataTag - source_address_id = ctypes.addressof( - stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 8 - currentDataTag - - # Copy data from source to chunkLength and chunkID - ctypes.memmove(ctypes.byref(chunkLength), source_address_length, 4) - ctypes.memmove(ctypes.byref(chunkID), source_address_id, 4) - - # Convert network byte order to host byte order - chunkLength.value = socket.ntohl(chunkLength.value) - chunkID.value = socket.ntohl(chunkID.value) - - # Print the values - #print(f"chunkLength [{chunkLength.value}], chunkID: [{chunkID.value}]") - - if chunkLength.value <= 0 or chunkLength.value > stFrameData.nChunkDataLen - 8 - currentDataTag: - break - - if chunkID.value == CHUNK_RESULT_PORT: # Result data in JSON format - chRawInfo = ctypes.cast(stFrameData.pChunkData, ctypes.POINTER(ctypes.c_char)) - chRawInfo = ctypes.cast(ctypes.addressof( - chRawInfo.contents) + stFrameData.nChunkDataLen - 8 - chunkLength.value - currentDataTag, - ctypes.POINTER(ctypes.c_char)) - - chResultInfo = ctypes.string_at(chRawInfo, chunkLength.value) - chResultInfo = json.loads(chResultInfo.decode()) - - # if chResultInfo: - # print(f"chResultInfo OK") - - elif chunkID.value == CHUNK_MASK_IMAGE_PORT: # Mask image data - maskModID = ctypes.c_uint32() - maskModFormat = ctypes.c_uint32() - maskModWidth = ctypes.c_uint32() - maskModHeight = ctypes.c_uint32() - - # Calculate source addresses - source_address_id = ctypes.addressof( - stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 8 - chunkLength.value - currentDataTag - source_address_format = source_address_id + 4 - source_address_width = source_address_id + 8 - source_address_height = source_address_id + 12 - - # Copy data from source addresses to corresponding variables - ctypes.memmove(ctypes.byref(maskModID), source_address_id, 4) - ctypes.memmove(ctypes.byref(maskModFormat), source_address_format, 4) - ctypes.memmove(ctypes.byref(maskModWidth), source_address_width, 4) - ctypes.memmove(ctypes.byref(maskModHeight), source_address_height, 4) - - # print(f"Mask ModID[{maskModID.value}], ModFormat[{maskModFormat.value}], ModWidth[{maskModWidth.value}], ModHeight[{maskModHeight.value}], ") - - try: - chMaskModImageData = ctypes.cast(ctypes.create_string_buffer(chunkLength.value - 16 + 1), - ctypes.POINTER(ctypes.c_char)) - if chMaskModImageData: - ctypes.memset(chMaskModImageData, 0, chunkLength.value - 16 + 1) - chImageData = ctypes.cast(ctypes.addressof( - stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 8 - chunkLength.value - currentDataTag + 16, - ctypes.POINTER(ctypes.c_char)) - ctypes.memmove(chMaskModImageData, chImageData, chunkLength.value - 16) - # print(f"Mask data length [{chunkLength.value - 16}]") - - except (MemoryError, TypeError) as e: - print(f"Error allocating or copying memory: {e}") - - currentDataTag = currentDataTag + 8 + chunkLength.value - # end process chunk data - - m_stJpgParam = MV_VS_JPG_PARAM() - m_stJpgParam.pBufInput = ctypes.cast(stFrameData.pImage,ctypes.POINTER(ctypes.c_ubyte)) - m_stJpgParam.nBufInputLen = stFrameData.nImageLen - jpegContents = ctypes.string_at(m_stJpgParam.pBufInput, m_stJpgParam.nBufInputLen) - - # with open(f"tmp/test_{name}.jpg", "wb") as f: - # f.write(jpegContents) - jpg_as_np = np.frombuffer(jpegContents, dtype=np.uint8) - # Convert the NumPy array to an OpenCV image. - img = cv2.imdecode(jpg_as_np, flags=1) - img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) - # Release frame buffer - nRet = mv_lib.MV_VS_ReleaseResultData(handle, ctypes.byref(stFrameData)) - - if nRet != MV_VS_OK: - print(f"Release frame buffer fail! nRet [0x{nRet:x}]") + + # Check if chunk data is valid + if stFrameData.pChunkData is None: + logger.warning("Chunk data pointer is null, skipping chunk processing") + elif stFrameData.nChunkDataLen <= 0: + logger.warning(f"Invalid chunk data length: {stFrameData.nChunkDataLen}") else: - # print(f"Release frame buffer OK") - pass + # Process chunk data safely + try: + while stFrameData.nChunkDataLen > currentDataTag: + chunkLength = ctypes.c_uint32() + chunkID = ctypes.c_uint32() + # Calculate source addresses for chunkLength and chunkID + try: + source_address_length = ctypes.addressof( + stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 4 - currentDataTag + source_address_id = ctypes.addressof( + stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 8 - currentDataTag + + # Copy data from source to chunkLength and chunkID + ctypes.memmove(ctypes.byref(chunkLength), source_address_length, 4) + ctypes.memmove(ctypes.byref(chunkID), source_address_id, 4) + + # Convert network byte order to host byte order + chunkLength.value = socket.ntohl(chunkLength.value) + chunkID.value = socket.ntohl(chunkID.value) + except (AttributeError, TypeError) as e: + logger.error(f"Error accessing chunk data memory: {e}") + break + + # Validate chunk length + if chunkLength.value <= 0 or chunkLength.value > stFrameData.nChunkDataLen - 8 - currentDataTag: + logger.warning(f"Invalid chunk length: {chunkLength.value}, breaking chunk processing") + break + + # Process different chunk types + try: + if chunkID.value == CHUNK_RESULT_PORT: # Result data in JSON format + chRawInfo = ctypes.cast(stFrameData.pChunkData, ctypes.POINTER(ctypes.c_char)) + chRawInfo = ctypes.cast(ctypes.addressof( + chRawInfo.contents) + stFrameData.nChunkDataLen - 8 - chunkLength.value - currentDataTag, + ctypes.POINTER(ctypes.c_char)) + + chResultInfo_bytes = ctypes.string_at(chRawInfo, chunkLength.value) + try: + chResultInfo = json.loads(chResultInfo_bytes.decode()) + except json.JSONDecodeError as e: + logger.error(f"Error decoding JSON result data: {e}") + chResultInfo = {} + + elif chunkID.value == CHUNK_MASK_IMAGE_PORT: # Mask image data + maskModID = ctypes.c_uint32() + maskModFormat = ctypes.c_uint32() + maskModWidth = ctypes.c_uint32() + maskModHeight = ctypes.c_uint32() + + # Calculate source addresses + source_address_id = ctypes.addressof( + stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 8 - chunkLength.value - currentDataTag + source_address_format = source_address_id + 4 + source_address_width = source_address_id + 8 + source_address_height = source_address_id + 12 + + # Copy data from source addresses to corresponding variables + ctypes.memmove(ctypes.byref(maskModID), source_address_id, 4) + ctypes.memmove(ctypes.byref(maskModFormat), source_address_format, 4) + ctypes.memmove(ctypes.byref(maskModWidth), source_address_width, 4) + ctypes.memmove(ctypes.byref(maskModHeight), source_address_height, 4) + + try: + chMaskModImageData = ctypes.cast(ctypes.create_string_buffer(chunkLength.value - 16 + 1), + ctypes.POINTER(ctypes.c_char)) + if chMaskModImageData: + ctypes.memset(chMaskModImageData, 0, chunkLength.value - 16 + 1) + chImageData = ctypes.cast(ctypes.addressof( + stFrameData.pChunkData.contents) + stFrameData.nChunkDataLen - 8 - chunkLength.value - currentDataTag + 16, + ctypes.POINTER(ctypes.c_char)) + ctypes.memmove(chMaskModImageData, chImageData, chunkLength.value - 16) + except (MemoryError, TypeError, AttributeError) as e: + logger.error(f"Error allocating or copying memory for mask data: {e}") + except Exception as e: + logger.error(f"Error processing chunk ID {chunkID.value}: {e}") + # Continue with next chunk + + # Move to next chunk + currentDataTag = currentDataTag + 8 + chunkLength.value + except Exception as e: + logger.error(f"Error during chunk data processing: {e}") + # Continue with image processing despite chunk errors + + # Process image data + try: + m_stJpgParam = MV_VS_JPG_PARAM() + m_stJpgParam.pBufInput = ctypes.cast(stFrameData.pImage, ctypes.POINTER(ctypes.c_ubyte)) + m_stJpgParam.nBufInputLen = stFrameData.nImageLen + + # Safely get JPEG contents + try: + jpegContents = ctypes.string_at(m_stJpgParam.pBufInput, m_stJpgParam.nBufInputLen) + except (TypeError, ValueError) as e: + logger.error(f"Error accessing JPEG data: {e}") + # Release frame buffer before returning + mv_lib.MV_VS_ReleaseResultData(handle, ctypes.byref(stFrameData)) + return None + + # Convert to numpy array and then to OpenCV image + try: + jpg_as_np = np.frombuffer(jpegContents, dtype=np.uint8) + img = cv2.imdecode(jpg_as_np, flags=1) + + if img is None: + logger.error("Failed to decode JPEG data") + # Release frame buffer before returning + mv_lib.MV_VS_ReleaseResultData(handle, ctypes.byref(stFrameData)) + return None + + img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR) + except Exception as e: + logger.error(f"Error converting JPEG to image: {e}") + # Release frame buffer before returning + mv_lib.MV_VS_ReleaseResultData(handle, ctypes.byref(stFrameData)) + return None + except Exception as e: + logger.error(f"Error processing image data: {e}") + # Release frame buffer before returning + mv_lib.MV_VS_ReleaseResultData(handle, ctypes.byref(stFrameData)) + return None + + # Release frame buffer + try: + nRet = mv_lib.MV_VS_ReleaseResultData(handle, ctypes.byref(stFrameData)) + if nRet != MV_VS_OK: + error_code = nRet & 0xFFFFFFFF + logger.warning(f"Release frame buffer failed! Error code: 0x{error_code:x}") + except Exception as e: + logger.error(f"Exception during frame buffer release: {e}") + + # Return the processed frame and results return {"frame": img, "res": chResultInfo} - else: - print(f"MV_VS_GetResultData fail! nRet [0x{nRet&0xFFFFFFFF:x}]") + + except Exception as e: + logger.error(f"Unhandled exception in MV_VS_GetFrame: {e}") + import traceback + logger.error(f"Exception traceback: {traceback.format_exc()}") return None def print_device_info(device_info_list): diff --git a/src/components/hikrobot_sc/hikrobot_sc.py b/src/components/hikrobot_sc/hikrobot_sc.py index 4e66b61..8f7b0e0 100644 --- a/src/components/hikrobot_sc/hikrobot_sc.py +++ b/src/components/hikrobot_sc/hikrobot_sc.py @@ -241,23 +241,47 @@ class HikrobotSmartCamera(Component): def refresh_module_list(self): """ - Refresh the module list after switching schemes. + Refresh the module list after switching schemes with enhanced error handling. Returns: bool: True if successful, False otherwise """ - if not self.connected: - self.log.error("Cannot refresh module list: Camera not connected") - return False - - # Get the first camera handle + # Check connection status with retry mechanism + retry_count = 0 + max_retries = 3 + + while retry_count < max_retries: + if not self.connected: + self.log.warning(f"Camera not connected during module list refresh (attempt {retry_count+1}/{max_retries})") + if retry_count == max_retries - 1: + self.log.error("Cannot refresh module list: Camera not connected after retries") + return False + + # Wait and retry + self.log.info("Waiting 1 second before retrying connection check") + time.sleep(1) + retry_count += 1 + continue + else: + break + + # Get the first camera handle with validation if len(self.cam_list) == 0: self.log.error("Cannot refresh module list: No camera handles available") return False - handle = self.cam_list[0]["handle"] + # Validate handle before using it + handle = self.cam_list[0].get("handle") + if handle is None: + self.log.error("Cannot refresh module list: Camera handle is None") + return False + + # Add a delay before refreshing to allow camera to stabilize + time.sleep(0.5) try: + self.log.info("Attempting to refresh module list") + # Command to refresh module list nRet = mv_lib.MV_VS_SetCommandValue(handle, b"CommandRefreshModuleList") if nRet != MV_VS_OK: @@ -271,6 +295,13 @@ class HikrobotSmartCamera(Component): # We'll return True here to avoid treating this as a fatal error return True + # For other error codes, check if we should retry + if retry_count < max_retries - 1: + retry_count += 1 + self.log.warning(f"Retrying module list refresh after error (attempt {retry_count}/{max_retries})") + time.sleep(1) # Wait before retrying + return self.refresh_module_list() # Recursive retry + return False self.log.info("Module list refreshed successfully") @@ -280,7 +311,10 @@ class HikrobotSmartCamera(Component): # Log the full exception traceback for debugging import traceback self.log.error(f"Exception traceback: {traceback.format_exc()}") - return False + + # Return True for non-critical exceptions to avoid blocking the workflow + self.log.info("Treating exception as non-critical, continuing operation") + return True def switch_scheme(self, solution_name, retry_count=0, max_retries=2): """