Ver código fonte

fix(camera): 修复Linux平台下Dahua SDK BOOL类型处理问题

修正Linux平台下BOOL类型应为int而非bool的问题,确保SDK函数调用兼容性
增加连接和PTZ控制时的详细日志输出
添加登录句柄有效性检查
wenhongquan 4 dias atrás
pai
commit
9d1f46544c
2 arquivos alterados com 48 adições e 22 exclusões
  1. 37 20
      dual_camera_system/dahua_sdk.py
  2. 11 2
      dual_camera_system/ptz_camera.py

+ 37 - 20
dual_camera_system/dahua_sdk.py

@@ -177,13 +177,16 @@ class DahuaSDK:
         不同平台/版本的SDK可能缺少部分函数,用可选绑定处理:
         - 必需函数:缺失则初始化失败
         - 可选函数:缺失时设为None,运行时检查可用性
+        
+        注意: 在 Linux 上 BOOL = int (4 bytes),不是 Windows 上的 bool (1 byte)
+        所有返回 BOOL 的函数使用 c_int 作为返回值类型
         """
         
         # === 必需函数 ===
         
-        # CLIENT_Init(BOOL (CALLBACK *cbDisConnect), LDWORD)
+        # CLIENT_Init(BOOL (CALLBACK *cbDisConnect), LDWORD) -> BOOL
         self.sdk.CLIENT_Init.argtypes = [ctypes.c_void_p, c_long]
-        self.sdk.CLIENT_Init.restype = ctypes.c_bool
+        self.sdk.CLIENT_Init.restype = c_int  # BOOL = int on Linux
         
         # CLIENT_Cleanup()
         self.sdk.CLIENT_Cleanup.argtypes = []
@@ -196,9 +199,9 @@ class DahuaSDK:
         ]
         self.sdk.CLIENT_LoginWithHighLevelSecurity.restype = c_long
         
-        # CLIENT_Logout(LLONG)
+        # CLIENT_Logout(LLONG) -> BOOL
         self.sdk.CLIENT_Logout.argtypes = [c_long]
-        self.sdk.CLIENT_Logout.restype = ctypes.c_bool
+        self.sdk.CLIENT_Logout.restype = c_int  # BOOL = int on Linux
         
         # CLIENT_RealPlay(LLONG, int, void*) -> LLONG
         self.sdk.CLIENT_RealPlay.argtypes = [c_long, c_int, ctypes.c_void_p]
@@ -206,17 +209,19 @@ class DahuaSDK:
         
         # CLIENT_StopRealPlay(LLONG) -> BOOL
         self.sdk.CLIENT_StopRealPlay.argtypes = [c_long]
-        self.sdk.CLIENT_StopRealPlay.restype = ctypes.c_bool
+        self.sdk.CLIENT_StopRealPlay.restype = c_int  # BOOL = int on Linux
         
-        # CLIENT_DHPTZControlEx(LLONG, int, DWORD, LONG, LONG, LONG, BOOL)
+        # CLIENT_DHPTZControlEx(LLONG, int, DWORD, LONG, LONG, LONG, BOOL) -> BOOL
+        # 注意: 在 Linux 上 BOOL = int (4 bytes),不是 Windows 上的 bool
+        # 使用 c_int 而不是 c_bool 以确保参数大小正确
         self.sdk.CLIENT_DHPTZControlEx.argtypes = [
-            c_long, c_int, c_uint32, c_int, c_int, c_int, ctypes.c_bool
+            c_long, c_int, c_uint32, c_int, c_int, c_int, c_int
         ]
-        self.sdk.CLIENT_DHPTZControlEx.restype = ctypes.c_bool
+        self.sdk.CLIENT_DHPTZControlEx.restype = c_int  # BOOL = int on Linux
         
         # CLIENT_SnapPicture(LLONG, SNAP_PARAMS*) -> BOOL
         self.sdk.CLIENT_SnapPicture.argtypes = [c_long, POINTER(self.SNAP_PARAMS)]
-        self.sdk.CLIENT_SnapPicture.restype = ctypes.c_bool
+        self.sdk.CLIENT_SnapPicture.restype = c_int  # BOOL = int on Linux
         
         # CLIENT_GetLastError() -> DWORD
         self.sdk.CLIENT_GetLastError.argtypes = []
@@ -227,7 +232,7 @@ class DahuaSDK:
         
         self._bind_optional('CLIENT_SetSnapRevCallBack', [ctypes.c_void_p, c_long], None)
         self._bind_optional('CLIENT_RealPlayEx', [c_long, c_int, ctypes.c_void_p, c_int], c_long)
-        self._bind_optional('CLIENT_StopRealPlayEx', [c_long], ctypes.c_bool)
+        self._bind_optional('CLIENT_StopRealPlayEx', [c_long], c_int)  # BOOL = int on Linux
         self._bind_optional('CLIENT_SetVideoProcCallBack', [ctypes.c_void_p, c_long], None)
     
     def _bind_optional(self, name: str, argtypes: list, restype):
@@ -262,13 +267,16 @@ class DahuaSDK:
         
         if disconnect_callback:
             # 保存回调引用,防止被垃圾回收
+            # 回调签名: BOOL (CALLBACK *cbDisConnect)(LLONG lLoginID, char *pchDVRIP, LONG nDVRPort, LDWORD dwUser)
+            # 在 Linux 上 BOOL = int,所以使用 c_int 作为返回值类型
             self._disconnect_callback = ctypes.CFUNCTYPE(
-                ctypes.c_bool, c_long, c_char_p, c_int, c_long
+                c_int, c_long, c_char_p, c_int, c_long
             )(disconnect_callback)
         
         result = self.sdk.CLIENT_Init(self._disconnect_callback, 0)
-        self.initialized = result
-        return result
+        # SDK 返回 TRUE(非0) 表示成功
+        self.initialized = (result != 0)
+        return self.initialized
     
     def cleanup(self):
         """清理SDK资源"""
@@ -314,7 +322,8 @@ class DahuaSDK:
         """登出设备"""
         if not self.sdk or login_handle <= 0:
             return False
-        return self.sdk.CLIENT_Logout(login_handle)
+        result = self.sdk.CLIENT_Logout(login_handle)
+        return result != 0  # SDK 返回 TRUE(非0) 表示成功
     
     def real_play(self, login_handle: int, channel: int = 0) -> Optional[int]:
         """
@@ -374,7 +383,8 @@ class DahuaSDK:
         """停止实时预览"""
         if not self.sdk or play_handle <= 0:
             return False
-        return self.sdk.CLIENT_StopRealPlay(play_handle)
+        result = self.sdk.CLIENT_StopRealPlay(play_handle)
+        return result != 0  # SDK 返回 TRUE(非0) 表示成功
     
     def ptz_control(self, login_handle: int, channel: int, 
                     command: int, param1: int, param2: int, param3: int,
@@ -404,18 +414,24 @@ class DahuaSDK:
         }
         cmd_name = cmd_names.get(command, f'CMD_{command:#x}')
         
+        # 将 stop (bool) 转换为 int (TRUE=1, FALSE=0),匹配 Linux SDK 的 BOOL 定义
+        stop_int = 1 if stop else 0
+        
         result = self.sdk.CLIENT_DHPTZControlEx(
-            login_handle, channel, command, param1, param2, param3, stop
+            login_handle, channel, command, param1, param2, param3, stop_int
         )
         
-        print(f"[PTZ] {cmd_name}(ch={channel}, p1={param1}, p2={param2}, p3={param3}, stop={stop}) → {'✓' if result else '✗'}")
+        # SDK 返回 TRUE(非0) 表示成功,FALSE(0) 表示失败
+        success = (result != 0)
+        
+        print(f"[PTZ] {cmd_name}(ch={channel}, p1={param1}, p2={param2}, p3={param3}, stop={stop}) → {'✓' if success else '✗'} (ret={result})")
         
-        if not result:
+        if not success:
             # 获取SDK错误码
             error = self.sdk.CLIENT_GetLastError() if hasattr(self.sdk, 'CLIENT_GetLastError') else -1
             print(f"[PTZ] 错误码: {error}")
         
-        return result
+        return success
     
     def snap_picture(self, login_handle: int, channel: int = 0, 
                      cmd_serial: int = 0) -> bool:
@@ -439,7 +455,8 @@ class DahuaSDK:
         params.Quality = 1
         params.PicFormat = 0  # BMP
         
-        return self.sdk.CLIENT_SnapPicture(login_handle, byref(params))
+        result = self.sdk.CLIENT_SnapPicture(login_handle, byref(params))
+        return result != 0  # SDK 返回 TRUE(非0) 表示成功
 
 
 # PTZ控制命令常量 (从dhnetsdk.h提取)

+ 11 - 2
dual_camera_system/ptz_camera.py

@@ -48,6 +48,8 @@ class PTZCamera:
         Returns:
             是否成功
         """
+        print(f"[PTZCamera] 正在连接球机: IP={self.config['ip']}:{self.config['port']}, 通道={self.config['channel']}")
+        
         login_handle, error = self.sdk.login(
             self.config['ip'],
             self.config['port'],
@@ -56,12 +58,14 @@ class PTZCamera:
         )
         
         if login_handle is None:
-            print(f"连接球机失败: IP={self.config['ip']}, 错误码={error}")
+            print(f"[PTZCamera] 连接球机失败: IP={self.config['ip']}, 错误码={error}")
+            print(f"[PTZCamera] 请检查: 1)IP地址是否正确 2)网络是否连通 3)用户名密码是否正确")
             return False
         
         self.login_handle = login_handle
         self.connected = True
-        print(f"成功连接球机: {self.config['ip']}")
+        print(f"[PTZCamera] 成功连接球机: {self.config['ip']}, handle={login_handle}")
+        print(f"[PTZCamera] 配置: 通道={self.config['channel']}, 默认变倍={self.ptz_config['default_zoom']}")
         return True
     
     def disconnect(self):
@@ -83,6 +87,11 @@ class PTZCamera:
             是否成功
         """
         if not self.connected:
+            print(f"[PTZCamera] PTZ控制失败: 未连接球机")
+            return False
+        
+        if self.login_handle is None or self.login_handle <= 0:
+            print(f"[PTZCamera] PTZ控制失败: 登录句柄无效 (handle={self.login_handle})")
             return False
         
         return self.sdk.ptz_control(