Key Mime Pi:Raspberry Piをリモートキーボードに変える
近年のRaspberry PiはUSBオン・ザ・ゴー(USB OTG)に対応しており、キーボードやUSBメモリ、マイクなどのUSBデバイスになりすますことができます。これを活用するため、私はPiを偽のキーボードに変えるオープンソースのWebアプリを作りました。Key Mime Piと呼んでいます。
この記事では、Key Mime Piの仕組みと、自分で作る方法を紹介します。
デモ
必要なもの
- USB OTGに対応したRaspberry Pi:
- Raspberry Pi 4(全バリエーション)
- Raspberry Pi Zero W
- Raspberry Pi AおよびA+ (要検証)
- この情報源によると、初期のPiはUSB OTGに対応しているようですが、これらのデバイスはまだ自分ではテストしていません。
- Raspberry Pi OS(旧称Raspbian)
- Stretch以降
- USBケーブル
- Pi 4用:USB-C - USB-A(オス-オス)
- Pi Zero W用:Micro-USB - USB-A(オス-オス)
Raspberry Pi OS Liteをインストールする
まず、microSDカードにRaspberry Pi OS Lite(旧称Raspbian)をインストールします。

私はPiのmicroSDカードへの書き込みにRufusを使っていますが、ディスクイメージを書き込めるツールなら何でも構いません。
microSDカードのブートパーティションにsshという名前のファイルを置いてSSHアクセスを有効にし、microSDカードをPiに挿入します。無線経由で接続する場合は、ブートパーティションにwpa_supplicant.confファイルを作成する必要もあります。
Piを接続する
USBケーブルをPiのUSB OTGポートに接続します。Pi 4ではUSB-Cポートです。Pi Zeroでは「USB」と表示されたMicro-USBポートです。


Raspberry Pi 4(左)ではUSB-Cポートに接続します。Raspberry Pi Zero W(右)ではMicro-USBデータポートに接続します。
USBケーブルのもう一方の端を、キーボードとして接続したいコンピューターに接続します。USB 3.0ポートのほうが出力できる電力が大きいため適していますが、私がテストしたUSB 2.0ポートもすべて問題なく動作しました。
PiはコンピューターのUSBポートから電力を取り込み、起動するはずです。
Key Mime Piをインストールする
Key Mime Piのインストール方法は2つあります。外部ツールを必要としない、昔ながらのbashを使う方法です。もう一つは、少ししゃれた方法として、私が愛用しているオープンソースの構成管理ツールAnsible経由でインストールする方法です。
方法1:純粋なbashを使う
bashシェルから次のコマンドを入力し、Piに接続して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 rebootPiが再起動するまで待ち、もう一度SSHでログインしてKey Mime PiのWebサーバーを起動します。
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方法2: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 sudoKey Mime Piを使う
インストールスクリプトを実行すると、Key Mime Piは次のアドレスで利用できるようになります。
インターフェースは次のようになります。

入力待ち状態のKey Mime Pi Webインターフェース
そして、まるで魔法のように、ブラウザーで入力すると、Piに接続されたマシンにキー入力が現れます。

Key Mime Piを使うと、ブラウザー経由でリモートコンピューターにキー入力を送信できます。
仕組み
USBデバイスエミュレーション
ここで本当の魔法を実現しているのは、LinuxのUSBヒューマンインターフェースデバイス(HID)ガジェットドライバーです。これにより、ユーザーモードのアプリケーションが、USBデバイスであるかのようにOSとやり取りできます。
key-mime-piの設定スクリプトは、/dev/hidg0というファイルパスを作成します。どのプログラムでもこのパスを読み書きでき、OSがデータをキーボード信号に変換します。
キーボードを再現するには、PiがUSB HID仕様に従ってOSと通信する必要があります。キーコードと表が97ページもあるこの文書を読み進めるのは少々大変ですが、キーボードのプロトコルは驚くほど単純だと分かります。
キーを1回押すたびに、キーボードは「レポート」と呼ばれる8バイトのメッセージを送信します。
| バイトインデックス | 用途 |
|---|---|
| 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バイトのゼロのブロックは、アクティブなキーがないことを示します。
上の例では一度に1つのキー入力を送信しましたが、HIDレポートには6キー分の領域があります。つまり、異なるキーであれば、1つのメッセージで最大6つのキー入力を送信できます。
echo -ne "\0\0\x1a\x0b\x04\x17\x18\x13" > /dev/hidg0 && \
echo -ne "\0\0\0\0\0\0\0\0" > /dev/hidg0whatupJavaScriptからHIDへの変換
ブラウザーウィンドウに入力すると、JavaScriptがキー入力ごとにイベントを生成します。Webサイトkeycode.infoでは、この機能が実際にどのように動作するかを見事に確認できます。
JavaScriptのキーイベントにはキーコードが含まれますが、HIDのキーコードとは異なります。幸い、両者にはほぼ1対1の対応関係があります。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, # dKey Mime Piサーバーは、ブラウザーからJavaScriptのキーコードイベントを受け取り、それをHIDコードに変換して、PiのHIDインターフェースである/dev/hidg0に送信します。
最初から最後までの流れは次のとおりです。
- ユーザーがブラウザーでキーを押します。
- ページ上のJavaScriptが、JavaScriptのキーコードをPi上のKey Mime Piサーバーに送信します。
- Key Mime Piサーバーが、JavaScriptのキーコードを対応するHIDコードに変換します。
- Key Mime Piサーバーが、HIDコードを
/dev/hidg0のUSBガジェットインターフェースに送信します。 - PiのUSBケーブルに接続されたコンピューターがこれをキーボード入力として受け取り、画面に文字が表示されます。
電力の問題
テストでは、コンピューターのUSBポートからPiを動かすのに十分な電力が供給されました。しかし、システムログには低電圧の警告が頻繁に現れました。
$ 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)Piは、標準的なUSB 2.0およびUSB 3.0ポートでは、必要な電力を満たすには不十分だと正しく検知していました。
| Raspberry Piのモデル | 必要電力 |
|---|---|
| Pi Zero W | 5 V / 1.2 A |
| Pi 4 | 5 V / 3.0 A |
標準的なUSBポートでは足りません。
| USBポートの種類 | 出力 |
|---|---|
| USB 2.0 | 5 V / 0.5 A |
| USB 3.0 | 5 V / 0.9 A |
私は現在もこの問題の解決策を探しています。まだテストしていない解決策として、次のようなものがあります。
Ethernetポートから電力を取り込むためにPoE HATを使うZero2Go Power Adaptorを使って、ACアダプターをmicroUSBに接続する。
更新:この問題を解決するため、私はエンジニアリング会社と協力し、カスタム回路基板を作りました。この基板はPiのUSB-Cポートを2つに分岐します。一方のポートはUSB電源を受け付けるため、Piに最大3Aの電力を供給できます。もう一方はUSBデータ出力を受け付けるため、PiはUSBキーボードになりすますことができます。


TinyPilot Power Connectorを使うと、USB OTG機能を失わずに、PiがUSB-Cポートから3Aの電力を受け取れるようになります。
重要なのは、この電源コネクターのデータポートにはUSBの電源線がないことです。これにより、コンピューター側の電源とPi側の電源の電圧差によって、望ましくない電流の逆流が発生するのを防げます。
トラブルシューティング
ドライバーが動作しているか確認する
Key Mime Piがキー入力を接続先のマシンに転送できない場合、最初にUSBガジェットが正常に動作しているか確認します。
次のコマンドを試してください。
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経由でPiに接続されたマシンに次の出力が表示されます。
hiHIDインターフェースへの書き込みがハングする
Pi Zero WでKey Mime Piをテストした際、/dev/hidg0への書き込みがいつまでも終わらないケースに遭遇しました。Micro-USB - USB-Aケーブルを別のものに交換すると、問題は解消しました。最初のケーブルが破損していたか、電源専用でデータ通信には対応していなかったのだと思います。/dev/hidg0への書き込みがハングする場合は、データ転送に対応したUSBケーブルを試してください(ほとんどのUSBケーブルは対応しています)。
次のステップ:画面出力の組み込み
リモートで文字を入力するのは楽しいものですが、実用性は少し低いでしょう。システムに入力するときは、通常、出力も見られると便利です。
次のステップとして、接続先コンピューターのHDMI出力をキャプチャーし、Key Mime PiのWebインターフェースに組み込む予定です。そうすれば、ヘッドレスサーバーにPiを接続して、ブラウザー上に仮想コンソールを表示できるようになります。これは本質的には、安価でハック可能なKVM over IPデバイスです。
ffplayとHDMIエクステンダーを使った動作するプロトタイプはできていますが、低遅延で、すべてを1つのブラウザーウィンドウに収める方法にはまだ取り組んでいます。

HDMIエクステンダーを使えばリモートマシンのモニター出力を表示できますが、すべてをブラウザーに統合する作業はまだ続いています。
設定済みキットが欲しいですか?
現在は、接続先デバイスの映像出力をキャプチャーし、キー入力も送信できる、動作するソリューションが完成しています。しかもすべてブラウザーウィンドウ内で行えます。

詳しい続編記事は近日公開予定です。それまでの間、自分でKVM Piを組み立てるために必要なものがすべて含まれた、設定済みKVM Piキットを予約注文できます。
ソースコード
Key Mime Piのコードは、寛容な条件のMITライセンスの下で完全にオープンソースとして公開されています。
- key-mime-pi:ブラウザーからのキー入力をPiの仮想キーボードに転送するWebサーバーです。
- ansible-role-key-mime-pi:PiのUSBガジェット機能を設定し、WebサーバーをsystemdサービスとしてインストールするAnsibleロールです。
謝辞
- raspberrypisig/pizero-usb-hid-keyboardは、私が見つけた中で、Piに仮想USB HIDデバイスを正常にインストールできた最初のサンプルコードでした。
- Fmstrat/diy-ipmiはこのプロジェクトの着想を与えてくれ、PiをKVM over IPとして動作させられることを示してくれました。
- Rafael Medinaは、私が見つけた中で最も読みやすいHIDプロトコルの解説を提供してくれました。
- USBガジェット機能を可能にしてくれたLinuxおよびRaspberry Pi OSの開発者に感謝します。
記事をランダムに読む