【DigiKey“智造万物,快乐不停”创意大赛】2,Pi400 HID 键盘功能的实现
[复制链接]
在github上有一个zero_hid的库,可以实现使用树莓派zero模拟hid键盘。但这个库有一些问题,直接使用在组合键上会出很多的问题,因此我参考这个项目,重写了一下这个库。
首先科普一下HID协议,HID键盘协议是一种基于报文的协议,通过在USB总线上进行通信。当用户按下键盘上的按键时,键盘将生成一个HID报文,并将其发送到计算机。计算机收到报文后,根据报文的内容来模拟相应的键盘操作,例如在文本编辑器中输入字符或执行特定的功能。
HID键盘报文包含多个字段,其中最重要的是按键码(Keycode)。按键码表示按下的键的唯一标识符,例如“A”键的按键码是0x04。除了按键码外,报文还可以包含其他信息,如修饰键(如Shift、Ctrl和Alt键)的状态和组合键的状态。
因此,在合成报文前,我们先要知道我们想输入的按键哪些是修饰键,而哪些是按键,他们要分开进行处理。
在进入代码部分前,我们需要先安装一下驱动。首先先新建一个文件,命名为isticktoit_usb,添加可执行权限,并填入以下内容:
···
#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p isticktoit
cd isticktoit
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2
mkdir -p strings/0x409
echo "fedcba9876543210" > strings/0x409/serialnumber
echo "Tobias Girstmair" > strings/0x409/manufacturer
echo "iSticktoit.net USB Device" > strings/0x409/product
mkdir -p configs/c.1/strings/0x409
echo "Config 1: ECM network" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower
# Add functions here
mkdir -p functions/hid.usb0
echo 1 > functions/hid.usb0/protocol
echo 1 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x05\\x01\\x09\\x06\\xa1\\x01\\x05\\x07\\x19\\xe0\\x29\\xe7\\x15\\x00\\x25\\x01\\x75\\x01\\x95\\x08\\x81\\x02\\x95\\x01\\x75\\x08\\x81\\x03\\x95\\x05\\x75\\x01\\x05\\x08\\x19\\x01\\x29\\x05\\x91\\x02\\x95\\x01\\x75\\x03\\x91\\x03\\x95\\x06\\x75\\x08\\x15\\x00\\x25\\x65\\x05\\x07\\x19\\x00\\x29\\x65\\x81\\x00\\xc0 > functions/hid.usb0/report_desc
ln -s functions/hid.usb0 configs/c.1/
# End functions
ls /sys/class/udc > UDC
···
接着运行以下命令,完成驱动配置:
···
#!/bin/bash
echo "" | sudo tee -a /boot/config.txt
echo "# BEGIN HID Keyboard Simulation" | sudo tee -a /boot/config.txt
echo "dtoverlay=dwc2" | sudo tee -a /boot/config.txt
echo "# END HID Keyboard Simulation" | sudo tee -a /boot/config.txt
echo "" | sudo tee -a /etc/modules
echo "# BEGIN HID Keyboard Simulation" | sudo tee -a /etc/modules
echo "dwc2" | sudo tee -a /etc/modules
echo "libcomposite" | sudo tee -a /etc/modules
echo "# END HID Keyboard Simulation" | sudo tee -a /etc/modules
# Move to before exit 0
echo "" | sudo tee -a /etc/rc.local
echo "# BEGIN HID Keyboard Simulation" | sudo tee -a /etc/rc.local
echo "sudo ./isticktoit_usb" | sudo tee -a /etc/rc.local
echo "# END HID Keyboard Simulation" | sudo tee -a /etc/rc.local
···
完成后,以后每次重启完成,只需要运行一下isticktoit_usb即可。
处理报文部分的代码如下:
···
from typing import List
from .hid import hidwrite
from .hid.keycodes import KeyCodes
from time import sleep
import json
import pkgutil
import os
import pathlib
class Keyboard:
def __init__(self, dev='/dev/hidg0') -> None:
self.dev = dev
self.set_layout()
self.control_pressed = []
self.key_pressed = []
def list_layout(self):
keymaps_dir = pathlib.Path(__file__).parent.absolute() / 'keymaps'
keymaps = os.listdir(keymaps_dir)
files = [f for f in keymaps if f.endswith('.json')]
for count, fname in enumerate(files, 1):
with open(keymaps_dir / fname , encoding='UTF-8') as f:
content = json.load(f)
name, desc = content['Name'], content['Description']
print(f'{count}. {name}: {desc}')
def set_layout(self, language='US'):
self.layout = json.loads( pkgutil.get_data(__name__, f"keymaps/{language}.json").decode() )
def gen_list(self, keys = []):
_control_pressed = []
_key_pressed = []
for key in keys:
if key[:3] == "MOD":
_control_pressed.append(KeyCodes[key])
else:
_key_pressed.append(KeyCodes[key])
return _control_pressed, _key_pressed
def gen_buf(self):
self.buf = [sum(self.control_pressed),0] + self.key_pressed
self.buf += [0] * (8 - len(self.buf)) # fill to lenth 8
##########################################################################
# For user
def press(self, keys = [], additive=False, hold=False):
_control_pressed, _key_pressed = self.gen_list(keys)
if not additive:
self.control_pressed = []
self.key_pressed = []
self.control_pressed.extend(_control_pressed)
self.control_pressed = list(set(self.control_pressed)) # remove repeated items
self.key_pressed.extend(_key_pressed)
self.key_pressed = list(set(self.key_pressed))[:6] # remove repeated items and cut until 6 items
self.gen_buf()
hidwrite.write_to_hid_interface(self.dev, self.buf)
if not hold:
self.release(keys)
def release(self, keys = []):
_control_pressed, _key_pressed = self.gen_list(keys)
try:
self.control_pressed = list(set(self.control_pressed) - set(_control_pressed))
except:
pass
try:
self.key_pressed = list(set(self.key_pressed) - set(_key_pressed))
except:
pass
self.gen_buf()
hidwrite.write_to_hid_interface(self.dev, self.buf)
def release_all(self):
self.control_pressed = []
self.key_pressed = []
self.gen_buf()
hidwrite.write_to_hid_interface(self.dev, self.buf)
def text(self, string, delay=0):
for c in string:
key_map = self.layout['Mapping'][c]
key_map = key_map[0]
mods = key_map['Modifiers']
keys = key_map['Keys']
self.press(mods + keys)
sleep(delay)
···
上面这段代码把想要输出的按键分为control(修饰按键)和key(普通按键)两块,再组合形成报文列表。使用的逻辑是输入当前想要按下的按键状态,然后程序发送对应的报文。
测试一下:
···
import os
import zero_hid
if os.geteuid() != 0:
raise ImportError('You must be root to use this library on linux.')
k = zero_hid.Keyboard()
k.press(["KEY_H"], additive=False, hold=False)
k.press(["KEY_E"], additive=False, hold=False)
k.press(["KEY_L"], additive=False, hold=False)
k.press(["KEY_L"], additive=False, hold=False)
k.press(["KEY_O"], additive=False, hold=False)
···
press方法中填入的是一个list,表示当前按下的所有按键。具体的键值列表在zero_hid/keymaps/US.json中。
如果电脑成功打印,表示功能正常。
|