Files
huojv/utils/device_scanner.py
2025-11-06 09:48:32 +08:00

198 lines
7.1 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
设备扫描工具 - 通过唯一标识符而非索引来识别设备
解决重启后设备序号混乱的问题
"""
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}")