タイムラインを活用したアプリのデザイン (ステートマシンデザイン)

一般にプログラム開発において、コードが肥大化、複雑化するなどして、開発した人間以外が理解できずメンテナンスが困難になっている状態のプログラムのことを「スパゲッティプログラム」と呼ぶことがあります。
Choregrapheでのボックスを並べてつないでいくプログラミングスタイルは簡単にはじめられ、小さいプログラムのうちは理解がしやすいという利点がありますが、ボックスの数が増え、相互の接続関係が増えると、文字通りスパゲッティなプログラムへとなってしまいます。

![spaghetti.png](https://qiita-image-store.s3.amazonaws.com/0/54394/d16aafb5-59ca-fe13-da9f-d7217ff8378f.png)

そのため、何らかの方法で、ボックス群を理解しやすい単位に分割して管理するということを考える必要があります。

ここでは、スパゲッティ化を防ぐアプローチの一つとして、「ステートマシンデザイン」という考え方を用いたフローの分割、アプリケーション構築方法について説明していきます。
なお、このチュートリアルは2014/9/20にベルサール渋谷ガーデンでおこなわれた [Pepper Tech Festival 2014](http://www.softbank.jp/robot/special/techfes/) の技術セッションで紹介された内容をベースにしています。

[![Pepper Tech Festival 2014 〜 技術セッション（応用篇）](http://img.youtube.com/vi/MjaOWfYWBjA/0.jpg)](http://www.youtube.com/watch?v=MjaOWfYWBjA&t=18m53s)


#ステートマシンデザインとは

ステート(状態)マシンとは、ある1つのシステムが、複数の(有限個の)状態のどれかの状態を取るものとし、何らかのイベント、条件によって別の状態へと変化していく(遷移する)という考え方です。
たとえば、Pepperにおいては、以下のように利用者との対話によって状態遷移していくようなモデルのアプリケーションを考えることができます。

![state-machine-example.png](https://qiita-image-store.s3.amazonaws.com/0/54394/5a27e59f-dee5-0cfd-7c6d-a00e739b8a69.png)

このモデルでは、アプリケーションが開始されると、「処理選択」という状態に遷移し、ユーザへの問い合わせがおこなわれます。聞き取った結果により「処理A」か「処理B」いずれかの状態に遷移し、何らかの処理を実行します。各処理の実行後、「続行確認」状態に遷移し、ユーザの応答により再度「処理選択」をおこなうか、このシステムを終了するかを選択する形になります。

これまでみてきた1つのフローダイアグラム(ビヘイビア)にボックスを配置していく方法でもこのような動作を実現することはできますが、1フローダイアグラムに多くのボックスが配置され、それらが相互に接続されることになり、見通しが悪くなってしまいます。
また、Speech Reco.ボックスのようにセンサー系ボックスは明示的にボックスを終了、あるいはUnloadする必要がありますが、どのような場合にこれらのボックスを開始し、どのようなタイミングでボックスを終了するかの管理まで考えると、さらに接続は複雑になります。

このように、1つのアプリケーションやシステムを複数の状態として分けて考えることで、それぞれの状態でどのようなボックスが機能するべきか明確になり、各状態でのボックスとボックス間の接続が単純になるというメリットがあります。

#ステートマシンデザインの実現

ステートマシンデザインを実現する方法として、タイムラインボックスの**動作レイヤー**機能を用いた実装方法について紹介します。

##動作レイヤー

これまで、タイムラインボックスは主に[モーション](http://qiita.com/Atelier-Akihabara/items/7a898f5e4d878b1ad889#%E4%B8%A6%E5%88%97%E5%AE%9F%E8%A1%8C)に関するボックスとして説明してきましたが、動作レイヤー機能を用いることで、ある時間範囲に自動的に起動するようなフローを構成することが可能です。

以下で、動作レイヤーに関する操作手順について紹介します。

###空のタイムラインボックスの作成

動作レイヤーを作る前に、空のタイムラインボックスを作ってみましょう。考え方は[Pythonボックス](http://qiita.com/Atelier-Akihabara/items/f6a05ca14be786f9a94c#python%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9)と同様です。

1. フローダイアグラム上で右クリックし、**[ボックスの新規作成]メニューの[タイムライン...]**を選択します

    ![create-timeline-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/bb197865-55de-2bc6-88f7-9e93b7c951ad.png)

2. 名前として**testtimeline と入力[A]**し、**[OK]ボタンをクリック [B]**します

    ![create-timeline-box-dialog.png](https://qiita-image-store.s3.amazonaws.com/0/54394/67166fb2-8d61-d64c-0c44-96b7bf847821.png)

    これで、以下のようにタイムラインボックスが作成されます。

    ![created-timeline-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/fee25da9-08ac-6cc2-1621-841f364ab785.png)

タイムラインボックスをダブルクリックし、タイムラインパネルを表示した状態にしてください。

###動作レイヤーの作成

動作レイヤーは、タイムラインパネル内の[動作レイヤー]にある**[新しいレイヤーの追加]**ボタンをクリックすることで作成することが可能です。

![create-behavior-layer.png](https://qiita-image-store.s3.amazonaws.com/0/54394/e5be2ff1-a878-f582-ef8b-0f181537baab.png)

動作レイヤーを作成すると、以下のように`keyframe1`という名前のレイヤーがあらわれます。**このレイヤーをクリックして選択[A]**すると、**フローダイアグラムパネルが表示され[B]**、ここにボックスを配置することができるようになります。

![initial-behavior-layer.png](https://qiita-image-store.s3.amazonaws.com/0/54394/67803bc7-19c4-510d-8c38-e59df72ac1f8.png)


このようにして、動作レイヤーを用いることでタイムラインボックス内でフローを定義することが可能になります。

###キーフレームによる分割

動作レイヤーは任意のフレームで分割することができます。
動作レイヤー上、分割したいフレーム位置で右クリックしてコンテキストメニューを表示させ、**[キーフレームの挿入]**を選択します。

![insert-keyframe.png](https://qiita-image-store.s3.amazonaws.com/0/54394/cdd7bfe7-5440-b44f-b56c-eaf665481cad.png)

すると、動作レイヤー上で先に示した`keyframe1`というレイヤーに続いて、`keyframe26`(末尾の数字は、右クリックしたフレーム番号により異なります)というレイヤーがあらわれます。

![inserted-keyframe.png](https://qiita-image-store.s3.amazonaws.com/0/54394/b4ddcf2b-3c1e-1c52-eea2-f7de1354201e.png)

これらのレイヤー上で右クリックし、**[キーフレームの編集]**を選択することでレイヤーの情報を変更することができます。

![edit-keyframe.png](https://qiita-image-store.s3.amazonaws.com/0/54394/33087895-6637-2ba4-b33f-aa47350547f7.png)

デフォルトではキーフレームとして選択したフレーム番号がレイヤー名に利用されますが、任意の名前に変更することが可能です。

![edit-keyframe-dialog.png](https://qiita-image-store.s3.amazonaws.com/0/54394/48aef245-5ce8-911b-d967-2fb802b71f77.png)


###動作レイヤーへのボックスの配置

作成した動作レイヤーは、キーフレームごとに別々のフローダイアグラムとして扱うことができます。
たとえば、以下のように、それぞれのレイヤーにボックスを配置し、接続することが可能です。

![behavior-layer-1.png](https://qiita-image-store.s3.amazonaws.com/0/54394/001ed9aa-6773-585b-b1e0-13f34bc0f2dc.png)

![behavior-layer-2.png](https://qiita-image-store.s3.amazonaws.com/0/54394/9ff59687-d98b-f2b8-9647-dd8da157d026.png)

タイムライン開始時は1フレーム目の動作レイヤーにあるボックス群がロードされ、フローダイアグラム左端の**onLoad出力**に接続されたボックスが開始されます。
デフォルトでは、このタイムラインは時間の進行にあわせて進んでいき、フレームの進行にともなって動作レイヤーも切り替わっていきます。動作レイヤーの終了フレームを過ぎたものはunloadされ、開始フレームに到達したものがloadされていきます。

このようにして、タイムラインボックスの動作レイヤーをキーフレームにより分割することで、独立した複数のフローダイアグラムを用意することができます。


##タイムライン制御ボックス

standardボックスライブラリのFlow Control > Timelineにあるボックスを利用することで、動作レイヤー内からタイムラインの時間進行を制御することが可能です。

###Stop

Stopボックスを用いると、このボックスが配置された動作レイヤーの親タイムラインの時間進行を停止することができます。
以下のように、動作レイヤー内のフローダイアグラムで利用します。

![stop-box.png](https://qiita-image-store.s3.amazonaws.com/0/54394/2a317a50-e110-e806-2e82-15af881da962.png)

たとえば、上の例のように動作レイヤー内、onLoad入力にStopボックスを接続すると、このフレームの開始にともなって自動的にタイムラインの進行を停止します。つまり、自動的に動作レイヤーが切り替わることがなくなります。


###Play

Playボックスにより、Stopボックスで停止した時間進行を再度開始することができます。

###Goto(name), Goto(number)

Gotoボックスは、任意のキーフレームに時間を進めるためのボックスです。Goto(name)ボックスを用いるとレイヤー名前を指定して移動先のフレームを指定することができ、Goto(number)ボックスを用いると、フレーム番号を指定して移動することができます。

![goto-name.png](https://qiita-image-store.s3.amazonaws.com/0/54394/1964c054-99a3-e3b1-2c5b-7dde90a37ce2.png)

たとえば、上の例のように、Sayボックス終了後にGoto(name)を使い、`action_a`フレームに移動するように指示することで、`choice`動作レイヤー実行後、Sayボックスの終了後に`action_a`レイヤーのボックス群が実行されるように定義することができます。

保守性の観点では、フレームにラベルをつけて、Goto(name)ボックスを使うことで移動することをおすすめします。フレーム番号はモーションの速度を変えた場合などに変動することがあるためです。

#ステートマシンデザインの例

ここでは、ステートマシンデザインによる構成の整理を、 **Pepper写真撮影アプリ** を例に説明していきます。

なお、このチュートリアルに関するプロジェクトファイルはGitHub https://github.com/Atelier-Akihabara/pepper-state-machine-design-example にて公開しています。
GitHubにあるコードの取得方法にはいくつかありますが、[Download ZIP]リンクからアーカイブを取得するのが簡単な方法のひとつです。他にもgit関連ツールを利用する方法などさまざまな方法がありますので、状況に応じて調べてみてください。

##例：Pepper写真撮影アプリ

ここでは、ある程度規模が大きいアプリケーションの例として、**人を見つけたら「写真はどうですか？」と話しかけ、「はい」と答えが返ってきたら、笑うことを促し、笑顔を検出したらカメラ画像を取得し、タブレットに表示する**というアプリケーションを考えてみます。
このアプリケーションで使う要素技術の説明については、以下のページを参考にしてください。

* 人の追跡 ... [Basic Awarenessボックス](http://qiita.com/Atelier-Akihabara/items/4162192129f366da1240)
* 言葉の聞き取り ... [Speech Reco.ボックス](http://qiita.com/Atelier-Akihabara/items/52e086e7c47d85b9ed3d)
* 笑顔の検出 ... [ALFaceCharacteristics API](http://qiita.com/Atelier-Akihabara/items/c64bded014801bc6a103)
* カメラ画像をタブレットに表示 ... [Take Pictureボックス・Show Imageボックス](http://qiita.com/Atelier-Akihabara/items/833e49a079788af55afb)

##ステートマシン

今回はアプリを以下のようにデザインします。

![take-picture-states.png](https://qiita-image-store.s3.amazonaws.com/0/54394/69274dbe-3bdf-f63f-8e32-a59ce267b299.png)

各状態での動作と遷移の概要は以下の通りです。

* 初期状態は「人が来るのを待機」状態で、自身に人が近づいてくるようにいくつかのパターンをランダムに選択してしゃべり、同時にBasic Awarenessを用いて人の追跡をおこないます。人を発見したら、「あいさつ」状態に遷移します
* 「あいさつ」状態では写真を撮らないかと質問し、「はい」ならば「笑顔を待機」状態へ、「いいえ」もしくは「はい」「いいえ」いずれの言葉も認識できなかった場合は「人が来るのを待機」状態に戻ります
* 「笑顔を待機」状態では、笑うことをうながすいくつかのパターンをランダムに選択してしゃべります。笑顔を検出したら「写真撮影」状態へ、人が離れていったことを検出したり、一定時間経過しても笑顔が検出できなかった場合は「人が来るのを待機」状態に戻ります
* 「写真撮影」状態では、カメラ画像を取得し、タブレットに表示して「人が来るのを待機」状態に戻ります

各状態遷移では、「恥ずかしがりやさんだなあ」など言葉を挟み込むことで、人間から状態の変化がわかるようにしてあります。細かな挙動については、後述するGitHubのサンプルプロジェクトをご覧ください。

##プロジェクトの実装

プロジェクトファイルは、GitHub https://github.com/Atelier-Akihabara/pepper-state-machine-design-example の**take-picture**プロジェクトを参照してください。

このプロジェクトのビヘイビアには、以下のようにTake Picture Appという名前のタイムラインボックスが配置してあります。

![take-picture-behavior.png](https://qiita-image-store.s3.amazonaws.com/0/54394/e0a4d6a5-a6b7-511a-fc44-20d092da4929.png)

このタイムラインボックスには動作レイヤーが1つあり、先に挙げた状態ごとにキーフレームを作成しています。

![keyframes.png](https://qiita-image-store.s3.amazonaws.com/0/54394/9869276c-ed05-18d5-3209-9b8f6163595f.png)

以下、各状態のフローの概要について説明します。

###「人が来るのを待機」(waiting)状態

Take Picture App起動後は、動作レイヤーのうちwaitingキーフレームのビヘイビアがロードされます。

![waiting-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/bcb77cc1-fdee-b3bc-4e10-6b0e4edb756c.png)

このビヘイビアがロードされると、**Stopボックス[A]**が実行され、タイムラインの進行を停止します。
自作のSelectorボックスにより、初期状態ではIdleボックス内に定義されたランダムな振る舞いが実行されます。Basic Awarenessが人を検出し、Selectorボックスの状態が変更されると、「お客さん発見」などとしゃべったのち、**Goto(name)ボックス[B]**によりgreeting状態に遷移します。

###「あいさつ」(greeting)状態

この状態では、Speech Reco.ボックスにより人間の言葉を聞き取り、次の状態への遷移を実行しています。

![greeting-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/3a9ee399-0431-a281-5298-81b0247d8ad5.png)

Waitボックスにより、一定期間経過した場合強制的にwaiting状態に戻すようなGoto (name)ボックスを用意しています。

###「笑顔を待機」(waitingforsmiling)状態

この状態では、 `FaceCharacteristics/PersonSmiling` メモリイベントを監視し、笑顔になったことを検出したらtakingpicture状態に遷移するようにしています。

![waitingforsmiling-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/0723b602-4901-65fd-5406-98ca8d4cd233.png)

###「写真撮影」(takingpicture)状態

この状態では、Pepperのカメラの画像を取得し、タブレットに表示して、利用者に見せるという動きをします。

![takingpicture-flow.png](https://qiita-image-store.s3.amazonaws.com/0/54394/74886a9c-779c-43ef-c5cc-6ffd48943f57.png)



このように、タイムラインボックスの動作レイヤーとキーフレームを用いて複数のフローを作成し、それぞれをステートとみなしてGotoボックスを用いて切り替えていくことで、ステートマシンデザインを実現することができます。


タイムラインボックスを用いたステートマシンデザインはボックス群を分割、管理するプラクティスの一つです。フローをボックス内にまとめる方法としては、他にも[フローダイアグラムボックス](http://qiita.com/Atelier-Akihabara/items/7a898f5e4d878b1ad889#%E8%A4%87%E6%95%B0%E3%81%AE%E3%83%9C%E3%83%83%E3%82%AF%E3%82%B9%E3%82%921%E3%81%A4%E3%81%AB%E3%81%BE%E3%81%A8%E3%82%81%E3%82%8B)を用いる方法もあります。これらの機能を活用しながら、ぜひ、管理、保守しやすいアプリケーションの実現を目指してください。