Exploring Tekkotsu Programming on Mobile Robots:

Signaling Between States

Prev: Storyboard tool
Up: Contents
Next: Defining new types

Introduction

When you create your own types of state nodes, you may need to signal when a transition to the next state should occur. We will look at three types of signaling mechanism, starting with the simple and now-familiar completion event.

Completion Events

Built-in nodes that cause actions (WalkNode, SoundNode, LedNode, etc.) signal the completion of those actions by posting a status event we call a completion event. The generator ID is statemachineEGID, the source ID is the memory address of the node itself, and the type ID is statusETID. You can detect these events using a CompletionTrans (written =C=> in shorthand notation) to move to the next state in your state machine.

If you want to use a CompletionTrans with your own state node, you can call postStateCompletion() to post a completion event. This method is inherited from the StateNode class. Writing

postStateCompletion();
is equivalent to writing
erouter->postEvent(EventBase::stateMachineEGID, (size_)this, EventBase::statusETID);

Why Have Completion Events?

A completion event is an abstract way of indicating that an action has completed without having to specify any details. Each state node class contains code to determine when its specific action has completed. When the sound manager finishes playing a sound, it posts a deactivate event (not a status event) using audioEGID and a source ID equal to the request ID the sound manager assigned. The SoundNode looks for this event. When a motion command completes its action, it posts a status event whose generator ID is motmanEGID and whose source ID is a special value (called an MC_ID) assigned by the motion manager when the motion command was registered. But this level of detail isn't important to the author of a state machine, who only wants to know when the action has completed and doesn't care what the generator ID or source ID are.

Completion events are produced by state nodes, not their underlying motion commands, sound manager requests, etc. All completion events follow the same format: stateMachineEGID, statusETID, and source ID equal to the memory address of the node that is posting the event. Thus a CompletionTrans can be used with any node type. Monitoring the state machine's execution can be achieved by using the Event Logger to display stateMachineEGID events, rather than monitoring a whole collection of generator IDs for the underlying facilities (motions, sounds, etc.) that are being used.

DataEvent and SignalTrans

A CompletionTrans is the right thing to use if the only information a node conveys is "I'm done; move on to the next state." But sometimes we want the node to signal which of several paths the machine should take next, e.g., "turn left" or "turn right" or "continue straight ahead". In order to do that, we have the node post a DataEvent containing a value indicating its choice. We then use SignalTrans transitions coming out of the node to look for specific values and activate their target node if the value is seen. You can think of these SignalTrans transitions as the equivalent of a switch statement in C++.

(diagram of 3-way state machine branch)

DataEvent is a templated class: we have to specify the type of data to transmit in the event. But rather than construct the DataEvent instance directly, it's usually more convenient to use the postStateSignal method, as shown in the example below. It's good programming practice to use enum types rather than constants to denote items from a defined set of choices, so instead of using the values 0, 1, and 2, we will define an enum type called proceedInstruction and use that in our DataEvent. Since the enum type is used both in the node that is sending the signal and the transition that is receiving it, it should be defined outside of either one. Here we define it in the enclosing state node MyBehavior. We use =S=<T>(v)=> as an abbreviation for SignalTrans, where T is the datatype of the signal and v is the signal value to check for.

class MyBehavior : public VisualRoutinesStateNode {
public:
  enum proceedInstruction {
  turnLeft,
  turnRight,
  straightAhead
  };

  MyBehavior() : VisualRoutinesStateNode("MyBehavior") {}

  class DecideNode : public VisualRoutinesStateNode {
  public:
    DecideNode() : VisualRoutinesStateNode("DecideNode") {}

    virtual void DoStart() {
      ...
      if ( x < -10 ) {
        postStateSignal<proceedInstruction>(turnLeft);
      return;
      }
      else ...      ...
    }
  };

  virtual void setup() {
#statemachine
startnode: DecideNode()

startnode =S<proceedInstruction>(turnLeft)=> WalkNode($,0,0,M_PI/2,1) =C=> startnode

startnode =S=S<proceedInstruction>(turnRight)=> WalkNode($,0,0,-M_PI/2,1) =C=> startnode

startnode =S=S<proceedInstruction>(straightAhead)=> WalkNode($,200,0,0,1) =C=> startnode
#endstatemachine
} }; DATAEVENT_IMPLEMENTATION(MyBehavior::proceedInstruction, unsigned int);

The DATAEVENT_IMPLEMENTATION macro above is used to provide SignalTrans with certain information it needs about the enumerated type that encodes the signal.

The postStateSignal method is inherited from StateNode. Writing

postStateSignal<proceedInstruction>(turnLeft);
is equivalent to writing
erouter->postEvent(DataEvent<proceedInstruction>(turnLeft, EventBase::stateMachineEGID, (size_t)this, EventBase::statusETID));

Receiving the Signal

A more advanced method of signaling between states is required when the target state needs to know the value of the signal that the source state transmitted. If the signal is one of a few fixed values, we can simply use a separate SignalTrans and target node for each case, as in the example above. But if the signal is drawn from a potentially infinite set, we must use a single target node and give it access to the signal value when the SignalTrans activates it. We do this by defining a triggeredBy method for the target node, and using that method in place of DoStart. The triggeredBy method takes a const EventBase& argument which will be a reference to the DataEvent that triggered the SignalTrans.

void triggeredBy(const EventBase&)

Prev: Storyboard tool
Up: Contents
Next: Defining new types


Last modified: Fri Feb 6 05:32:57 EST 2009