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

Michael Lynch

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

原文は Michael Lynch により に公開されました。 このブログを購読する

最近のRaspberry PiはUSB On-The-Go(USB OTG)に対応しており、キーボードやUSBメモリ、マイクなどのUSBデバイスになりすますことができます。これを活用するため、私はPiを仮想キーボードに変えるオープンソースのウェブアプリを作りました。名前はKey Mime Piです。

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

デモ

必要なもの

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

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

Rufusのスクリーンショット

私はPi用のmicroSDカードの書き込みにRufusを使っていますが、ディスク全体を書き込めるツールなら何でも構いません。

microSDカードのbootパーティションにsshという名前のファイルを置くことでSSHアクセスを有効化し、microSDカードをPi本体に挿入します。無線で接続する場合は、bootパーティションにwpa_supplicant.confファイルを作成する必要もあります。

Piを接続する

USBケーブルをPiのUSB OTGポートに接続します。Pi 4ではUSB-Cポート、Pi Zeroでは「USB」と表示されたMicro-USBポートがそれにあたります。

USB-Cポートにケーブルを挿したPi 4Micro-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のウェブサーバーを起動します:

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ウェブインターフェースのスクリーンショット

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

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

ブラウザからキーストロークを送信するKey Mime Pi

Key Mime Piを使えば、ブラウザ経由でリモートコンピューターにキーストロークを送信できます。

仕組み

USBデバイスエミュレーション

ここでの本当の肝は、LinuxのUSB Human Interface Device(HID)ガジェットドライバです。これにより、ユーザーモードのアプリケーションがあたかもUSBデバイスであるかのようにOSとやり取りできるようになります。

key-mime-pi設定スクリプト/dev/hidg0にファイルパスを作成します。どのプログラムでもこのパスに対して読み書きができ、OSがデータをキーボード信号に変換します。

キーボードになりすますために、PiはUSB HID仕様に従ってOSと通信する必要があります。97ページにわたるキーコードと表が並ぶその仕様書は少々骨が折れますが、キーボード用のプロトコル自体は驚くほどシンプルです。

キーストロークごとに、キーボードは「レポート」と呼ばれる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回に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がキーストロークごとにイベントを生成します。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を使う
  • AC - microUSBアダプタを接続するためにZero2Go Power Adaptorを使う

追記:この問題を解決するために、私はエンジニアリング会社と協力して、PiのUSB-Cポートを2つに分岐させるカスタム基板を作成しました。1つ目のポートはUSB電源入力用で、Piに3Aのフル電力を供給できます。2つ目はUSBデータ出力用で、Piは引き続きUSBキーボードとして振る舞うことができます。

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

TinyPilot Power Connectorを使えば、USB OTG機能を失うことなく、USB-Cポート経由でPiに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

すべてが正常に動作していれば、PiがUSBで接続されているマシンに次の出力が表示されるはずです:

hi

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

Pi Zero WでKey Mime Piをテストした際、/dev/hidg0への書き込みが無限にハングするケースに遭遇しました。別のMicro-USB - USB-Aケーブルに交換したところ問題は解消したので、最初のケーブルは破損していたか、データ転送に対応しておらず電力供給のみに対応していたのだと思います。/dev/hidg0への書き込みがハングする場合は、データ転送に対応したUSBケーブル(ほとんどのUSBケーブルは対応しています)を試してみてください。

次のステップ:画面出力の埋め込み

リモートで入力するのは面白いですが、やや実用的とは言えません。システムに入力する際は、通常出力も見えた方が便利です。

次のステップとして、ターゲットコンピューターのHDMI出力をキャプチャしてKey Mime Piのウェブインターフェースに埋め込むことを考えています。これにより、Piをヘッドレスサーバーに接続し、ブラウザ上で仮想コンソールを利用できるようになります。実質的に、低コストでハッカブルなKVM over IPデバイスになるわけです。

ffplayHDMIエクステンダーを使った動作するプロトタイプはすでにありますが、すべてを低遅延で単一のブラウザウィンドウに収めるソリューションをまだ開発中です。

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

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

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

追記:2020年6月29日

現在、ターゲットデバイスの映像出力をキャプチャしつつ、ブラウザウィンドウ内でキーストロークも送信できるソリューションが完成しました。

kvmpi-bios.gif

詳細な続報は近日公開予定ですが、それまでの間、独自のKVM Piを構築するために必要なものがすべて含まれた設定済みKVM Piキットを予約注文できます:

ソースコード

Key Mime Piのコードは、寛容なMITライセンスの下で完全にオープンソースとして公開されています:

  • key-mime-pi:キーストロークをPiの仮想キーボードに転送するウェブサーバー。
  • ansible-role-key-mime-pi:PiのUSBガジェット機能を設定し、ウェブサーバーをsystemdサービスとしてインストールするためのAnsibleロール。

謝辞

  • raspberrypisig/pizero-usb-hid-keyboardは、私のPiで仮想USB HIDデバイスのインストールに初めて成功したサンプルコードでした。
  • Fmstrat/diy-ipmiはこのプロジェクトの着想元であり、PiをKVM over IPとして機能させることが可能であることを証明してくれました。
  • Rafael Medina氏は、私が見つけた中で最も分かりやすいHIDプロトコルの解説を提供してくれました。
  • LinuxおよびRaspberry Pi OSの開発者の皆さんに感謝します。USBガジェット機能を実現してくれました。

この記事は「muse-spark-1.2-contributor」を使用して翻訳されました。

コメント