198 lines
7.1 KiB
Python
198 lines
7.1 KiB
Python
"""
|
||
设备扫描工具 - 通过唯一标识符而非索引来识别设备
|
||
解决重启后设备序号混乱的问题
|
||
"""
|
||
import cv2
|
||
import serial.tools.list_ports
|
||
import warnings
|
||
import sys
|
||
import io
|
||
from typing import List, Dict, Optional
|
||
|
||
def get_camera_info(cam_index: int) -> Optional[Dict]:
|
||
"""
|
||
获取采集卡的详细信息(包括设备名称)
|
||
:param cam_index: 采集卡索引
|
||
:return: 包含设备信息的字典,如果失败返回None
|
||
"""
|
||
old_stderr = sys.stderr
|
||
suppressed_output = io.StringIO()
|
||
|
||
try:
|
||
sys.stderr = suppressed_output
|
||
|
||
with warnings.catch_warnings():
|
||
warnings.filterwarnings('ignore')
|
||
|
||
# 尝试多种后端
|
||
backends = [
|
||
(cam_index, cv2.CAP_DSHOW),
|
||
(cam_index, cv2.CAP_ANY),
|
||
(cam_index, None),
|
||
]
|
||
|
||
cap = None
|
||
for idx, backend in backends:
|
||
try:
|
||
if backend is not None:
|
||
cap = cv2.VideoCapture(idx, backend)
|
||
else:
|
||
cap = cv2.VideoCapture(idx)
|
||
|
||
if cap.isOpened():
|
||
ret, frame = cap.read()
|
||
if ret and frame is not None:
|
||
# 尝试获取设备名称(不同后端可能支持不同)
|
||
device_name = None
|
||
backend_name = None
|
||
|
||
if backend == cv2.CAP_DSHOW:
|
||
backend_name = "DirectShow"
|
||
# DirectShow可能支持获取设备名称
|
||
try:
|
||
# 某些情况下可以通过属性获取
|
||
pass # OpenCV限制,无法直接获取设备名称
|
||
except:
|
||
pass
|
||
elif backend == cv2.CAP_ANY:
|
||
backend_name = "Any"
|
||
else:
|
||
backend_name = "Default"
|
||
|
||
# 获取分辨率信息作为辅助标识
|
||
width = int(cap.get(cv2.CAP_PROP_FRAME_WIDTH))
|
||
height = int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))
|
||
|
||
cap.release()
|
||
|
||
return {
|
||
'index': cam_index,
|
||
'backend': backend_name,
|
||
'width': width,
|
||
'height': height,
|
||
'name': f"采集卡-{cam_index}",
|
||
'available': True
|
||
}
|
||
else:
|
||
if cap:
|
||
cap.release()
|
||
cap = None
|
||
except Exception:
|
||
if cap:
|
||
try:
|
||
cap.release()
|
||
except:
|
||
pass
|
||
cap = None
|
||
continue
|
||
|
||
return None
|
||
finally:
|
||
sys.stderr = old_stderr
|
||
|
||
def scan_cameras_with_info(max_index: int = 10) -> List[Dict]:
|
||
"""
|
||
扫描所有可用采集卡,返回详细信息列表
|
||
:param max_index: 最大扫描索引
|
||
:return: 采集卡信息列表
|
||
"""
|
||
cameras = []
|
||
for i in range(max_index + 1):
|
||
info = get_camera_info(i)
|
||
if info:
|
||
cameras.append(info)
|
||
return cameras
|
||
|
||
def get_serial_port_info(port_name: str) -> Optional[Dict]:
|
||
"""
|
||
获取串口的详细信息(包括硬件ID和描述)
|
||
:param port_name: 串口名称(如 "COM3")
|
||
:return: 包含串口信息的字典
|
||
"""
|
||
try:
|
||
ports = serial.tools.list_ports.comports()
|
||
for port in ports:
|
||
if port.device == port_name:
|
||
return {
|
||
'device': port.device,
|
||
'description': port.description,
|
||
'hwid': port.hwid, # 硬件ID(唯一标识)
|
||
'vid': port.vid if hasattr(port, 'vid') else None,
|
||
'pid': port.pid if hasattr(port, 'pid') else None,
|
||
'serial_number': port.serial_number if hasattr(port, 'serial_number') else None,
|
||
'manufacturer': port.manufacturer if hasattr(port, 'manufacturer') else None,
|
||
'name': port.description or port.device
|
||
}
|
||
except Exception as e:
|
||
print(f"⚠️ 获取串口信息失败: {e}")
|
||
|
||
return None
|
||
|
||
def scan_serial_ports_with_info() -> List[Dict]:
|
||
"""
|
||
扫描所有可用串口,返回详细信息列表
|
||
:return: 串口信息列表
|
||
"""
|
||
ports_info = []
|
||
try:
|
||
ports = serial.tools.list_ports.comports()
|
||
for port in ports:
|
||
info = {
|
||
'device': port.device,
|
||
'description': port.description,
|
||
'hwid': port.hwid,
|
||
'vid': port.vid if hasattr(port, 'vid') else None,
|
||
'pid': port.pid if hasattr(port, 'pid') else None,
|
||
'serial_number': port.serial_number if hasattr(port, 'serial_number') else None,
|
||
'manufacturer': port.manufacturer if hasattr(port, 'manufacturer') else None,
|
||
'name': port.description or port.device
|
||
}
|
||
ports_info.append(info)
|
||
except Exception as e:
|
||
print(f"⚠️ 扫描串口失败: {e}")
|
||
|
||
# 按设备名排序
|
||
ports_info.sort(key=lambda x: int(x['device'].replace('COM', '')) if x['device'].replace('COM', '').isdigit() else 999)
|
||
return ports_info
|
||
|
||
def find_camera_by_index(cameras: List[Dict], index: int) -> Optional[Dict]:
|
||
"""通过索引查找采集卡"""
|
||
for cam in cameras:
|
||
if cam['index'] == index:
|
||
return cam
|
||
return None
|
||
|
||
def find_serial_by_hwid(ports: List[Dict], hwid: str) -> Optional[Dict]:
|
||
"""通过硬件ID查找串口(最可靠)"""
|
||
for port in ports:
|
||
if port['hwid'] == hwid:
|
||
return port
|
||
return None
|
||
|
||
def find_serial_by_device(ports: List[Dict], device: str) -> Optional[Dict]:
|
||
"""通过设备名查找串口(兼容旧配置)"""
|
||
for port in ports:
|
||
if port['device'] == device:
|
||
return port
|
||
return None
|
||
|
||
if __name__ == "__main__":
|
||
# 测试代码
|
||
print("=" * 60)
|
||
print("采集卡扫描:")
|
||
print("=" * 60)
|
||
cameras = scan_cameras_with_info()
|
||
for cam in cameras:
|
||
print(f" 索引 {cam['index']}: {cam['name']} ({cam['width']}x{cam['height']})")
|
||
|
||
print("\n" + "=" * 60)
|
||
print("串口扫描:")
|
||
print("=" * 60)
|
||
ports = scan_serial_ports_with_info()
|
||
for port in ports:
|
||
print(f" {port['device']}: {port['name']}")
|
||
print(f" 硬件ID: {port['hwid']}")
|
||
if port['vid'] and port['pid']:
|
||
print(f" VID/PID: {port['vid']:04X}/{port['pid']:04X}")
|
||
|