0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【後編(マウス追加)】Raspberry Pi Zero 2 W + gRPC + protobuf で「キーボード&マウス操作」をターゲットPCに送る仕組み

Last updated at Posted at 2025-01-08

概要

  • 前編 では、Raspberry Pi Zero 2 WUSB OTG ガジェットモード(HIDキーボード) として認識させ、gRPC + protobuf によってリモート(ネット経由)から文字列を受け取り、ターゲットPC へキーボード入力を注入するシステムを構築しました。
  • 本記事 (前編+続編の統合版) では、さらに「マウス操作」も同じUSBガジェットに追加し、キーボード+マウス の複合HIDとして Target PC に見せる仕組みを紹介します。
  • これにより、gRPC呼び出しで「キー入力」だけでなく「マウス移動・クリック」もターゲットPCに送れる強力なリモート制御が可能になります。

1. 全体構成イメージ

  1. Raspberry Pi Zero 2 W

    • USB OTGガジェット:キーボード+マウス(HID) を合成
    • gRPCサーバ:SendText() / SendMouse() RPC でキー操作&マウス操作リクエストを受け取り
    • /dev/hidg0 (キーボード用) と /dev/hidg1 (マウス用) に書き込む
  2. ターゲットPC

    • Pi Zero 2 W が「キーボード&マウスを備えたUSBデバイス」として認識される
    • 受け取ったキー入力やマウス移動がOS上で反映される
  3. クライアント (Windows / Linux / Mac など)

    • ネットワーク経由で Pi に gRPC リクエストを送る
    • たとえば Python 版 stub.SendText(...) / stub.SendMouse(...)

ファイル構成例(フォルダmy_hid_project):

my_hid_project/
  ├─ keyboard.proto         // gRPC定義 (SendText & SendMouse)
  ├─ server.py              // Pi側サーバ (キーボード+マウス両対応)
  ├─ client.py              // クライアント (キーボード+マウス両対応)
  ├─ keyboard_report.desc   // HIDキーボードのレポートデスクリプタ (ブートプロトコル等)
  ├─ mouse_report.desc      // HIDマウスのレポートデスクリプタ
  └─ ... (その他設定ファイル等)

2. USBガジェット設定(キーボード+マウス)

2-1. config.txt / cmdline.txt

  • /boot/config.txt に:
    dtoverlay=dwc2
    
  • /boot/cmdline.txtrootwait のすぐ後に:
    modules-load=dwc2,libcomposite
    
  • 再起動後、dwc2 & libcomposite が有効になり、PiをUSBデバイスモードで設定できる。

2-2. 複合HIDスクリプト例

sudo modprobe libcomposite
cd /sys/kernel/config/usb_gadget
sudo mkdir myhid
cd myhid

# ベンダーID/プロダクトIDなど共通情報
echo 0x1d6b > idVendor
echo 0x0104 > idProduct
echo 0x0200 > bcdUSB
echo 0x0100 > bcdDevice

# 文字列記述子
mkdir strings/0x409
echo "1234567890" > strings/0x409/serialnumber
echo "MyPiVendor" > strings/0x409/manufacturer
echo "PiKeyboardMouse" > strings/0x409/product

# config
mkdir configs/c.1
mkdir configs/c.1/strings/0x409
echo "Config 1: Keyboard+Mouse" > configs/c.1/strings/0x409/configuration
echo 120 > configs/c.1/MaxPower

# 1) キーボード (hid.usb0)
mkdir functions/hid.usb0
echo 1 > functions/hid.usb0/protocol    # 1=キーボード
echo 1 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
cat /home/pi/keyboard_report.desc > functions/hid.usb0/report_desc
ln -s functions/hid.usb0 configs/c.1/

# 2) マウス (hid.usb1)
mkdir functions/hid.usb1
echo 2 > functions/hid.usb1/protocol    # 2=マウス
echo 1 > functions/hid.usb1/subclass
echo 4 > functions/hid.usb1/report_length
cat /home/pi/mouse_report.desc > functions/hid.usb1/report_desc
ln -s functions/hid.usb1 configs/c.1/

# バインド
ls /sys/class/udc
echo "20200000.usb" > UDC
  • keyboard_report.desc:キーボード用レポートデスクリプタ(ブートプロトコル8バイト等)
  • mouse_report.desc:マウス用(4バイト程度、[buttons, x, y, wheel])
  • 実行後、USBケーブルでターゲットPCに繋ぐと「キーボード & マウス」の複合デバイスとして認識される。

3. .proto ファイル (keyboard.proto)

3-1. 前編の「SendText」+続編の「SendMouse」両方

syntax = "proto3";

package hid;

service KeyboardService {
  // 1) キーボード入力
  rpc SendText(TextMessage) returns (Empty);

  // 2) マウス操作
  rpc SendMouse(MouseMessage) returns (Empty);
}

message TextMessage {
  string text = 1;
}

message MouseMessage {
  // 移動量 (相対), 左ボタン押下/離し
  int32 dx = 1;
  int32 dy = 2;
  bool leftDown = 3; 
  bool leftUp = 4;   
  // 必要に応じて右クリック, 中クリック, ホイールなど拡張
}

message Empty {}

ポイント:

  • 前編で使った SendText / TextMessage はそのまま。
  • MouseMessageSendMouse()追加して、一つのサービス KeyboardService で両方対応。

4. サーバプログラム (server.py)

4-1. 前編でのキーボード部

  • /dev/hidg0 への書き込み関数(ASCIIをキーコードに変換 → ファイルにバイナリ出力)
  • SendText(TextMessage) RPC

4-2. 続編でマウスを追加

  • /dev/hidg1 を使う
  • SendMouse(MouseMessage) RPC

4-3. 統合した最終例

import grpc
from concurrent import futures
import keyboard_pb2
import keyboard_pb2_grpc

# HID デバイスファイル
HID_KEYBOARD = "/dev/hidg0"
HID_MOUSE    = "/dev/hidg1"

def send_text_as_keys(text):
    # 前編: テキストをキーボード入力に変換
    with open(HID_KEYBOARD, "wb") as f:
        # 例: 8バイトレポート [modifier, ..., keycodes...]
        # ASCII→キーコード変換をここに書く
        pass

def send_mouse(dx, dy, leftDown, leftUp):
    # 4バイト [buttons, x, y, wheel=0]
    buttons = 0
    if leftDown:
        buttons |= 0x01  # 左ボタン(ビット0)

    # dx, dy は ±127 程度にクリップ
    def clamp(v):
        return max(-127, min(127, v))
    bx = clamp(dx) & 0xFF
    by = clamp(dy) & 0xFF

    report = bytes([buttons, bx, by, 0x00])
    with open(HID_MOUSE, "wb") as f:
        f.write(report)

class KeyboardServiceServicer(keyboard_pb2_grpc.KeyboardServiceServicer):

    def SendText(self, request, context):
        send_text_as_keys(request.text)
        return keyboard_pb2.Empty()

    def SendMouse(self, request, context):
        send_mouse(request.dx, request.dy, request.leftDown, request.leftUp)
        return keyboard_pb2.Empty()

def serve():
    server = grpc.server(futures.ThreadPoolExecutor(max_workers=1))
    keyboard_pb2_grpc.add_KeyboardServiceServicer_to_server(
        KeyboardServiceServicer(), server
    )
    server.add_insecure_port('[::]:50051')
    server.start()
    print("Server started on port 50051")
    server.wait_for_termination()

if __name__ == "__main__":
    serve()

挿入ポイント:

  1. send_text_as_keys() (前編) → そのままコピーし、このファイルのトップレベルに追加。
  2. send_mouse() (新規) をその下に配置。
  3. KeyboardServiceServicer の中に SendText()(前編) と SendMouse()(今回追加) を並べて実装する。

5. クライアントプログラム (client.py)

5-1. 前編のキーボード送信

import grpc
import keyboard_pb2
import keyboard_pb2_grpc

def main():
    channel = grpc.insecure_channel('192.168.x.y:50051')
    stub = keyboard_pb2_grpc.KeyboardServiceStub(channel)

    # キーボード入力
    stub.SendText(keyboard_pb2.TextMessage(text="Hello from client!"))

if __name__ == "__main__":
    main()

5-2. 続編のマウス送信を追加

def main():
    channel = grpc.insecure_channel('192.168.x.y:50051')
    stub = keyboard_pb2_grpc.KeyboardServiceStub(channel)

    # 1) キーボード
    stub.SendText(keyboard_pb2.TextMessage(text="Hello from client!"))

    # 2) マウス例: 右方向へ50px移動 + 左ボタン押し下げ
    stub.SendMouse(keyboard_pb2.MouseMessage(dx=50, dy=0, leftDown=True, leftUp=False))
    # ボタン離し
    stub.SendMouse(keyboard_pb2.MouseMessage(dx=0, dy=0, leftDown=False, leftUp=True))

if __name__ == "__main__":
    main()

挿入ポイント:

  • 前編のまま SendText(...) 呼び出しを残しておき、
  • 続けて SendMouse(...) 呼び出し追加すればOK。

6. 注意点

  1. マウスのレポートデスクリプタ

    • mouse_report.desc の中身と send_mouse() のレポート構造を一致させる。
    • 例えば 4バイト [buttons, X, Y, wheel] なら、デスクリプタも同じ順序で定義。
  2. クリックのタイミング

    • leftDown=True, leftUp=False → ボタン押しっぱなし
    • すぐ後に leftDown=False, leftUp=True で離す
    • ドラッグ操作をする場合は、押しっぱなし状態で複数の移動 RPC を発行など工夫が必要。
  3. 連続移動

    • 大量の RPC 呼び出しはネットワーク負荷が高いかもしれないので、適度にインターバルを入れるなど調整が必要。
  4. ターゲットPC 側の認識

    • Pi Zero 2 W は 1つのUSBポートから「Keyboard + Mouse」HIDデバイスを出すため、正常に合成デバイスとして認識されるかチェック。
    • Windows / Linux / MacOS など大抵は問題なく認識するが、特殊OSだと複合HIDが怪しいケースも。

7. まとめ

  1. 前編の「SendText」(キーボード) と、続編の「SendMouse」(マウス)同じprotoファイル同じサーバプログラムにまとめることで、キーボード+マウス複合HIDをターゲットPCに見せられる。
  2. 複合HIDガジェットをlibcompositeで設定し、hid.usb0(keyboard) と hid.usb1(mouse) を同時に configs/c.1 に紐付けるだけ。
  3. gRPC で受信した「キー入力」「マウス移動」要求を Pi が /dev/hidg0 / /dev/hidg1 に書き込む → Target PC にリアルタイムで操作が注入される。

これで、**ネット経由でPiに指示し、Piを介してターゲットPCをリモート操作(キー&マウス)**する環境が完成します。ぜひ応用してみてください!


参考リンク


以上

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?