Events

前のページ
目次
次のページ


このページの内容


イベントとは

 一般のC++プログラムでは、main()関数に書かれている順番に処理が実行されます。
 しかし、Tekkotsuの場合は違います。
 Tekkotsuでは、イベントを利用してAIBOを制御します。
 AIBOでは、いろいろな入力や、AIBOの動きに応じてイベントが発生します。
 イベントには、次の種類があります。

イベント名Event Generator ID(EGID)説明
 タイマーイベント timerEGID タイマーが設定時間に到達したら発生する
 テキストメッセージイベント textmsgEGID パソコン等からテキストメッセージが届いたら発生する
 ボタンイベント buttonEGID AIBOのボタンが押されたら発生する
 ビジョンオブジェクトイベント visObjEGID AIBOが物体を見たら発生する
 モーションマネージャイベント motmanEGID AIBOが特定の動きをしたとき、または動きを終了したとき発生する

 イベントに応じた処理プログラムは、processEvent() 関数に書きます。
 processEvent() 関数は、イベントが発生するたびに自動的に呼び出されます。
 したがって、main()関数は存在せず、processEvent()関数の中にイベントに応じた処理プログラムを書きます(下図)。
 イベントが発生しても、そのイベントに対する処理プログラムがprocessEvent()関数に書かれていない場合、そのイベントは無視されます。

 イベントは次に示す3組の値で区別されます。

 例えば、AIBOのボタンを押したり、離したりすると、ボタンジェネレータがイベントを発生させます。
 generatorとは「発生させるもの」という意味です。
 つまり、イベントの発生元のことで、ボタンを押した時のイベント発生元はボタンジェネレータです。
 ボタンといってもAIBOには頭に1個、背中に3個、足先の肉球に1個(4本足なので4個)のボタンがあります。
 どのボタンが押されたかを区別するのがsourceです。
 また、そのボタンが押されたのか、ボタンから手(指)が離れたのかを区別するための値がtypeです。

 これらの値は、それぞれ関数を使用して求めることができます。
 その関数が、getGeneratorID()getSourceID()getTypeID()です。

 ボタンが押されたときは、次の値を求めることができます。

 もし、ボタンを押していた状態から手(指)を離すと、ETIDの箇所がEventBase::deactivateETID になります。押した、離しただけではなく、押す強さもETIDで表現できます。その場合のETIDはEventBase::statusETIDになります。そして強さはgetMagnitude()関数で求めることができます。その値は押したときが1で、離したときが0です。

 Behaviorでイベントが発生したことを知るためには、processEvent()にイベントに対応する処理プログラムを書けば良い、と先ほど述べました。しかし、これだけでは足りません。もう1つ作業があります。それは、イベントに応じたEvent listner(イベントリスナー)をプログラムに追加する作業です。イベントリスナー(以下、リスナー)は、イベントが発生したことを聞き取るためのプログラムです。イベントが発生すると、そのイベントをリスナーが聞き取り、processEvent()関数に知らせる仕組みになっています。リスナーはprocessEvent()関数にとって耳の役割だと思えばよいでしょう。リスナーはイベントごとに追加する必要があります(下図)。


 リスナーを追加するための構文

 erouter->addListener(this, generatorID, sourceID, typeID);

  sourceidとtypeidは省略可能

 ちなみに、すべてのイベントは EventBase派生クラスです。


練習: イベントリスナーを設定しよう

 Behaviorのプログラムを改造して、イベントリスナーを設定する練習をします。

 下記プログラムは、AIBOのボタンを押したり、離したりしたときに発生するイベント情報をTelnet画面に表示するプログラムです。
 このプログラムをコピーして、秀丸に貼り付けてください。
 そして次の通り保存してください。

    保 存 先: マイドキュメント → usXX → projectフォルダ
    ファイル名: MyButtonEvent.h
    ファイル種類: C言語ヘッダーファイル(*.h)

 プログラムで赤字の箇所が新しく追加された部分です。
 また、ファイル名が MyButtonEvent.h になったので、茶色の部分も変更しています。


#ifndef INCLUDED_MyButtonEvent_h_
#define INCLUDED_MyButtonEvent_h_


#include "Behaviors/BehaviorBase.h"
#include "Events/EventRouter.h"        // イベントを処理するためのヘッダファイル

class MyButtonEvent : public BehaviorBase {
public:
  MyButtonEvent() : BehaviorBase("MyButtonEvent") {}

  virtual void DoStart() {
    BehaviorBase::DoStart();
    std::cout << getName() << " is starting up." << std::endl;

    erouter->addListener(this,EventBase::buttonEGID); // ボタンイベントのリスナーを追加
  }

  virtual void DoStop() {
    std::cout << getName() << " is shutting down." << std::endl;
    BehaviorBase::DoStop();
  }

  // イベントを処理する関数
  virtual void processEvent(const EventBase &event) {

    // ボタンイベントを聞き取ったことを表示する
    std::cout << "MyButtonEvent got event: " << event.getDescription() << std::endl;
  }

};

#endif

 ファイルを保存したら、次の手順を実行してください。

課題

  1. 押したボタンの場所により、source IDの違いを確認してください。

  2. 押したときに表示される()内の最後のアルファベットA, D, Sは何を表していると思いますか?

  3. addListenerには、source IDやtype IDを追加することも可能です。左前足の肉球が押されたときだけに、イベントを表示するようにプログラムを改造してください。改造できたら試してください。なお、左前足のsource IDは ERS7Info::LFrPawOffset 、押されたときのtype IDは EventBase::activateETIDです。

  4. 複数のaddListenerを追加することができます。前項の左前足に加えて、右前足の肉球から手が離れたときに、イベントを表示するようにして試してください。なお、右前足のsource IDはERS7Info::RFrPawOffset 、ボタンから手が離れたときのtype IDは EventBase::deactivateETIDです。

  5. Tekkotsu にはもともとイベントをモニタするプログラムが内蔵されています。コントローラ画面のRoot Controlメニューから、Status Reports → Event Logerに入ってください。そして、buttonEGIDをクリックしてください。ボタンを押したり離したりすると、Telnet画面に結果が表示されます。


Vision ObjectイベントとText Message イベント

 視覚システムは、物体検出モジュールを持ちます。物体検出モジュールとは、カメラ視野内に特定の色をした物体が存在する場合に、それを検出するプログラムのことです。そして物体検出モジュールは、物体を検出するとVision Objectイベントを発生させます。
 Vision Objectイベントは、Eventクラスの派生クラスで、Eventクラスの情報に加えて画像内における物体の中心の(x,y)座標の情報を持ちます。(x,y)座標は-1.0〜+1.0の間の値に変換されています。つまり、座標(0,0)が画像の中心になります。
 物体の種類はsource IDによって区別されますが、この区別は色によって行われます。つまり、同じ赤色をした物体は、AIBOにとっては丸でも四角でも棒でも形に関係なく同じ物体(赤い物体)に見えてしまいます。また、同じ赤色ならサッカーボールでもゴルフボールでも大きさに関係なく同じ物体(赤い物体)です。どの色をAIBOに検出させるかは、あらかじめAIBOに覚えこませる必要があります。ピンク色のボールはあらかじめAIBOに覚えこませてあります。そのsource IDは、ProjectInterface::visPinkBallSID です。

 Text messageイベントはユーザーによって送られる文字列です。コントローラ画面にはSend Input: と記された窓があります。この窓に "!msg 送りたい文字列 " を打ち込み、キーボードのEnterキーを押すと、「送りたい文字列」の箇所がAIBOに送信されます。ただし、送りたい文字列は英数字だけです。
 TextMsgEventは、Eventの派生クラスで、Eventクラスの情報に加えて、getText()関数を持ちます。getText()関数はメッセージから文字列を取り出す関数です。
 1つのBehaviorで、複数のイベントを処理するためには、switch文を使用します。switch ( event.getGeneratorID() ) と記せば、generator IDを識別して、generator IDに応じた処理を行うことができるようになります。


練習: ピンクボールとテキストメッセージを検出しよう

 ピンクボールをカメラの前に置いたり、パソコンからテキストメッセージを送ったりすると、これらイベント情報をTelnet画面に表示するプログラムを作りましょう。
 上で作成したプログラムを下記の通り改造します。追加した箇所は赤字で示しています。
 このプログラムをコピーして、秀丸に貼り付けて保存してください。

    保 存 先: マイドキュメント → usXX → projectフォルダ
    ファイル名: MyTextEvent.h
    ファイル種類: C言語ヘッダーファイル(*.h)

#ifndef INCLUDED_MyTextEvent_h_
#define INCLUDED_MyTextEvent_h_

#include "Behaviors/BehaviorBase.h"
#include "Events/EventRouter.h"

#include "Events/TextMsgEvent.h"                  // TextMsgEventを処理するためのヘッダファイル
#include "Events/VisionObjectEvent.h"             // VisonObjectEventを処理するためのヘッダファイル
#include "Shared/ProjectInterface.h"              // visPinkBallSIDを定義しているヘッダファイル


class MyTextEvent : public BehaviorBase {
public:
  MyTextEvent() : BehaviorBase("MyTextEvent") {}

  virtual void DoStart() {
    BehaviorBase::DoStart();
    std::cout << getName() << " is starting up." << std::endl;
    erouter->addListener(this,EventBase::buttonEGID);   // ボタンイベントのリスナー
    erouter->addListener(this,EventBase::visObjEGID,    // ピンクボールのリスナー
                         ProjectInterface::visPinkBallSID);
    erouter->addListener(this,EventBase::textmsgEGID);  // テキストメッセージのリスナー
  }

  virtual void DoStop() {
    std::cout << getName() << " is shutting down." << std::endl;
    BehaviorBase::DoStop();
  }


  virtual void processEvent(const EventBase &event) {

    // generator IDに応じてスイッチする
    switch ( event.getGeneratorID() ) {
	
    // generator IDがvisObjEGIDの場合
    case EventBase::visObjEGID: {

      // eventをvisevにキャスト変換
      const VisionObjectEvent &visev = *reinterpret_cast<const VisionObjectEvent*>(&event);

      // ボールを見つけたときのtype IDの場合
      if ( visev.getTypeID() == EventBase::activateETID ||
           visev.getTypeID() == EventBase::statusETID )

	// ピンクボールを見たよ、と表示する
	std::cout << "MyTextEvent saw a pink ball at ( " <<
	  // ボールのX座標、Y座標を表示する
	  visev.getCenterX() << " , " << visev.getCenterY() << " )" << std::endl;
	  
      // ボールを見失った場合
      else

	// ボールを見失ったよ、と表示する
	std::cout << "MyTextEvent lost sight of the pink ball." << std::endl;
      break;
    };

    // generator IDがtextmsgEGIDの場合
    case EventBase::textmsgEGID: {

      // eventをtxtevにキャスト変換
      const TextMsgEvent &txtev = *reinterpret_cast<const TextMsgEvent*>(&event);

      // 受け取ったメッセージを表示する
      std::cout << "MyTextEvent heard: '" << txtev.getText() << "'" << std::endl;
      break;
    };

    case EventBase::buttonEGID:

      std::cout << "MyTextEvent got event: " << event.getDescription() << std::endl;
      break;

    default:
      std::cout << "Unexpected event: " << event.getDescription() << std::endl;
    }
  }

};

#endif

 ファイルを保存したら、次の手順を実行してください。


Timer Events

 タイマーには2種類の使い方があります。1つは繰り返し、もう1つはタイムアウトです。
 例えば、AIBOのLEDランプを15秒間隔で光らせたいときは「繰り返し」を使います。
 あるいは、AIBOがピンクボールを探し続けているときに、2分経過したらAIBOに探索をあきらめさせたいときは「タイムアウト」を使います。

 Behaviorにタイマーを設定するためには、Event Routerを使用します。
 タイマーはBehaviorごとに設定するので、タイマー同士が衝突することはありません。
 1つのBehaviorが同時に複数のタイマーを動かすこともできます。これらのタイマーは整数のIDで区別されます。プログラマは addTimer()関数を用いて任意のIDを与えることができます。
 Behaviorは、同じIDを持つタイマーを1回だけ使用できます。もし、同じIDでaddTimer()を使用すると、以前に設定されたタイマーはリセットされます。
 タイマーは removeTimer()関数を使用して、タイマーをキャンセルできます。
 タイマーイベントID(timerEGID)を聞き取るためのリスナーはプログラムで追加する必要はありません。addTimer()でタイマーを設定すると自動的にリスナーが追加されます。


 タイマーを設定するための構文
 erouter->addTimer(this,id,msec,false);

  idは任意の整数、msecはミリ秒(設定したい時間)


 タイマーを解除するための構文
 erouter->removeTimer(this,id);

  idは設定したときの値

練習: タイマーの設定と解除

 以下のプログラムはタイマーを使用する例です。
 AIBOの左前足の肉球が押されたときに、タイマーをID 1234で設定します。
 タイマーは5秒(5000ミリ秒)動きます。タイマーの単位はミリ秒(千分の1秒)です。
 タイマー設定後、5秒以内に右前足の肉球が押されると、タイマーが削除されます。
 タイマー設定後、5秒以内に右前足の肉球が押されなければ、タイマーが時間切れになります。時間切れになると、タイマーイベントが発生し、Telnet画面に "Timer expired!"と表示されます。

 このプログラムをコピーして、秀丸に貼り付けて保存してください。
    保 存 先: マイドキュメント → usXX → projectフォルダ
    ファイル名: MyTimerEvent.h
    ファイル種類: C言語ヘッダーファイル(*.h)

#ifndef INCLUDED_MyTimerEvent_h_
#define INCLUDED_MyTimerEvent_h_
 
#include "Behaviors/BehaviorBase.h"
#include "Events/EventRouter.h"

class MyTimerEvent : public BehaviorBase {
public:
	MyTimerEvent() : BehaviorBase("MyTimerEvent") {}

virtual void DoStart() {
	BehaviorBase::DoStart();
	std::cout << getName() << " is starting up." << std::endl;
	erouter->addListener(this,EventBase::buttonEGID); // ボタンイベントのリスナー
}

virtual void DoStop() {
	std::cout << getName() << " is shutting down." << std::endl;
	BehaviorBase::DoStop();
}

virtual void processEvent(const EventBase &event) {

	// generator IDに応じて処理を切り替える
	switch ( event.getGeneratorID() ) {

		// generator IDがbuttonEGID(ボタンイベントの場合)
		case EventBase::buttonEGID:

			// type IDがactivateETID(ボタンを押されたとき)
			if ( event.getTypeID() == EventBase::activateETID ) {
				std::cout << "MyTimerEvent got event: " << event.getDescription() << std::endl;

				// source IDがLFrPawOffset(押されたのが左前足のとき)
				if ( event.getSourceID() == RobotInfo::LFrPawOffset )

					// タイマーを設定する。ID 1234、時間5秒
					erouter->addTimer(this,1234,5000,false);

				// source IDがRFrPawOffset(押されたのが右前足のとき)
				else if ( event.getSourceID() == RobotInfo::RFrPawOffset )

					// タイマーを解除する。ID 1234
					erouter->removeTimer(this,1234);
			};
			break;

		// generator IDがtimerEGID(タイマーイベントの場合)
		case EventBase::timerEGID:

			// Timer expired! を表示
			std::cout << "Timer expired! " << event.getDescription() << std::endl;

			break;

		default:
			std::cout << "Unexpected event: " << event.getDescription() << std::endl;
	}
}

};

#endif

 ファイルを保存したら、次の手順を実行してください。


前のページ
目次
次のページ