[後編(US配列対応)] Raspberry Pi Zero 2 W + gRPC + protobuf でターゲットPCに USB キーボード入力を送る

---

前編では **Raspberry Pi Zero 2 W** を **USB OTG** の「HIDキーボード」として設定し、**gRPC + Protobuf** を使ってテキストを受信すると、その文字列を**ターゲットPC**へキー入力として送る一連の仕組みを解説しました。  
しかし、前編のサンプルでは **`convert_char_to_scan_code()`** 関数で “`a`” のみしか対応していなかったため、他の文字を送ることができないという課題がありました。

本記事 [後編(US配列対応)] では、この問題を解消するため、**「USキーボード配列」で使用される一般的な文字群 (英字・数字・記号・エンター・バックスペースなど) に対応したスキャンコード変換関数**を紹介します。  
これにより、実際に英数字や各種記号を送れるようになり、キーボード入力としての実用性が一気に高まります。

---

## 前提: 前編のおさらい

1. **Raspberry Pi Zero 2 W** 上で **USB OTG ガジェット機能**を有効化。  
   - USBケーブルでつないだ **ターゲットPC** からは「USBキーボード」と認識される。
2. **Python + gRPCサーバ**を Pi 上で動かし、ネットワーク (Wi-Fi または USB Ethernet) で文字列を受信。  
3. 受信した文字を `/dev/hidg0` に書き込むことで、ターゲットPC 上でキー入力が行われる。  

前編サンプルでは以下のような実装がありました:

```python
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)** を送ります。  
- 例:  
  1. `b"\x00\x00\x04\x00\x00\x00\x00\x00"` → “a” を押す  
  2. `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)などとは異なるので注意してください。）

```python
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. 使い方の例

```python
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](https://usb.org/document-library/hid-usage-tables-121)  
- [前編: Raspberry Pi Zero 2 W + gRPC + Protobuf でターゲットPCに USBキーボード入力を送る](前編の記事URLなど)  
- [hid-gadget-test (Linux USBテストツールの一部)](https://github.com/torvalds/linux/tree/master/tools/usb/usbtest)  
- [Raspberry Pi 公式ドキュメント - USB ガジェットモード](https://www.raspberrypi.com/documentation/)

---

以上