Pepper用のAndroidアプリを作る話

初めまして。
最近PepperのAndroidアプリを作成してます。
Pepper開発の日本語情報はChoregrapheを利用した開発の情報は上がっていますが、
Androidアプリの開発という観点での情報はほぼ見受けられなかったので、
公式情報を参考に、入門用ということで、表面的ではありますが情報を提供できればと思います。

##前提
本記事は、Android版Pepperの情報になります。
本記事に記載したほぼ全ての情報は公式のリファレンスに依拠しています。
詳しく知りたい方はそちらをご覧ください。
[公式リファレンスリンク](https://android.aldebaran.com/sdk/doc/pepper-sdk/index.html)

Androidアプリ開発者がAndroid版のPepperアプリを開発するという視点で書かれたものになりますので、Androidの知識はあるがPepperの知識がない方が読者と仮定します。


##そもそもどうやって作るの？
**Pepper SDK for Android**及び**QiSDK**を導入することで開発できるようになります。
AndroidStudioを使用して開発ができるようになっています。
主にやることはプラグインの導入とSDKの導入です。
(Pepper界隈では**NAOqi**というワードも頻出ですが、こちらはPepperのOSのことを指しています。
AndroidアプリでベースになるのはQiSDKです)

###AndroidStudioにPepperSDKプラグインを入れる
*Prefarences>plugins>Browse Repositories>"pepper"で検索*

検索結果で出てきたプラグインをインストールします。
このようなアイコンが出てきたらOKです。
<img width="128" alt="ツールバー.png" src="https://qiita-image-store.s3.amazonaws.com/0/323477/ba616715-5ec8-ee20-cd1a-33c2e1564459.png">
出てこない場合はView>Toolbarにチェックを入れてください。
この時点でカラーになっているアイコンをクリックしてSDKをインストールしてください。

###ロボットアプリケーションの作成
AndroidのバージョンなどPepperに合わせないといけないので、
公式チュートリアルを見ながらやったほうが間違いがないと思います。
(リンクの1,2)
[ロボットアプリケーションを作成する](https://android.aldebaran.com/sdk/doc/pepper-sdk/ch1_gettingstarted/starting_project.html)
QiSDKをバージョンアップしたいときはbuild.gradleの該当箇所を変更すればOKです

###エミュレーターで動作確認
上記画像の左から2番目のアイコンはエミュレータの起動ボタンです。
完全再現とはいきませんがアプリの動作とPepperの会話の確認には使用可能です。
よく使うのはDialog Viewという機能です。ここで人間の発話を入力することができるようになっています。
ちなみに実機がある場合は真ん中のアイコンでPCを接続してからアプリをビルドします。



##基本的な動き
Pepperの発話ライフサイクル（ロボットライフサイクル）はActivityのライフサイクルに依存しています。

<img width="670" alt="スクリーンショット 2018-12-16 22.03.59.png" src="https://qiita-image-store.s3.amazonaws.com/0/323477/79dabb7c-202e-43c8-794d-764923b0aa17.png">

ロボットライフサイクルをActivityのライフサイクルに紐づける場合、以下の実装が必要です

>onCreateでQiSDK.register()
onDestoryでQiSDK.unregister()

そのActivityでPepperとの連携を求める場合は必ず必要になるので、
今回はBaseActivityを用意してそちらに実装してみました。

さらに、Pepperの動作を行わせたい各ActivityにはRobotLifecycleCallbacksを実装します。
このコールバックでは以下のイベントを管理します。

| メソッド名 | 発火ポイント |
|:-:|:-:|
|onRobotFocusGained   |そのActivityでPepperのフォーカスを得られた   |
|onRobotFocusLost   |フォーカスを失った時<br>(Activityの切り替え時など)   |
|onRobotFocusRefused   |フォーカスが得られなかった時   |

**onRobotFocusGained**がコールされたらPepperを喋らせることができるようになります。
BaseActivityと絡めた場合、以下のような実装になるでしょう。

```Kotlin:BaseActivity.kt
abstract class BaseActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        QiSDK.register(this, this)
    }

    override fun onDestroy() {
        super.onDestroy()
        QiSDK.unregister(this)
    }
}
```
```Kotlin:TopActivity.kt
class TopActivity : BaseActivity(), RobotLifecycleCallbacks {
    override fun onRobotFocusGained(qiContext: QiContext?) {
        super.onRobotFocusGained(qiContext)
        //なんかやらせる
    }
}
```

##発話処理の実装
超簡単な会話サンプルを作成してみます。
Pepperの発言には**Say**
Pepperの聞き取りには**Listen**
を使用しています。（上記は[Pepperが行うことのできるアクション](https://android.aldebaran.com/sdk/doc/pepper-sdk/ch2_principles/action_getting_started.html)の一部です）

Pepperの発話開始処理は同期実行か非同期実行を選択できます。
onRobotFocusGainedに直接発話処理を記述していく場合、基本的には非同期実行にしておけば良いかと思います。
非同期実行の場合、その後の処理を続けたければFutureを使用します。
気をつけなければならないのは、非同期実行の場合、Pepperがあるアクションをしている間は他のアクションの実行ができないという点です。
下記のサンプルコードの場合、Listenの継続中にSayができません。
requestCancellation()を実行して処理を止めてあげます。


```Kotlin
class TopActivity : BaseActivity(), RobotLifecycleCallbacks {
    override fun onRobotFocusGained(qiContext: QiContext?) {
        super.onRobotFocusGained(qiContext)

        SayBuilder.with(qiContext).withText("うーん。高さんさあ。ペッパーじゃ余りに平凡じゃない？").build().async().run().thenConsume {

            //Pepperに聞き取ってほしいワードの一覧
            val answerPhraseSet = PhraseSetBuilder.with(qiContext).withTexts("だよなぁ！").build()

            val future =
                ListenBuilder.with(qiContext).withPhraseSet(answerPhraseSet).build().async().run()
            future.andThenConsume { _ ->
                //これを入れないと真下のSayが続きません
                future.requestCancellation()
                SayBuilder.with(qiContext).withText("ペッパー・オブ・シャイニングってのはどう？").build().async().run()
            }
        }
    }
}
```

下記のような会話になります

![スクリーンショット 2018-12-17 15.00.55.png](https://qiita-image-store.s3.amazonaws.com/0/323477/3018789a-7907-b8f6-5989-a31a8a7d8147.png)


##もっと複雑な会話をする
複雑な会話をさせたい場合はQiChat(*)の出番です。
最新のQiSDKではQiChatBotとChatという機能を使用することになるでしょう。
QiChatを使用する場合は、発話関係のロジックとアプリのコードが分離されるので、
必要に応じてQiChatとアプリのコードを行き来する仕組みが必要になってきます。

QiChatとAndroidのコードを行き来するのにBookmarkを使用しました
QiChatにはラベルが付けられるようになっており、アプリからラベルに飛ばす、QiChatのラベルに辿り着いたらアプリに戻る、といった使い方ができます。（サンプル内の %bookmarkHead,%makeLogが"ラベル"です）


```qichat.top
topic: ~top()

concept:(answer) [だよなぁ!]
concept:(answer2) [いいぞ〜]
concept:(ng) [そんなことないよ]

proposal: %bookmarkHead
うーん。高さんさあ。ペッパーじゃ余りに平凡じゃない？
    u1:({*}~answer{*})ペッパー・オブ・シャイニングってのはどう？
        u2:({*}~answer2{*})それとNAOqiなんだけど、僕の解釈だとあれはOSじゃないんだよね %makeLog
    u1:({*}~ng{*})そっかあ...
    u1:(e:Dialog/NotUnderStood)え、何？聞こえない？ ^stayInScope
```

アプリ側がこんな感じです。onBookmarkReachedListenerを付けることでQiChatのラベルに到達した時の振る舞いを設定することができます。

```TopActivity.kt
class TopActivity : BaseActivity(), RobotLifecycleCallbacks {
    override fun onRobotFocusGained(qiContext: QiContext?) {
        super.onRobotFocusGained(qiContext)

        val topic = TopicBuilder.with(qiContext).withResource(R.raw.top).build()
        val qiChatbot = QiChatbotBuilder.with(qiContext).withTopic(topic).build()
        val bookmarks = topic?.bookmarks
        val headBookmark = bookmarks?.get("bookmark_head")


        qiChatbot.addOnBookmarkReachedListener {
            when (it.name) {
                "makeLog" -> {
                    Log.d("debug", "うんうん")
                }
            }
        }

        val chat = ChatBuilder.with(qiContext).withChatbot(qiChatbot)
        chat.addOnStartedListener {
            qiChatbot.goToBookmark(headBookMark,
AutonomousReactionImportance.HIGH,AutonomousReactionValidity.IMMEDIATE)
        }
        chat.build().async().run()

    }
}
```

~~この際、QiChat内のどこから発話するかですが、原則一番上に記述したproposal（メソッドのようなものだと思ってください）を発話します。
複数proposalを定義しており、~~
アプリから任意のproposalに飛ばしたい場合は**QiChatbot#goToBookmark**を使用します。他の引数についてはドキュメントをご確認ください。（基本的にはサンプル通りで良いと思います。）
https://qisdk.softbankrobotics.com/sdk/doc/pepper-sdk/ch4_api/conversation/tuto/bookmarks_tutorial.html

**追記**
初回発話に関してもBookmarkで飛ばさないと発話しないかもしれなかったので、上記コードに追記しました。
https://qisdk.softbankrobotics.com/sdk/doc/pepper-sdk/ch4_api/conversation/tuto/bookmarks_tutorial.html#bookmarks-tutorial
また、画面遷移して新たに発話をしたい時、前のchatを掴んだままな場合があるので、
onRobotFocusLostで**future.requestCancellation()**しておいたほうが良さそうです。
futureはchatを非同期実行するとリターンされます。

*QiChatとは...
JavaないしKotlinでもPepperとの会話を作成することは可能です。
ですが、複雑かつそれなりの文量を話してもらおうと思うと何かと面倒です。
QiChatはPepperの発話に特化したスクリプト言語です。
これを利用することにより、アプリ側のロジックとPepperの発話のロジックをうまく分離することができるようになります。
今回QiChatの詳しい説明は致しません。
QiChatの情報は幸いにしてそこそこあるので各自ご確認ください。

##苦労話
いくらAndroidアプリといえ、本体はロボットですので、一筋縄でいかないことが結構ありました。
###喋らない
アプリのロジックは間違ってないのになぜか喋らない...よくあります
よくあるのはQiChatのエラーが出ているパターンです。使用している関数を見直して見たりするとよかったです。
エミュレーターの場合はエミュレーターを再起動してみるとうまくいったりいかなかったりします。
###OSのアップデートでアプリが落ちる
Pepper側のOSのアップデートで結構影響を受けてしまいますのでNAOqiのアップデート後はデバッグを入念にしましょう...:innocent::innocent::innocent:
###情報が少ない
（少ない）
公式情報を頑張って読み解く他ないでしょう。
公式情報はひとまず下記のページに目を通しておけば大丈夫だと思います。

・Principlesのページは実装におけるお作法が書いてあるので全部大事です。
　https://android.aldebaran.com/sdk/doc/pepper-sdk/ch2_principles/focus_lifecycle.html
・会話に関するAPIのリファレンスです
　https://android.aldebaran.com/sdk/doc/pepper-sdk/ch4_api/conversation/index_conversation.html
・QiChatを使うならば
　https://android.aldebaran.com/sdk/doc/pepper-sdk/ch4_api/conversation/qichat/qichat_index.html