使用 PyUSB(不是 HIDAPI)读写 HID 设备

Posted

技术标签:

【中文标题】使用 PyUSB(不是 HIDAPI)读写 HID 设备【英文标题】:Read and Write HID device with PyUSB (not HIDAPI) 【发布时间】:2021-09-07 02:44:35 【问题描述】:

我想在pysub中实现python hidapi的读写调用。

使用 python hidapi 的示例代码如下所示:

import hid

hdev = hid.device()
h = hdev.open_path( path )

h.write( send_buffer )

res = h.read( 64 )    
receive_buffer = bytearray( res )

我遇到的主要问题是 python hidapi read() 返回一个整数列表(从硬件接收的缓冲区中的每个字节都有一个 python int),我需要将缓冲区作为字节并忠实于收到了什么。(*)

次要问题是打开、读取和写入是我唯一需要的东西,我需要让系统尽可能轻便。因此我想避免额外的依赖。

(*) bytearray() 在这种情况下不是一个好的解决方案,原因超出了这个问题的范围。

【问题讨论】:

澄清一下,整数列表不适合您的用例的原因是什么?您可以迭代它们或对它们进行随机访问,就像它是一个字节字符串一样。您是否将它传递给其他特别需要字节字符串的东西? @Kemp,我将它传递给需要发送数据的东西。它来自数据采集硬件,可以是来自成像传感器和波形记录器的两个字节整数或浮点数。必须将其转换回其原始格式的问题与性能和吞吐量有关。 阅读 pyUSB 的文档时,它似乎会从 read 调用中返回数组,因此您将遇到与 hidapi 相同的问题。 @kemp Pyusb,对于读取,返回传输类型的数组对象。如果传输类型是字节,它是一个字节数组。数据忠实​​于发送的内容,我们没有将值作为整数转换回以字节为单位的实际结构的问题。注意 read 调用中的第二个参数是字节数。 @kemp 请参阅下面的答案。它可以工作,并且数据确实是一个字节数组。 【参考方案1】:

我已经进行了一些快速基准测试,似乎DrM's answer 在处理arrays 时肯定朝着正确的方向前进,但是有一个更好的转换选项。以下是在 64 字节数据缓冲区上运行 1000 万次迭代的结果。

使用

data_list = [30] * 64
data_array = array('B', data_list)

我在几秒钟内得到了以下运行时间:

Technique Time (seconds, 10 million iterations)
bytearray(data_list) 12.7
bytearray(data_array) 3.0
data_array.tobytes() 2.0
struct.pack('%uB' % len(data_list), *data_list) 18.6
struct.pack('%uB' % len(data_array), *data_array) 22.5

看来使用array.tobytes方法最快,其次是使用array作为参数调用bytearray

显然,我在每次迭代中都重用了相同的缓冲区,可能还有其他不切实际的因素,所以 YMMV。这些应该是相对于彼此的指示性结果,即使不是绝对的。此外,这显然不能说明当时使用 bytearraybytes 的性能。

【讨论】:

我相信 pysub read() 返回类型数组。那会是上面第二种情况的data_array吗?如果是这样,我可以简单地做 read().tobytes() 吗?很有趣,我想我会试试的。这将节省 33% 的时间,当然值得。【参考方案2】:

这是一个似乎可行的最小代码示例。 HID 设备是一个 Teensy 板,它使用其 RAWHID.recv() 和 RAWHID.send() 与主机交换文本和二进制文件。

#!/usr/bin/python

import usb.core
import usb.util

dev = usb.core.find(idVendor=0x16C0, idProduct=0x0486)
    
try:
    dev.reset()
except Exception as e:
    print( 'reset', e)

if dev.is_kernel_driver_active(0):
    print( 'detaching kernel driver')
    dev.detach_kernel_driver(0)

endpoint_in = dev[0][(0,0)][0]
endpoint_out = dev[0][(0,0)][1]

# Send a command to the Teensy
endpoint_out.write( "version".encode() + bytes([0]) )

# Read the response, an array of byte, .tobytes() gives us a bytearray.
buffer = dev.read(endpoint_in.bEndpointAddress, 64, 1000).tobytes()

# Decode and print the zero terminated string response
n = buffer.index(0)
print( buffer[:n].decode() )

【讨论】:

您可能想查看arraytobytes 方法,考虑到对象的内部知识,它可能能够更快地工作。至少值得一个基准。 我还应该指出bytearray 是一个可变对象,与bytes 相比会有一些开销。可能值得看看如何简单地执行 struct.pack('%uB' % len(buffer), *buffer) 来获得 bytes 执行。 我的好奇心战胜了我,我已将我的基准测试结果发布为an answer 以供参考。 我会注意到这个解决方案可能有效——但严格来说不是 HID。在这里,您可以直接进行准系统 USB 传输(批量或中断,具体取决于设备在其端点描述符中声明的内容)。这比实际 HID 低 1 级。以此类推,这就像通过打开 TCP 套接字并手动格式化并手动处理所有标头、状态行和 GET/POST 方法来执行 HTTP 请求。这种方法本身没有问题;请注意,这只是 PyUSB 的使用——还不是 HID,上面还有一层细节。 @ulidtko 是的,这完全跳过了隐藏,这就是我想要的。对于这个用例,HID 不贡献任何东西,只会添加依赖项和更深的调用堆栈。

以上是关于使用 PyUSB(不是 HIDAPI)读写 HID 设备的主要内容,如果未能解决你的问题,请参考以下文章

Python PyUSB HID 功能报告

CreateFile 无法在 Windows 中打开 HID 设备

需要使用hid库获取账本nano的设备的唯一标识符

Qt usb通讯

QtUSBHID—— 数据

USB_HID读写上位机VC++