Flutter + gRPC + Kachaka APIで始めるロボットのあるスマートな生活

# 概要

こんにちは。Preferred Roboticsで「カチャカ」のスマホアプリを開発している西山です。

弊社ではスマホアプリにFlutterを採用しており、ロボット側（サーバサイド）との通信にはgRPCを採用しています。そこで採用に至った経緯や、実際にそれらを使ったサンプルコードの解説を行っていきたいと思います。

# カチャカとは

カチャカは、ロボットと家具が一体となって機能する、スマートファニチャープラットフォームです。 
ざっくりと言うと

**家具を運んでくれるロボット**

です！！かっこう良さそう！ワクワクしますね！

私の家でも使っているのですが、洗濯物を運んでもらったり帰宅したら真っ先にPC・タブレット・開発用アダプター...でぱんぱんのバックを家具に載せて自動で片付けてもらっています。まさに、小学生の時にやっていた帰宅&ランドセルぶん投げ&遊びへ直行というのが怒られずできる？わけです。

お子さんがいらっしゃる家庭では家族の一員のように迎えていただいたり子育てで手が離せない時に手伝ってもらったりと幅広いご家庭で生活に馴染みながら活躍しています！（ぜひ様々なユーザ様のお声をいただいているのでお時間あればnoteの記事をご覧ください）

https://note.kachaka.life/n/n3bf7eaf544cc

# カチャカ用のスマホアプリ開発

このカチャカですが、専用のアプリや音声コマンドで操作可能ですが今回はアプリの話をしていきたいと思います。


## 技術選定

#### なぜFlutterを選んだのか？

> すべての人に　ロボットを。
> Robots for everyone.

弊社の理念にこのようなものがあります。

これを実現するためにアプリ開発者としてできることはユーザの裾野を限りあるリソースの中で最大化することです。そのためにクロスプラットフォームのフレームワークを使うことにしました。
私の開発している印象だと例えばiOSにSwift、AndroidにKotlinを用いるより、Flutterで一本化した方が開発や運用のコストは70%くらいにできるイメージです。その分対応範囲を広げたり新機能開発にリソースをさけたりできます。

次にFlutter, React Native, Unity等どのフレームワークにするかですが、**流行り**と**社内スキルセット**を考慮し、Flutterを選択しました。

- 流行りはどれくらいメンテされているかとも関わりますが、新しいOS(iOS17, Android14等)が出た時にそれに関わる仕様変更をどれくらい早く対応してくれるか、Framework内のバグをどれほどで手早く解決してもらえるか、今後も存続するか、とても大事な要因です。プロダクトの質に大きく関わってきます。選定の際にはGitHubでコミットログやスター数を考慮に入れました。
- 社内スキルセットは開発速度に大きく関わりますが、弊社では私が主に開発をする予定でFlutterでの開発経験が長かったので採用しました。（ほぼやりたい！やりたい！しか言っていませんでしたが採用していただきました。。。笑ありがとうございました。）


#### なぜgRPCを選んだのか？

RESTやGraphQLと比較して以下のようなメリットがあるため採用しました。

- "HTTP/2基盤のバイナリ通信を行うことで軽量なメッセージ交換が可能"
- Protocol Buffersでは、プロトコルの仕様変更に強く、種々の言語にコードを自動生成できるため開発がしやすい
- 弊社で使ったことがある人が多かった

カチャカとスマホアプリは以下の図のように、家庭内や社内などのLAN内で通信を行います。

![data-flow.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274467/68ccc72e-ea67-26fb-9e1b-7cc99fb640a7.png)

カチャカ体内では様々な推論（音声認識、Free Space Segmentation、障害物認識、自己位置推定等）が行われ使えるリソースが限られているので、gRPCのような軽量な通信プロトコルと相性が良いです。

また全く新しいプロダクトで仕様変更が多く発生しながらも素早く開発をしているため、"プロトコルの仕様変更に強い"という恩恵を受けたいという思いがありました。



## Kachaka用のアプリ開発

実際に上記の技術を使ってカチャカを操作していきたいと思います。

https://github.com/pf-robotics/kachaka-api-flutter

本記事で解説したコードは↑こちらにあるのでぜひご覧ください。

#### 1\. カチャカ（バックエンド側）の事前準備

まずは [カチャカ](https://kachaka.life/) のAPI機能（カチャカAPI）を有効化します。
こちらの記事の [#事前準備](https://qiita.com/ksyundo/items/f10f9a6c704442bb3a10#%E4%BA%8B%E5%89%8D%E6%BA%96%E5%82%99) が非常にわかりやすいです。
このようにカチャカAPIを有効化すると、protoファイルとして https://github.com/pf-robotics/kachaka-api/tree/main/protos ここに定義されているrpcを呼べるようになります。

#### 2\. アプリ側：準備

まずはprotoファイルからdartのrpcクライアントのコードを生成しましょう。
```dart:tools/generate_proto.sh
protoc --proto_path=kachaka-api/protos \
    --dart_out=grpc:kachaka_api_package/lib/src \
    kachaka-api/protos/kachaka-api.proto
```
kachaka-api-flutter内では生成したファイルを
```
kachaka-api-flutter/kachaka_api_packages/lib/src
```
に出力しているためそちらを使っても良いです。

#### 3\. アプリ側：gRPCクライアントの作成

https://pub.dev/packages/grpc

こちらのパッケージを使います。
（※ grpcパッケージでは、gRPCの実装とgRPC-Webとの実装とが抽象化されていないので、webとモバイルアプリとでよしなに呼び分ける必要があり、kachaka-api-flutterのサンプルコードだとコードが多少複雑になっていますが、以下では簡易化しています。）

```dart:kachaka_api_flutter_sample/lib/repositories/kachaka/kachaka_repository.dart

import 'package:grpc/grpc.dart';
import 'package:grpc/grpc_connection_interface.dart';
import 'package:kachaka_api/kachaka_api.dart';

final channel = ClientChannel(
      "192.168.1.9", // LAN内でのカチャカのip address
      port: 26400, // kachakaではport26400がgrpc用のportとして空いています
      options: const ChannelOptions(credentials: ChannelCredentials.insecure()), // tls/ssl暗号化を使用しません（本番では使った方が良い）
    );
final client = KachakaApiClient(channel);
```

このようにカチャカと接続を貼ります。その後実際にrpcを呼んでみましょう。
targetLocationIdは、ひとまず適当な値を入れて大丈夫です。実行して適切にrpcを呼べるとカチャカが"〇〇"に行きます等の発話をします。

```dart:kachaka_api_flutter_sample/lib/repositories/kachaka/kachaka_repository.dart

import 'package:kachaka_api/kachaka_api.dart';

Future<StartCommandResponse> startAction(String targetLocationId) async {
  final res = await client.startCommand(
    StartCommandRequest(
        command: Command(
            moveToLocationCommand:
                MoveToLocationCommand(targetLocationId: targetLocationId))),
    options: CallOptions(timeout: const Duration(seconds: 5)),
  );
  return res;
}

```

#### 4\. カチャカ側のデータと同期する（ロングポーリング）

これだけでは、targetLocationIdがうまく指定できずカチャカがどこに行くかもわからないので目的地（カチャカが到着する場所）一覧を取得して、そこからidを取得して実行してみましょう。
カチャカとスマホアプリはロングポーリングを用いてデータの同期を行っています。gRPCにはBidirectional streamingという方法でstreamingすることが可能ですが、gRPC-webでは使用することができないためロングポーリングを用いています。

以下の手順で処理を進め、データの同期を続けます。

> a. クライアント側がgetLocationsを呼ぶ。初回はcursorに0を入れる。
> b. サーバ側（カチャカ）では内部で持っているcursor（Locationsが更新されると増加する）とクライアントから来たcursorの値を見て、
> - 変更があったら即座に新しいLocationsと新しいcursorを返す。
> - 変更がなければあるまで待って60sec経ったらresponseを返す
> 
> c. クライアント側はレスポンスを見て新しいcursorとLocationsを受け取り、再度新しいcursorを使ってgetLocationsを呼ぶ
> 
> そしてb,cを繰り返す。


```dart:kachaka_api_flutter_sample/lib/repositories/kachaka/kachaka_repository.dart

Int64 _getLocationsCursor = _zeroCursor;
Future<GetLocationsResponse> getLocations() async {
  final res = await _client!.getLocations(
    GetRequest(metadata: Metadata(cursor: _getLocationsCursor)),
    options: _pollingRequestOptions,
  );
  _getLocationsCursor = res.metadata.cursor;
  return res;
}

```

#### 5\. 任意の目的地に移動してみる！

最後に、4.で取得した目的地から、3.で定義したrpcを呼んでカチャカをどこかの目的地に呼んでみましょう。

```dart
final res = await getLocations();

// 目的地一覧の中の一番目の場所に移動してみる
final targetLocationId = res.locations.first.id;

startAction(targetLocationId);
```

# 最後に

今回紹介した内容はこちらのレポジトリでサンプルコードを公開しています。

https://github.com/pf-robotics/kachaka-api-flutter/tree/main

こちらのレポジトリではさらに目的地以外にも地図や家具の情報を取得して地図上に表示し、地図上から家具を目的地を指定して移動するというようなコードを紹介しています！

また **ストアに配信しているカチャカアプリとは異なりWebアプリで動かせる** ようになっていますので、ぜひ色々遊んでみてください！

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/274467/228366c6-0918-79bd-b122-21a010632306.png" width=200>


さらに
カチャカご興味あればぜひ見てってください！カチャカシェルフに加えてカチャカベースという新しい家具のラインナップも追加されどんどんパワーアップしています！
カチャカご興味あればぜひ見てってください！

https://note.kachaka.life/n/n380d7deed716


#### 参考
- https://www.wantedly.com/companies/wantedly/post_articles/309513
- https://zenn.dev/shibata/articles/9374d29fced86b