Exploring Tekkotsu Programming on Mobile Robots:

Events

Prev: Behaviors
Up: Contents
Next: Playing sounds

Contents: Overview of events, Vision and text message events, Timer events

Overview of events

Events are defined by a 3-tuple consisting of a generator, a source, and a type. For example, the button generator posts an event every time one of the buttons on the robot's body is pressed or released. If we examine a button press event, using the supplied accessor functions to extract the components, we will find that: If this were a button release event, the ETID would instead be EventBase::deactivateETID. There is also a third event type, EventBase::statusETID, which is used to report a change in value of a continuous-valued sensor reading, such as a change in pressure for buttons that are pressure-sensitive. The sensor value is included as an additional data member, and can be accessed with getMagnitude(). The magnitude is normally 1 for activate events and 0 for deactivate events.

A Behavior can receive events by adding itself to the list of listeners for the desired event stream. This list is maintained by the event router, erouter. Once the Behavior is registered as a listener, its doEvent() method will be called each time an event arrives, and the member variable event will be set to a pointer to the event. Behaviors are automatically removed from the list of listeners when their doStop() method (inherited from BehaviorBase) is called, but you can also remove a listener for a specific event type by using removeListener().

All events are subclasses of EventBase.

Exercise: Setting Up An Event Listener

The list of listeners and subscription requests is managed by the EventRouter, which is accessed via the global variable erouter. We will use doStart() to set up the Behavior as a listener. Inside these functions, the variable this refers to the Behavior instance. Make the modifications shown in red below to your DstBehavior.cc file:

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

class DstBehavior : public BehaviorBase {
public:
  DstBehavior() : BehaviorBase("DstBehavior") {}
 
  virtual void doStart() {
    std::cout << getName() << " is starting up." << std::endl;
    erouter->addListener(this,EventBase::buttonEGID); // subscribe to all button events
  }
 
  virtual void doStop() {
    std::cout << getName() << " is shutting down." << std::endl;
  }

  virtual void doEvent() {
    std::cout << getname() << " got event: " << event->getDescription() << std::endl;
  }

};

REGISTER_BEHAVIOR(DstBehavior);

Recompile Tekkotsu and start it up again. Be sure to unpause the robot using the Stop/Go button in the ControllerGUI window, or your behavior will not receive any events. On some robots, you can also pause or unpause by pressing a button on the robot's body. For the Chiara, press the red button; for the AIBO, double clikc on its back button.

With DstBehavior active and the robot unstopped, you can press a button on the robot and see the events as they are reported on the console.

Explore more:

  1. How can you find out what other event generator types exist besides the button generator, and where are they defined? Hint: start by going to the Classes Tab, select the Class Members subtab, and check under "b" for buttonEGID, which you'll see is a member of EventBase. Click on the EventBase link to get your answer.

  2. What are the source IDs for the various buttons on the robot? Note that these are model-specific. Hint: the documentation for EventBase::buttonEGID refers to ERS210Info::ButtonOffset_t. You can follow that link, or call up the list of "Namespace Members" in the left navigation frame, where you will see that ButtonOffset_t is defined in several robot-specific namespaces.

  3. It is possible to subscribe to a subset of events from a given generator by specifying a source ID, and optionally, an event type ID, as extra arguments in the call to addListener(). Modify your Behavior so the only events it receives are button events from one button you select, and the events must be of type EventBase::activateETID.

  4. By adding a second call to addListener() you can make your Behavior receive another type of event. Have your Behavior also receive button press events from another button, but these should be of type EventBase::deactivateETID.

  5. The EventBase::getDescription() method takes optional arguments. What do these arguments do? Try modifying DstBehavior to produce more verbose event descriptions.

  6. Tekkotsu provides a built-in event logger for monitoring event streams. From the Root Control menu, go to Status Reports, and then choose Event Logger. Tell it to log button events, and you will see the output on the consol when you press a button.


Vision and Text Message Events

The Vision system includes an object detection module that posts a stream of VisionObjectEvents when objects are detected in the color segmented camera image. VisionObjectEvent is a subclass of Event, and contains some additional members specifying the X and Y coordinates of the center of the object in the image. The coordinates are normalized to the range -1.0 to +1.0, so that (0,0) is the center of the image. Different objects are distinguished by their source IDs, which are based on color. The pink ball such as the one included with the AIBO has source ID equal to ProjectInterface::visPinkBallSID.

Text message events are strings sent by the user. The ControllerGUI contains a little text box in which you can type commands, which must begin with an exclamation point. The command "!msg sequence-of-characters" causes the Controller to post a text message event containing the specified string. (You can also give this command, without the exclamation point, in the Tekkotsu command line.) TextMsgEvent is a subclass of Event, and provides an additional member function getText() that returns the message text as a string.

Multiple event types can be handled within a single Behavior by subscribing to several streams and using a switch statement keyed on the generator ID to process each event acording to its type.

Exercise: Detecing Pink Balls and Text Messages

In order to access the additional members that VisionObjectEvent and TextMsgEvent provide to extend EventBase, we must cast the event argument to the correct type. This is done using the dynamic_cast operator, as shown below.

#include "Behaviors/BehaviorBase.h"
#include "Events/EventRouter.h"
#include "Events/TextMsgEvent.h"
#include "Events/VisionObjectEvent.h"
#include "Shared/ProjectInterface.h"

class DstBehavior : public BehaviorBase {
public:
  DstBehavior() : BehaviorBase("DstBehavior") {}
 
  virtual void doStart() {
    std::cout << getName() << " is starting up." << std::endl;
    erouter->addListener(this,EventBase::buttonEGID);   // subscribe to all button events
    erouter->addListener(this,EventBase::visObjEGID,    // and pink ball detection events
                         ProjectInterface::visPinkBallSID);
    erouter->addListener(this,EventBase::textmsgEGID);  // and text message events
  }
 
  virtual void doStop() {
    std::cout << getName() << " is shutting down." << std::endl;
  }

  virtual void doEvent() {
    switch ( event->getGeneratorID() ) {

    case EventBase::visObjEGID: {
      const VisionObjectEvent *visev = dynamic_cast<const VisionObjectEvent*>(event);
      if ( visev->getTypeID() == EventBase::activateETID ||
           visev->getTypeID() == EventBase::statusETID )
	std::cout << "Saw a pink ball at ( " <<
	  visev->getCenterX() << " , " << visev->getCenterY() << " )" << std::endl;
      else    // deactivateETID
	std::cout << "Lost sight of the pink ball." << std::endl;
      break; };

    case EventBase::textmsgEGID: {
      const TextMsgEvent *txtev = dynamic_cast<const TextMsgEvent*>(event);
      std::cout << "Heard: '" << txtev->getText() << "'" << std::endl;
      break; };

    case EventBase::buttonEGID:
      std::cout << getName() << " got event: " << event->getDescription() << std::endl;
      break;

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

};

REGISTER_BEHAVIOR(DstBehavior);

Recompile, run Tekkotsu again, and activate DstBehavior. Using the ControllerGUI, call up the SegCam window so you can see what the robot sees. Make sure the camera isn't pointed at anything red or pink. Now wave the pink ball in front of the robot and see what happens.

In the ControllerGUI window, type !msg hello in the text box and see what happens.

Explore more:

  1. Look up the documentation for VisionObjectEvent. What other methods does it provide?

  2. Use the getObjectArea() method of VisionObjectEvent to compute the area of the pink ball in the image. Note that its value is expressed in generalized square coordinates (based on the x coordinate ranging from -1 to +1), so you will have to multiply the result by the value of RobotInfo::CameraResolutionX squared and divide by four to get the area in pixels. Modify the example program above to display the area of the pink ball.

  3. Where does the formula "multiply by the value of RobotInfo::CameraResolutionX squared and divide by four" come from?

  4. Using a ruler, measure how the area of the ball changes as a function of distance from the dog. Take lots of measurements, since the results will be noisy. Can you fit an equation to your data?


Timer Events

Timers are useful for two kinds of things: repetition, and timeouts. If the robot is waiting for something, and you want it to make a noise or flash its LEDs every 15 seconds to show that it's still alive, you could set up a repeating timer with a 15 second interval. Or, if the robot is busily searching for its pink ball, but you want it to give up if it hasn't found the ball within 2 minutes, you could set up a non-repeating 2 minute timer and, if the timer expires and post an event, the robot can stop searching and to go do something else.

The event router provides a built-in timer facility, allowing any Behavior to set up timers and receive event notifications whenever a timer expires. Timers are specific to the Behavior that created them; so there is no danger of accidental crosstalk. A single Behavior may have several timers active at once; they are distinguished by unique integer IDs specified in the call to addTimer() that sets up the timer. A Behavior can have only one timer with a given ID, so if addTimer() is called again using the same ID, the existing timer with that ID is reset. Timers can be cancelled with removeTimer(). It is not necessary to subscribe explicitly to the timerEGID event stream, as a call to addTimer() automatically establishes the behavior as a listener for timer events.

Exercise: Setting Up and Removing a Timer

The example below starts a timer with id 1234 whenever the robot's green button is pressed. The timer runs for 5 seconds (5000 milliseconds). If the green button is pressed, the timer is removed. If the button is not pressed and the timer expires, it posts a timer status event, and the Behavior prints a "Timer expired!" message.

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

class DstBehavior : public BehaviorBase {
public:
  DstBehavior() : BehaviorBase("DstBehavior") {}
 
  virtual void doStart() {
    std::cout << getName() << " is starting up." << std::endl;
    erouter->addListener(this,EventBase::buttonEGID); // subscribe to all button events
  }
 
  virtual void doStop() {
    std::cout << getName() << " is shutting down." << std::endl;
  }

  virtual void doEvent() {
    switch ( event->getGeneratorID() ) {

    case EventBase::buttonEGID:
      if ( event->getTypeID() == EventBase::activateETID ) {
        std::cout << "Dst got event: " << event->getDescription() << std::endl;
	if ( event->getSourceID() == RobotInfo::GreenButOffset )
	  erouter->addTimer(this,1234,5000,false);
	else if ( event->getSourceID() == RobotInfo::GreenButOffset )
	  erouter->removeTimer(this,1234);
      };
      break;

    case EventBase::timerEGID:
      std::cout << "Timer expired! " << event->getDescription() << std::endl;
      break;

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

};

REGISTER_BEHAVIOR(DstBehavior);

Explore more:

  1. What is the meaning of the fourth argument to addTimer(), and why is its value false?

  2. How would this Behavior function if the fourth argument to addTimer() had been omitted?

  3. Write a behavior that creates two repeating timers. The first timer should have an interval of 1.2 seconds, and each time it expires, doEvent() should print an "A" without a newline. The second timer should have an interval of 3 seconds, and each time it expires, doEvent() should print a newline. (To distinguish the two timers, you must assign them different source ids.) When you run the behavior, what patterns of A's do you see?

  4. A template for writing new behaviors is provided in the file ~/project/templates/behavior.h. Have a look at it.

Prev: Behaviors
Up: Contents
Next: Playing sounds

Last modified: Tue Jun 22 18:30:18 EDT 2010