Jupyter部件重运行时Libusb出现Windows访问拒绝错误(Error 5)的问题求助
Jupyter部件重运行时Libusb出现Windows访问拒绝错误(Error 5)的问题求助
大家好,我现在遇到一个头疼的USB IO问题,想请教下懂USB设备或者Jupyter部件开发的朋友:
问题背景
我正在Jupyter Notebook里开发一个部件,用来读取一款旧旋转运动传感器的USB数据。为了满足20Hz的传感器数据刷新率,我试过matplotlib ipywidgets、Dash-plotly和Bokeh——其中只有Bokeh的性能能跟上需求,但现在遇到了一个棘手的问题:每次重运行部件所在的单元格时,对USB设备的配置请求都会抛出Windows访问拒绝错误(Error 5),只有重启Jupyter内核才能解决。
直接运行连接和读取逻辑(不封装在部件里)时完全正常,Dash-plotly的实现也不会出现这个错误,但它性能不够,没法处理20Hz的数据流。
设备连接逻辑
这款传感器的连接流程比较特殊:
- 刚插上电脑时会识别为引导加载器设备
- 发送配置指令后,设备会自动重启进入应用状态(设备管理器里能看到状态变化,只要不拔掉设备就会保持这个状态)
- 找到重启后的应用态设备,配置为默认的配置1
我的核心连接代码如下:
import time import logging import usb.core as libusb_package import usb.util # 设备VID/PID(示例占位) ROTATION_BOOT_VID = 0xXXXX ROTATION_BOOT_PID = 0xXXXX ROTATION_APP_VID = 0xYYYY ROTATION_APP_PID = 0xYYYY class RotationSensor: def __init__(self): self.__dev: libusb_package.Device | None = None def connect(self, timeout_s=10): t0 = time.time() # 第一步:连接引导加载器设备并触发重启 while time.time() - t0 < timeout_s: try: self.__dev = libusb_package.find( idVendor=ROTATION_BOOT_VID, idProduct=ROTATION_BOOT_PID ) if self.__dev: logging.info("找到旋转传感器引导加载器设备") self.__initialize_configuration() logging.info("已配置引导加载器设备") self.__write_firmware() # 发送指令触发设备重启 self.__wait_for_disconnect() self.__dev = None # 清空引导加载器设备引用 break except usb.core.USBError as e: time.sleep(0.05) # 第二步:连接重启后的应用态设备 t0 = time.time() while time.time() - t0 < timeout_s: try: self.__dev = libusb_package.find( idVendor=ROTATION_APP_VID, idProduct=ROTATION_APP_PID ) if self.__dev: logging.info("找到旋转传感器应用态设备") active_config = self.__dev.get_active_configuration() logging.info(f"当前激活配置:{active_config}") self.__initialize_configuration() logging.info("已连接到应用态设备") return except (usb.core.USBError, NotImplementedError) as e: if isinstance(e, usb.core.USBError) and e.errno == 13: logging.warning("此处触发访问拒绝错误") return self.__handle_usb_error(e) time.sleep(0.05) raise ValueError("超时未找到可用的旋转传感器设备") def __del__(self): """析构时确保释放USB资源""" if self.__dev is not None: logging.info("正在清理传感器设备资源...") usb.util.dispose_resources(self.__dev) self.__dev = None # 以下为省略的辅助方法 def __initialize_configuration(self): # 设备配置逻辑(略) pass def __write_firmware(self): # 发送重启指令逻辑(略) pass def __wait_for_disconnect(self): # 等待设备重启断开逻辑(略) pass def __handle_usb_error(self, e): # USB错误处理逻辑(略) pass
Bokeh部件实现(出问题的版本)
为了实现实时绘图,我把传感器类封装到了Bokeh的周期性回调里,核心代码如下:
from bokeh.plotting import figure from bokeh.models import ColumnDataSource, Button from bokeh.layouts import column, row from bokeh.io import show import time def gyroscope_app(doc): source = ColumnDataSource(data=dict(time=[], radial_velocity=[])) rotation_sensor = RotationSensor() rotation_sensor.connect() # 初始化绘图 plot = figure( x_axis_label='时间 (s)', y_axis_label='径向速度 (arcsec / s)', min_width=800, min_height=400 ) plot.line('time', 'radial_velocity', source=source) callback_id = {'value': None} # 周期性读取传感器数据并更新绘图 def update(t): reading = rotation_sensor.read() if reading is None or len(reading) != 4: print("收到无效读数,跳过本次更新") return reading_data = reading[0] if reading[1] > 0 else -reading[0] new_data = dict(time=[t], radial_velocity=[reading_data]) source.stream(new_data, rollover=30) # 控制按钮逻辑 def start(): if callback_id['value'] is None: rotation_sensor.start_sensor_reading() callback_id['value'] = doc.add_periodic_callback(update, 100) def stop(): cid = callback_id['value'] if cid is not None: doc.remove_periodic_callback(cid) callback_id['value'] = None rotation_sensor.stop_sensor_reading() def reset(): stop() source.data = dict(time=[], radial_velocity=[]) # 组装部件布局 start_btn = Button(label="开始读取", button_type="success") start_btn.on_click(start) stop_btn = Button(label="停止读取", button_type="danger") stop_btn.on_click(stop) reset_btn = Button(label="重置数据", button_type="warning") reset_btn.on_click(reset) doc.add_root( column( row(start_btn, stop_btn, reset_btn), plot ) ) show(gyroscope_app)
我已经尝试过的方法
- 在传感器类的错误处理和
__del__析构方法里调用usb.util.dispose_resources,尝试强制释放设备资源 - 调试时确认:重运行单元格后,Python侧已经没有残留的设备对象
- 检查设备状态:设备重启后的应用态在设备管理器里显示正常,不会自动变回引导加载器
具体疑问
- 为什么重运行Jupyter部件单元格会导致USB设备被占用,直接运行代码却不会?
- 有没有可能是Bokeh的周期性回调或者Jupyter的部件机制,导致USB资源没有被正确释放?
- 除了
usb.util.dispose_resources,还有什么方法能确保USB设备资源被完全释放,让我可以重运行代码而不用重启内核? - Windows下有没有办法检查USB设备的内核驱动状态,或者强制释放设备占用?
麻烦各位大佬帮忙看看,感激不尽!
内容来源于stack exchange




