Adding Behavior Functionality

This document will walk you through the creation of your first Behavior.  Behaviors are high level programs which direct the robot to complete some task, comparable to an application on a PC.  This behavior will light up LEDs corresponding to which button is pressed.

Prerequisites

  1. You should have already completed the "Writing your First Behavior" guide.

  2. If you're new to C++, or just rusty, there is a C++ review available.

Adding Functionality

  1. Now let's get some LED control set up.  We do this with an LED MotionCommand (specifically, LedMC), which provides some handy functions for LED special effects.

    SampleBehavior.h

    class SampleBehavior : public BehaviorBase {
    public:
      SampleBehavior() : BehaviorBase("SampleBehavior"),
      // Add an initializer for leds_id
    leds_id(MotionManager::invalid_MC_ID)
    {}

    virtual void DoStart() {
    //call superclass first for housekeeping:
    BehaviorBase::DoStart();
      //now do your code:
     
    SharedObject<LedMC> leds; // creates an LED controller in shared memory
    leds_id=motman->addPersistentMotion(leds); // handoff to MotionManager
    }
    // [...]
    protected:
    // we only store an ID number, not the actual MC
      MotionManager::MC_ID leds_id;
    }

    It's important to realize that the MotionCommands (MC's) are not stored in your behavior's memory space, all you store is an ID number.  The actual MC is wrapped in a shared memory region and tracked by the MotionManager (globally instantiated as 'motman').  This is so the MC can be accessed by both the Motion and Main processes.

    So the first line of the new code in DoStart() creates a MC wrapped in a shared memory region.  The SharedObject class does the work of setting this up, and takes the type of the class to be created as a template argument.  Any constructor arguments to SharedObject are passed on to the target class's constructor.

    The second line actually adds the MotionCommand to the MotionManager.  There are two ways to add an MC to the motion manager - addPrunableMotion() and addPersistentMotion().  If an MC is added as a prunable motion, this signals that the motion manager should remove the MC after it is "completed", the definition of which depends on the MC in question.  In our case, we want to keep and reuse the same LedMC, so we will mark it as persistent, and delete it ourselves when we're done (next step).

  2. So now we've set up, but it's also important to clean up when it's time to stop.  Leaking MotionCommands not only wastes memory, but they will continue to do their thing, which may cause conflicts with later commands:

    SampleBehavior.h
      virtual void DoStop() {
    //do your code:
    motman->removeMotion(leds_id); // removes the LED controller
    //but don't forget to call superclass:
    BehaviorBase::DoStop();
    }
  3. So now you may ask, "Great, so I can control the LEDs now, but where do i control them from?"  Good question!  We need to know whenever a button is pressed.  All events go through the EventRouter (globally instantiated as 'erouter'), so we just need to tell it to send the button events to this behavior:

    SampleBehavior.h
    // [...] 

    virtual void DoStart() {
    //call superclass first for housekeeping:
    BehaviorBase::DoStart();

    //now do your code:
    // creates and adds a new LedMC to the MotionManager
    leds_id=motman->addMotion(SharedObject<LedMC>());
    // subscribe to all button events
    erouter->addListener(this,EventBase::buttonEGID);
    }

    ... and also tell it when to stop:

    SampleBehavior.h
      virtual void DoStop() {
    //do your code:
    motman->removeMotion(leds_id); // removes the LED controller
    erouter->removeListener(this); // stops getting events
    // (and timers, if we had any)

    //but don't forget to call superclass:
    BehaviorBase::DoStop();
    }
  4. So where does it send these events?  We need to add one more function.  If you have looked at the documentation yet, you might have noticed that EventRouter's functions take EventListeners as a parameter for its functions.  We can pass this as an EventListener because BehaviorBase inherits from EventListener.  So all you have to do is override the processEvent() function:

    SampleBehavior.h
    virtual void processEvent(const EventBase& event) {}
  5. Of course, we want to do something when we receive an event, namely turn on an LED.  First, just for style, let's check the event we've been sent is really a button event:

    SampleBehavior.h
      virtual void processEvent(const EventBase& event) {
    // to be more general, let's check that it's the right event first:
    if(event.getGeneratorID()==EventBase::buttonEGID) {
    // we should do something interesting here
    } else {
    //should never happen
    cout << "Bad Event:" << event.getName() << endl;
    }
    }
  6. And now into that if statement, we'll put the code to update the LEDs:

    SampleBehavior.h
    // [...]

    virtual void processEvent(const EventBase& event) {
    // to be more general, let's check that it's the right event first:
    if(event.getGeneratorID()==EventBase::buttonEGID) {
    // if it's an activate or status event, turn the LED on (1), // otherwise turn it off (0)
    double value = (event.getTypeID()!=EventBase::deactivateETID)?1:0;
    LEDBitMask_t bitmask = (1<<event.getSourceID()); // pick which LED(s)
    MMAccessor<LedMC> leds_mc(leds_id); // "checks out" the MC
    leds_mc->set(bitmask,value); // set the bitmask and value of the LedMC

    // notice no "check in" - MMAccessor's destructor does this
    } else {
    //should never happen
    cout << "Bad Event:" << event.getName() << endl;
    }
    }

    The value variable holds the level the LED should be set at - notice it's type is not bool.  Values between 1 and 0 will cause the LED to blink rapidly to approximate them.  You may find that binary fractions (x/2, x/4, x/8, etc.) will look the "smoothest".  Very high or low values will simply be always on or off (respectively) with an occasional 8ms flash.

    The bitmask variable holds a bitmask of which LEDs should be modified by the set command.  Other LEDs are left untouched.  However, if we used the cset command (clear-set), the non-selected LEDs are turned off.

    And now we get to the more interesting code.  Since all the behavior stores is an ID number, we need to get access to the actual MotionCommand.  The easiest way to do this is using MMAccessor.  This class will automatically check out the MotionCommand from MotionManager, cast it to the correct type (specified by the template argument), pass commands to it, and then check it back in upon destruction.

    Notice how commands are sent to the MotionCommand:

    SampleBehavior.h
          leds_mc->set(bitmask,value); // set the bitmask and value of the LedMC

    ...just a normal function call.  But wait a second, leds_mc is an instance of MMAccessor, not LedMC!  How does it know what the set function means?  Well, C++ gives programmers a lot of rope, both to hang yourself or do something slick (often both).  In this case, MMAccessor overrides the -> operator!  So any function you call on MMAccessor is actually passed directly to the MotionCommand which it checked out.  You can also check out MotionCommands from the MotionManager manually, but using MMAccessor makes the code much more elegant and less error-prone.

  7. All done!  Now you have done everything you need to with SampleBehavior.  It should look like this.  When you're running, don't forget to "unpause" by double-tapping the back button to turn off the emergency stop mode, otherwise this will override what your behavior is trying to do.

Further exercises

  1. Remember the buttons on the top of the head?  There's one push plate, but two buttons underneath it, so it can tell if you're pushing on the front or the back.  You can test this with what we're already written.  However, these buttons are also pressure sensitive! (The others are not.) This pressure information is sent to you as the magnitude field of the events you receive.  For this exercise, set the LEDs to brighten according to pressure on the buttons.

  2. Explore some of the other special effects provided by LedMC's underlying LedEngine.

  3. Trigger other MotionCommands.  See the MotionCommand documentation for a list of subclasses, including walking, tail wagging, keyframing (MotionSequence), and more.

  4. Write your own MotionCommand!