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

Michael Lynch

Key Mime Pi:Raspberry Piをリモートキーボードに変える

近年のRaspberry PiはUSBオン・ザ・ゴー(USB OTG)に対応しており、キーボードやUSBメモリ、マイクなどのUSBデバイスになりすますことができます。これを活用するため、私はPiを偽のキーボードに変えるオープンソースのWebアプリを作りました。Key Mime Piと呼んでいます。

この記事では、Key Mime Piの仕組みと、自分で作る方法を紹介します。

デモ

必要なもの

Raspberry Pi OS Liteをインストールする

まず、microSDカードにRaspberry Pi OS Lite(旧称Raspbian)をインストールします。

Rufusのスクリーンショット

私は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ポートです。

USB-Cポートにケーブルを挿したPi 4USB Micro-USBデータポートにケーブルを挿したPi Zero W

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 reboot

Piが再起動するまで待ち、もう一度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 sudo

Key Mime Piを使う

インストールスクリプトを実行すると、Key Mime Piは次のアドレスで利用できるようになります。

インターフェースは次のようになります。

Key Mime PiのWebインターフェースのスクリーンショット

入力待ち状態のKey Mime Pi Webインターフェース

そして、まるで魔法のように、ブラウザーで入力すると、Piに接続されたマシンにキー入力が現れます。

ブラウザーからキー入力を送信するKey Mime 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/hidg0
whatup

JavaScriptから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,  # d

Key Mime Piサーバーは、ブラウザーからJavaScriptのキーコードイベントを受け取り、それをHIDコードに変換して、PiのHIDインターフェースである/dev/hidg0に送信します。

最初から最後までの流れは次のとおりです。

  1. ユーザーがブラウザーでキーを押します。
  2. ページ上のJavaScriptが、JavaScriptのキーコードをPi上のKey Mime Piサーバーに送信します。
  3. Key Mime Piサーバーが、JavaScriptのキーコードを対応するHIDコードに変換します。
  4. Key Mime Piサーバーが、HIDコードを/dev/hidg0のUSBガジェットインターフェースに送信します。
  5. 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 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

私は現在もこの問題の解決策を探しています。まだテストしていない解決策として、次のようなものがあります。

  • Ethernetポートから電力を取り込むためにPoE HATを使う
  • Zero2Go Power Adaptorを使って、ACアダプターをmicroUSBに接続する。

更新:この問題を解決するため、私はエンジニアリング会社と協力し、カスタム回路基板を作りました。この基板はPiのUSB-Cポートを2つに分岐します。一方のポートはUSB電源を受け付けるため、Piに最大3Aの電力を供給できます。もう一方はUSBデータ出力を受け付けるため、PiはUSBキーボードになりすますことができます。

電源コネクターのクローズアップRaspberry PiとmicroUSBケーブルに接続した電源コネクター

TinyPilot Power Connectorを使うと、USB OTG機能を失わずに、PiがUSB-Cポートから3Aの電力を受け取れるようになります。

重要なのは、この電源コネクターのデータポートにはUSBの電源線がないことです。これにより、コンピューター側の電源とPi側の電源の電圧差によって、望ましくない電流の逆流が発生するのを防げます。

:適切なコネクターを使わず、コンピューターに接続した状態でPiに外部電源を供給すると、ハードウェアが損傷するおそれがあります。詳しくはTinyPilot wikiを参照してください。

トラブルシューティング

ドライバーが動作しているか確認する

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に接続されたマシンに次の出力が表示されます。

hi

HIDインターフェースへの書き込みがハングする

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デバイスです。

ffplayHDMIエクステンダーを使った動作するプロトタイプはできていますが、低遅延で、すべてを1つのブラウザーウィンドウに収める方法にはまだ取り組んでいます。

リモートマシンの画面を表示するKey Mime Piのスクリーンショット

HDMIエクステンダーを使えばリモートマシンのモニター出力を表示できますが、すべてをブラウザーに統合する作業はまだ続いています。

設定済みキットが欲しいですか?

更新:2020年6月29日

現在は、接続先デバイスの映像出力をキャプチャーし、キー入力も送信できる、動作するソリューションが完成しています。しかもすべてブラウザーウィンドウ内で行えます。

kvmpi-bios.gif

詳しい続編記事は近日公開予定です。それまでの間、自分で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の開発者に感謝します。

原文は Michael Lynch により に公開されました。

この記事は「gpt-5.6-luna」を使用して翻訳されました。