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.
You should have already completed the "Writing your First Behavior" guide.
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.
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
// [...]
}
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).
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:
virtual void DoStop() {//do your code://but don't forget to call superclass:
motman->removeMotion(leds_id); // removes the LED controller
BehaviorBase::DoStop();
}
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:
#include "Events/EventRouter.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:
virtual void DoStop() {
//do your code:
motman->removeMotion(leds_id); // removes the LED controllererouter->removeListener(this); // stops getting events//but don't forget to call superclass:
// (and timers, if we had any)
BehaviorBase::DoStop();
}
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:
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:
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;
}
And now into that if statement, we'll put the code to update the LEDs:
#include "Motion/MMAccessor.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)} else {
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
//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:
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.
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.
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.
Explore some of the other special effects provided by LedMC's underlying LedEngine.
Trigger other MotionCommands. See the MotionCommand documentation for
a list of subclasses, including walking,
tail wagging,
keyframing (MotionSequence),
and more.
Write your own MotionCommand!