python libusb1 : asynchronous TRANSFER_NO_DEVICE status just after successful syncronous transfers

标签 python linux asynchronous libusb-1.0

故事

我正在为科学相机编写驱动程序。它使用 Cypress FX3 USB 外设 Controller 。为了与之通信,我使用 libusb1 for python,特别是模块 usb1。我的操作系统是 Ubuntu 16.04 LTS。

通信有两个步骤:

  • 摄像头已配置。计算机同步发送指令对摄像头进行编程,每条指令后摄像头响​​应一个状态字,同步读取。

  • 拍了一张照片。计算机同步发送一条指令,相机开始传输数据。计算机以异步方式读取此数据。

异步通信在主线程中完成。所以即使通信本身是异步的,操作也是阻塞的。

问题

每次异步传输时我都得到 TRANSFER_NO_DEVICE 状态,这很奇怪,因为我刚刚在配置步骤中与相机通信。我在 Windows 中使用 cypress 库在 C# 中有一个类似的代码并且它工作正常,所以我可以排除相机。此外,在尝试拍照后,部分图像数据出现在 FX3 缓冲区中,我可以使用 cypress 提供的示例应用程序恢复这些数据。

我已经构建了一个最小的示例脚本。注意 configure 和 take_picture 函数:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# StackOverflow.py

import usb1 as usb1    # Libusb, needed to provide a usb context

import GuideCamLib.binary as binary # Handles bytecode formatting
import GuideCamLib.ccd as ccd       # Generates configuration information
import GuideCamLib.usb as usb       # Manages communication

# Camera usb parameters
vid = 0x04B4;
pid = 0x00F1;

read_addr = 0x81;
write_addr = 0x01;

interface = 0;

success = [0x55, 0x55, 0x55, 0x55] + [0]*(512 - 4); # A successful response

# Objects that generate instructions for the camera
ccd0 = ccd.CCD_47_10();
formatter = binary.ByteCode();

def configure(context):
  # Generating bytes to transfer, outputs a list of int lists
  bytecode_lines = ccd0.create_configuration_bytecode(formatter);

  # Opens device
  with usb.Device(vid=vid, pid=pid, context= context) as dev:

    # Opens read / write ports
    port_write = dev.open_port(write_addr);
    port_read = dev.open_port(read_addr);

    print(" Start configuration...")
    # Transfer synchronously
    for line in bytecode_lines:
      written_bytes = port_write.write_sync(line);
      response = port_read.read_sync(512);
      if(response != success):
        raise RuntimeError(" Configuration failed. (" + str(response) + ")");
    print(" End configuration")

def take_picture(context):
  # Generating bytes to transfer, generates a list of ints
  take_photo_bytecode = formatter.seq_take_photo(ccd0.get_take_photo_mode_address());

  # Opens device
  with usb.Device(vid=vid, pid=pid, context= context) as dev:

    # Opens read / write ports
    port_write = dev.open_port(write_addr);
    port_read = dev.open_port(read_addr, 10000); # 10 sec timeout

    # Prepare asynchronous read
    print(" Prepare read")
    with port_read.read_async(512) as data_collector:
      print(" Writing")
      written_bytes = port_write.write_sync(take_photo_bytecode); # Write synchronously
      print(" Reading...")
      recieved_image = data_collector(); # Read asynchronously (but in a blocking manner)

  print " Recieved: " + str(len(recieved_image)) + " bytes.";

with usb1.USBContext() as context:
  print "Configuring camera:"
  configure(context);      # Configure camera
  print "Taking picture:"
  take_picture(context);   # Take picture
  print "Done."

这是用于所需上下文的 GuideCamLib/usb.py。类 _TransferCollector 完成大部分工作,而 _AsyncReader 只是一个具有状态的函数。端口和设备只是辅助类,以减少每次传输中的样板代码:

#!/usr/bin/env python
# -*- coding: UTF-8 -*-
#
# GuideCamLib/usb.py

import usb1 as usb
import six as six

import traceback

# For human-readable printing
transfer_status_dict = \
{ \
  usb.TRANSFER_COMPLETED : "TRANSFER_COMPLETED",
  usb.TRANSFER_ERROR     : "TRANSFER_ERROR",
  usb.TRANSFER_TIMED_OUT : "TRANSFER_TIMED_OUT",
  usb.TRANSFER_CANCELLED : "TRANSFER_CANCELLED",
  usb.TRANSFER_STALL     : "TRANSFER_STALL",
  usb.TRANSFER_NO_DEVICE : "TRANSFER_NO_DEVICE",
  usb.TRANSFER_OVERFLOW  : "TRANSFER_OVERFLOW" \
};

# Callback to accumulate succesive transfer calls
class _AsyncReader:
  def __init__(self):
    self.transfers = [];

  def __call__(self, transfer):
    print "Status: " + transfer_status_dict[transfer.getStatus()]; # Prints the status of the transfer
    if(transfer.getStatus() != usb.TRANSFER_COMPLETED):
      return;
    else:
      self.transfers.append(transfer.getBuffer()[:transfer.getActualLength()]);
      transfer.submit();

# A collector of asyncronous transfer's data.
# Stops collection after port.timeout time of recieving the last transfer.
class _TransferCollector:
  # Prepare data collection
  def __init__(self, transfer_size, pararell_transfers, port):
    self.interface_handle = port.device.dev.claimInterface(port.interface);
    self.reader = _AsyncReader();
    self.port = port;
    transfers = [];

    # Queue transfers
    for ii in range(pararell_transfers):
      transfer = port.device.dev.getTransfer();
      transfer.setBulk(
        port.address,
        transfer_size,
        callback=self.reader,
        timeout=port.timeout );
      transfer.submit();
      transfers.append(transfer);
    self.transfers = transfers;

  def __enter__(self):
    self.interface_handle.__enter__();
    return self;

  def __exit__(self, exception_type, exception_value, traceback):
    self.interface_handle.__exit__(exception_type, exception_value, traceback);

  # Activate data collection
  def __call__(self):
    # Collect tranfers with _AsyncReader while there are active transfers.
    while any(x.isSubmitted() for x in self.transfers):
      try:
        self.port.device.context.handleEvents();
      except usb.USBErrorInterrupted:
        pass;
    return [six.byte2int(d) for data in self.reader.transfers for d in data];

# Port class for creating syncronous / asyncronous transfers
class Port:
  def __init__(self, device, address, timeout = None):
    self.device = device;
    self.address = address;
    self.interface = self.device.interface;
    self.timeout = timeout;
    if(timeout is None):
      self.timeout = 0;

  def read_sync(self, length):
    with self.device.dev.claimInterface(self.interface):
      data = self.device.dev.bulkRead(self.address, length, timeout=self.timeout);
      return [six.byte2int(d) for d in data];

  def write_sync(self, data):
    data = [six.int2byte(d) for d in data];
    with self.device.dev.claimInterface(self.interface):
      return self.device.dev.bulkWrite(self.address, data, timeout=self.timeout);

  # Make asyncronous transfers blocking. Collects data as long as the device
  # sends data more frecuently than self.timeout or all the transfers fails
  def read_async(self, length, pararell_transfers = 32):
    return _TransferCollector(length, pararell_transfers, self);

# Device class for creating ports
class Device:
  def __init__(self, vid = None, pid = None, context = None, interface = 0):

    if(not context):
      self.backend = usb.USBContext();
      context = self.backend.__enter__();

    self.context = context;
    self.interface = interface;

    self.dev = context.openByVendorIDAndProductID(vid, pid, skip_on_error = False);    
    if self.dev is None:
      raise RuntimeError('Device not found');

  def __enter__(self):
    return self;

  def __exit__(self, exception_type, exception_value, traceback):
    if(hasattr(self, "backend")):
      self.backend.__exit__(exception_type, exception_value, traceback);

  def open_port(self, address, timeout = None):
    return Port(self, address, timeout);

脚本输出以下内容,清楚地表明同步传输是成功的,但每个排队的异步传输都失败并返回 NO_DEVICE:

>>> python StackOverflow.py 
Configuring camera:
 Start configuration...
 End configuration
Taking picture:
 Prepare read
 Writing
 Reading...
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Status: TRANSFER_NO_DEVICE
Traceback (most recent call last):
  File "StackOverflow.py", line 70, in <module>
    take_picture(context);   # Take picture
  File "StackOverflow.py", line 62, in take_picture
    recieved_image = data_collector();
  File "/media/jabozzo/Data/user_data/jabozzo/desktop/sigmamin/code/workspace_Python/USB/USB wxglade libusb1/GuideCamLib/usb.py", line 62, in __exit__
    self.interface_handle.__exit__(exception_type, exception_value, traceback);
  File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 1036, in __exit__
    self._handle.releaseInterface(self._interface)
  File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 1171, in releaseInterface
    libusb1.libusb_release_interface(self.__handle, interface),
  File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 121, in mayRaiseUSBError
    raiseUSBError(value)
  File "/home/jabozzo/.local/lib/python2.7/site-packages/usb1.py", line 117, in raiseUSBError
    raise STATUS_TO_EXCEPTION_DICT.get(value, USBError)(value)
usb1.USBErrorNotFound: LIBUSB_ERROR_NOT_FOUND [-5]

更新

我已经更改了 Device 和 Port 类,以便在打开设备时打开接口(interface)。这样接口(interface)只打开(和关闭)一次,与打开的端口数无关:

# Port class for creating syncronous / asyncronous transfers
class Port:
  def __init__(self, device, address, timeout = None):
    self.device = device;
    self.address = address;
    self.timeout = timeout;
    if(timeout is None):
      self.timeout = 0;

  def read_sync(self, length):
    data = self.device.dev.bulkRead(self.address, length, timeout=self.timeout);
    return [six.byte2int(d) for d in data];

  def write_sync(self, data):
    data = [six.int2byte(d) for d in data];
    return self.device.dev.bulkWrite(self.address, data, timeout=self.timeout);

  # Make asyncronous transfers blocking. Collects data as long as the device
  # sends data more frecuently than self.timeout or all the transfers fails
  def read_async(self, length, pararell_transfers = 32):
    return _TransferCollector(length, pararell_transfers, self);

# Device class for creating ports
class Device:
  def __init__(self, vid = None, pid = None, context = None, interface = 0):

    if(not context):
      self.backend = usb.USBContext();
      context = self.backend.__enter__();

    self.context = context;
    self.interface = interface;

    self.dev = context.openByVendorIDAndProductID(vid, pid, skip_on_error = False);    
    if self.dev is None:
      raise RuntimeError('Device not found');

    self.interface_handle = self.dev.claimInterface(self.interface);

  def __enter__(self):
    self.interface_handle.__enter__();
    return self;

  def __exit__(self, exception_type, exception_value, traceback):
    self.interface_handle.__exit__(exception_type, exception_value, traceback);
    if(hasattr(self, "backend")):
      self.backend.__exit__(exception_type, exception_value, traceback);

  def open_port(self, address, timeout = None):
    return Port(self, address, timeout);

我仍然有同样的错误。但是打印显示它在读取准备时失败了:

>>> python StackOverflow.py 
Configuring camera:
 Start configuration...
 End configuration
Taking picture:
 Prepare read
Traceback (most recent call last):
...

我开始怀疑我不需要打开接口(interface)来执行异步传输。

最佳答案

作为dryman指出,我在完成之前释放了上下文(因为我打开了上下文两次)。如果我们在代码提取中内联 read_async 和 write_sync 调用:

print(" Prepare read")
with port_read.read_async(512) as data_collector:
  print(" Writing")
  written_bytes = port_write.write_sync(take_photo_bytecode); # Write synchronously
  print(" Reading...")
  recieved_image = data_collector(); # Read asynchronously (but in a blocking manner)

我们会得到类似下面的伪代码:

print(" Prepare read")
with port_read.claimInterface(0) as ?:
  # Read preparation code
  print(" Writing")
  with port_write.claimInterface(0) as ?:
    written_bytes =  # Write synchronously
  # Here the port_write.claimInterface context has exited,
  # leaving the prepared read transfers in a invalid state.
  print(" Reading...")
  recieved_image =  # Read asynchronously (Fails, out of interface context)

在我的问题更新中,我忘记删除 _TransferCollector 上的接口(interface)声明,所以我遇到了类似的问题。应用问题更新并将 _TransferCollector 定义为:

# A collector of asyncronous transfer's data.
# Stops collection after port.timeout time of recieving the last transfer.
class _TransferCollector:
  # Prepare data collection
  def __init__(self, transfer_size, pararell_transfers, port):
    self.reader = _AsyncReader();
    self.port = port;
    transfers = [];

    # Queue transfers
    for ii in range(pararell_transfers):
      transfer = port.device.dev.getTransfer();
      transfer.setBulk(
        port.address,
        transfer_size,
        callback=self.reader,
        timeout=port.timeout );
      transfer.submit();
      transfers.append(transfer);
    self.transfers = transfers;

  # Activate data collection
  def __call__(self):
    # Collect tranfers with _AsyncReader while there are active transfers.
    while any(x.isSubmitted() for x in self.transfers):
      try:
        self.port.device.context.handleEvents();
      except usb.USBErrorInterrupted:
        pass;
    return [six.byte2int(d) for data in self.reader.transfers for d in data];

解决了这个问题。

请注意,现在必须对调用 read_async 进行一些更改:

# Prepare asynchronous read
print(" Prepare read")
data_collector = port_read.read_async(512):
print(" Writing")
written_bytes = port_write.write_sync(take_photo_bytecode); # Write synchronously
print(" Reading...")
recieved_image = data_collector(); # Read asynchronously (but in a blocking manner)


print " Recieved: " + str(len(recieved_image)) + " bytes.";

关于 python libusb1 : asynchronous TRANSFER_NO_DEVICE status just after successful syncronous transfers,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38779019/

相关文章:

node.js - 在 Node.js 7.6+ 中是否有任何可能的方法可以使用内置的等待而无需异步?

javascript - Protractor 超时 - "Failed: Timed out waiting for asynchronous Angular tasks to finish after 11 seconds."

javascript - 从异步请求循环中获取值

python - ImportError : No module named concurrent. futures.process

python - 显示随机选择(Python)

linux - 我可以使用 openJDK 而不是 Oracle JDK 安装 RubyMine 5 吗?

linux - 禁用调度程序以减少自旋锁上的 CPU 时间

python - 如何将字典与相同的键结合起来?

datetime.now() 的 Python 解析

c++ - popen 上的死锁