gRPC 使って TOPPERS/箱庭 単体ロボットシミュレータにBluetooth デバイスを追加する(サーバー編)

# モチベーション
TOPPERS/箱庭 単体ロボットシミュレータに仮想的な Bluetooth デバイスを追加することにしました．そのための実装方法として，より汎用的な技術である gRPC を利用することにしましたので，サーバー側の gRPC を利用したプログラムの作成方法について解説します．

なお，gRPC を利用しようと思った動機は，以下の２点です．

* 様々な言語でサーバー側とクライアント側を開発できる
* RPC 実装を作る必要がない(データ型定義するだけで自動生成されちゃう！）

サーバー側のプログラムは，Bluetooth 用のシリアルI/Oライブラリで，EV3RTの Bluetooth APIとの連携を可能にします．ただし，本記事では，EV3RT側の解説は含まれておりません．
※別途，gRPC クライアント側の解説記事を書く予定ですので，その際に併せて説明します～．

# 本記事を通して学べる gRPC の知識は？

* gRPC 概要
* gRPC のデータ定義方法
* gRPC を使ったサーバー側の設計(RAWデータ管理)
* gRPC の開発環境(C#)の作成方法
* gRPC の実装例(C#)

# gRPC 概要
以下の記事がとても参考になります．

* [いまさらだけどgRPCに入門したので分かりやすくまとめてみた](https://qiita.com/gold-kou/items/a1cc2be6045723e242eb)

深く学びたい方は，本家サイトを参照ください．

* https://www.grpc.io/

# gRPC のデータ定義方法
まず，論より証拠，今回作成した gRPC の定義は以下になります．

```C#
service SerialService {
  rpc PutData (SerialPutData) returns (SerialPutResult) {}
  rpc GetData (SerialGetData) returns (SerialGetResult) {}
}
```

`service` キーワードから gRPC の定義が始まります．今回のケースでは，RPCのサービス名が`SerialService`です．

そして，このサービスには，以下の２つのAPI(関数)があります(`rpc`キーワードで始まる部分です)．

* PutData
* GetData

それぞれの処理について簡単に説明します．

## PutData
本APIは，引数に `SerialPutData` というデータ型をとり，結果を `SerialputResult`というデータ型で返します．クライアントがサーバーに Bluetooth 用のシリアルデータを送信する際に利用します．
※クライアントは送信したいデータを  `SerialPutData` に詰めます．

## GetData
本APIは，引数に `SerialGetData` というデータ型をとり，結果を `SerialGetResult`というデータ型で返します．クライアントがサーバーから Bluetooth 用のシリアルデータを受信する際に利用します．
※クライアントは受信するデータを  `SerialGetResult` で受け取ります．

ここから，各データ型の定義方法について解説します．
まず，gRPC で利用するデータ型は，[プロトコルバッファー](https://ja.wikipedia.org/wiki/Protocol_Buffers)で定義します．

今回作成したデータ型の定義は以下の通りです．

```c#
message SerialPutData {
  int32  channel = 1;
  bytes data = 2;
}

message SerialPutResult {
  int32  channel = 1;
  string ercd = 2;
}

message SerialGetData {
  int32  channel = 1;
}
message SerialGetResult {
  int32  channel = 1;
  string ercd = 2;
  bytes data = 3;
}
```

`message` で始まるキーワード部分がプロトコルバッファー定義部分です．
message の中のメンバは，データ型毎にそれぞれ自由に定義できます．

* `channel` は，シリアルI/Oをマルチチャネルに対応するためのメンバです(今回は1チャネルのみしかやりませんが)．
* `ercd`には，サーバー側のエラーメッセージが格納されます．
* `data`には，バイナリデータが格納されます．Bluetoothのシリアルデータが対応します．

たったこれだけの定義をするだけで，クライアント側とサーバー側の RPC 実装コードが様々な言語で生成されちゃうわけですから，実装方法の自由度が大きく広がりますし，実装コストも激減ですよね！！

# gRPC を使ったサーバー側の設計(RAWデータ管理)
サーバー側の Bluetooth I/O 設計内容(UML)をお示しします．

なお，本設計では，UMLツールとして，[astah* professional](https://astah.change-vision.com/ja/product/astah-professional.html)を利用しています．直感的な操作ができるところがとても良いです(ハマります)．設計検討中は常にモヤモヤしているので，ストレスなく思いを描けるツールの存在はとても大事です．さらに，設計中は様々な視点での検討が必要となるので，UML図だけでなく，DFDやロバストネス図なども使える点がさらに良いです．

##クラス設計
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/1964e07d-ef6d-1de5-a105-bfb128bfcf18.png)

##シーケンス設計
### 起動シーケンス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/c29d7c8b-7d60-14a5-1c70-35915b8346da.png)

### PutData シーケンス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/abc710b9-6dd9-9905-31ea-fd46b98b03d3.png)

### GetData シーケンス
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/34de7049-16c4-b19b-0756-212862ece436.png)

# gRPC の開発環境(C#)の作成方法
さて，ここから実際にプログラミングの話になります．
今回のBluetoothサーバー実装言語は，C#にしました．

理由は，以下の記事にあるロガー機能がC#で実装されており，将来的に，このロガーを実機とシミュレータとで切り替えできるようにしたいという野望があるためです．

* [WRO勢による EV3rt汎用Bluetoothロガーの開発](https://qiita.com/koushiro/items/dffdd4375d3ce110cb41)

C#での gRPC の開発環境ですが，こちらの記事が大変参考になります．

* [gRPCの概要をC#でサクッと理解する](https://light11.hatenadiary.com/entry/2020/07/28/210411)


# gRPC の実装例(C#)

各クラスの実装は，以下で公開しております．

* https://github.com/toppers/athrill-device/tree/main/device/ev3com/grpc/server/csharp

program.cs がユーザプログラムになります．

```C#
static void Main(string[] args)
{
    string ipaddr = "172.25.0.1";
    int portno = 50051;
    int buffer_size = 1024 * 1024; /* 1MB */
    Console.WriteLine("ipaddr=" + ipaddr + " portno=" + portno.ToString());

    VirtualBluetoothSerial serial = new VirtualBluetoothSerial();
    serial.SetServerInfo(ipaddr, portno, buffer_size);
    serial.Open();
    string data = null;
    Console.WriteLine("START BT TEST>>>>");
    for (int i = 1; i <= 10; i++)
    {
        data = "Hello World[ " + i.ToString() + " / 10 ]";
        Console.WriteLine("SEND DATA:  " + data);
        serial.WriteLine(data);
        string rcv_data = serial.ReadLine().Trim();
        Console.WriteLine("RECV DATA:  " + rcv_data);

        if (data.Equals(rcv_data))
        {
            Console.WriteLine("TEST PASSED [ " + i.ToString() + " ]");
        }
        else
        {
            Console.WriteLine("TEST FAILED [ " + i.ToString() + " ]");
        }
    }
    Console.WriteLine("<<<<END BT TEST");
    serial.Close();
}
```

本プログラムは，シリアルデータをサーバー側からクライアントに送信して，そのデータをクライアントからそのまま送り返してもらって，送信データと受信データが一致しているかどうかをチェックしています(10回繰り返します)．

# デモ
このプログラムの実行するとこんな感じで動きます．
クライアント側は，EV3RTのBluetooth APIを使ってデータの送受信をするようにしています(別途こちらは解説します)．

![demo.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/244147/977b435d-0b06-e6e9-a8f9-8c68b8a3197d.gif)