Exploring Tekkotsu Programming on Mobile Robots:

Events

Prev: Behaviors
Up: Contents
Next: Motion Commands

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 throws 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 processEvent() method will be called each time an event arrives. Behaviors are automatically removed from the list of listeners when their DoStop() method 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, and DoStop() to remove it from the list of listeners. Inside these functions, the variable this refers to the Behavior instance. Make the modifications shown in red below to your DstBehavior.h file:

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

class DstBehavior : public BehaviorBase {
public:
  DstBehavior() : BehaviorBase("DstBehavior") {}
 
  virtual void DoStart() {
    BehaviorBase::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;
    BehaviorBase::DoStop();
  }

  virtual void processEvent(const EventBase &event) {
    std::cout << getname() << " got event: " << event.getDescription() << std::endl;
  }

};

#endif

Recompile and update the memory stick by doing "make update". Then boot up the AIBO, open a telnet connection to port 59000, and activate DstBehavior. Be sure to unpause the AIBO using the Stop/Go button in the ControllerGUI window. You can also pause/unpause the robot by double clcking on its back button.

With DstBehavior active, you can press the paw, head, or back buttons and see the events as they are reported in the telnet window.

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 looking up the documentation for the class member called buttonEGID.

  2. What are the source IDs for the various buttons on the AIBO? Note that they are model-specific; the ERS-210, ERS-220, and ERS-7 have slightly different configurations. Hint: the documentation for buttonEGID refers to 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 model-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 the left front paw button 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 the right front paw 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 in your telnet window 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 that is 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. 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.

#ifndef INCLUDED_DstBehavior_h_
#define INCLUDED_DstBehavior_h_
 
#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() {
    BehaviorBase::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;
    BehaviorBase::DoStop();
  }

  virtual void processEvent(const EventBase &event) {
    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;
    }
  }

};

#endif

Recompile and update the memory stick by doing "make update". Then boot up the AIBO, open a telnet connection to port 59000, and activate DstBehavior. Using the ControllerGUI, call up the SegCam window so you can see what the AIBO sees. Make sure the camera isn't pointed at anything red or pink. Now wave the pink ball in front of the AIBO 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 throws 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 left front paw button is pressed. The timer runs for 5 seconds (5000 milliseconds). If the right front paw button is pressed, the timer is removed. If the button is not pressed and the timer expires, it throws a timer status event, and the Behavior prints a "Timer expired!" message.

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

class DstBehavior : public BehaviorBase {
public:
  DstBehavior() : BehaviorBase("DstBehavior") {}
 
  virtual void DoStart() {
    BehaviorBase::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;
    BehaviorBase::DoStop();
  }

  virtual void processEvent(const EventBase &event) {
    switch ( event.getGeneratorID() ) {

    case EventBase::buttonEGID:
      if ( event.getTypeID() == EventBase::activateETID ) {
        std::cout << "Dst got event: " << event.getDescription() << std::endl;
	if ( event.getSourceID() == RobotInfo::LFrPawOffset )
	  erouter->addTimer(this,1234,5000,false);
	else if ( event.getSourceID() == RobotInfo::RFrPawOffset )
	  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;
    }
  }

};

#endif

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, processEvent should print an "A" without a newline. The second timer should have an interval of 3 seconds, and each time it expires, processEvent 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: Motion Commands

Last modified: Wed Jan 16 01:00:45 EST 2008