|
|
|
このページの内容
Tekkotsu は状態機械を実現するために、StateNode(状態ノード) と Transition(遷移) という2つのクラスを用意しています。これらクラスは BehaviorBaseのサブクラスです。
1つの状態は、そのDoStart()関数により呼び出されて起動します。この起動が次に StateNode::DoStart() を呼び、さらに StateNode::DoStart() が、その状態につながる全ての遷移の DoStart()関数を呼びます(下図)。それぞれの遷移は、いろいろなタイプのイベントリスナーを設定します。特定のタイプのイベントを受け取ると、遷移が起動します。遷移は、前の状態のDoStop()関数を呼ぶことにより、前の状態の活動を止め、次の状態のDoStart()関数を呼ぶことにより、次の状態を起動します。前の状態が止まると、その状態から出ている遷移も、それらのDoStop()関数により全て止まり、それらが持つリスナーが削除されます。下図は、3個のノードで構成される簡単な状態機械です。この状態機械は、状態Bark からスタートします(Bark=吠える)。Barkには2本の出力遷移があります(出力遷移=出て行く矢印)。1本は「Head button pressed(頭ボタンが押された)」というイベントのリスナーを設定します。もう1本は「5 second timer expires(タイマーが5秒経過した)」というイベントのリスナーを設定します。 起動すると、Barkは吠え声を再生し、AIBOを座らせます。そしてイベントが起こるのを待ちます。AIBOの頭ボタンが押されると、ping.wavサウンドを再生して Wait状態に遷移します。Bark状態のときに、5秒以内にボタンが押されないと、タイマーが時間切れになり、Howl状態に遷移します(howl=遠吠え)。ボタンが押されたときも、押されずに5秒が経過したときも、Bark状態は止まり、Barkから出る2つの遷移も止まります。Howl状態は、howl.wavを再生し、再生が完了したイベント(howl completed)を投げます。このイベントが Howl から Wait への遷移を起動します。Wait状態は何もしませんが、タイマーが15秒経過するのを持ちます。タイマーが時間切れになると、この遷移が起こり、Wait状態から Bark状態へ戻ります。
状態機械としてビヘイビアを定義するための一般的な手順は次の通りです。
例えば "SampleState" のような名前を付けて1つのStateNodeを作ります。この親ノードで、setup()関数を提供し、この関数内で状態機械に必要な全てのノードと遷移のインスタンスを作ります。
練習: Bark/Howl 状態機械 状態機械を作るために次の2つのファイルをインクルードします。
Behaviors/StateNode.h
Behaviors/Transition.hさらに、使用するサブクラスのファイルをインクルードします。
StateNode のサブクラスは、Behaviors/Nodes に含まれます。
Transition のサブクラスは、Behaviors/Transitions に含まれます。さて、SampleState はStateNode の子クラスです。 StateNode は BahaviorBase の子クラスです。スタート・ノードへのポインタを作るために、protected変数を使用します。この場合のスタート・ノードは、bark_node です。
#ifndef INCLUDED_SampleState_h_ #define INCLUDED_SampleState_h_ #include "Behaviors/StateNode.h" #include "Behaviors/Transition.h" #include "Behaviors/Nodes/SoundNode.h" #include "Behaviors/Transitions/CompletionTrans.h" #include "Behaviors/Transitions/EventTrans.h" #include "Behaviors/Transitions/TimeOutTrans.h" #include "Events/EventRouter.h"setup() 関数は、状態機械を構築します。この関数は、SampleStateのDoStart()関数が最初に呼ばれるときに自動的に呼ばれます。ローカル変数 bark_node、howl_node、wait_node は、setup()が再び呼ばれると捨てられます。しかし、これらノードへのポインタは、addNode()を呼ぶことにより、StateNode自身が維持します。また、これら3ノードは、出力遷移へのポインタを維持します。
class SampleState : public StateNode { protected: StateNode *startnode; public: SampleState() : StateNode("SampleState"), startnode(NULL) {}DoStart() は状態機械を起動したいときに呼ばれます。DoStart() は最初に StateNode::DoStart() を呼びます。それから、その子ノードの1つのDoStart()を呼んで状態機械をスタートします。
virtual void setup() { // ここは決まりごと // StateNode::setup(); std::cout << getName() << " is setting up the state machine." << std::endl; // ノードを作成 // SoundNode *bark_node = new SoundNode("bark","barkmed.wav"); SoundNode *howl_node = new SoundNode("howl","howl.wav"); StateNode *wait_node = new StateNode("wait"); // ノードを追加 // addNode(bark_node); addNode(howl_node); addNode(wait_node); // 遷移を作成 // EventTrans *btrans = new EventTrans(wait_node,EventBase::buttonEGID, RobotInfo::HeadFrButOffset,EventBase::activateETID); btrans->setSound("ping.wav"); // 遷移を追加 // bark_node->addTransition(btrans); bark_node->addTransition(new TimeOutTrans(howl_node,5000)); howl_node->addTransition(new CompletionTrans(wait_node)); wait_node->addTransition(new TimeOutTrans(bark_node,15000)); // スタート・ノードを指定 // startnode = bark_node; }
DoStart() は同様に StateNode::DoStop() を呼びます。下の方の privateセクションは、コンパイラのwarning(警告)を避けるために必要なダミーのコードです。クラスがポインターデータの変数を持つときに書きます。ここでのポインターデータ変数は、startnode
です。秀丸を開き、上記プログラムをコピー&貼り付け、次の通り保存してください。
virtual void DoStart() { StateNode::DoStart(); std::cout << getName() << " is starting up." << std::endl; startnode->DoStart(); } virtual void DoStop() { std::cout << getName() << " is shutting down." << std::endl; StateNode::DoStop(); } private: // コンパイル時の警告を避けるためのダミー関数 SampleState(const SampleState&); SampleState& operator=(const SampleState&); }; #endif保 存 先 : マイドキュメント → usXX → project
ファイル名 : SampleState.h
ファイル種類: C言語ヘッダーファイル(*.h)ファイルを保存したら、次の手順で実行してください。
構文
- ノードを作成するためのコンストラクタ StateNode
StateNode *node = new StateNode (const std::string & nodename);
- サウンドノードを作成するためのコンストラクタ SoundNode
SoundNode *node = new SoundNode ( const std::string & nodename,
const std::string & soundfilename);
- ノードを追加するためのメソッド addNode
addNode( StateNode *node );
- 遷移を作成するためのコンストラクタ EventTrans
その他のEventTransについては、こちら。
EventTrans *trans = new EventTrans ( StateNode *destination,
EventBase::EventGeneratorID_t gid,
unsigned int sid,
EventBase::EventTypeID_t tid);
- 遷移を追加するためのメソッド addTransition
node->addTransition ( Transition *trans );
- "ノードの活動が完了したときに実行する遷移"を作成するためのコンストラクタ CompletionTrans
node->addTransition ( new CompletionTrans ( StateNode *destination) );
- "タイムアウトになったときに実行する遷移"を作成するためのコンストラクタ CompletionTrans
delay の単位はミリ秒
node->addTransition ( new TimeOutTrans ( StateNode *destination
unsigned int delay) );
さらに、状態ノードがモーションコマンドや音声再生のようなアクションを実行する場合、状態ノードが投げるイベントタイプは statusETID です。
また、遷移が起こるときは常に、状態ノードは stateTransitionEGID 状態イベントを投げます。これらのイベントは Tekkotsuの event logger を使用して観察できます。
Telnet画面に表示されるイベントの例を示します。
Event Logger の使用
- 上記の状態機械のプログラムをコンパイルしてください。
- コンパイルしたプログラムをAIBOに入れ、AIBOを起動してください。
- Cygwin画面を開き、下記のように打ち込んで、AIBOに無線接続してください。
us0X@xxxxx ~ $ telnet AIBOのIPアドレス 59000- コントローラ画面のメニューから次の順に選んでください。
Root Control > Status Reports > Event Logger
stateMachineEGID と stateTransitionEGID をダブルクリックしてください。
それらの横にチェックマークが表示されます。- Root Control > 0. Mode Switch に戻ってください。
SampleState を起動してください。
Telnet画面に一連のイベントが表示されるでしょう。
最初のイベントは、SampleState が起動したことによるものです。
次は、barkノードの起動によるイベントです。
このイベントは、ボタンを押したか、タイマーの時間切れのどちらかにより発生します。- Event Logger に戻ってください。
buttonEGID と audioEGID をダブルクリックして、チェックを表示します。
これで、状態機械を動かすと、2つの状態の変遷の引き金となるボタンと音声のイベントをTelnet画面で見ることができます。
表示されるイベントの順序が直感に反するかもしれません。
SoundManager::LoadBuffer() of 8836 bytes 音声ファイル読み込み EVENT: (audioEGID,/ms/data/sound/ping.wav,A) 音声ファイル ping.wav 再生 EVENT: (stateTransitionEGID,{bark}--EventTrans-->{wait},S) bark - イベント遷移 -> wait EVENT: (stateMachineEGID,bark,D) bark 停止 EVENT: (stateMachineEGID,wait,A) wait 開始 EVENT: (buttonEGID,HeadBut,A) ボタン押下開始 EVENT: (buttonEGID,HeadBut,S) ボタン押下中 EVENT: (buttonEGID,HeadBut,D) ボタン押下停止 EVENT: (stateTransitionEGID,{wait}--TimeOutTrans-->{bark},S) wait - タイムアウト遷移 -> bark EVENT: (stateMachineEGID,wait,D) wait 停止 EVENT: (stateMachineEGID,bark,A) bark 開始 SoundManager::LoadBuffer() of 8836 bytes 音声ファイル読み込み EVENT: (audioEGID,/ms/data/sound/barkmed.wav,A) 音声ファイル barkmed.wav 再生 EVENT: (stateTransitionEGID,{bark}--TimeOutTrans-->{howl},S) bark - タイムアウト遷移 -> howl
以下にPlayNTimesNodeのソースを示します。
これを次の通り保存してください。
保 存 先 : マイドキュメント → usXX → project
ファイル名 : PlayNTimesNode.h
ファイル種類: C言語ヘッダーファイル(*.h)
ここで、3回吠える状態機械を示します。
#ifndef INCLUDED_PlayNTimesNode_h_ #define INCLUDED_PlayNTimesNode_h_ #include "Behaviors/Nodes/SoundNode.h" class PlayNTimesNode : public SoundNode { protected: int ntimes; // 再生の反復回数 public: PlayNTimesNode(std::string nodename="PlayNTimesNode", std::string soundfilename="", int _ntimes=1) : SoundNode("PlayNTimesNode",nodename,soundfilename), ntimes(_ntimes) {} virtual void DoStart() { SoundNode::DoStart(); // 反復 for (int i=2; i<=ntimes; i++) // 音声ファイルをバッファに追加 sndman->ChainFile(curplay_id,filename); }; void setNTimes(int const n) { ntimes = n; } protected: PlayNTimesNode(std::string &classname, std::string &nodename, std::string &soundfilename, int _ntimes=1) : SoundNode(classname,nodename,soundfilename), ntimes(_ntimes) {} }; #endif
PlayNTimesNode が2つのコンストラクタをもつ理由(ここは理解できなくてもOK)
#ifndef INCLUDED_SampleState_h_ #define INCLUDED_SampleState_h_ #include "Behaviors/StateNode.h" #include "Behaviors/Nodes/SoundNode.h" #include "Behaviors/Transitions/TimeOutTrans.h" #include "PlayNTimesNode.h" class SampleState : public StateNode { private: StateNode* startnode; public: SampleState() : StateNode("SampleState"), startnode(NULL) {} virtual void setup() { StateNode::setup(); // 音声ファイルをロード sndman->LoadFile("barkmed.wav"); // woofノードを作成 StateNode* woof_node = new PlayNTimesNode("woof","barkmed.wav",3); // woofノードを追加 addNode(woof_node); // タイムアウト遷移を追加 woof_node->addTransition(new TimeOutTrans(woof_node,5000)); // スタート・ノード startnode = woof_node; } // 音声ファイルを解放 virtual void teardown() { sndman->ReleaseFile("barkmed.wav"); StateNode::teardown(); } virtual void DoStart() { StateNode::DoStart(); std::cout << getName() << " is starting up." << std::endl; startnode->DoStart(); } virtual void DoStop() { std::cout << getName() << " is shutting down." << std::endl; StateNode::DoStop(); } private: // Dummy functions to satisfy the compiler SampleState(const SampleState&); SampleState& operator=(const SampleState&); }; #endif
発展:
getName()メソッドは起こっている遷移の名前を調べます。
virtual void DoStart() { TimeOutTrans::DoStart(); std::cout << getName() << std::endl; erouter->addListener(this,EventBase::visObjEGID,sid); }
一般にgetName()を使用すると、遷移の様子は次の形式で表示されます。
{woof}--LostTargetTrans-->{woof}
ここで、src1, src2, ...は遷移元、
{src1,src2,...}--TransitionClassName-->{dest1,dest2,...}
|
|
|