基于Python的HIDAPI输出解析及USB扫码枪数据读取问题
解析USB HID扫码枪数据:Python HIDAPI在OS X上的常见疑问
背景
我目前在OS X系统中尝试用Python的HIDAPI读取USB HID扫码枪的字符串,已经完成了基础适配:成功调用h.open(),能打印厂商和产品字符串,也通过EVDEV验证了扫描码。现在的核心问题是如何解析HIDAPI返回的数据并映射为ASCII字符串。
参考示例代码
from __future__ import print_function import hid import time print("Opening the device") h = hid.device() h.open(1118, 2048) # A Microsoft wireless combo keyboard & mouse print("Manufacturer: %s" % h.get_manufacturer_string()) print("Product: %s" % h.get_product_string()) print("Serial No: %s" % h.get_serial_number_string()) try: while True: d = h.read(64) if d: print('read: "{}"'.format(d)) finally: print("Closing the device") h.close()
执行结果(sudo python try.py)
Opening the device Manufacturer: Microsoft Product: Microsoft® Nano Transceiver v2.0 Serial No: None read: "[0, 0, 0, 0, 0, 0, 0, 0]" read: "[0, 0, 0, 0, 0, 0, 0, 0]" read: "[0, 0, 0, 0, 0, 0, 0, 0]" --8<-- snip lots of repeated lines --8<-- read: "[0, 0, 0, 0, 0, 0, 0, 0]" read: "[0, 0, 0, 0, 0, 0, 0, 0]" read: "[0, 0, 21, 0, 0, 0, 0, 0]" read: "[0, 0, 21, 0, 0, 0, 0, 0]" read: "[0, 0, 21, 0, 0, 0, 0, 0]" read: "[0, 0, 21, 0, 0, 0, 0, 0]" read: "[0, 0, 0, 0, 0, 0, 0, 0]" read: "[0, 0, 4, 0, 0, 0, 0, 0]" read: "[0, 0, 4, 22, 0, 0, 0, 0]" read: "[0, 0, 4, 22, 0, 0, 0, 0]" read: "[0, 0, 4, 22, 0, 0, 0, 0]" read: "[0, 0, 4, 22, 0, 0, 0, 0]" read: "[0, 0, 4, 22, 0, 0, 0, 0]" read: "[0, 0, 4, 0, 0, 0, 0, 0]" read: "[0, 0, 4, 0, 0, 0, 0, 0]" read: "[0, 0, 4, 9, 0, 0, 0, 0]" read: "[0, 0, 4, 9, 0, 0, 0, 0]" read: "[0, 0, 4, 9, 0, 0, 0, 0]" read: "[0, 0, 4, 9, 0, 0, 0, 0]" read: "[0, 0, 4, 9, 7, 0, 0, 0]" read: "[0, 0, 4, 9, 7, 0, 0, 0]" read: "[0, 0, 7, 0, 0, 0, 0, 0]" ^Closing the device Traceback (most recent call last): File "try.py", line 17, in <module> d = h.read(64) KeyboardInterrupt
我找不到像EVDEV那样的优质示例文档,导致解析输出很困难。h.read()返回一个列表,我有以下技术疑问:
疑问解答
1. 为何h.read()选择参数64?当参数替换为1-8时,列表元素数量不变;参数≥9时,列表仅含8个元素。
这个参数是你请求读取的最大字节数,但实际返回的字节数由HID设备的报告描述符决定。你的扫码枪(或者这里的微软收发器)使用的是8字节的HID报告,所以不管你请求1-64字节,它只会返回固定的8字节报告。当你请求≥9字节时,底层HIDAPI还是只会返回设备规定的8字节报告长度,所以列表始终是8个元素。
2. 变量d为何是包含8个元素的输出列表?代码中未指定该长度。
这完全由你的HID设备决定。每个USB HID设备都会通过报告描述符定义它的输入报告长度——你的设备定义的是8字节,所以每次read()调用都会返回8个元素的列表(每个元素对应1字节的数值)。你可以通过调用h.get_report_descriptor()方法获取设备的报告描述符来确认这一点。
3. 每个输出列表代表什么?是否对应一个输入字符?
不一定。HID输入报告是设备发送的状态快照:
- 大部分全0的列表是设备的“空报告”,用来维持连接或者表示没有按键动作。
- 非空报告可能对应按键按下/释放的状态:比如你看到的
[0,0,21,0,0,0,0,0]可能是某个修饰键或者扫描码的按下事件,后续的全0报告则是按键释放的标记。 - 扫码枪通常模拟键盘输入,所以每个有效字符可能对应多个报告:按下报告、可能的重复报告、释放报告。
4. 列表中每个元素分别代表/编码什么内容?
对于键盘类HID设备(扫码枪属于这类),标准8字节报告的格式通常是:
- 第1个字节:修饰键位掩码(比如Shift、Ctrl、Alt等,每个位对应一个修饰键)。
- 第2个字节:通常为保留位(固定为0)。
- 第3到第8个字节:扫描码(最多支持6个同时按下的按键,适配多键输入场景)。
你看到的[0,0,21,0,0,0,0,0]里的21就是某个按键的扫描码,[0,0,4,22,0,0,0,0]里的4和22则是同时触发的两个扫描码(可能是修饰键+普通键,或者设备自定义的格式)。
5. 是否有公开的对照表可将这些数值映射为键盘字符?
有的,USB组织定义了HID键盘扫描码对照表(称为Usage ID)。标准的键盘扫描码可以参考HID Usage Tables文档里的Keyboard/Keypad Page(Page 0x07),常见映射示例:
- 扫描码4对应字母'A'(未按Shift),22对应'G',9对应'I',7对应'9'(按下Shift时则对应'(')。
- 你需要先判断修饰键的状态(比如Shift是否按下),再将扫描码映射为对应的ASCII字符。另外,多数扫码枪会发送回车(扫描码0x28)作为一次扫描完成的标记,你可以通过这个标记来拼接完整的扫描字符串。
内容的提问来源于stack exchange,提问作者gatorback




