vpelletier / python-libusb1

Python ctype-based wrapper around libusb1
GNU Lesser General Public License v2.1
168 stars 65 forks source link

getDevice() is mostly unusable with WinUSB #39

Closed whitequark closed 5 years ago

whitequark commented 5 years ago

WinUSB always opens an USB device in exclusive mode. This means that you can't do something like...

handle = self.usb_context.openByVendorIDAndProductID(vendor_id, product_id)
serial = handle.getDevice().getSerialNumber()

because USBDevice.getSerialNumber() opens the device a second time internally and that fails with LIBUSB_ERROR_ACCESS.

I think getDevice() should return a "pre-opened" USBDevice object to avoid this.

vpelletier commented 5 years ago

Here is the proposed fix, implemented in edb6b0020b26f431bee3ba085d1bea8a232a04f6 (and released as 1.6.6):

handle.getASCIIStringDescriptor(handle.getDevice().getSerialNumberDescriptor())

The idea of USBDevice is that it gives access to what can be accessed without opening the device: what the OS has cached and exposes after it enumerated the device. Opening an USBDevice means producing an USBHandle, so to me the fix is rather to call methods available on USBHandle.

getSerialNumber along with getManufacturer and getProduct are exceptions to this rule, and rather intended as shortcuts to be used while looking for a device.

Sadly, I did not expose the raw descriptor ids for these 3, which is what I just added in 1.6.6 .

whitequark commented 5 years ago

I agree that your proposed solution solves the problem, but I feel like it doesn't go far enough. It is tempting to use the API in what appears the easiest possible way, and most people would likely just do that, and here that would still produce an obscure portability hazard.

This is veering into opinion territory though, so feel free to close this if this doesn't mesh with your approach to API design.

kevindawson commented 5 years ago

Sorry if I don't get the above, but when I append getSerialNumber() to your example

import usb1

with usb1.USBContext() as context:
    for device in context.getDeviceIterator(skip_on_error=True):
        print(
            "ID %04x:%04x" % (device.getVendorID(), device.getProductID()),
            "->".join(
                str(x)
                for x in ["Bus %03i" % (device.getBusNumber(),)]
                + device.getPortNumberList()
            ),
            "Device",
            device.getDeviceAddress(),
            "SerialNumber",
            device.getSerialNumber(),
        )

I get the following output libusb1 => 1.6.6

/usr/bin/python3.6 /home/kevin/Git_Python/pySerial/sandbox/scratch_08.py
Traceback (most recent call last):
  File "/home/kevin/Git_Python/pySerial/sandbox/scratch_08.py", line 16, in <module>
    device.getSerialNumber(),
  File "/usr/local/lib/python3.6/dist-packages/usb1/__init__.py", line 2057, in getSerialNumber
    self.device_descriptor.iSerialNumber)
  File "/usr/local/lib/python3.6/dist-packages/usb1/__init__.py", line 2018, in _getASCIIStringDescriptor
    return self.open().getASCIIStringDescriptor(descriptor)
  File "/usr/local/lib/python3.6/dist-packages/usb1/__init__.py", line 2092, in open
    mayRaiseUSBError(libusb1.libusb_open(self.device_p, byref(handle)))
  File "/usr/local/lib/python3.6/dist-packages/usb1/__init__.py", line 133, in mayRaiseUSBError
    __raiseUSBError(value)
  File "/usr/local/lib/python3.6/dist-packages/usb1/__init__.py", line 125, in raiseUSBError
    raise __STATUS_TO_EXCEPTION_DICT.get(value, __USBError)(value)
usb1.USBErrorAccess: LIBUSB_ERROR_ACCESS [-3]

Process finished with exit code 1

sorry to be a pain

kevindawson commented 5 years ago

Hello again!

code snippet of USB serial number, kind of works


    import usb1

    # arduino - 0x2341:0x0043
    VENDOR_ID = 0x2341
    PRODUCT_ID = 0x0043

    with usb1.USBContext() as context:

        device = context.getByVendorIDAndProductID(
            VENDOR_ID, PRODUCT_ID, skip_on_error=True, skip_on_access_error=True
        )

        try:
            handle = device.open()

            try:
                serial_number = handle.getASCIIStringDescriptor(
                    handle.getDevice().getSerialNumberDescriptor()
                )
                print("SerialNumber", serial_number)
            except usb1.USBError as e:
                print(e, " -> Looking for SerialNumber Descriptor")

so after hacking around in the dark I got the above to work but I still have an issue

I have 2 Arduino's

can I use getDeviceIterator and if so how?

many thanks

ps. libusb1.0 documentation => libusb.info

vpelletier commented 5 years ago

can I use getDeviceIterator and if so how?

Scavenging your original code, here is how I would do it in a nutshell:

with usb1.USBContext() as context:
    for device in context.getDeviceIterator(skip_on_error=True):
        try:
            print(
                "ID %04x:%04x" % (device.getVendorID(), device.getProductID()),
                "->".join(
                    str(x)
                    for x in ["Bus %03i" % (device.getBusNumber(),)]
                    + device.getPortNumberList()
                ),
                "Device",
                device.getDeviceAddress(),
                "SerialNumber",
                device.getSerialNumber(),
            )
       except usb1.USBError:
           continue

You may want to restrict try scope to only retrieving the descriptors, and print once you have all the values you need. It could make a difference in code which does not just print, but here it should be equivalent.

kevindawson commented 5 years ago

Thanks @vpelletier

you need to bang your drum about v1.6.6

as you are the only current python package that can supply Serial_Number with out opening a handle

much appreciated, keep up the good work

vpelletier commented 5 years ago

@whitequark

It is tempting to use the API in what appears the easiest possible way

Indeed, and closest-available-method would certainly qualify (foo.getBar() rather than foo.getBoo().getBar()).

I thought a bit more about this, and it now feels natural to me to have:

So I went this way in 023344d3f86d1067a1fc56124cb105cf075f5612 .

whitequark commented 5 years ago

@vpelletier Great, thanks!