Pepper SDK入門(27) BaseQiChatExecutorを潤滑油にしてPepperと交流する🎞🌹

#BaseQiChatExecutor's profile🎙
BaseQiChatbotExecutorを実装すると、QiChatのTopicファイル中の^executeで呼び出すことができます。
一見Bookmarkのような機能ですがBookmarkとは異なり、順番に処理を実行できます🟥🟧🟨🟩🟦🟪例えば「ダンスをします」と言ってから実際にダンスのアニメーションを実行し、ダンスが終わったら「如何でしたか？」と発話させる、といった内容をTopicファイル上で記述できるようになります。

#習いながら慣れてみる⌨️🔁

以下の内容は<ins>[Git](https://github.com/aldebaran/qisdk-tutorials/tree/master/app/src/main/java/com/softbankrobotics/qisdktutorials/ui/tutorials)</ins>のMastering Executeで体験できるものです。
ここではTopicファイルを次のように変身🌜🧹させています。

```
topic: ~execute()
concept:(dance_request) [踊ってみせて 踊ってほしい]

proposal: %execute_proposal 静かな夜景は贅沢ですね
u:(~dance_request) 見ていてください ^execute(myExecutor,param1) 今夜は月が綺麗です
```
会話中に^executeへ到達するとExecutorのrunWithメソッドが呼ばれ、引数がList<String>で渡されます。
キー名はmyExecutorで、パラメータは好きな数だけ設定することが可能です。~~不可説不可説転は不可能~~

#QiChatExecutorクラスの生誕🧁👑
BaseQiChatExecutorからMyQiChatExecutorクラスを作成し、runWithとstopを実装します💅💫
<font color="red">ここではコードの書き方に注意が必要です👀</font>
例えば、以下のコードを見てみましょう。

```java
class MyQiChatExecutor extends BaseQiChatExecutor {

        protected MyQiChatExecutor(QiContext context) {
            super(context);
        }

    @Override
    public void runWith(List<String> params) {
            //アニメーションのオブジェクトを作成
            Animation animation =
            AnimationBuilder.with(getQiContext())
            .withResources(R.raw.taichichuan_a001).build();
            //アクションをビルド
            Animate animate =
            AnimateBuilder.with(getQiContext())
            .withAnimation(animation).build();
           //アニメーション開始時に"Animation started."とログを出す
            Log.i(TAG, "Animation started."
                 , ConversationItemType.INFO_LOG)
            //アクションを実行
            animate.run()
            //アニメーション終了時に"Animation finished."とログを出す
            Log.i(TAG, "Animation finished."
                  , ConversationItemType.INFO_LOG)
    }

    @Override
    public void stop() {
        //Executeが実行中にChatがキャンセル、もしくは停止された時にコール
        Log.i(TAG, "QiChatExecutor stopped");
    }
```
このコードでは、アニメーション中にチャットが終了してもアニメーションをキャンセルすることができません。これはアニメーションが非同期処理になっていないためです。

それでは、次のコードはどうでしょう。

```java
class MyQiChatExecutor extends BaseQiChatExecutor {
        private Future<Void> future = null;

        protected MyQiChatExecutor(QiContext context) {
            super(context);
        }

        @Override
        public void runWith(List<String> params) {
            //アニメーションのオブジェクトを非同期に作成
            future = AnimationBuilder.with(getQiContext())
                    .withResources(R.raw.taichichuan_a001).buildAsync()
                    .andThenCompose(new Function<Animation, Future<Animate>>
             () {
         @Override
         public Future<Animate> execute(Animation animation) throws Throwable 
         {
         //アクションを非同期にビルド
         return
         AnimateBuilder.with(getQiContext())
         .withAnimation(animation).buildAsync();
             }
           })
           .andThenCompose(new Function<Animate, Future<Void>>() {
         @Override
         public Future<Void> execute(Animate animate) throws Throwable {
                  Log.i(TAG, "Animation started."
                       , ConversationItemType.INFO_LOG)
                  //アクションを非同期に実行
                  return animate.async().run();
                  Log.i(TAG, "Animation finished."
                        , ConversationItemType.INFO_LOG)
                        }
                    });
        }
        @Override
        public void stop() {
            //アクションをキャンセルする
            future.requestCancellation();
            Log.i(TAG, "QiChatExecutor stopped");
        }
    }
```

このコードでは非同期処理でキャンセルを呼べるようにしていますが、これではいけません。
executorを使ったにもかかわらずrunWith内の処理が非同期のため、runWithを一瞬で抜けてTopicの会話をすぐに再開してしまいます。

では、どうすれば良いのかと言うと…

```java
class MyQiChatExecutor extends BaseQiChatExecutor {
        private Future<Void> future = null;

        protected MyQiChatExecutor(QiContext context) {
            super(context);
        }

        @Override
        public void runWith(List<String> params) {
            future = AnimationBuilder.with(getQiContext())
                    .withResources(R.raw.taichichuan_a001).buildAsync()
                    .andThenCompose(new Function<Animation
                                    , Future<Animate>>() {
         @Override
         public Future<Animate> execute(Animation animation) throws Throwable
         {
         return AnimateBuilder.with(getQiContext())
         .withAnimation(animation).buildAsync();
           }
         })
           .andThenCompose(new Function<Animate, Future<Void>>() 
           {
        @Override
        public Future<Void> execute(Animate animate) throws Throwable {
                  Log.i(TAG, "Animation started."
                        , ConversationItemType.INFO_LOG)
                  return animate.async().run();
                  Log.i(TAG, "Animation finished."
                        , ConversationItemType.INFO_LOG)
                        }
                    });
            try {
                //アニメーションが完了するまで待機
                future.get();
               //中断されたタスクの結果を受け取る
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void stop() {
            future.requestCancellation();
            Log.i(TAG, "QiChatExecutor stopped");
        }
    }

```
これでアニメーションに対して拒否権のあるアプリとなりました❌🩹
上記を参考に、異種への変身[^1]の第一歩を這い出してみてください。🍼🐛
[^1]: フランツ・カフカの『変身』に登場するグレゴール・ザムザ(ヒト)が毒虫になるように、Pepper(機械)が井蛙や窮鼠になる可能性も存在しています。[^2]
[^2]: 可能性が存在していない可能性も存在しています。

#The Map of QiChatExecutor🌒🔭
^executeを記したTopicファイルを作成してから、QiChatbotを作成しましょう。
Topicファイルにあるexecutorの名前をセットすれば、最終的にChatは非同期に実行されます。

```java
@Override
public void onRobotFocusGained(QiContext qiContext) {

    //Topicを作成
    final Topic topic = TopicBuilder.with(qiContext)
            .withResource(R.raw.execute)
            .build();

    //QiChatbotを作成
    QiChatbot qiChatbot = QiChatbotBuilder.with(qiContext).withTopic(topic).build();

    Map<String, QiChatExecutor> executors = new HashMap<>();

    //TopicファイルからQiChatExecutorにexecutorの名前をセット
    executors.put("myExecutor", new MyQiChatExecutor(qiContext));

    //QiChatbotにExecutorをセット
    qiChatbot.setExecutors(executors);

    //ChatbotBuilderと共にChatをビルド
    Chat chat = ChatBuilder.with(qiContext).withChatbot(qiChatbot).build();

    //非同期にアクションを実行
    chat.async().run();
}
``` 

以上を経たサンプルアプリの改造によって、映画のワンシーンのようなやり取りが可能となったPepperがこちらになります🌃👓(※背景はイメージです)
<iframe width="560" height="315" src="https://www.youtube.com/embed/vWIqM0HOCYU" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>

#あとがき
今回も[PepperSDKforAndroid](https://android.aldebaran.com/sdk/doc/pepper-sdk/ch4_api/conversation/reference/baseQiChatExecutor.html)を参考に書かせていただきました。
さらに詳しい情報は[QiChatExecutorのAPIリファレンス](https://android.aldebaran.com/sdk/doc/javadoc/qisdk/com.aldebaran.qi.sdk.object.conversation/-base-qi-chat-executor/index.html)を参照してみてください。

次回はなんと、Basechatbotが登場！🎭💡塩基(base)がDNA上でアデニン(A)・チミン(T)・グアニン(G)・シトシン(C)がスートに変形して合体!？トランプで出来上がったタワーが育ちバベルの塔になる時、天上で見たものは…?[^3]
乞うご期待！🎊🕊

[^3]: 予告は未来の事象を確約するものではありません。