PyUSB:从 USB 设备读取

Posted

技术标签:

【中文标题】PyUSB:从 USB 设备读取【英文标题】:PyUSB: reading from a USB device 【发布时间】:2020-03-25 01:46:00 【问题描述】:

这是一个更新和缩短的问题。

通过PyUSB 与 USB 设备通信应该很容易。所以,我试图在 Win10 下使用 PyUSB 从 USB 设备(示波器)读取数据。显然,自从找到设备以来,USB 驱动程序(libusb-win32 v1.2.6.0)已正确安装,我收到了来自print(dev) 的一些回复(见下文)。 从这里我可以看到输出端点地址是0x3,输入端点地址是0x81

根据Oscilloscope manual,我应该将:SDSLSCPI# 发送到设备以将其设置为SCPI 模式并且应该得到响应':SCPION'。 但是,当发送:SDSLSCPI# 时,示波器的监视器会冻结并重新启动。

如果我发送*IDN?,我应该会收到回复,P1337,1842237,V2.4.0->。但前提是设备已经处于 SCPI 模式。显然,它不是,我得到一个超时错误(见下文)。

那么,我在这里做错了什么? 我在PyUSB tutorial 中缺少什么信息。 我使用了错误的 PyUSB 命令/参数,还是缺少其他驱动程序,还是与硬件有关,无论是 Win10 还是设备硬件?感谢您提供有关如何找出问题所在的提示。

顺便问一下,dev.read(0x81,7) 中的第二个值是多少?要读取的字节数?好吧,通常我不知道设备会发送多少字节。我期待一个命令在超时时间内读取到换行符或其他终止符。我在哪里可以找到有关 PyUSB 的“万无一失”的文档、教程和示例?

代码:

import usb.core
import usb.util

dev = usb.core.find(idVendor=0x5345, idProduct=0x1234)
if dev is None:
    raise ValueError('Device is not found')
# device is found :-)
print(dev)

dev.set_configuration()

msg = ':SDSLSCPI#'
print("Write:", msg, dev.write(3,msg))

print("Read:", dev.read(0x81,7))

来自print(dev)的输出:

DEVICE ID 5345:1234 on Bus 000 Address 001 =================
 bLength                :   0x12 (18 bytes)
 bDescriptorType        :    0x1 Device
 bcdUSB                 :  0x200 USB 2.0
 bDeviceClass           :    0x0 Specified at interface
 bDeviceSubClass        :    0x0
 bDeviceProtocol        :    0x0
 bMaxPacketSize0        :   0x40 (64 bytes)
 idVendor               : 0x5345
 idProduct              : 0x1234
 bcdDevice              :  0x294 Device 2.94
 iManufacturer          :    0x1 System CPU
 iProduct               :    0x2 Oscilloscope
 iSerialNumber          :    0x3 SERIAL
 bNumConfigurations     :    0x1
  CONFIGURATION 1: 500 mA ==================================
   bLength              :    0x9 (9 bytes)
   bDescriptorType      :    0x2 Configuration
   wTotalLength         :   0x20 (32 bytes)
   bNumInterfaces       :    0x1
   bConfigurationValue  :    0x1
   iConfiguration       :    0x5 Bulk Data Configuration
   bmAttributes         :   0xc0 Self Powered
   bMaxPower            :   0xfa (500 mA)
    INTERFACE 0: Physical ==================================
     bLength            :    0x9 (9 bytes)
     bDescriptorType    :    0x4 Interface
     bInterfaceNumber   :    0x0
     bAlternateSetting  :    0x0
     bNumEndpoints      :    0x2
     bInterfaceClass    :    0x5 Physical
     bInterfaceSubClass :    0x6
     bInterfaceProtocol :   0x50
     iInterface         :    0x4 Bulk Data Interface
      ENDPOINT 0x81: Bulk IN ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :   0x81 IN
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :  0x200 (512 bytes)
       bInterval        :    0x0
      ENDPOINT 0x3: Bulk OUT ===============================
       bLength          :    0x7 (7 bytes)
       bDescriptorType  :    0x5 Endpoint
       bEndpointAddress :    0x3 OUT
       bmAttributes     :    0x2 Bulk
       wMaxPacketSize   :  0x200 (512 bytes)
       bInterval        :    0x0

错误信息:

Traceback (most recent call last):
  File "Osci.py", line 15, in <module>
    print("Read:", dev.read(0x81,7))
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\core.py", line 988, in read
    self.__get_timeout(timeout))
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\backend\libusb0.py", line 542, in bulk_read
    timeout)
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\backend\libusb0.py", line 627, in __read
    timeout
  File "C:\Users\Test\Programs\Python3.7.4\lib\site-packages\usb\backend\libusb0.py", line 431, in _check
    raise USBError(errmsg, ret)
usb.core.USBError: [Errno None] b'libusb0-dll:err [_usb_reap_async] timeout error\n'

更新:

我收到了供应商的回复。他确认示波器(或至少这个特定系列)在发送命令:SDSLSCPI# 时崩溃。他将联系下周回来的开发商。好的,到目前为止,我似乎没有机会让它与这个特定的设备和可用的文档一起运行:-(。

【问题讨论】:

我认为你混合了端点。尝试写入 0x81 并从 0x03 读取。 好吧,我很确定地址是正确的。与此同时,我可以通过发送*IDN? 获得序列号,但是,其他 SCPI 命令仍然会产生超时错误。我怀疑实际上不需要发送:SDSLSCPI#。整件事很烦人也很奇怪...... 如果您收到任何响应,则说明通信通道设置正确,pyUSB 没有问题。所以它正在寻找设备的通用语言;)你确定它支持 SCPI 模式吗?如果设备不支持 SCPI,文档中的 BC 将不会返回任何内容。 我被告知该设备可以理解 SCPI。至少它理解*IDN?。但根据文档,其他列出的命令也应该给出一些响应。我需要下周再检查一次。 【参考方案1】:

我想没有机会回答这个问题,除非有人已经遇到过同样的问题。 对于花时间提出帮助建议的所有人(@Alex P.、@Turbo J、@igrinis、@2xB),我深表歉意。

我的发现:(希望对其他人有用):

    PyUSB 似乎一切正常。 供应商提供了过时和错误的文档。我非常希望他们能尽快更新主页上的文档。 发送命令 :SDSLSCPI# 不是进入 SCPI 模式所必需的(但实际上会导致崩溃/重新启动) 例如::CHAN1:SCAL 10v 是错误的,它必须是 :CH1:SCALe 10v(命令显然不能缩写,尽管在文档中提到 :CH1:SCAL 10v 也应该工作。) 手册中缺少获取数据:DATA:WAVE:SCREen:CH1? 的基本命令。

它对我的工作方式(到目前为止):

以下是我期望供应商/制造商提供的最少代码。但相反,我浪费了大量时间调试他们的文档。 但是,仍然发生了一些奇怪的事情,例如似乎只有在您事先要求标头时才能获得数据。但是,这不是原始问题的主题。

代码:

### read data from a Peaktech 1337 Oscilloscope (OWON)
import usb.core
import usb.util

dev = usb.core.find(idVendor=0x5345, idProduct=0x1234)

if dev is None:
    raise ValueError('Device not found')
else:
    print(dev)
    dev.set_configuration()

def send(cmd):
    # address taken from results of print(dev):   ENDPOINT 0x3: Bulk OUT
    dev.write(3,cmd)
    # address taken from results of print(dev):   ENDPOINT 0x81: Bulk IN
    result = (dev.read(0x81,100000,1000))
    return result

def get_id():
    return send('*IDN?').tobytes().decode('utf-8')

def get_data(ch):
    # first 4 bytes indicate the number of data bytes following
    rawdata = send(':DATA:WAVE:SCREen:CH?'.format(ch))
    data = []
    for idx in range(4,len(rawdata),2):
        # take 2 bytes and convert them to signed integer using "little-endian"
        point = int().from_bytes([rawdata[idx], rawdata[idx+1]],'little',signed=True)
        data.append(point/4096)  # data as 12 bit
    return data

def get_header():
    # first 4 bytes indicate the number of data bytes following
    header = send(':DATA:WAVE:SCREen:HEAD?')
    header = header[4:].tobytes().decode('utf-8')
    return header

def save_data(ffname,data):
    f = open(ffname,'w')
    f.write('\n'.join(map(str, data)))
    f.close()

print(get_id())
header = get_header()
data = get_data(1)
save_data('Osci.dat',data)
### end of code

结果:(使用 gnuplot)

【讨论】:

很好地解决了这个问题。我知道很多 SCPI 仪器都有像你描述的那样非常糟糕的错误。【参考方案2】:

msg = '*IDN?'

这不是一个完整的 SCPI 命令:它缺少末尾的换行符 \n

这也是设备无法通过 USB 发送答案的原因。

【讨论】:

感谢您的提示。啊!!!太愚蠢了,在文档中有一个msg='test' 的示例,这给我的印象是 PyUSB 模块关心行尾字符。我只能在下周进行测试,如果可行,我会投票并接受。 对不起,没有。简单地添加 \n 不会改变任何超时错误。设备制造商关于通信的文档也很差。 显然,PyUSB 会处理结束字符。【参考方案3】:

一旦您收到设备对*IDN? 查询的响应,您就可以开始了。这是 SCPI ;)

尝试发送:CHAN1:SCAL 10v,并观看显示。应该将通道1的垂直刻度改为10V/div。

观看此video,它将帮助您掌握。

关于read() 参数的问题。引用 PyUSB 源代码:

def read(self, endpoint, size_or_buffer, timeout = None):
    r"""Read data from the endpoint.
    This method is used to receive data from the device. The endpoint
    parameter corresponds to the bEndpointAddress member whose endpoint
    you want to communicate with. The size_or_buffer parameter either
    tells how many bytes you want to read or supplies the buffer to
    receive the data (it *must* be an object of the type array).
    The timeout is specified in miliseconds.
    If the size_or_buffer parameter is the number of bytes to read, the
    method returns an array object with the data read. If the
    size_or_buffer parameter is an array object, it returns the number
    of bytes actually read.
    """

省略超时时,使用Device.default_timeout property作为操作超时。值以毫秒为单位。

如果您将缓冲区大小设置得足够大,您将只能获得实际读取的字节数。所以你的期望是正确的。

【讨论】:

感谢您的建议。不,发送:CHAN1:SCAL 10v 或任何其他值不会发生任何事情。我收到了卖家的回复。我会更新问题。 抱歉,供应商提供的过时文档是问题所在。看我的回答。顺便说一句,我不明白为什么人们要为应该放在 2 页纸上的东西制作(或观看)一个 1 小时的视频。【参考方案4】:

首先请注意,@igrinis 发布了一段视频,展示了您想要达到的目标。

(正如@igrinis 所说:)对于read(...) 中的第二个值,理论上你是对的。好消息是,实际上你可以要求更长的答案。所以尝试例如请求 256 字节并查看是否修复了您当前的代码。

如果这不能解决您的问题:

您可以尝试使用来自例如的软件的第二台 PC/笔记本电脑。能够与设备通信的制造商,并使用 Wireshark(安装了 USBPcap)读取设备通信。发送和接收的 USB 批量数据写入 Wiresharks “Leftover Capture Data”字段。通过查看,您可以比较脚本发送的内容以及发现错误的外观。您可以通过右键单击并选择“应用为列”将其作为列添加到数据包列表中。您的问题可能例如将您的命令编码为大端或小端。

PyUSB 文档:

主要是 Python Docstring,所以在每个对象/函数上调用 help(...) 你从 PyUSB 获得。或者,在 PyUSB 源代码中搜索您要读取其 Docstring 的每个函数和对象。

https://github.com/pyusb/pyusb/blob/master/README.rst

https://github.com/pyusb/pyusb/blob/master/docs/faq.rst

https://github.com/pyusb/pyusb/blob/master/docs/tutorial.rst

https://github.com/pyusb/pyusb/wiki

[更新] 为已经给出了一些答案的精彩评论添加了提示等等。

【讨论】:

感谢您的回复。我收到了卖家的回复。我想问题是另一个问题。我会更新问题。 @theozh 供应商的回复听起来很棒。我会顺便说一句。不要经常编辑一个问题,因为这里已经有其他人可能想要在上下文中搜索和阅读的当前/旧问题的答案。如果您有新问题,只需创建一个新问题。这就是像 *** 这样的问答系统相对于基于论坛线程的系统的优势^^ @theozh 另外,不要忘记为有帮助的答案投票,并对没有帮助的答案发表评论,如果您的原始问题得到了回答,请投票选出正确的最佳答案一。祝你好运! 似乎我必须调试供应商的文档。这就是为什么给出的善意的答案实际上无助于解决问题。【参考方案5】:

默认情况下,OWON 设备上未启用 SCPI。根据manual的page#3,您需要发送:SDSLSCPI#命令切换到SCPI模式。

【讨论】:

这不会改变任何东西。问题出在ret = dev.read(0x81, 23, 1000) 行中。它给了usb.core.USBError: [Errno None] b'libusb0-dll:err [_usb_reap_async] timeout error\n'。似乎没有数据可读取。 抱歉,供应商提供的过时/错误文档是问题所在。看我的回答。

以上是关于PyUSB:从 USB 设备读取的主要内容,如果未能解决你的问题,请参考以下文章

PyUsb 无法识别我的 USB 设备,而我的 PC 可以

利用pyusb来查询当前所以usb设备

windodws pyusb hub端口对应连接的usb设备

如何用Python读取USB操纵杆的输入

Windows 7 上的 Pyusb 找不到任何设备

PyUSB ValueError:没有可用的后端