pyvisa / pyvisa-py

A pure python PyVISA backend
https://pyvisa-py.readthedocs.io
MIT License
282 stars 120 forks source link

On macOS, opening a USB0::INSTR device often results in usb.core.USBError: [Errno 19] No such device #308

Open sgoadhouse opened 2 years ago

sgoadhouse commented 2 years ago

Trying to open a PyVISA connection to pyvisa-py to a USB device. Sometimes it works and other times it fails. The USB device is connected to the MacBook Pro through a USB Hub, if that matters.

I stripped down my code to make it more reproducible and testPYVISA.py is attached. My resource string is: USB0::0x1AB1::0x0E11::DP8B153600499::INSTR

If I run python testPYVISA.py and it works, I see:

PyVISA Resources Found:
   ASRL/dev/cu.Bluetooth-Incoming-Port::INSTR
   ASRL/dev/cu.usbmodemHIDPC1::INSTR
   ASRL/dev/cu.usbserial-FTE1XGBL::INSTR
   ASRL/dev/cu.usbmodem1421401::INSTR
   USB0::6833::3601::DP8B153600499::0::INSTR
opening resource: USB0::0x1AB1::0x0E11::DP8B153600499::INSTR
RIGOL TECHNOLOGIES,DP832A,DP8B153600499,00.01.16

When it fails, which is quite often, I get:

PyVISA Resources Found:
   ASRL/dev/cu.Bluetooth-Incoming-Port::INSTR
   ASRL/dev/cu.usbmodemHIDPC1::INSTR
   ASRL/dev/cu.usbserial-FTE1XGBL::INSTR
   ASRL/dev/cu.usbmodem1421401::INSTR
   USB0::6833::3601::DP8B153600499::0::INSTR
opening resource: USB0::0x1AB1::0x0E11::DP8B153600499::INSTR
Traceback (most recent call last):
  File "testPYVISA.py", line 7, in <module>
    inst = rm.open_resource(resource)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/pyvisa/highlevel.py", line 3304, in open_resource
    res.open(access_mode, open_timeout)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/pyvisa/resources/resource.py", line 298, in open
    self._resource_name, access_mode, open_timeout
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/pyvisa/highlevel.py", line 3232, in open_bare_resource
    return self.visalib.open(self.session, resource_name, access_mode, open_timeout)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/pyvisa_py/highlevel.py", line 167, in open
    sess = cls(session, resource_name, parsed, open_timeout)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/pyvisa_py/sessions.py", line 323, in __init__
    self.after_parsing()
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/pyvisa_py/usb.py", line 84, in after_parsing
    self.parsed.serial_number,
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/pyvisa_py/protocols/usbtmc.py", line 293, in __init__
    self.usb_dev.set_configuration()
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/usb/core.py", line 915, in set_configuration
    self._ctx.managed_set_configuration(self, configuration)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/usb/core.py", line 113, in wrapper
    return f(self, *args, **kwargs)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/usb/core.py", line 159, in managed_set_configuration
    self.backend.set_configuration(self.handle, cfg.bConfigurationValue)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/usb/_debug.py", line 62, in do_trace
    return f(*args, **named_args)
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/usb/backend/libusb1.py", line 812, in set_configuration
    _check(self.lib.libusb_set_configuration(dev_handle.handle, config_value))
  File "/Users/sdg6h/miniconda3/lib/python3.7/site-packages/usb/backend/libusb1.py", line 604, in _check
    raise USBError(_strerror(ret), ret, _libusb_errno[ret])
usb.core.USBError: [Errno 19] No such device (it may have been disconnected)

To check if it may be an issue with my PyUSB/libusb1 installation or something else related to USB, I pulled out most of the code from __init__ from pyvisa_py/protocols/usbtmc.py and created testUSB2.py. I never get an error when running this script.

If I set the environment variable PYUSB_DEBUG to 'debug', then python testPYVISA.py often works (~90% of the time). If I set PYUSB_DEBUG to '', then python testPYVISA.py works about 40% of the time.

It seems as if the various bits of code called before the failing self.usb_dev.set_configuration() may be accessing USB to check things but the OS has not completed closing the USB connection before self.usb_dev.set_configuration() is called resulting in the message. This is my deduction from seeing that a simple self.usb_dev.set_configuration() works fine and that adding a bunch of debug output which slows things down when opening the device through pyvisa-py works most of the time.

To Reproduce

Steps to reproduce the behavior:

  1. Run python testPYVISA.py
  2. See error often

testPYVISA.py testUSB2.py

Output of pyvisa-info

Machine Details:
   Platform ID:    Darwin-20.6.0-x86_64-i386-64bit
   Processor:      i386

Python:
   Implementation: CPython
   Executable:     /Users/sdg6h/miniconda3/bin/python
   Version:        3.7.4
   Compiler:       Clang 4.0.1 (tags/RELEASE_401/final)
   Bits:           64bit
   Build:          Aug 13 2019 15:17:50 (#default)
   Unicode:        UCS4

PyVISA Version: 1.11.3

Backends:
   ivi:
      Version: 1.11.3 (bundled with PyVISA)
      Binary library: Not found
   py:
      Version: 0.5.2.dev60+g500d1de
      ASRL INSTR: Available via PySerial (3.5)
      USB INSTR: Available via PyUSB (1.2.1). Backend: libusb1
      USB RAW: Available via PyUSB (1.2.1). Backend: libusb1
      TCPIP INSTR: Available 
      TCPIP SOCKET: Available 
      GPIB INSTR:
         Please install linux-gpib (Linux) or gpib-ctypes (Windows, Linux) to use this resource type. Note that installing gpib-ctypes will give you access to a broader range of funcionality.
         No module named 'gpib'
MatthieuDartiailh commented 2 years ago

I have seen USB hubs sometimes cause havoc in VISA over USB (each time the user moved the mouse connected on the hub, the connection would drop). So if you can test without, I would suggest you try that first.

chinaman110b commented 2 years ago

I have a similar issue here. I use the USB connector directly without USB hubs.

This issue is happening intermittently, and 70% of the time open_resource will give me the error.

I found that if I reseat the USB plug (same USB insert and same orientation on my mac), the issue disappeared.

This is like black magic. Keysight 34460A is the instrument that I am connecting to.

This is the error code that open_resource gave me:

---------------------------------------------------------------------------
USBError                                  Traceback (most recent call last)
Input In [2], in <cell line: 5>()
      3 print(USB_ID)
      4 # inst = rm.open_resource('USB0::10893::5633::MY57102284::0::INSTR', read_termination='\r', send_end=False)
----> 5 inst = rm.open_resource('USB0::10893::5633::MY57102284::0::INSTR')

File /usr/local/lib/python3.10/site-packages/pyvisa/highlevel.py:3284, in ResourceManager.open_resource(self, resource_name, access_mode, open_timeout, resource_pyclass, **kwargs)
   3278     if not present:
   3279         raise ValueError(
   3280             "%r is not a valid attribute for type %s"
   3281             % (key, res.__class__.__name__)
   3282         )
-> 3284 res.open(access_mode, open_timeout)
   3286 for key, value in kwargs.items():
   3287     setattr(res, key, value)

File /usr/local/lib/python3.10/site-packages/pyvisa/resources/resource.py:278, in Resource.open(self, access_mode, open_timeout)
    274 logger.debug("%s - opening ...", self._resource_name, extra=self._logging_extra)
    275 with self._resource_manager.ignore_warning(
    276     constants.StatusCode.success_device_not_present
    277 ):
--> 278     self.session, status = self._resource_manager.open_bare_resource(
    279         self._resource_name, access_mode, open_timeout
    280     )
    282     if status == constants.StatusCode.success_device_not_present:
    283         # The device was not ready when we opened the session.
    284         # Now it gets five seconds more to become ready.
    285         # Every 0.1 seconds we probe it with viClear.
    286         start_time = time.time()

File /usr/local/lib/python3.10/site-packages/pyvisa/highlevel.py:3209, in ResourceManager.open_bare_resource(self, resource_name, access_mode, open_timeout)
   3180 def open_bare_resource(
   3181     self,
   3182     resource_name: str,
   3183     access_mode: constants.AccessModes = constants.AccessModes.no_lock,
   3184     open_timeout: int = constants.VI_TMO_IMMEDIATE,
   3185 ) -> Tuple[VISASession, StatusCode]:
   3186     """Open the specified resource without wrapping into a class.
   3187 
   3188     Parameters
   (...)
   3207 
   3208     """
-> 3209     return self.visalib.open(self.session, resource_name, access_mode, open_timeout)

File /usr/local/lib/python3.10/site-packages/pyvisa_py/highlevel.py:167, in PyVisaLibrary.open(self, session, resource_name, access_mode, open_timeout)
    158     return (
    159         VISASession(0),
    160         self.handle_return_value(None, StatusCode.error_invalid_resource_name),
    161     )
    163 cls = sessions.Session.get_session_class(
    164     parsed.interface_type_const, parsed.resource_class
    165 )
--> 167 sess = cls(session, resource_name, parsed, open_timeout)
    169 return self._register(sess), StatusCode.success

File /usr/local/lib/python3.10/site-packages/pyvisa_py/sessions.py:325, in Session.__init__(self, resource_manager_session, resource_name, parsed, open_timeout)
    322 default_timeout = attributes.AttributesByID[attr].default
    323 self.set_attribute(attr, default_timeout)
--> 325 self.after_parsing()

File /usr/local/lib/python3.10/site-packages/pyvisa_py/usb.py:81, in USBSession.after_parsing(self)
     80 def after_parsing(self) -> None:
---> 81     self.interface = self._intf_cls(
     82         int(self.parsed.manufacturer_id, 0),
     83         int(self.parsed.model_code, 0),
     84         self.parsed.serial_number,
     85     )
     87     for name in ("SEND_END_EN", "SUPPRESS_END_EN", "TERMCHAR", "TERMCHAR_EN"):
     88         attribute = getattr(constants, "VI_ATTR_" + name)

File /usr/local/lib/python3.10/site-packages/pyvisa_py/protocols/usbtmc.py:293, in USBTMC.__init__(self, vendor, product, serial_number, **kwargs)
    288 self.usb_intr_in = find_endpoint(
    289     self.usb_intf, usb.ENDPOINT_IN, usb.ENDPOINT_TYPE_INTERRUPT
    290 )
    292 self.usb_dev.reset()
--> 293 self.usb_dev.set_configuration()
    295 time.sleep(0.01)
    297 self._capabilities = self._get_capabilities()

File /usr/local/lib/python3.10/site-packages/usb/core.py:915, in Device.set_configuration(self, configuration)
    906 def set_configuration(self, configuration = None):
    907     r"""Set the active configuration.
    908 
    909     The configuration parameter is the bConfigurationValue field of the
   (...)
    913     without arguments is enough to get the device ready.
    914     """
--> 915     self._ctx.managed_set_configuration(self, configuration)

File /usr/local/lib/python3.10/site-packages/usb/core.py:113, in synchronized.<locals>.wrapper(self, *args, **kwargs)
    111 try:
    112     self.lock.acquire()
--> 113     return f(self, *args, **kwargs)
    114 finally:
    115     self.lock.release()

File /usr/local/lib/python3.10/site-packages/usb/core.py:159, in _ResourceManager.managed_set_configuration(self, device, config)
    156     raise ValueError("Invalid configuration " + str(config))
    158 self.managed_open()
--> 159 self.backend.set_configuration(self.handle, cfg.bConfigurationValue)
    161 # cache the index instead of the object to avoid cyclic references
    162 # of the device and Configuration (Device tracks the _ResourceManager,
    163 # which tracks the Configuration, which tracks the Device)
    164 self._active_cfg_index = cfg.index

File /usr/local/lib/python3.10/site-packages/usb/backend/libusb1.py:812, in _LibUSB.set_configuration(self, dev_handle, config_value)
    810 @methodtrace(_logger)
    811 def set_configuration(self, dev_handle, config_value):
--> 812     _check(self.lib.libusb_set_configuration(dev_handle.handle, config_value))

File /usr/local/lib/python3.10/site-packages/usb/backend/libusb1.py:604, in _check(ret)
    602         raise USBTimeoutError(_strerror(ret), ret, _libusb_errno[ret])
    603     else:
--> 604         raise USBError(_strerror(ret), ret, _libusb_errno[ret])
    606 return ret

USBError: [Errno 19] No such device (it may have been disconnected)

In terminal, after pyvisa-info, everything looks good:

Machine Details:
   Platform ID:    macOS-12.5.1-x86_64-i386-64bit
   Processor:      i386

Python:
   Implementation: CPython
   Executable:     /usr/local/opt/python@3.10/bin/python3.10
   Version:        3.10.6
   Compiler:       Clang 13.1.6 (clang-1316.0.21.2.5)
   Bits:           64bit
   Build:          Aug 30 2022 05:12:36 (#main)
   Unicode:        UCS4

PyVISA Version: 1.12.0

Backends:
   ivi:
      Version: 1.12.0 (bundled with PyVISA)
      Binary library: Not found
   py:
      Version: 0.5.3
      ASRL INSTR: Available via PySerial (3.5)
      USB INSTR: Available via PyUSB (1.2.1). Backend: libusb1
      USB RAW: Available via PyUSB (1.2.1). Backend: libusb1
      TCPIP INSTR: Available 
      TCPIP SOCKET: Available 
      GPIB INSTR:
         Please install linux-gpib (Linux) or gpib-ctypes (Windows, Linux) to use this resource type. Note that installing gpib-ctypes will give you access to a broader range of funcionality.
         No module named 'gpib'
kevoc commented 1 year ago

I have a workaround for this bug. There's a device reset() issued immediately before set_configuration() in pyvisa_py/protocols/usbtmc.py. It might be causing the device to momentarily drop off the bus while macOS re-enumerates it, but this is just speculation on my part. Anyway, by delaying the configuration call by half a second, it fixes this issue for me. I hope this works for the rest of you.

import usb
import time

old_config = usb.core.Device.set_configuration

def new_with_delay(*args, **kwargs):
    time.sleep(0.5)
    old_config(*args, **kwargs)

usb.core.Device.set_configuration = new_with_delay