使用 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 在处理array
s 时肯定朝着正确的方向前进,但是有一个更好的转换选项。以下是在 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。这些应该是相对于彼此的指示性结果,即使不是绝对的。此外,这显然不能说明当时使用 bytearray
与 bytes
的性能。
【讨论】:
我相信 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() )
【讨论】:
您可能想查看array
的tobytes
方法,考虑到对象的内部知识,它可能能够更快地工作。至少值得一个基准。
我还应该指出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 设备的主要内容,如果未能解决你的问题,请参考以下文章