Key Mime Pi: Turn Your Raspberry Pi into a Remote Keyboard

Michael Lynch

Key Mime Pi:把树莓派变成远程键盘

原文由 Michael Lynch 发布,订阅该博客

近期的树莓派已支持 USB OTG(USB On-The-Go),可以模拟键盘、U 盘、麦克风等 USB 设备。为了充分利用这一特性,我做了一个开源网页应用,能把树莓派变成一台虚拟键盘,我把它叫做Key Mime Pi

本文将演示 Key Mime Pi 的工作原理,并教你如何自己动手做一个。

演示

准备工作

安装 Raspberry Pi OS Lite

首先,在 microSD 卡上安装Raspberry Pi OS Lite(前身为 Raspbian)。

Rufus 截图

我用Rufus来烧录树莓派的 microSD 卡,当然任何全盘镜像工具都可以。

在 microSD 卡的 boot 分区放置一个名为ssh的文件来开启 SSH 访问,然后将 microSD 卡插入树莓派。如果你使用无线连接,还需要在 boot 分区创建一个wpa_supplicant.conf文件

连接树莓派

将 USB 数据线连接到树莓派的 USB OTG 接口。在树莓派 4 上是 USB-C 接口,在树莓派 Zero 上则是标有“USB”字样的 Micro-USB 接口。

已插入 USB-C 接口的树莓派 4已插入 Micro-USB 数据口的树莓派 Zero W

树莓派 4(左)请连接 USB-C 接口,树莓派 Zero W(右)请连接 Micro-USB 数据口。

将数据线的另一端连接到你想作为键盘来控制的电脑。USB 3.0 接口效果更好,因为它能提供更大电流,不过我测试过的所有 USB 2.0 接口也都能正常工作。

树莓派会通过电脑的 USB 接口取电并启动。

安装 Key Mime Pi

安装 Key Mime Pi 有两种方式。你可以直接用最原始的 bash,不需要任何外部工具;或者,如果想更优雅一点,也可以用我最喜欢的开源配置管理工具Ansible来安装。

方案一:纯 bash 方式

在 bash 终端中输入以下命令,连接到树莓派并将其配置为支持 USB 设备模拟:

# SSH into your Pi
PI_HOSTNAME="raspberrypi" # Change to your pi's hostname
PI_SSH_USERNAME="pi"      # Change to your Pi username

# Connect to the Pi (default password is "raspberry")
ssh "${PI_SSH_USERNAME}@${PI_HOSTNAME}"

# Install pre-requisites
sudo apt-get update && \
  sudo apt-get install -y \
    git \
    python-pip \
    python3-venv

# Install Key Mime Pi
git clone https://github.com/mtlynch/key-mime-pi.git
cd key-mime-pi
sudo ./enable-usb-hid
sudo reboot

等待树莓派重启完成后,再次通过 SSH 登录并启动 Key Mime Pi 网页服务:

ssh "${PI_SSH_USERNAME}@${PI_HOSTNAME}"
cd key-mime-pi
python3 -m venv venv
. venv/bin/activate
pip install --requirement requirements.txt
PORT=8000 ./app/main.py

方案二:Ansible 方式

如果你本身就在用 Ansible,可以使用我的Key Mime Pi Ansible 角色来实现更自动化的部署。以下命令会将 Key Mime Pi 作为systemd 服务安装到设备上:

PI_HOSTNAME="raspberrypi" # Change to your pi's hostname
PI_SSH_USERNAME="pi"      # Change to your Pi username

# Install the Key Mime Pi Ansible role
ansible-galaxy install mtlynch.keymimepi

# Create a minimal Ansible playbook to configure your Pi
echo "- hosts: $PI_HOSTNAME
  roles:
    - role: mtlynch.keymimepi" > install.yml

# Install all software (default password is "raspberry")
ansible-playbook \
  --inventory "$PI_HOSTNAME", \
  --user "$PI_SSH_USERNAME" \
  --become \
  --become-method sudo \
  --ask-pass \
  install.yml

# Reboot the Pi
ansible \
  "$PI_HOSTNAME" \
  -m reboot \
  --inventory "$PI_HOSTNAME", \
  --user "$PI_SSH_USERNAME" \
  --ask-pass \
  --become \
  --become-method sudo

使用 Key Mime Pi

运行安装脚本后,可通过以下地址访问 Key Mime Pi:

界面如下所示:

Key Mime Pi 网页界面截图

等待输入的 Key Mime Pi 网页界面

就像变魔术一样,你在浏览器里敲下的按键,会出现在与树莓派相连的那台机器上。

Key Mime Pi 通过浏览器发送按键

Key Mime Pi 可让你通过浏览器向远程电脑发送按键。

工作原理

USB 设备模拟

真正的魔法来自Linux 的 USB 人机接口设备(HID)小工具驱动。它允许用户态程序像 USB 设备一样与操作系统交互。

key-mime-pi 配置脚本会在/dev/hidg0路径下创建一个设备文件。任何程序都可以对该路径进行读写,操作系统会将数据转换为键盘信号。

要模拟键盘,树莓派必须按照USB HID 规范与操作系统通信。这份长达 97 页、满是键码和表格的文档读起来相当枯燥,但好在键盘相关的协议其实非常简单。

每次按键时,键盘都会发送一个 8 字节的消息,也就是所谓的“报告(report)”。

字节序号用途
0修饰键(Ctrl、Alt、Shift)
1厂商保留
2按键 #1
3按键 #2
4按键 #3
5按键 #4
6按键 #5
7按键 #6

以发送“Hi”为例,操作如下:

# H (Right shift + h)
echo -ne "\x20\0\xb\0\0\0\0\0" > /dev/hidg0
# i
echo -ne "\0\0\xc\0\0\0\0\0" > /dev/hidg0
# Release all keys
echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0

除了发送按键按下信号,键盘还必须指示按键释放。8 字节全为零的数据块表示当前没有按键处于按下状态。

上面的例子一次只发送一个按键,但 HID 报告最多可容纳六个按键。这意味着只要按键各不相同,你就可以在一条消息中一次发送最多六个按键:

echo -ne "\0\0\x1a\x0b\x04\x17\x18\x13" > /dev/hidg0 && \
  echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0
whatup

从 JavaScript 到 HID 的转换

当你在浏览器窗口中输入时,JavaScript 会为每次按键生成事件。网站keycode.info很好地演示了这一机制的实际效果。

JavaScript 按键事件中包含键码,但它们与 HID 键码并不相同。好在两者之间几乎是一一对应的。为了实现从 JavaScript 到 HID 的转换,我创建了一个如下所示的对照表

_JS_TO_HID_KEYCODES = {
    3: 0x48,  # Pause / Break
    8: 0x2a,  # Backspace / Delete
    9: 0x2b,  # Tab
    ...
    65: 0x04,  # a
    66: 0x05,  # b
    67: 0x06,  # c
    68: 0x07,  # d

Key Mime Pi 服务端会监听来自浏览器的 JavaScript 键码事件,将其翻译成对应的 HID 编码,然后发送到树莓派上位于/dev/hidg0的 HID 接口。

完整的端到端流程如下:

  1. 用户在浏览器中按下某个按键。
  2. 页面上的 JavaScript 将该按键的 JavaScript 键码发送给树莓派上的 Key Mime Pi 服务端。
  3. Key Mime Pi 服务端将 JavaScript 键码转换为对应的 HID 编码。
  4. Key Mime Pi 服务端将 HID 编码发送到位于/dev/hidg0的 USB gadget 接口。
  5. 与树莓派 USB 线相连的电脑会将其识别为键盘输入,从而在屏幕上显示相应字符。

供电问题

在我的测试中,电脑的 USB 接口提供的电量足以给树莓派供电,但系统日志中频繁出现了欠压警告:

 $ sudo journalctl -xe | grep "Under-voltage"
Jun 05 03:46:05 keymimepi kernel: Under-voltage detected! (0x00050005)
Jun 05 03:48:29 keymimepi kernel: Under-voltage detected! (0x00050005)
Jun 05 03:54:22 keymimepi kernel: Under-voltage detected! (0x00050005)

树莓派正确地检测到,标准的 USB 2.0 和 USB 3.0 接口无法满足其供电需求。

树莓派型号供电需求
Pi Zero W5 V / 1.2 A
Pi 45 V / 3.0 A

而标准 USB 接口的供电能力则明显不足:

USB 接口类型输出功率
USB 2.05 V / 0.5 A
USB 3.05 V / 0.9 A

我目前仍在寻找这个问题的解决方案。以下是一些我尚未测试过的可能方案:

  • 使用PoE HAT通过以太网口取电
  • 使用 Zero2Go 电源转接板连接 AC 转 microUSB 电源。

更新:为了解决这个问题,我与一家工程公司合作制作了一块定制电路板,可将树莓派的 USB-C 接口一分为二。一个接口用于接入 USB 电源,这样仍可为树莓派提供完整的 3A 供电;另一个接口用于输出 USB 数据,使树莓派仍能模拟 USB 键盘。

电源连接器特写已连接树莓派和 microUSB 数据线的电源连接器

TinyPilot Power Connector可让树莓派通过 USB-C 接口获得 3A 供电,同时不丧失 USB OTG 功能。

重要的是,该电源连接器的数据口去掉了 USB 供电线。这样可以确保电脑电源与树莓派电源之间的电压差不会导致不必要的电流回灌。

注意:如果没有合适的连接器,在树莓派已连接到电脑的情况下再用外部电源供电,存在损坏硬件的风险。详情请参阅TinyPilot wiki

故障排查

验证驱动是否正常工作

如果 Key Mime Pi 无法将按键转发到目标机器,第一步是判断 USB gadget 是否正常工作。

请尝试执行以下命令:

echo -ne "\0\0\xb\0\0\0\0\0" > /dev/hidg0 && \
  echo -ne "\0\0\xc\0\0\0\0\0" > /dev/hidg0 && \
  echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0

如果一切正常,你应该会在通过 USB 与树莓派相连的机器上看到如下输出:

hi

写入 HID 接口时卡死

在树莓派 Zero W 上测试 Key Mime Pi 时,我遇到过向/dev/hidg0写入时无限挂起的情况。换了一根 Micro-USB 转 USB-A 数据线后问题就消失了。我怀疑第一根线要么已损坏,要么只支持供电而不支持数据传输。如果你遇到向/dev/hidg0写入时卡死的情况,请尝试更换一根支持数据传输的 USB 数据线(大多数 USB 数据线都支持)。

下一步:嵌入显示输出

远程输入固然有趣,但不太实用。毕竟在向系统输入时,通常也需要看到输出。

我的下一步是从目标电脑捕获 HDMI 输出,并将其嵌入到 Key Mime Pi 的网页界面中。这样,我只需把树莓派插到一台无头服务器上,就能在浏览器中获得一个虚拟控制台。本质上,这就是一台低成本、可 hack 的KVM over IP 设备

我已经用ffplay和一个HDMI 延长器做出了可用的原型,但仍在研究如何将所有功能整合到单个浏览器窗口中,并实现低延迟。

显示远程机器屏幕的 Key Mime Pi 截图

我可以通过HDMI 延长器查看远程机器的显示器输出,但仍在努力将所有功能整合到浏览器中。

想要开箱即用的套件?

更新:2020 年 6 月 29 日

我现在已经有了一套可在浏览器窗口内同时捕获目标设备视频输出并发送按键的完整方案。

kvmpi-bios.gif

详细的后续文章很快就会发布,在此期间,你可以预订包含搭建 KVM Pi 所需全部配件的预配置套件:

源代码

Key Mime Pi 的代码完全开源,采用宽松的MIT 许可证

  • key-mime-pi:将按键转发到树莓派虚拟键盘的网页服务。
  • ansible-role-key-mime-pi:用于配置树莓派 USB gadget 功能并将网页服务安装为 systemd 服务的 Ansible 角色。

致谢

  • raspberrypisig/pizero-usb-hid-keyboard是我找到的第一份能在树莓派上成功安装虚拟 USB HID 设备的示例代码。
  • Fmstrat/diy-ipmi是本项目的灵感来源,它证明了让树莓派充当 KVM over IP 是可行的。
  • Rafael Medina提供了我所见过的对 HID 协议最通俗易懂的讲解。
  • 感谢让 USB gadget 功能得以实现的 Linux 和 Raspberry Pi OS 开发者们。

本文章由 muse-spark-1.2-contributor 进行翻译

评论