Raspberry Pi Zero 2 W 用のマウスコントロール gRPC API 設計例 (キャリブレーション対応)

---
## はじめに

この記事では、**Raspberry Pi Zero 2 W** で **gRPC サーバー**を立ち上げ、  
- マウスの絶対座標 / 相対座標移動  
- 左クリック / 右クリック / 中クリック  
- ダブルクリック  
- そして **キャリブレーション**用の API

などを管理するための **`.proto` ファイル設計案**を紹介します。  
想定としては、**Raspberry Pi 5** 側がクライアントとなり、HDMI キャプチャで取得した PC 画面の情報（SikuliX や ChatGPT Vision で解析したボタン座標など）を、この gRPC API を介して Zero 2 W に送信。Zero 2 W が **USB HID デバイス（マウス）** として PC を操作する、という構成です。

---

## 全体のイメージ

1. **Raspberry Pi 5 (クライアント)**  
   - HDMI キャプチャデバイス経由で PC 画面を取得  
   - 画面解析（OpenCV / SikuliX / ChatGPT Vision など）  
   - **gRPC クライアント**として Zero 2 W に「(x,y) へ移動」「クリックして」などの指示を送信  

2. **Raspberry Pi Zero 2 W (サーバー)**  
   - **gRPC サーバー**を動かしていて、クライアントからの指示を受け取る  
   - USB HID（マウス/キーボード）として PC に物理操作のようなイベントを送信  
   - PC から見ると、単なる「外付けマウス/キーボード」デバイスとして振る舞う  

3. **PC**  
   - Zero 2 W が USB 接続されており、マウス/キーボード入力をそのまま受け取る  
   - 特別なソフトは不要で、文字通り「マウスが操作している」感覚

---

## なぜキャリブレーションが必要か？

- **HDMI キャプチャ解像度と PC の実解像度が異なる**場合、  
  「見た目（キャプチャ画像）上で x=200, y=150」のボタンが、  
  実際の PC 画面では異なる座標にある可能性が高いです。  
- また、**マウスの絶対座標モード / 相対座標モード** や、OS のマウス加速設定などによって、同じ指示でも実際のカーソル位置が変わり得ます。

そこで、**システム起動時に複数の既知ポイントをクリック→HDMI 上の座標とのズレを確認し、スケールやオフセットを算出する** という手順が必要になります。このときにも、**gRPC API** 経由で Zero 2 W に座標を送り、実際にクリックしてみる → キャプチャ画像で確認、のループが必要です。

---

## `.proto` ファイルのサンプル

ここからは、gRPC で定義する **サービスとメッセージ** の例を示します。  
以下を参考にして、自分の環境や要件に合わせて拡張してください。

```proto
syntax = "proto3";

package mousecontrol;

// マウス操作やキャリブレーションをまとめたサービス
service MouseControlService {

  // ------- マウス基本操作 -------

  // 絶対座標でマウスを移動
  rpc MoveAbsolute(MoveAbsoluteRequest) returns (OperationResult);

  // 相対座標（dx, dy）でマウスを移動
  rpc MoveRelative(MoveRelativeRequest) returns (OperationResult);

  // クリック操作（左・右・中ボタン、単/ダブルクリック等）
  rpc Click(ClickRequest) returns (OperationResult);

  // ------- キャリブレーション -------

  // キャリブレーション開始
  // 画面サイズや内部ステータスをリセットするなどの準備を想定
  rpc StartCalibration(StartCalibrationRequest) returns (OperationResult);

  // キャリブレーション実行:
  // capturedX, capturedY と actualX, actualY の対応関係を送って
  // スケールやオフセットを計算するイメージ
  rpc Calibrate(CalibrationRequest) returns (CalibrationResponse);

  // キャリブレーション完了:
  // 計算結果を確定させる
  rpc FinishCalibration(FinishCalibrationRequest) returns (OperationResult);
}

// ===== メッセージ定義ここから =====

// 絶対座標指定
message MoveAbsoluteRequest {
  float x = 1; // 画面上の絶対X座標
  float y = 2; // 画面上の絶対Y座標
}

// 相対座標指定
message MoveRelativeRequest {
  float dx = 1; // 前の位置からの相対移動量 dx
  float dy = 2; // 前の位置からの相対移動量 dy
}

// マウスボタン種別
enum MouseButton {
  LEFT = 0;
  RIGHT = 1;
  MIDDLE = 2;
}

// クリック操作リクエスト
message ClickRequest {
  MouseButton button = 1;  
  bool doubleClick = 2;    // trueならダブルクリック
}

// 汎用的な結果レスポンス
message OperationResult {
  bool success = 1;
  string message = 2;
}

// キャリブレーション開始
message StartCalibrationRequest {
  // たとえば過去のデータをリセットするフラグなど
  bool resetPreviousData = 1;
}

// キャリブレーション実行時
message CalibrationRequest {
  // HDMIキャプチャ画像上での座標
  float capturedX = 1;
  float capturedY = 2;

  // 実際に指してほしいディスプレイ上の座標
  float actualX = 3;
  float actualY = 4;
}

// キャリブレーション時のレスポンス
message CalibrationResponse {
  bool success = 1;
  string message = 2;

  // 例として、サーバー側で計算したスケールやオフセットを返す
  float scaleX = 3;
  float scaleY = 4;
  float offsetX = 5;
  float offsetY = 6;
}

// キャリブレーション完了
message FinishCalibrationRequest {
  bool commitCalibration = 1; // 計算結果を確定させるフラグ
}
```

---

## 設計ポイント

1. **MoveAbsolute / MoveRelative**  
   - 絶対座標指定ができるモードなら、(0, 0) 〜 (ディスプレイの最大幅, 最大高さ) の範囲で座標を送ればよいです。  
   - 相対座標モードは、通常のマウスのように (dx, dy) を送るだけ。  
   - OS のマウス加速設定やマルチモニタ構成などで挙動が変わる可能性がある点に注意。

2. **クリック操作**  
   - 左 / 右 / 中ボタンを列挙型 `MouseButton` で管理。  
   - `doubleClick` フラグを使えば、ダブルクリックを一回の RPC で完結できるようにする。  
   - 必要があれば、ホイールスクロールなどの操作も別の RPC で追加してもよいです。

3. **キャリブレーション関連**  
   1. **StartCalibration**: 前回の値や状態をリセットする。たとえば PC 解像度が変わったとき、マルチモニタ構成が変わったときに再キャリブレーションするイメージ。  
   2. **Calibrate**:  
      - `capturedX, capturedY` は HDMI キャプチャ画像上の座標。  
      - `actualX, actualY` は「本来そこをクリックしたいディスプレイ上の座標」。  
      - サーバー側（Zero 2 W）がこれを複数回受け取ってスケールやオフセットを計算したり、誤差を最小にするように調整する。  
   3. **FinishCalibration**: キャリブレーションが終わったら確定。今後の MoveAbsolute などに補正を適用する設計が考えられる。  

4. **OperationResult / CalibrationResponse**  
   - 成功か失敗かを示す `success`、エラーメッセージを入れる `message` を用意。  
   - キャリブレーション時はスケールやオフセットをサーバーからクライアントに返して、**クライアント側でも補正値を保持**しておくという使い方も想定できます。

---

## サンプルフロー

1. **キャリブレーション開始**
   ```plaintext
   -> StartCalibration(resetPreviousData=true)
   <- OperationResult{ success = true, message="Calibration reset" }
   ```

2. **テストクリックの実施**  
   - 例: PC 画面左上 (actualX=0, actualY=0) をクリックしたい。  
   - しかし、HDMI キャプチャ画像で見ると、そこが (capturedX=50, capturedY=30) に写っていた、という情報を Calibrate() に送る。
   ```plaintext
   -> Calibrate(capturedX=50, capturedY=30, actualX=0, actualY=0)
   <- CalibrationResponse{ success=true, scaleX=..., scaleY=..., offsetX=..., offsetY=... }
   ```
   - 複数点で Calibrate() を呼び出し、精度を高める。

3. **キャリブレーション完了**
   ```plaintext
   -> FinishCalibration(commitCalibration=true)
   <- OperationResult{ success=true, message="Calibration done" }
   ```

4. **通常のマウス操作**
   - MoveAbsolute(x=200, y=150) などを送ると、サーバー側は内部で補正値を用いて USB マウス移動を実行。  
   - ClickRequest { button=LEFT, doubleClick=false } などでクリック。

---

## まとめ

- この `.proto` ファイルを使うことで、**Raspberry Pi 5 (クライアント) → Raspberry Pi Zero 2 W (サーバー)** の間で座標やクリックをやり取りし、**キャリブレーション付きのマウス操作**を実現できます。  
- **キャリブレーションは最初に行っておく**のがポイントで、HDMI キャプチャ画像の座標系と、実際の PC 画面でのマウスポインタ座標系を同期させます。  
- こうした仕組みを組み合わせることで、**SikuliX / ChatGPT Vision** などで判定したボタン位置を **正しく** クリックし、想定外の座標ずれを防止できます。  

もしさらに複雑なシナリオ（ホイールスクロール、ドラッグ&ドロップ、他のキー入力など）に対応したければ、サービスやメッセージを拡張してみてください。 

---

### 関連情報

- **Raspberry Pi Zero 2 W で HID デバイス化して PC を操作する方法**:  
  \[他の記事やリポジトリへのリンク\]  
- **SikuliX 公式サイト**:  
  <https://sikulix.com/>  
- **ChatGPT Vision (OpenAI 公式)**:  
  <https://openai.com/blog/chatgpt-vision/>  

以上が、**マウスコントロールの gRPC API (.proto ファイル) と キャリブレーション対応**の設計例です。プロジェクトに合わせてカスタマイズして、より高度なリモート UI 自動化を試してみてください。

以下に、前回の内容をさらに見直し・加筆し、**理解しやすくなるための追加説明**や**補足情報**を盛り込みました。**誤字脱字や内容の整合性をチェック**したうえで、最終的なQiita記事としてまとめています。ご確認ください。

---

# Raspberry Pi Zero 2 W 用のマウスコントロール gRPC API 設計例 (キャリブレーション対応)

## はじめに

本記事では、**Raspberry Pi Zero 2 W** を **USBマウスのHIDエミュレーション**装置として動作させ、  
- **Raspberry Pi 5** などのクライアントから **gRPC API** を介して指示を送り、  
- Zero 2 W が物理的なマウス入力のように PC を遠隔操作する

という仕組みを構築する際に必要となる **`.proto` ファイルの設計例**を紹介します。

### どんな場面で活用できる？

- PC の画面を **Raspberry Pi 5** + HDMIキャプチャで取り込み、  
  **SikuliX** や **ChatGPT Vision** などで「どのボタンをクリックすればよいか」を判断。  
- その判断結果を **Zero 2 W** に送ることで、実際のマウス操作（クリック・移動）を行う。  
- たとえば、自動ログインやアプリケーションの操作、ダイアログが出た場合のエラーハンドリングなど、**画面レベルの遠隔操作**を実現できる。

---

## 全体像

1. **Raspberry Pi 5 (クライアント)**  
   - PC 画面を HDMI でキャプチャし、画像解析（SikuliX / ChatGPT Vision など）を行う。  
   - 解析結果から「座標 (x, y) をクリックしよう」という指示を作成し、**gRPC クライアント**として **Zero 2 W** へ送信。

2. **Raspberry Pi Zero 2 W (サーバー)**  
   - **gRPC サーバー**を起動しておき、クライアントからの操作リクエストを受け取る。  
   - USB HID（マウス / キーボード）をエミュレートして PC に対して移動やクリックイベントを送信。  
   - **PC 側では特別なソフト不要**で、普通のマウス入力として認識される。

3. **PC**  
   - Zero 2 W から送られるマウスイベントを受け取り、通常のマウス操作として処理。  
   - HDMI 出力が Pi 5 側でキャプチャされ、解析 → 再び Zero 2 W が操作するループが成り立つ。

---

## キャリブレーションが必要な理由

- **HDMIキャプチャ解像度 vs. 実際のPC解像度**  
  例: PC が 1920×1080 で出力していても、キャプチャが 1280×720 などに縮小して取り込まれる場合、  
  画面上の座標とキャプチャ画像上の座標が一致しません。

- **HIDの座標モード（相対 / 絶対）**  
  - 相対モード: 「前の位置から (dx, dy) 移動する」という通常のマウス操作。  
  - 絶対モード: タブレットのように (0,0) 〜 (width,height) の範囲で座標を指定。  
  - OS のマウス加速やマルチモニタ設定によって、**同じ (dx, dy) でも実際のポインタ移動量が変わる**ことがある。

- **画面上でマウスが意図しない場所に行くリスク**  
  - キャリブレーションを行わないままだと、「ボタンの座標」を指示したはずが、全く別の部分をクリックしてしまう可能性が高い。  
  - その結果、誤操作でシステムが不安定になる場合もあるので、**最初にスケールやオフセットを合わせてから運用**するのが安全。

---

## `.proto` ファイル例

下記では、**マウス操作**（絶対座標・相対座標移動、クリック）と、**キャリブレーション**に必要な gRPC API を1つのサービスにまとめています。使い方としては、**Raspberry Pi 5** がこのサービスのクライアントとなり、**Raspberry Pi Zero 2 W** 側がこの `.proto` を実装した gRPC サーバーを起動するイメージです。

```protobuf
syntax = "proto3";

package mousecontrol;

// マウス操作やキャリブレーションをまとめたサービス
service MouseControlService {

  // ------- マウス基本操作 -------

  // 絶対座標でマウスを移動
  rpc MoveAbsolute(MoveAbsoluteRequest) returns (OperationResult);

  // 相対座標（dx, dy）でマウスを移動
  rpc MoveRelative(MoveRelativeRequest) returns (OperationResult);

  // クリック操作（左・右・中ボタン、単/ダブルクリック等）
  rpc Click(ClickRequest) returns (OperationResult);

  // ------- キャリブレーション -------

  // キャリブレーション開始:
  // 内部でスケールやオフセットをリセットしたり、
  // 状態初期化を行うことを想定
  rpc StartCalibration(StartCalibrationRequest) returns (OperationResult);

  // キャリブレーション実行:
  // HDMIキャプチャ座標 (capturedX, capturedY) と
  // 実際のディスプレイ座標 (actualX, actualY) の対応を送って
  // スケールやオフセットなどの誤差補正を計算
  rpc Calibrate(CalibrationRequest) returns (CalibrationResponse);

  // キャリブレーション完了:
  // 計算結果を確定し、今後のマウス操作に適用する
  rpc FinishCalibration(FinishCalibrationRequest) returns (OperationResult);
}

// ===== メッセージ定義ここから =====

// 絶対座標指定
message MoveAbsoluteRequest {
  // 画面上の絶対X座標
  float x = 1;
  // 画面上の絶対Y座標
  float y = 2;
}

// 相対座標指定
message MoveRelativeRequest {
  // 前の位置からの相対移動量 dx
  float dx = 1;
  // 前の位置からの相対移動量 dy
  float dy = 2;
}

// マウスボタン種別
enum MouseButton {
  LEFT = 0;
  RIGHT = 1;
  MIDDLE = 2;
}

// クリック操作リクエスト
message ClickRequest {
  MouseButton button = 1;  
  bool doubleClick = 2;    // ダブルクリックなら true
}

// 汎用的な結果レスポンス
message OperationResult {
  bool success = 1;
  string message = 2;
}

// キャリブレーション開始
message StartCalibrationRequest {
  bool resetPreviousData = 1; // 過去のデータをリセットするフラグ
}

// キャリブレーション実行時
message CalibrationRequest {
  // HDMIキャプチャ画像上の座標 (capturedX, capturedY)
  float capturedX = 1;
  float capturedY = 2;

  // 実際のディスプレイ上で指してほしい座標 (actualX, actualY)
  float actualX = 3;
  float actualY = 4;
}

// キャリブレーション時のレスポンス
message CalibrationResponse {
  bool success = 1;
  string message = 2;

  // 計算されたスケールやオフセット値などを返す例
  float scaleX = 3;
  float scaleY = 4;
  float offsetX = 5;
  float offsetY = 6;
}

// キャリブレーション完了
message FinishCalibrationRequest {
  // 計算結果を確定させるフラグ
  bool commitCalibration = 1;
}
```

---

### 主なポイント

1. **MoveAbsolute / MoveRelative**  
   - **絶対座標**: (0, 0) 〜 (ディスプレイ幅, ディスプレイ高さ) の範囲で指定。  
   - **相対座標**: 前の位置から (dx, dy) だけ移動。一般的なマウス動作に相当。  
   - OS によってはマウス加速がかかる場合があるため、相対モードでは想定通りの移動量にならない可能性がある点に注意。

2. **Click**  
   - `MouseButton` で左・右・中を切り替え。  
   - `doubleClick` を true にすれば、ダブルクリックとして実装。  
   - 必要に応じてマウスホイールやドラッグ&ドロップに対応する RPC を追加しても良い。

3. **キャリブレーションAPI**  
   1. **StartCalibration**  
      - 内部ステータスリセット。実際には「現在のスケール・オフセットをクリアする」「新たにキャリブレーションを受け付ける」などを行う。  
   2. **Calibrate**  
      - `(capturedX, capturedY)` (HDMIキャプチャ画像上で見えた座標) と、  
        `(actualX, actualY)` (本来そこをクリックしたい PC 画面上の座標) をセットで送る。  
      - 複数点で何度か `Calibrate()` を呼び、誤差を最小化するよう計算する実装が考えられる。  
   3. **FinishCalibration**  
      - 最終的なスケール・オフセットを確定させ、今後の MoveAbsolute などに反映する。  
      - 返り値として `OperationResult` を返す例にしているが、必要なら計算結果をもう一度返す処理を入れても良い。

4. **CalibrationResponse**  
   - サーバー側で計算した `scaleX, scaleY, offsetX, offsetY` をクライアントにも通知する例。  
   - クライアント側（Raspberry Pi 5）もこの値を使って、事前に座標を補正して送るようにする等、連携の仕方は自由。

---

## サンプルのフロー例

1. **キャリブレーション開始**  
   ```plaintext
   -> StartCalibration(resetPreviousData=true)
   <- OperationResult{ success=true, message="Calibration reset" }
   ```
2. **テストクリックで座標を確認**  
   - 例: PC 画面左上を (actualX=0, actualY=0) と想定してクリックするが、  
     HDMIキャプチャ上は (capturedX=50, capturedY=30) にその箇所が写っていた、  
     という情報を `Calibrate()` に送る。
   ```plaintext
   -> Calibrate(capturedX=50, capturedY=30, actualX=0, actualY=0)
   <- CalibrationResponse{ success=true, scaleX=..., scaleY=..., offsetX=..., offsetY=... }
   ```
   - 他の角や中央など、複数ポイントを同様に `Calibrate()` して精度を高める。

3. **キャリブレーション完了**  
   ```plaintext
   -> FinishCalibration(commitCalibration=true)
   <- OperationResult{ success=true, message="Calibration done" }
   ```

4. **通常の操作**  
   - MoveAbsolute(x=200, y=150)  
   - Click(ClickRequest{ button=LEFT }) など  
   - サーバー側は内部に保持している補正を適用しつつ、USBマウスイベントを OS へ送る。

---

## 追加説明: 相対 vs. 絶対モード

- **相対モード**  
  - 一般的な USB マウスは「最後に報告した座標からどれだけ移動したか」でカーソルを動かします。  
  - OS の設定によって加速や速度調整が入り、**同じ (dx, dy) でも画面上の移動量が異なる**場合がある。  
  - すでにマウスカーソルが画面左上にいたのか中央にいたのかを、外部（Zero 2 W）からは把握しにくい。

- **絶対モード**  
  - タブレットやタッチパネルのように、(0,0) 〜 (width, height) の範囲で「画面上のどこを指すか」を絶対座標で報告する。  
  - OS がサポートしていれば、**正確な座標指定がしやすく、キャリブレーションとの相性が良い**。  
  - しかし PC 側の設定が必要な場合や、マウスアクセルが無効化されないケースもありうるので注意。

---

## よくある質問（FAQ）

**Q. キャリブレーションは毎回必要？**  
A. PC の解像度や HDMI キャプチャのスケーリングが変わらないなら、一度のキャリブレーションで十分です。ただし、OS や環境設定が変わったときは再度行うのがおすすめ。

**Q. クリックするとき、マウス移動とは別の RPC を毎回呼ぶ必要がある？**  
A. 今回の設計例では分けていますが、もし「移動とクリックを一度に行う API」が欲しければ設計を追加しても構いません。

**Q. ドラッグ操作やホイールスクロールはどうする？**  
A. 必要に応じて、`MouseDown / MouseUp` や `Scroll` の RPC を足してください。`.proto` は用途に合わせて柔軟に拡張可能です。

---

## まとめ

1. **HDMI キャプチャ画像と PC 実画面のズレを補正するために、キャリブレーション機構が必須**。  
2. **相対座標 / 絶対座標 のモードや、OS の設定**によっては意図した通りにマウスが動かないこともあるため、**トライ&エラー**での調整が必要。  
3. `.proto` ファイルで **MoveAbsolute / MoveRelative / Click** といった RPC と、**キャリブレーション用 RPC**（StartCalibration, Calibrate, FinishCalibration）を定義することで、**Raspberry Pi 5 ↔ Zero 2 W 間の操作フロー**を分かりやすく設計できる。

こうした仕組みを整えれば、**SikuliX / ChatGPT Vision** などで「ボタンが画面のどこにあるか」を解析し、**Zero 2 W が正確にその座標をクリックする**という高度な遠隔自動化が実現可能です。ログイン操作からアプリケーション管理まで、様々なシーンで応用してみてください。

---

### 参考リンク

- **Raspberry Pi Zero 2 W で HID デバイス化**  
  - （別記事やリポジトリ）  
- **SikuliX 公式サイト**  
  - <https://sikulix.com/>  
- **ChatGPT Vision (OpenAI 公式)**  
  - <https://openai.com/blog/chatgpt-vision/>

---

以上