【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
-
- echo 0x0104 > idProduct
-
- echo 0x0100 > bcdDevice
-
- echo 0x0200 > bcdUSB
-
- 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
-
-
-
-
-
- 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/
-
-
-
-
-
- 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
-
-
-
-
-
- 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))
-
-
-
-
-
-
-
-
-
- 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))
-
- self.key_pressed.extend(_key_pressed)
-
- self.key_pressed = list(set(self.key_pressed))[:6]
-
-
-
- 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中。
如果电脑成功打印,表示功能正常。
|