[Python] Getting a libusb windows access denied error when re-running a widget in Jupyter

Discussão em 'Python' iniciado por Stack, Agosto 20, 2025.

  1. Stack

    Stack Membro Participativo

    Summary


    I am trying to create a widget to read sensor data from a usb device within a Jupyter Notebook. I have tried both matplotlib ipywidgets, Dash-plotly, and Bokeh.

    When re-executing the widget notebook cell any configuration requests to the usb device return a Windows access denied error 5. Restarting the kernel resolves the issue.

    The issue does not occur when running outside a widget, executing the requests directly. It also does not occur with the Dash-plotly implementation but this is not performant enough to keep up with the sensor data at 20Hz.

    I don't have much experience with USB devices or IO in general, but have tried running usb.util.dispose_resources as an error handler within the class. My current connection logic is:

    t0 = time.time()
    while time.time() - t0 < timeout_s:
    try:
    self.__dev: usb.core.Device | None = libusb_package.find(
    idVendor=ROTATION_BOOT_VID, idProduct=ROTATION_BOOT_PID
    )
    if self.__dev:
    logging.info("Found ROTATION bootloader device.")
    self.__initialize_configuration()
    logging.info("Connected to ROTATION bootloader device.")
    self.__write_firmware()
    self.__wait_for_disconnect()
    self.__dev = None # Reset device to connect to application
    except usb.core.USBError as e:
    time.sleep(0.05)

    try:
    self.__dev: usb.core.Device = libusb_package.find(
    idVendor=ROTATION_APP_VID, idProduct=ROTATION_APP_PID
    )
    if self.__dev:
    logging.info("Found ROTATION application device.")

    bConfiguration = self.__dev.get_active_configuration()
    logging.info(f"Active configuration: {bConfiguration}")

    self.__initialize_configuration()
    logging.info("Connected to ROTATION application device.")
    return
    except (usb.core.USBError, NotImplementedError) as e:
    if (isinstance(e, usb.core.USBError) and e.errno == 13):
    logging.warning("Access Denied error here") # Taken out of handler for debug
    return
    self.__handle_usb_error(e)
    time.sleep(0.05)

    raise ValueError("No ROTATION device found within timeout period")


    Cleanup for the device is performed by a modified del member:

    def __del__(self):
    """Ensure the device is properly released"""
    if self.__dev is not None:
    logging.info("Cleaning up ROTATION device resources...")
    usb.util.dispose_resources(self.__dev)
    self.__dev = None


    This class is used in the current Bokeh (most performant) implementation of the widget:

    def gyroscope_app(doc):
    source = ColumnDataSource(data=dict(time=[], radial_velocity=[]))
    rotation_sensor = RotationSensor()
    rotation_sensor.open_device()

    plot = figure(
    x_axis_label='Time (s)', y_axis_label='Radial velocity (arcsec / s)',
    min_width=800, min_height=400
    )
    plot.line('time', 'radial_velocity', source=source)

    callback_id = {'value': None}

    @count()
    def update(t):
    reading = rotation_sensor.read()

    if reading is None or len(reading) != 4:
    print("Invalid reading received, skipping update.")
    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_button = Button(label="Start", button_type="success")
    start_button.on_click(start)

    stop_button = Button(label="Stop", button_type="danger")
    stop_button.on_click(stop)

    reset_button = Button(label="Reset", button_type="warning")
    reset_button.on_click(reset)

    doc.add_root(
    column(
    row(start_button, stop_button, reset_button),
    plot
    )
    )
    show(gyroscope_app)

    Notes


    • User has appropriate permissions and connection functions with no issue when not run within a widget.


    • No python side objects associated with the device remain visible in the debugger stack trace after cell execution and output clearing. Yet issue prevails.
    Attempted Solutions


    • No jupyter objects remain after cell execution for the Bokeh implementation. However for the matplotlib ipywidget their removal had no impact.


    • Logging for libusb shows:

    [1898.785734] [000103bc] libusb: debug [libusb_open] open 2.2
    [1898.785900] [000103bc] libusb: error [winusbx_open] could not open device \\?\USB#VID_0945&PID_0002#6&2F625D12&0&1#{A5DCBF10-6530-11D2-901F-00C04FB951ED} (interface 0): [5] Access is denied.
    [1898.785973] [000103bc] libusb: debug [libusb_open] open 2.2 returns -3

    • Jupyter cell output is:

    2025-08-20 15:46:56,447 DEBUG:usb.backend.libusb1:_LibUSB.get_device_descriptor(<usb.backend.libusb1._Device object at 0x000001FA52517090>)
    DEBUG:usb.backend.libusb1:_LibUSB.get_device_descriptor(<usb.backend.libusb1._Device object at 0x000001FA52517090>)
    2025-08-20 15:46:56,447 DEBUG:usb.backend.libusb1:_Device._finalize_object()
    DEBUG:usb.backend.libusb1:_Device._finalize_object()
    INFO:root:Found ROTATION application device.
    2025-08-20 15:46:56,448 DEBUG:usb.backend.libusb1:_LibUSB.open_device(<usb.backend.libusb1._Device object at 0x000001FA52517090>)
    DEBUG:usb.backend.libusb1:_LibUSB.open_device(<usb.backend.libusb1._Device object at 0x000001FA52517090>)

    WARNING:root:Device already configured /* Issue caught here but prevents reading */

    INFO:tornado.access:200 GET /autoload.js?bokeh-autoload-element=e97c48c3-168f-4e7c-9b22-33e50eb6464d&bokeh-absolute-url=http://localhost:53080&resources=none (127.0.0.1) 89.15ms
    INFO:tornado.access:101 GET /ws (127.0.0.1) 1.00ms
    INFO:bokeh.server.views.ws:WebSocket connection opened
    INFO:bokeh.server.views.ws:ServerConnection created

    1. Reading packets with Wireshark and Usbpcan shows only USB_INTERRUPT messages. Doesn't show any lingering messages if the cell is not re-run, so communication not active.


    No. Time Source Destination Protocol Length Info
    52 7.472741 host 2.4.1 USB 27 URB_INTERRUPT in

    Frame 52: 27 bytes on wire (216 bits), 27 bytes captured (216 bits) on interface \\.\USBPcap2, id 0
    Section number: 1
    Interface id: 0 (\\.\USBPcap2)
    Interface name: \\.\USBPcap2
    Interface description: USBPcap2
    Encapsulation type: USB packets with USBPcap header (152)
    Arrival Time: Aug 20, 2025 15:46:51.434821000 GMT Daylight Time
    UTC Arrival Time: Aug 20, 2025 14:46:51.434821000 UTC
    Epoch Arrival Time: 1755701211.434821000
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 0.000037000 seconds]
    [Time delta from previous displayed frame: 0.000037000 seconds]
    [Time since reference or first frame: 7.472741000 seconds]
    Frame Number: 52
    Frame Length: 27 bytes (216 bits)
    Capture Length: 27 bytes (216 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: usb]
    USB URB
    [Source: host]
    [Destination: 2.4.1]
    USBPcap pseudoheader length: 27
    IRP ID: 0xffffe48f6a499820
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
    IRP information: 0x00, Direction: FDO -> PDO
    0000 000. = Reserved: 0x00
    .... ...0 = Direction: FDO -> PDO (0x0)
    URB bus id: 2
    Device address: 4
    Endpoint: 0x81, Direction: IN
    1... .... = Direction: IN (1)
    .... 0001 = Endpoint number: 1
    URB transfer type: URB_INTERRUPT (0x01)
    Packet Data Length: 0
    [Response in: 55]
    [bInterfaceClass: HID (0x03)]

    No. Time Source Destination Protocol Length Info
    53 12.032803 2.4.1 host USB 35 URB_INTERRUPT in

    Frame 53: 35 bytes on wire (280 bits), 35 bytes captured (280 bits) on interface \\.\USBPcap2, id 0
    Section number: 1
    Interface id: 0 (\\.\USBPcap2)
    Interface name: \\.\USBPcap2
    Interface description: USBPcap2
    Encapsulation type: USB packets with USBPcap header (152)
    Arrival Time: Aug 20, 2025 15:46:55.994883000 GMT Daylight Time
    UTC Arrival Time: Aug 20, 2025 14:46:55.994883000 UTC
    Epoch Arrival Time: 1755701215.994883000
    [Time shift for this packet: 0.000000000 seconds]
    [Time delta from previous captured frame: 4.560062000 seconds]
    [Time delta from previous displayed frame: 4.560062000 seconds]
    [Time since reference or first frame: 12.032803000 seconds]
    Frame Number: 53
    Frame Length: 35 bytes (280 bits)
    Capture Length: 35 bytes (280 bits)
    [Frame is marked: False]
    [Frame is ignored: False]
    [Protocols in frame: usb:usbhid]
    USB URB
    [Source: 2.4.1]
    [Destination: host]
    USBPcap pseudoheader length: 27
    IRP ID: 0xffffe48f6a4b2010
    IRP USBD_STATUS: USBD_STATUS_SUCCESS (0x00000000)
    URB Function: URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER (0x0009)
    IRP information: 0x01, Direction: PDO -> FDO
    0000 000. = Reserved: 0x00
    .... ...1 = Direction: PDO -> FDO (0x1)
    URB bus id: 2
    Device address: 4
    Endpoint: 0x81, Direction: IN
    1... .... = Direction: IN (1)
    .... 0001 = Endpoint number: 1
    URB transfer type: URB_INTERRUPT (0x01)
    Packet Data Length: 8
    [Request in: 50]
    [Time from request: 4.928069000 seconds]
    [bInterfaceClass: HID (0x03)]
    HID Data: 0200000000000000

    Constraints


    My task requires that the end product run on a Windows 10 machine as a Jupyter Notebook with all dependencies available via Conda or PyPi

    Is there a way to Debug this on the js side that I am missing? (Looking at how to remove background proccess causing issue)

    Not sure how I would do that with Jupyter.

    Continue reading...

Compartilhe esta Página