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

---

前編・後編 (US配列) では、**Raspberry Pi Zero 2 W** を **USB OTG ガジェットモード**で「キーボード(HID)」として認識させ、ネットワーク経由で受け取ったテキストをターゲットPCへ入力する仕組みを構築しました。

しかし、**前編・後編 (US配列)** の実装例は主に**US配列**ベースのマッピングだったため、日本語キーボード(JIS配列)を使う環境では**記号の配置ズレ**や**IMEキーの扱い**など、追加の調整が必要になります。

本記事 **続編(JIS配列対応)** では、**日本語(JIS)配列**で使われるキーや記号を極力網羅したサンプルを紹介します。特に「無変換」「変換」「半角/全角」などのIME制御キーや、大文字入力(Shift)時の記号、Fキー・矢印キーなどもまとめています。**環境によってはそのまま動作しないキーもある**ため、参考にしながら調整いただければ幸いです。

---

## 前提

- **Raspberry Pi Zero 2 W** で USB ガジェットモードを有効にし、`/dev/hidg0` へ書き込みできる状態 (前編・後編参照)。  
- ターゲットPC の OS が **「日本語キーボード(106/109キー)として認識している」**こと。  
  - Windows: デバイスマネージャやキーボード設定で「日本語キーボード」と認識されているか確認  
  - Linux/Mac: XKB設定やシステム設定で `jp106`/JIS配列になっていること  
- Python でスクリプトを動かせる環境が整っている(例: Raspberry Pi OS)。

---

## なぜJIS配列だと難しい？

- **US配列** に比べて、記号キーの配置が複雑かつ、OS依存要素が多いため。  
- 日本語IMEsでの「半角/全角キー」「無変換/変換キー」の扱いが統一されていない。  
- **JIS配列のキーコード** と **OSが期待する配列** がずれると、入力結果が変わってしまう。  
- 特に「円記号(\\¥)」や「Shift + 数字キー記号」のマッピングは環境によっては異なります。

---

## サンプル実装例 (改良版)

以下のコードは、**JIS配列＋IME制御キーも含め、できる限り広く対応**するためのサンプルです。  

- **大文字 (A–Z)** を入力する際は、自動的に **Shift + 対応する小文字** を送信します。  
- **Shiftが必要な記号** (例: `!`, `"` , `#`, `(`, `)`, など) もマッピング済み。  
- **IMEキー (半角/全角, 無変換, 変換, カタカナ/ひらがな)** も追加しています。  
- **Fキー・矢印キー** などは **JIS/US共通** なので、同じUsage IDを使用しています。  

```python
import time

#
# 修飾キーのビット定義 (modifier)
#
MOD_LCTRL   = 0x01
MOD_LSHIFT  = 0x02
MOD_LALT    = 0x04
MOD_LGUI    = 0x08
MOD_RCTRL   = 0x10
MOD_RSHIFT  = 0x20
MOD_RALT    = 0x40
MOD_RGUI    = 0x80

#
# JIS配列で「Shift不要」の文字（英小文字・数字・一部記号など）
#
KEYMAP_JIS = {
    # --- 英小文字 ---
    'a': 0x04, 'b': 0x05, 'c': 0x06, 'd': 0x07,
    'e': 0x08, 'f': 0x09, 'g': 0x0A, 'h': 0x0B,
    'i': 0x0C, 'j': 0x0D, 'k': 0x0E, 'l': 0x0F,
    'm': 0x10, 'n': 0x11, 'o': 0x12, 'p': 0x13,
    'q': 0x14, 'r': 0x15, 's': 0x16, 't': 0x17,
    'u': 0x18, 'v': 0x19, 'w': 0x1A, 'x': 0x1B,
    'y': 0x1C, 'z': 0x1D,

    # --- 数字 (Shift不要) ---
    '1': 0x1E, '2': 0x1F, '3': 0x20, '4': 0x21,
    '5': 0x22, '6': 0x23, '7': 0x24, '8': 0x25,
    '9': 0x26, '0': 0x27,

    # --- 制御・空白系 ---
    '\n': 0x28,      # Enter
    '\r': 0x28,      # Enter(別表現)
    '\t': 0x2B,      # Tab
    ' ': 0x2C,       # Space
    '\b': 0x2A,      # Backspace
    '\x1b': 0x29,    # ESC (ASCII 0x1B)

    # --- JIS配列での「そのまま」記号 ---
    '-': 0x2D,   # '-'
    '^': 0x2E,   # '^'
    '@': 0x2F,   # '@'
    '[': 0x30,   # '['
    ']': 0x31,   # ']' (実際のキーボード刻印では省略されがち)
    ';': 0x33,   # ';'
    ':': 0x34,   # ':'
    ',': 0x36,   # ','
    '.': 0x37,   # '.'
    '/': 0x38,   # '/'

    # --- 円記号/バックスラッシュ問題 ---
    #   環境によって円記号が 0x31 や 0x89 など異なる実装がある。
    #   ここでは便宜上 '\' を 0x31, '¥' を 0x89 にしてみる。
    '\\': 0x31,  # バックスラッシュ(US配列で']'位置相当)
    '¥': 0x89,   # Keyboard International3 などを使う例
}

#
# JIS配列で「Shiftが必要」な記号
# (例: '!' は Shift + '1', '"' は Shift + '2' 等)
#
SHIFTKEYMAP_JIS = {
    '!': 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'
    '=': 0x2D,  # Shift + '-'
    '~': 0x2E,  # Shift + '^'
    '`': 0x2F,  # Shift + '@' (バッククォート)
    '{': 0x30,  # Shift + '['
    '+': 0x33,  # Shift + ';'
    '*': 0x34,  # Shift + ':'
    '<': 0x36,  # Shift + ','
    '>': 0x37,  # Shift + '.'
    '?': 0x38,  # Shift + '/'
    '|': 0x89,  # Shift + '¥' (例: '|')
}

#
# 日本語特有キー (IME切替など)
# 実際に機能するかは OS＆IME設定次第
#
SPECIAL_KEYS_JIS = {
    "HANKAKU_ZENKAKU": 0x35,  # 半角/全角キー(IME ON/OFF)の例
    "MUHENKAN":       0x88,   # 無変換
    "HENKAN":         0x8A,   # 変換
    "KATAHIRA":       0x90,   # カタカナ/ひらがな
}

#
# Fキー、矢印キーなど (JIS/US共通)
#
FUNCTION_KEYS = {
    "F1": 0x3A, "F2": 0x3B, "F3": 0x3C, "F4": 0x3D,
    "F5": 0x3E, "F6": 0x3F, "F7": 0x40, "F8": 0x41,
    "F9": 0x42, "F10": 0x43, "F11": 0x44, "F12": 0x45,

    "PRINT_SCREEN": 0x46,
    "SCROLL_LOCK":  0x47,
    "PAUSE":        0x48,

    "INSERT":       0x49, "HOME":       0x4A, "PAGE_UP":    0x4B,
    "DELETE":       0x4C, "END":        0x4D, "PAGE_DOWN":  0x4E,
    "RIGHT_ARROW":  0x4F, "LEFT_ARROW": 0x50,
    "DOWN_ARROW":   0x51, "UP_ARROW":   0x52,
}

def convert_jis_key(ch: str) -> tuple[int,int]:
    """
    1文字/特殊キー名を受け取り、(modifier, usage_id) を返す。
    usage_id=0 は押下なしとみなす。
    """

    # --- 大文字A-Z => Shift + 小文字キー
    if 'A' <= ch <= 'Z':
        keycode = KEYMAP_JIS.get(ch.lower(), 0)
        if keycode != 0:
            return (MOD_LSHIFT, keycode)
        else:
            return (0, 0)

    # --- 小文字/数字/記号(Shift不要)
    if ch in KEYMAP_JIS:
        return (0, KEYMAP_JIS[ch])

    # --- Shift必須記号
    if ch in SHIFTKEYMAP_JIS:
        return (MOD_LSHIFT, SHIFTKEYMAP_JIS[ch])

    # --- 日本語特有キー
    if ch in SPECIAL_KEYS_JIS:
        return (0, SPECIAL_KEYS_JIS[ch])

    # --- Fキー・矢印・他特殊キー
    if ch in FUNCTION_KEYS:
        return (0, FUNCTION_KEYS[ch])

    # --- 未定義
    return (0, 0)

def make_report(modifier: int, usage_id: int) -> bytes:
    """
    8バイトのHIDレポートを組み立てる。
    [modifier, 0x00, usage_id, 0x00, 0x00, 0x00, 0x00, 0x00]
    """
    return bytes([modifier, 0x00, usage_id, 0x00, 0x00, 0x00, 0x00, 0x00])

def send_jis_keys(sequence):
    """
    引数 sequence は:
      - 文字列("Hello!\n")なら1文字ずつ処理
      - リスト(["F1","LEFT_ARROW","HANKAKU_ZENKAKU"])なら各要素を1キーとみなす
    """
    if isinstance(sequence, str):
        items = list(sequence)
    else:
        items = sequence

    with open("/dev/hidg0", "wb") as f:
        for ch in items:
            mod, usage = convert_jis_key(ch)
            if usage != 0:
                # 押下
                f.write(make_report(mod, usage))
                time.sleep(0.01)
                # 離す
                f.write(make_report(0, 0))
                time.sleep(0.01)
```

### 使用例

```python
# 例1: 通常の文字列送信
send_jis_keys("Hello, World!\n")

# 例2: 半角/全角キーでIME ON/OFFトグルを試す
send_jis_keys(["HANKAKU_ZENKAKU"])

# 例3: 大文字＋記号などを混在
send_jis_keys("ABC!@#(JIS)\n")

# 例4: Fキーや矢印キー
send_jis_keys(["F1", "LEFT_ARROW", "RIGHT_ARROW"])
```

---

## IME周りの動作について

- **半角/全角 (HANKAKU_ZENKAKU)**  
  - WindowsではIMEのON/OFFトグルとして働くことが多いですが、設定やIMEによっては動作が異なる場合もあります。
- **無変換 / 変換 / カタカナ/ひらがな**  
  - OS・IMEによって挙動はまちまち。全く反応しないIMEもあれば、変換モードを切り替えるIMEも。  
- **IMEでの日本語入力**  
  - `"あいうえお"` のように **直接全角文字を送る** ことはできません。**キーボードはあくまでキー押下イベント**を送るため、最終的に文字を確定するのはOS側のIMEです。

---

## Fキー、矢印キー、Home/End/Insert/Delete

- これらは **JIS/US配列に依存しないUsage ID** を使います。  
- 多くの場合、そのまま正常に動作しますが、一部キー(ScrollLockなど)が期待通りに動かない例もあります。  
- 必要に応じて OS側の設定やキーマッピングを確認してください。

---

## よくある質問

1. **「¥」が入力できない / 「\」になってしまう**  
   - Windows日本語環境では、ASCII `0x5C`(バックスラッシュ) をフォント上「円記号」で表示する仕様があり混乱しがち。  
   - キーコードレベルとフォント表示が一致しない例も多いので、レジストリやIMEの設定を調整する場合があります。

2. **大文字や記号が合わない**  
   - ターゲットPC側が **US配列** として認識している可能性。必ず **JIS配列(106/109キー)** になっているか確認してください。

3. **IMEキー(無変換/変換/カタカナ/ひらがな等)が効かない**  
   - IMEの設定で無効化されている / 別のキー割り当てになっている可能性があります。  
   - WindowsやLinuxのIME設定を見直してみてください。

4. **Unicode文字や絵文字を入力したい**  
   - キー入力レベルではASCIIやJP106配列の範囲しか送れません。絵文字や特殊文字は、OS側のIMEやソフトウェアで入力対応している必要があります。

---

## まとめ

- **JIS配列** で記号や大文字入力、IME制御キーまで幅広くカバーするには、**相応のマッピング辞書**が必要です。  
- 本記事のサンプルコードでは「Shift記号」や「大文字(A–Z)」も考慮した**改良版マップ**を用意しています。  
- ただし、OSやIME、キーボード固有の違いによって挙動が変わる場合があるため、**自環境に合わせて微調整** してください。  
- **Raspberry Pi Zero 2 W** を USBキーボードとしてエミュレートし、遠隔で入力する用途などにぜひ活用してみてください。

---

## 参考リンク

- [USB HID Usage Tables (公式ドキュメント)](https://usb.org/document-library/hid-usage-tables-121)  
- [Microsoft Docs: Keyboard Scan Code Specification](https://learn.microsoft.com/en-us/windows-hardware/drivers/hid/keyboard-and-keypad-page-usages)  
- [Raspberry Pi Zero 2 W で USBキーボードをエミュレート（前編・後編）](前編・後編の記事URL)  
- [Linuxのキーコード設定 (XKB)](https://wiki.archlinux.org/title/X_keyboard_extension) など  

---

以上