post-cover

2024-09-04 技術ブログ

USB キーボード信号の送受信とマウス操作|Raspberry Pi Zero 2 W を USB マウスに変える(記事第2弾)

はじめに

前回の記事では、Raspberry Pi Zero 2 W を USB マウスとして認識させ、LAN 経由から PC のマウス操作を行う方法について紹介しました。

今回は、さらに一歩進めて、2 台の Raspberry Pi Zero 2 W を使用し、1 台を送信機としてキーボード信号を送信し、もう 1 台を受信機としてその信号を受け取り、PC のマウスカーソルを動かす方法を解説します。

raspberry-pi-usb-hid-2-001

このシステムを利用することで、キーボード操作を通じて遠隔で PC の操作が可能になります。

関連記事

余談

当初は、マウス操作の信号をそのまま送信機から受信機へ送り、遠隔でマウスカーソルを操作することを目指していました。


しかし、マウスから発信される信号量が多く、少し動かしただけでプログラムが強制終了することが頻発しました。何度も試行錯誤を重ねたのですが、動作を安定させることが難しく、最終的にキーボードの信号を使用する方法に切り替えることにしました。


もし、マウス操作の信号を安定して送受信する方法をご存知の方がいらっしゃいましたら、ぜひご教示いただけると幸いです。


さて、今回の記事では、送信機側の Raspberry Pi Zero 2 W に接続したキーボードの信号を受信機に送り、その信号に基づいてマウスカーソルを操作する方法についてご紹介します。

受信機の設定

受信機は USB マウスとして認識される必要があります。

設定方法については、前回の記事をご確認ください。

受信側のプログラム

以下のプログラムを作成して、受信機として動作する Raspberry Pi 上で実行します。

このプログラムは送信機から送られてくるキーボード信号を受け取り、それに応じてマウスカーソルを移動させたり、クリック操作を行ったりします。

nano keyboard-rx.py
import socket
import json
import sys
import termios
import tty

HOST = '0.0.0.0'
PORT = 65432

# HIDファイルのパス
MOUSE_DEV = "/dev/hidg0"

# 初期設定
movement_amount = 10
min_movement = 1
max_movement = 127

# キーボード入力を非同期で取得するための設定
def get_key():
    fd = sys.stdin.fileno()
    old_settings = termios.tcgetattr(fd)
    try:
        tty.setraw(sys.stdin.fileno())
        ch = sys.stdin.read(1)
    finally:
        termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
    return ch

# マウスの移動やクリックを送信する関数
def move_mouse(dx, dy, buttons=0):
    report = bytearray([buttons, dx & 0xFF, dy & 0xFF])
    with open(MOUSE_DEV, "wb") as f:
        f.write(report)

# ソケットサーバーでの接続待機
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    print('Waiting for a connection...')

    conn, addr = s.accept()
    with conn:
        print(f'Connected by {addr}')
        buffer = ""
        while True:
            data = conn.recv(1024)
            if not data:
                break
            buffer += data.decode('utf-8')
            while True:
                try:
                    # JSONオブジェクトの境界を探す
                    obj, index = json.JSONDecoder().raw_decode(buffer)
                    buffer = buffer[index:].lstrip()  # バッファから処理済み部分を削除

                    if obj['type'] == 1:  # EV_KEY
                        if obj['value'] in (1, 2):  # キーが押された、または押し続けられている
                            if obj['code'] == 103:  # 上方向キー
                                move_mouse(0, -movement_amount)
                            elif obj['code'] == 108:  # 下方向キー
                                move_mouse(0, movement_amount)
                            elif obj['code'] == 105:  # 左方向キー
                                move_mouse(-movement_amount, 0)
                            elif obj['code'] == 106:  # 右方向キー
                                move_mouse(movement_amount, 0)
                            elif obj['code'] == 44:  # Zキー
                                movement_amount = max(min_movement, movement_amount - 4)
                            elif obj['code'] == 45:  # Xキー
                                movement_amount = min(max_movement, movement_amount + 4)
                            elif obj['code'] == 30:  # Aキーで左クリック
                                move_mouse(0, 0, buttons=1)
                            elif obj['code'] == 31:  # Sキーで右クリック
                                move_mouse(0, 0, buttons=2)
                            elif obj['code'] == 16:  # Qキー
                                print("Q key pressed. Exiting...")
                                conn.close()
                                s.close()
                                exit()
                        elif obj['value'] == 0:  # キーが離されたとき、クリックを解除
                            if obj['code'] in (30, 31):  # AキーまたはSキー
                                move_mouse(0, 0, buttons=0)

                    print(f"Received: {obj}")

                except json.JSONDecodeError:
                    break

print("終了しました。")

プログラムの実行

以下のコマンドでプログラムを実行します。

sudo python3 keyboard-rx.py

実行すると、以下のように送信機からの接続待ち状態になります。

Waiting for a connection...

この状態で送信機が接続されると、受信機は送信機から送られたキーボード信号を受け取り、対応するマウス操作を PC 上で実行します。

送信機の設定

送信機として使用する Raspberry Pi には、USB キーボードを接続します。このキーボードからの信号を受信機に送信することで、遠隔での操作が可能になります。

送信側のプログラム

以下のプログラムを作成して、送信機として動作する Raspberry Pi 上で実行します。

このプログラムは、接続されたキーボードの信号を読み取り、それを受信機に送信します。

送信側のプログラム

nano keyboard-tx.py
import evdev
import socket
import json

# 送信先のホスト名とポート番号
HOST = 'zero-2.local'  # 受信側のRaspberry Piのホスト名またはIPアドレス
PORT = 65432

# デバイスのevent番号を指定(キーボードのイベントデバイス)
device = evdev.InputDevice('/dev/input/event0')

# ソケットを作成し接続
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    try:
        s.connect((HOST, PORT))
        print(f"Connected to {HOST}:{PORT}")
        # キーボードイベントの読み取りと送信
        for event in device.read_loop():
            if event.type == evdev.ecodes.EV_KEY:
                # データの送信 (JSON + 改行)
                data = json.dumps({'type': event.type, 'code': event.code, 'value': event.value})
                s.sendall((data + '\n').encode('utf-8'))
                print(f"Sent: {data}")  # 送信したデータを表示
    except BrokenPipeError:
        print("Connection lost. Exiting.")
    except Exception as e:
        print(f"An error occurred: {e}")

プログラムの実行

以下のコマンドでプログラムを実行します。

nano keyboard-tx.py

プログラムが正常に動作すると、送信機は接続されたキーボードの入力を検知し、その信号を受信機に送信します。

受信機側でこの信号を受け取り、対応するマウス操作が実行されます。