简介:本资源是一套跨平台Python HID设备控制脚本,面向嵌入式开发、硬件调试及自动化测试领域的初学者与工程师,解决Linux(Ubuntu)与Windows环境下Python直接读写HID设备的兼容性难题。压缩包共4个文件,含3个核心Python脚本(分别实现Ubuntu/Linux下基于pyusb的HID通信、Windows下基于pywinusb的驱动控制,以及屏幕点击模拟功能)和1份详细说明文本,总大小仅4KB,轻量易集成。已有1525人学习下载,脚本经Ubuntu 20.04+Python 2.7、Windows+Python 3.9双平台实测可用,并附权限配置、模块安装路径差异等关键排错提示——尤其针对多Python版本共存时模块误装问题给出明确规避方案,帮助用户绕过常见环境陷阱,快速启动HID设备调试。
1. Python跨平台HID设备控制:从USB协议层直连硬件,绕过驱动封装实现稳定读写
你有没有遇到过这样的场景:手头有个定制的USB HID设备(比如工业传感器、加密UKey、带按键的LED面板),厂商只提供了Windows DLL,Linux下连lsusb -v都看不到端点描述符?或者用现成的hidapi绑定总在权限、内核模块、Python版本间反复踩坑?这个脚本包不是调用高层API,而是直接基于pyusb和pywinusb,在用户态解析HID报告描述符、构造原始数据包、通过控制传输/中断传输与设备通信。它不依赖hidraw节点或hid-generic驱动,Ubuntu下用sudo python2 hid_ubuntu.py就能发0x09 Report ID指令,Windows下用python hid_win.py可捕获键盘类设备的原始输入流。适合嵌入式测试工程师、硬件联调人员、需要绕过系统HID栈做底层调试的开发者——尤其当你发现dmesg | grep hid里全是device reset failed时,这套方案反而更稳。
2. Linux平台HID控制原理与Ubuntu实操:USB描述符解析与中断传输实战
2.1 为什么不用hidraw而选pyusb?协议层差异决定稳定性
Linux内核的hidraw接口虽方便,但存在三个硬伤:一是设备拔插后/dev/hidraw*节点编号会变,脚本需动态探测;二是某些HID设备(如Report ID为0的复合设备)在hidraw中无法区分不同逻辑单元;三是内核hid-core模块对自定义Report Descriptor解析有缓存,修改固件后需modprobe -r usbhid && modprobe usbhid才能生效。而pyusb直接操作USB设备,通过get_descriptor()读取原始HID描述符,用ctrl_transfer()发送Set_Report请求,用interrupt_read()监听IN端点——这正是USB HID Class Spec 1.11第6.2节定义的标准流程。hid_ubuntu.py中find_hid_device()函数先遍历所有USB设备,用bInterfaceClass == 0x03筛选HID类,再通过get_string()验证厂商名,避免误匹配摄像头等伪HID设备。
提示:Ubuntu 20.04默认内核为5.4,
usbhid模块已启用ignore_other_devices=1参数,若设备被hid-generic接管,需在/etc/modprobe.d/blacklist.conf中添加blacklist hid_generic并执行sudo update-initramfs -u
2.2 安装与权限配置:解决“Operation not permitted”核心报错
# 确认Python2环境(Ubuntu 20.04默认无python2,需手动安装) sudo apt update && sudo apt install python2.7 python2.7-dev curl https://bootstrap.pypa.io/pip/2.7/get-pip.py --output get-pip.py sudo python2.7 get-pip.py # 安装pyusb(必须指定python2.7解释器,否则pip3会装到Python3环境) sudo python2.7 -m pip install pyusb==1.2.1 # 创建udev规则(避免每次sudo运行) echo 'SUBSYSTEM=="usb", ATTRS{idVendor}=="04d8", ATTRS{idProduct}=="003f", MODE="0664", GROUP="plugdev"' | sudo tee /etc/udev/rules.d/99-hid-device.rules sudo usermod -a -G plugdev $USER sudo udevadm control --reload-rules sudo udevadm triggeridVendor/idProduct需替换为你的设备实际值(用lsusb查看,如Bus 001 Device 005: ID 04d8:003f Microchip Technology, Inc.)MODE="0664"赋予读写权限,GROUP="plugdev"将当前用户加入设备组- 执行后需重新插拔设备或重启udev:
sudo systemctl restart systemd-udevd
2.3 hid_ubuntu.py核心代码解析:从枚举到数据收发
#!/usr/bin/env python2.7 import usb.core import usb.util import sys def find_hid_device(): # 查找VID=0x04d8, PID=0x003f的HID设备(Microchip示例) dev = usb.core.find(idVendor=0x04d8, idProduct=0x003f) if dev is None: raise ValueError("Device not found") # 检查是否已配置(避免ConfigurationNotFound异常) if dev.is_kernel_driver_active(0): dev.detach_kernel_driver(0) # 强制接管接口 # 设置配置(多数HID设备仅1个配置) dev.set_configuration() # 获取HID接口(通常为Interface 0,Alternate Setting 0) cfg = dev.get_active_configuration() intf = cfg[(0,0)] # 查找中断IN端点(bEndpointAddress & 0x80 == 0x80表示IN方向) ep_in = usb.util.find_descriptor( intf, custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_IN ) # 查找中断OUT端点(用于发送数据) ep_out = usb.util.find_descriptor( intf, custom_match=lambda e: usb.util.endpoint_direction(e.bEndpointAddress) == usb.util.ENDPOINT_OUT ) return dev, ep_in, ep_out def send_report(dev, ep_out, report_data): """发送HID Report数据包,支持Report ID""" # 若设备要求Report ID,需在数据前加1字节ID(如report_data = b'\x01\x00\x01') try: # 控制传输方式(标准HID Set_Report请求) dev.ctrl_transfer( bmRequestType=0x21, # Host-to-Device, Class, Interface bRequest=0x09, # SET_REPORT wValue=0x0200, # HID Report Type (Output) << 8 | Report ID (0x00) wIndex=0x0000, # Interface Number data_or_wLength=report_data ) except usb.core.USBError as e: print("Control transfer failed:", e) def read_report(ep_in, timeout=1000): """读取中断IN端点数据""" try: data = ep_in.read(ep_in.wMaxPacketSize, timeout) return bytes(data) # 转为Python2.7兼容的str类型 except usb.core.USBError as e: if e.errno == 110: # ETIMEDOUT return None raise e if __name__ == "__main__": dev, ep_in, ep_out = find_hid_device() print("HID device connected: %s" % dev) # 发送输出报告(模拟按键按下) send_report(dev, ep_out, b'\x01\x01\x00') # Report ID=1, Key Code=0x01 (KEY_ESC) # 循环读取输入报告 while True: data = read_report(ep_in) if data: print("Received:", data.encode('hex')) # Python2.7中str的encode('hex')ctrl_transfer()参数详解:bmRequestType=0x21:二进制00100001,表示Host-to-Device(0)、Class(0x01)、Interface(0x01)bRequest=0x09:HID Class Spec定义的SET_REPORT请求码wValue=0x0200:高8位为Report Type(0x02=Output),低8位为Report ID(0x00)wIndex=0x0000:目标接口号,HID设备通常为0
read_report()中ep_in.wMaxPacketSize需与设备描述符中wMaxPacketSize一致(常见为64字节),超时设为1000ms避免阻塞
2.4 常见故障排查表:从设备识别到数据校验
| 现象 | 根本原因 | 解决方案 |
|---|---|---|
usb.core.NoBackendError | 系统缺少libusb-1.0库 | sudo apt install libusb-1.0-0-dev |
usb.core.USBError: Resource busy | 内核hid-generic已接管设备 | sudo modprobe -r hid_generic && sudo modprobe usbhid |
usb.core.USBError: Operation not permitted | udev规则未生效或用户未加入plugdev组 | 执行groups确认含plugdev,重插设备 |
read_report()返回空数据 | 设备未配置中断IN端点或Report Descriptor错误 | 用sudo lsusb -v -d 04d8:003f检查bEndpointAddress和wMaxPacketSize |
send_report()后设备无响应 | Report ID不匹配或wValue中Report Type错误 | 用逻辑分析仪抓包比对标准HID Set_Report请求格式 |
3. Windows平台HID控制实现:pywinusb底层通信与事件回调机制
3.1 pywinusb替代hidapi的技术动因:Windows 10 RS5+的驱动兼容性问题
Windows平台传统方案是hidapi(通过hid.dll调用),但在Windows 10 1903+版本中,微软强化了HID驱动签名策略,导致未签名的第三方hid.dll加载失败。pywinusb则直接调用Windows原生SetupAPI和HidD_GetPreparsedData,绕过用户态DLL依赖。其核心优势在于:支持HidD_GetFeature()/HidD_SetFeature()访问Feature Report(常用于设备配置),提供register_raw_data_handler()回调函数实时处理输入事件,且无需管理员权限即可访问大多数HID设备(除需FILE_DEVICE_SECURE_OPEN的加密设备外)。hid_win.py中HidDevice类封装了设备打开、报告描述符解析、异步读取全流程,比直接调用ctypes.windll.hid更健壮。
3.2 安装与环境适配:Python3.9下pywinusb的编译陷阱
# PowerShell中以管理员身份运行(确保能写注册表) # 先卸载可能存在的旧版本 pip uninstall pywinusb # 安装预编译wheel(避免VS编译失败) pip install --only-binary=all pywinusb==0.4.2 # 验证安装 python -c "import pywinusb; print(pywinusb.__version__)"pywinusb==0.4.2是最后一个支持Python3.9的版本(后续版本已停止维护)- 若提示
Microsoft Visual C++ 14.0 is required,需安装 Build Tools for Visual Studio - 关键配置:在脚本开头添加
import pywinusb.hid,否则HidDevice类无法正确初始化
3.3 hid_win.py事件驱动模型:从轮询到回调的性能跃迁
#!/usr/bin/env python # -*- coding: utf-8 -*- import pywinusb.hid as hid import time class HidDevice: def __init__(self, vendor_id=0x04d8, product_id=0x003f): self.vendor_id = vendor_id self.product_id = product_id self.device = None self.handler = None def find_device(self): # 枚举所有HID设备,按VID/PID匹配 all_devices = hid.find_all_hid_devices() for device in all_devices: if (device.vendor_id == self.vendor_id and device.product_id == self.product_id): self.device = device break if not self.device: raise RuntimeError("HID device not found") def open_device(self): if not self.device: self.find_device() self.device.open() # 注册输入报告回调(自动触发,非轮询) self.device.set_raw_data_handler(self.on_input_report) # 获取设备信息 print("Connected to: %s" % self.device.product_name) print("Firmware: %s" % self.device.firmware_revision) def on_input_report(self, data): """输入报告回调函数,data为bytearray类型""" # data[0]为Report ID,data[1:]为有效载荷 report_id = data[0] payload = bytes(data[1:]) print("IN Report ID=%d, Data=%s" % (report_id, payload.hex())) # 可在此处添加业务逻辑,如解析传感器数据 if report_id == 0x01: temp = (payload[0] << 8) | payload[1] # 16-bit temperature print("Temperature: %d°C" % temp) def send_output_report(self, report_id, data): """发送输出报告""" # 构造完整报告包:[Report ID] + data report_buffer = bytearray([report_id]) + bytearray(data) # 获取输出报告特征(需设备支持) try: output_report = self.device.find_output_reports()[0] output_report.set_raw_data(report_buffer) output_report.send() except (IndexError, AttributeError): # 若无Output Report,尝试Feature Report try: feature_report = self.device.find_feature_reports()[0] feature_report.set_raw_data(report_buffer) feature_report.send() except Exception as e: print("Failed to send report:", e) def close_device(self): if self.device: self.device.close() if __name__ == "__main__": hid_dev = HidDevice() try: hid_dev.open_device() # 发送输出报告(如点亮LED) hid_dev.send_output_report(0x02, b'\x01') # Report ID=2, LED ON # 保持程序运行以接收回调 print("Press Ctrl+C to exit...") while True: time.sleep(1) except KeyboardInterrupt: print("\nExiting...") finally: hid_dev.close_device()set_raw_data_handler()注册的回调函数在新数据到达时由系统线程自动调用,避免while True: read()的CPU空转find_output_reports()返回列表,索引[0]取第一个Output Report(多数设备仅1个)send()方法内部调用HidD_SetOutputReport(),失败时降级到HidD_SetFeatureReport()
3.4 Windows HID报告类型深度解析:Input/Output/Feature Report的应用边界
| Report类型 | 传输方向 | 典型用途 | Windows API对应 |
|---|---|---|---|
| Input Report | Device → Host | 按键、鼠标移动、传感器数据 | HidD_GetInputReport() |
| Output Report | Host → Device | LED控制、蜂鸣器、电机启停 | HidD_SetOutputReport() |
| Feature Report | 双向 | 设备配置(灵敏度、采样率)、固件升级 | HidD_GetFeature()/HidD_SetFeature() |
screen_click.py示例利用Input Report捕获触摸屏坐标,通过data[2:4]提取X轴(小端序),data[4:6]提取Y轴,再调用pyautogui.click(x,y)模拟点击- 若设备无Output Report(
find_output_reports()返回空列表),必须使用Feature Report进行控制,此时wValue参数需设为0x0300(Feature Report Type)
4. 跨平台调试技巧与HID协议级验证方法
4.1 使用USB协议分析仪验证数据包结构:避免“看似成功实则无效”
当脚本运行无报错但设备无响应时,最可靠的方法是用USB协议分析仪(如Total Phase Beagle USB 480)抓包。重点比对三类数据:
- HID描述符请求:
GET_DESCRIPTOR请求中wValue=0x2200(HID Descriptor),确认bDescriptorType=0x22且wDescriptorLength与设备手册一致 - Set_Report请求:检查
bmRequestType=0x21、bRequest=0x09、wValue高位是否匹配Report Type(0x01=Input, 0x02=Output, 0x03=Feature) - 中断传输数据:对比
ep_in收到的数据长度是否等于wMaxPacketSize,内容是否含预期Report ID
注意:Linux下
pyusb的ctrl_transfer()默认使用bmRequestType=0x21,而某些设备要求0xA1(Device-to-Host),此时需改用dev.ctrl_transfer(0xA1, 0x01, 0x0100, 0, 8)读取Input Report
4.2 报告描述符(Report Descriptor)逆向工程速查表
HID设备行为由二进制Report Descriptor决定,01说明.txt中应包含此字段。常用Item解析:
| Item | 含义 | 示例值 | 说明 |
|---|---|---|---|
0x05, 0x01 | Usage Page (Generic Desktop) | 0x05, 0x01 | 后续Usage基于此页 |
0x09, 0x06 | Usage (Keyboard) | 0x09, 0x06 | 定义设备功能类别 |
0x15, 0x00 | Logical Minimum | 0x15, 0x00 | 数据最小值(0) |
0x25, 0xFF | Logical Maximum | 0x25, 0xFF | 数据最大值(255) |
0x75, 0x08 | Report Size | 0x75, 0x08 | 每个字段8位 |
0x95, 0x06 | Report Count | 0x95, 0x06 | 共6个字段(6字节数据) |
0x81, 0x02 | Input (Data, Variable, Absolute) | 0x81, 0x02 | 输入报告字段 |
- 若
01说明.txt缺失,可用sudo lsusb -v -d VID:PID 2>/dev/null | grep -A 5 "HID Device"提取原始Descriptor - 在线解析工具推荐: HID Descriptor Tool (粘贴十六进制字符串即可生成C结构体)
4.3 Ubuntu与Windows环境变量隔离实践:避免Python版本冲突
生产环境中常需同时运行Python2.7(旧脚本)和Python3.9(新服务),通过环境变量隔离:
# Ubuntu下创建专用环境 mkdir ~/hid_env && cd ~/hid_env python2.7 -m virtualenv venv27 source venv27/bin/activate pip install pyusb==1.2.1 # Windows下PowerShell脚本启动 $env:PYTHONPATH="C:\hid_env\Lib\site-packages" & "C:\Python27\python.exe" "C:\hid_control\hid_ubuntu.py" # 或使用shebang指定解释器(Linux) #!/usr/bin/env python2.7 # 第一行确保调用python2.7而非系统默认python- Ubuntu中
/usr/bin/python指向python2.7时,#!/usr/bin/env python才安全;否则必须写死#!/usr/bin/env python2.7 - Windows中
.py文件关联到Python3.9时,需在命令行显式调用python2.7 script.py
4.4 实时监控HID设备状态的Bash/PowerShell脚本
# Ubuntu实时监控(保存为monitor_hid.sh) #!/bin/bash DEVICE_PATH="/sys/bus/usb/devices/*/idVendor" while true; do echo "$(date): $(lsusb | grep '04d8:003f' | wc -l) devices found" # 检查hidraw节点是否存在 if ls /dev/hidraw* 2>/dev/null | grep -q "hidraw"; then echo " hidraw nodes: $(ls /dev/hidraw*)" fi sleep 2 done# Windows PowerShell监控(monitor_hid.ps1) while ($true) { $devices = Get-PnpDevice -Class HID | Where-Object {$_.InstanceId -match "04D8&003F"} Write-Host "$(Get-Date): Found $($devices.Count) HID devices" if ($devices.Count -gt 0) { $devices | ForEach-Object { Write-Host " $($_.Name) - $($_.Status)" } } Start-Sleep -Seconds 2 }- Ubuntu脚本通过
lsusb和/dev/hidraw*双重验证设备在线状态 - Windows脚本用
Get-PnpDevice获取即插即用设备状态,Status为OK表示正常
本文还有配套的精品资源,点击获取