PyUSB:从 USB 设备读取

PyUSB: reading from a USB device

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

通过 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 commands/parameters 或是否缺少其他驱动程序或是否与硬件有关,Win10 或设备硬件?感谢您提供有关如何找出问题所在的提示。

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

代码:

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# 时崩溃了。他将联系下周返回的开发商。好吧,到目前为止,我似乎没有机会使用这个特定的设备和可用的文档来达到 运行 :-(.

msg = '*IDN?'

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

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

OWON 设备默认不启用 SCPI。根据manual的第3页,您需要发送:SDSLSCPI#命令来切换到SCPI模式。

一旦您在 *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作为操作超时。值以毫秒为单位。

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

First note that @igrinis posted a video showing what you want to reach.

(As stated by @igrinis:) For the second value in read(...), you are right in theory. Good thing is that practically often enough you can request way longer answers. So try e.g. requesting 256 bytes and look if that fixes your current code.

If that does not solve your issue:

You can try to have a second PC/Laptop around with software from e.g. the manufacturer that is capable of communicating with the device, and use Wireshark (with USBPcap installed) to read the device communication. The USB bulk data transmitted and received is written in Wiresharks "Leftover Capture Data" field. By looking at that you can compare what your script sends and how it should look like to spot mistakes. You can add it as a column to the list of packets by right-clicking and selecting "Apply as column". Your problem might e.g. be the encoding of your command to big or little endian.

Documentation for PyUSB:

[Update] Added hints to a great comment that already gave some of the answers and more.

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

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

  1. PyUSB 似乎一切正常。
  2. 供应商提供了过时且错误的文档。非常希望他们能尽快更新主页上的文档。
  3. 发送命令:SDSLSCPI#不是必须输入SCPI-mode(但实际上会导致crash/restart)
  4. 例如::CHAN1:SCAL 10v是错误的,它必须是:CH1:SCALe 10v(显然命令不能被缩写,虽然在文档中提到:CH1:SCAL 10v 也应该有效。)
  5. 手册中缺少获取数据的基本命令 :DATA:WAVE:SCREen:CH1?

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

以下是我预期的 vendor/manufacturer 的最少代码。但相反,我浪费了很多时间来调试他们的文档。 然而,仍然有一些奇怪的事情发生,例如似乎只有事先询问 header 才能获得数据。但是,好吧,这不是原题的主题。

代码:

### 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)