前編では Raspberry Pi Zero 2 W を USB OTG の「HIDキーボード」として設定し、gRPC + Protobuf を使ってテキストを受信すると、その文字列をターゲットPCへキー入力として送る一連の仕組みを解説しました。
しかし、前編のサンプルでは convert_char_to_scan_code()
関数で “a
” のみしか対応していなかったため、他の文字を送ることができないという課題がありました。
本記事 [後編(US配列対応)] では、この問題を解消するため、「USキーボード配列」で使用される一般的な文字群 (英字・数字・記号・エンター・バックスペースなど) に対応したスキャンコード変換関数を紹介します。
これにより、実際に英数字や各種記号を送れるようになり、キーボード入力としての実用性が一気に高まります。
前提: 前編のおさらい
-
Raspberry Pi Zero 2 W 上で USB OTG ガジェット機能を有効化。
- USBケーブルでつないだ ターゲットPC からは「USBキーボード」と認識される。
- Python + gRPCサーバを Pi 上で動かし、ネットワーク (Wi-Fi または USB Ethernet) で文字列を受信。
- 受信した文字を
/dev/hidg0
に書き込むことで、ターゲットPC 上でキー入力が行われる。
前編サンプルでは以下のような実装がありました:
def convert_char_to_scan_code(ch):
if ch == 'a':
return b"\x00\x00\x04\x00\x00\x00\x00\x00"
else:
return b"\x00\x00\x00\x00\x00\x00\x00\x00"
このため、'a'
以外の文字はすべて“押下なし”扱いになり、全く入力されませんでした。
1. USB HIDキーボードで他の文字を送る仕組み
1-1. HIDレポート
USBキーボードでは、8バイトのレポート (ブートプロトコル準拠の簡易形式) を通じてキー入力を伝えます。
- 1バイト目: modifier (Shift, Ctrl, Alt, GUI などの同時押しビット)
- 2バイト目: 予約 (常に0x00)
- 3〜8バイト目: 同時押ししているキーのスキャンコード(最大6個)。
1文字分の入力であれば、3バイト目
にスキャンコードを入れ、4〜8バイト目は 0x00 のままという形がシンプルです。
1-2. 押下とリリース
キー入力を再現するには、押下レポートを送ってから、すぐにリリースレポート (全キー0x00) を送ります。
- 例:
-
b"\x00\x00\x04\x00\x00\x00\x00\x00"
→ “a” を押す -
b"\x00\x00\x00\x00\x00\x00\x00\x00"
→ キーを離す
-
これを連続で行えば、ターゲットPC から見ると高速タイピングしているように見えます。
1-3. 大文字や記号
大文字 'A'
は 小文字 'a'
と同じスキャンコードを使い、modifier の Shiftビット (0x02) を立てて送ります。
記号類も US配列だと Shift + 数字キー など複数の組み合わせがあります。
たとえば '!'
は "Shift + '1'" という形なので、modifier=Shift, keycode=0x1E (='1') になります。
2. 拡張版 convert_char_to_scan_code()
(US配列向け)
以下のコードは、英小文字・英大文字・数字・記号・スペース・タブ・改行などを広くカバーしたサンプルです。
(日本語キーボード配列(JIS)などとは異なるので注意してください。)
import time
# Modifier ビットマスク
MOD_SHIFT = 0x02 # Left Shift
def convert_char_to_scan_code(ch: str) -> bytes:
"""
`ch` (1文字) を USB HID キーボード用 8バイトレポートに変換する。
主に US配列を想定。英字・数字・記号など基本的なASCII文字をサポート。
"""
# 1) 英小文字 a-z
if 'a' <= ch <= 'z':
# 'a' (0x04) ~ 'z' (0x1D)
keycode = ord(ch) - ord('a') + 0x04
modifier = 0x00
return make_report(modifier, keycode)
# 2) 英大文字 A-Z (Shift + 対応する小文字)
if 'A' <= ch <= 'Z':
keycode = ord(ch.lower()) - ord('a') + 0x04
modifier = MOD_SHIFT
return make_report(modifier, keycode)
# 3) 数字 '1' ~ '9'
if '1' <= ch <= '9':
# '1' (0x1E) ~ '9' (0x26)
keycode = ord(ch) - ord('1') + 0x1E
return make_report(0x00, keycode)
# 4) '0'
if ch == '0':
# '0' -> 0x27
return make_report(0x00, 0x27)
# 5) シンプル記号
simple_map = {
' ': 0x2C, # space
'-': 0x2D,
'=': 0x2E,
'[': 0x2F,
']': 0x30,
'\\': 0x31, # バックスラッシュ
';': 0x33,
'\'': 0x34,
'`': 0x35,
',': 0x36,
'.': 0x37,
'/': 0x38,
'\t': 0x2B, # tab
'\n': 0x28, # LF -> Enter
'\r': 0x28, # CR -> Enter
}
if ch in simple_map:
return make_report(0x00, simple_map[ch])
# 6) Shiftが必要な記号
shift_map = {
'!': 0x1E, # Shift + '1'
'@': 0x1F, # Shift + '2'
'#': 0x20, # Shift + '3'
'$': 0x21, # Shift + '4'
'%': 0x22, # Shift + '5'
'^': 0x23, # Shift + '6'
'&': 0x24, # Shift + '7'
'*': 0x25, # Shift + '8'
'(': 0x26, # Shift + '9'
')': 0x27, # Shift + '0'
'_': 0x2D, # Shift + '-'
'+': 0x2E, # Shift + '='
'{': 0x2F, # Shift + '['
'}': 0x30, # Shift + ']'
'|': 0x31, # Shift + '\\'
':': 0x33, # Shift + ';'
'"': 0x34, # Shift + '\''
'~': 0x35, # Shift + '`'
'<': 0x36, # Shift + ','
'>': 0x37, # Shift + '.'
'?': 0x38, # Shift + '/'
}
if ch in shift_map:
return make_report(MOD_SHIFT, shift_map[ch])
# 7) 特殊キー例
if ch == '\b':
# バックスペース
return make_report(0x00, 0x2A)
if ch == '\x1b':
# ESC (ASCII 27)
return make_report(0x00, 0x29)
# 8) 未定義文字
# 今回は押下なし(0x00)を返す
return make_report(0x00, 0x00)
def make_report(modifier: int, keycode: int) -> bytes:
"""
8バイトのHIDキーボードレポートを作成する。
byte0 = modifier, byte1 = reserved(0x00), byte2 = keycode, 3..7 = 0x00
"""
return bytes([modifier, 0x00, keycode, 0x00, 0x00, 0x00, 0x00, 0x00])
def send_text_as_keys(text: str):
"""
複数文字を連続で入力させるデモ関数。
各文字について:
1) convert_char_to_scan_code() で押下レポート作成
2) /dev/hidg0 に書き込み
3) リリースレポートを送信
4) 微小ウェイト
"""
with open("/dev/hidg0", "wb") as f:
for ch in text:
press = convert_char_to_scan_code(ch)
f.write(press)
# リリース
f.write(b"\x00\x00\x00\x00\x00\x00\x00\x00")
time.sleep(0.01)
2-1. 使い方の例
from my_hid_keyboard import send_text_as_keys
send_text_as_keys("Hello, World!\n")
send_text_as_keys("This is a test: 12345!\n")
send_text_as_keys("Backspace test\b<- done.\n")
-
Hello, World!
や12345!
などがターゲットPCへ順番に入力されます。 - 中で
time.sleep(0.01)
を入れているので、10msごとに押下→リリースを送っているイメージです。
3. よくある質問やつまずきポイント
Q1. 日本語キーボードで記号配置がズレる
- ここで示したマッピングは US配列 を想定しています。
- ターゲットPCが日本語配列だと、記号がズレて入力される場合があります。
- 対処法としては (a) ターゲットPC側をUS配列設定にする、あるいは (b) JIS配列用のスキャンコードを組む などが考えられます。
Q2. 超高速で送ったら文字が欠ける
- 短い間隔で押下→離すを繰り返すと、ターゲットPCによっては取りこぼしが発生する可能性があります。
-
time.sleep(0.01)
を0.02
や0.05
に増やすなど、適宜調整してください。
Q3. 他の特殊キー (F1〜F12、Ctrlなど) を使いたい
-
convert_char_to_scan_code()
内で F1 (0x3A) や Ctrl (modifier=0x01) などを定義することもできます。 - 左Ctrl は 0x01、左Shift は 0x02、左Alt は 0x04、左GUI (Windowsキー) は 0x08 といった具合にmodifierビットを足し合わせられます。
Q4. 全角文字 (日本語など) はどうなる?
- 標準的なHIDキーボードでは ASCII以外の拡張文字を入力するには、OS側のIME などを通す必要があります。
- そのため、基本は ASCIIベースのキーシーケンス しか直接送れません。IMEをOn/Offする場合は OSごとの特殊キー操作になるため、実装がさらに複雑になります。
4. 最後に: 発展的な活用例
-
(1) gRPC 以外の通信
- HTTP / WebSocket / MQTT など、Pi 側の受け口を好きなプロトコルにしてもOK。
- 目的は「テキスト →
/dev/hidg0
への書き込み」なので、手段は問いません。
-
(2) ファームウェア化
- Raspberry Pi Zero 2 W 上に Linux を載せるのではなく、Arduino系(ATmega32U4) や RP2040 (Pico) などの組み込みでHID実装する方法もあります。
- ただしネットワークや高度なソフトウェア環境が必要な場合、Pi Zero 2 W が便利。
-
(3) セキュリティ・不正利用の防止
- gRPCサーバやネットワークを公開してしまうと、誰でもターゲットPCにキー送信できるリスクがあります。
- 必要に応じてファイアウォールやVPN、認証を実装しましょう。
5. まとめ
- 本記事では
convert_char_to_scan_code()
を拡張し、US配列における英数字・記号・改行やバックスペースなど幅広い文字をキー入力で送信する実装例を示しました。 - 前編 で構築した「Pi Zero 2 W を USB HID キーボードにして、gRPC でテキストを受信 → ターゲットPCに送信」の仕組みに組み込めば、実用度が大幅にアップします。
- さらに日本語配列やFキー、IME制御などを突き詰める場合は、キーマップ拡張やOS依存の処理を追加することになるでしょう。
以上で、前編の 「a しか送れない問題」 は解消され、多数の文字に対応できるようになりました。前編と後編を合わせてお読みいただくことで、リモートでターゲットPCに文字入力を送り込むシステムの全体像がより明確になるはずです。ぜひ、各自の用途に合わせてアレンジしてください。
参考リンク
- USB HID Usage Tables
- 前編: Raspberry Pi Zero 2 W + gRPC + Protobuf でターゲットPCに USBキーボード入力を送る
- hid-gadget-test (Linux USBテストツールの一部)
- Raspberry Pi 公式ドキュメント - USB ガジェットモード
以上