Tutorial?
前回はクライアント(SIGViewer)からエージェントにメッセージを送信して受信したエージェントはお辞儀をするというサンプルを紹介しました。今回はエージェント間でのやり取りを行うサンプルコードを紹介します。
まずメッセージの送信を行うコントローラを作成します。
$ cd ~/sigverse-<version>/bin/NewWorld $ emacs SendController.cpp
SendController.cpp
#include "Controller.h" #include "Logger.h" #include "ControllerEvent.h" #define PI 3.141592 #define DEG2RAD(DEG) ( (PI) * (DEG) / 180.0 ) #define ARY_SIZE(ARY) ( (int)(sizeof(ARY)/sizeof(ARY[0])) ) class SendController : public Controller { public: //シミュレーション開始時に一度だけ呼出される関数onInitの利用を宣言します。 void onInit(InitEvent &evt); double onAction(ActionEvent &evt); }; void SendController::onInit(InitEvent &evt) { SimObj *my = getObj(myname()); if (!my->dynamics()) { //右手、左手を下げ、体全体を180°後ろに回転させます。 my->setJointAngle("LARM_JOINT2", DEG2RAD(-90)); my->setJointAngle("RARM_JOINT2", DEG2RAD(90)); my->setAxisAndAngle(0, 1.0, 0, DEG2RAD(180)); } } double SendController::onAction(ActionEvent &evt) { //メッセージを作成します。 char *strs[] = { "Hello!!", }; //エージェント全員にメッセージを送信します。 broadcastMessage(ARY_SIZE(strs),strs); //5秒置きにメッセージを送信します。 return 5.0; } extern "C" Controller * createController () { return new SendController; }
これは5秒置きにメッセージ"Hello!"を送信するコントローラのサンプルです。broadcastMessageではエージェント全員にメッセージを送信します。
メッセージを送る相手を指定したいときはbroadcastMessageの代わりにsendMessageを用いて以下のように、メッセージを送る相手を指定します。
broadcastMessage(ARY_SIZE(strs),strs);
↓
sendMessaage("robot_000",ARY_SIZE(strs), strs);
メッセージを送信する相手に"robot_000"を指定しました。
次にメッセージの受信を行うエージェントコントローラを作成します。
$ emacs RecvController.cpp
RecvController.cpp
#include "Controller.h" #include "Logger.h" #include "ControllerEvent.h" #define PI 3.141592 #define DEG2RAD(DEG) ( (PI) * (DEG) / 180.0 ) class RecvController : public Controller { public: double onAction(ActionEvent &evt); //メッセージ受信時に呼び出される関数onRecvMessageの利用を宣言します。 void onRecvMessage(RecvMessageEvent &evt); }; double RecvController::onAction(ActionEvent &evt) { SimObj *my = getObj(myname()); if (!my->dynamics()) { //上がった手を下げます。 my->setJointAngle("LARM_JOINT2", DEG2RAD(0)); } return 1.0; } //メッセージ受信時 void RecvController::onRecvMessage(RecvMessageEvent &evt) { //自分自身の取得 SimObj *my = getObj(myname()); // メッセージの送り主表示 LOG_MSG(("sender : %s", evt.getSender())); // getSize で送られてきた文字列数を取得する int n = evt.getSize(); LOG_MSG(("# of string : %d", n)); for (int i=0; i<n; i++) { // 文字列を順に取得する LOG_MSG(("string[%d] : %s", i, evt.getString(i))); //手を上げます。 if (!my->dynamics() ) { my->setJointAngle("LARM_JOINT2", DEG2RAD(180)); } } } extern "C" Controller * createController () { return new RecvController; }
これはメッセージを受信したら手を上げるというサンプルです。また、onActionで1秒ごとに上がった手を下げようとします。
それではコンパイルします。 まずMakefileを修正します。
$ emacs Makefile
変更前
#オブジェクトファイルの指定 OBJS = AgentController.so
変更後
#オブジェクトファイルの指定 OBJS = SendController.so RecvController.so
共有オブジェクトにSendController.so, RecvController.soの2つを指定しました。
$ make
次に世界ファイルの作成をします。
$ cd .. $ emacs xml/MessageWorld.xml
MessageWorld.xml
<?xml version="1.0" encoding="utf8"?> <world name="VisTest2"> <gravity x="0.0" y="-9.8" z="0.0"/> <!--メッセージ送信エージェントMan-niiの設定--> <instanciate class="Man-nii.xml"> <set-attr-value name="name" value="man_000"/> <set-attr-value name="dynamics" value="false"/> <set-attr-value name="language" value="c++"/> <!--メッセージ送信者のコントローラ指定--> <set-attr-value name="implementation" value="./NewWorld/SendController.so"/> <!--エージェントの位置(x,y,z)--> <set-attr-value name="x" value="0.0"/> <set-attr-value name="y" value="60.0"/> <set-attr-value name="z" value="60.0"/> </instanciate> <!--メッセージ受信エージェントRobot-niiの設定--> <instanciate class="Robot-nii.xml"> <set-attr-value name="name" value="robot_000"/> <set-attr-value name="language" value="c++"/> <!--メッセージ受信者のコントローラ指定--> <set-attr-value name="implementation" value="./NewWorld/RecvController.so"/> <!--動力学演算をfalseに設定--> <set-attr-value name="dynamics" value="false"/> <!--エージェントの位置(x,y,z)--> <set-attr-value name="x" value="0.0"/> <set-attr-value name="y" value="60.0"/> <set-attr-value name="z" value="-60.0"/> </instanciate>
人型エージェントが、ヒューマノイド型エージェントにメッセージを送信します。
起動して見てみましょう。
$ ./sigserver.sh -w MessageWorld.xml -p 9001
SIGViewerで接続して、シミュレーションを開始するとonInitにより、人型エージェント(メッセージ送信者)が手を下して、後ろを振り返ります。そして5秒に一回ロボット(メッセージ受信者)がメッセージに反応して手を挙げているのがわかります。
SIGViewerの下のほうにあるmessagesタブをクリックします。するとロボットがメッセージに反応して送信者と、文字列数、とその内容を表示します。
文字列の数(# of string)は1と表示されましたが、メッセージ作成時に以下のようにすることで、文字列は同時に複数送ることもできます。
//メッセージを作成します。 char *strs[] = { "Hello!!", "How are you", };
次にエージェント間でのテキスト文字列の送受信について説明します。テキスト文字列は人間が声を出すのと同じように、自分の声が届く範囲を指定することができます。
まずはテキストを送信するコントローラを作成します。
$ cd NewWorld $ emacs SendText.cpp
SendText.cpp
#include "Controller.h" #include "Logger.h" #include "ControllerEvent.h" #define PI 3.141592 #define DEG2RAD(DEG) ( (PI) * (DEG) / 180.0 ) class SendController : public Controller { public: void onInit(InitEvent &evt); double onAction(ActionEvent &evt); }; void SendController::onInit(InitEvent &evt) { SimObj *my = getObj(myname()); if (!my->dynamics()) { my->setJointAngle("LARM_JOINT2", DEG2RAD(-90)); my->setJointAngle("RARM_JOINT2", DEG2RAD(90)); my->setAxisAndAngle(0, 1.0, 0, DEG2RAD(180)); } } double SendController::onAction(ActionEvent &evt) { try { SimObj *my = getObj(myname()); //自分の位置を得ます。 double x = my->x(); double y = my->y(); double z = my->z(); //z方向に20移動します。 my->setPosition( x, y, z + 20 ); //半径300.0以内のすべてのエージェントに対してテキスト"Hello!"を送ります。 sendText(evt.time(), NULL, "Hello!", 300.0); }catch(SimObj::Exception &) { ; } //3秒後にonActionが呼び出されます。 return 3.0; } extern "C" Controller * createController () { return new SendController; }
これは3秒に1回テキスト"Hello!"を送信するコントローラです。sendText関数でテキストを送信します。sendTextの2番目の引数がNULL、4番目の引数が300.0となっています。こうすることにより、テキストを送信する相手を指定せず、周囲300(cm)にいるすべてのエージェントにテキストを送信します。テキストを送信する相手を指定したい場合は2番目の引数に指定するエージェントの名前を入力します。
このエージェントはz方向にどんどん移動しながらテキストを送信します。つまりテキストを受信するエージェントからはどんどん遠ざかりながらテキストを送信します。
テキスト受信者のコントローラを作成します。
$ emacs RecvText.cpp
RecvText.cpp
#include <string> #include "Controller.h" #include "Logger.h" #include "ControllerEvent.h" #define PI 3.141592 #define DEG2RAD(DEG) ( (PI) * (DEG) / 180.0 ) class RecvController : public Controller { public: double onAction(ActionEvent &evt); //テキスト受信時に呼出される関数onRecvTextの利用を宣言します。 void onRecvText(RecvTextEvent &evt); }; double RecvController::onAction(ActionEvent &evt) { SimObj *my = getObj(myname()); if (!my->dynamics()) { my->setJointAngle("LARM_JOINT2", DEG2RAD(0)); } return 1.0; } //テキスト受信時 void RecvController::onRecvText(RecvTextEvent &evt) { //自分自身を取得します。 SimObj *my = getObj(myname()); // テキストの送り主を取得します。 const char *caller = evt.getCaller(); //テキストの内容を取得します。 const char *text = evt.getText(); //送り主と内容を表示します。 LOG_MSG(("receive from %s : \"%s\"", caller, text)); //手を挙げます。 if (!my->dynamics()) { my->setJointAngle("LARM_JOINT2", DEG2RAD(180)); } } extern "C" Controller * createController () { return new RecvController; }
先ほどと同様にテキスト受信時に手を上げます。
コンパイルします。
$ emacs Makefile
変更前
#オブジェクトファイルの指定 OBJS = SendController.so RecvController.so
変更後
#オブジェクトファイルの指定 OBJS = SendText.so RecvText.so
$ make
次に世界ファイルを変更します。
$ cd .. $ emacs xml/MessageWorld.xml
変更前
: <!--メッセージ送信者コントローラ指定--> <set-attr-value name="implementation" value="./NewWorld/SendController.so"/> : <!--メッセージ受信者コントローラ指定--> <set-attr-value name="implementation" value="./NewWorld/RecvController.so"/> :
変更後
: <!--メッセージ送信者コントローラ指定--> <set-attr-value name="implementation" value="./NewWorld/SendText.so"/> : <!--メッセージ受信者コントローラ指定--> <set-attr-value name="implementation" value="./NewWorld/RecvText.so"/> :
それでは実行してみます。
$ ./sigserver.sh -w MessageWorld.xml -p 9001
SIGVierwerで接続して、シミュレーションを開始してみます。
テキスト送信者が遠ざかりながらテキストを送信しているのがわかります。 エージェントの声がどこまで届くのか確認してみてください。
English version?