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 signal completion of a state node that you define, 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_t)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. Most state node classes contain code to determine when their specific action has completed. When the sound manager finishes playing a sound, it posts a status or deactivate 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: the generator ID is stateMachineEGID, the type is statusETID, and the source ID is 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.

Failure Transitions

A completion event usually signals that the state node's action completed normally. Sometimes we want to also be able to signal an abnormal completion. This can be done using postStateFailure(). The shorthand for a failure transition is =F=>. For example, you might write a state node that watches a ball and signals completion when the ball is within reach of the robot. If the camera loses sight of the ball, the state node might post a failure event.

To complement the failure transition, there is a success transition, abbreviated =S=>. Sometimes you want to signal one of two outcomes where neither is a "completion" or an abnormal condition; they're just two possible alternatives, such as an answer to a true/false question. You can use success and failure transitions for this purpose.

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++.

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.

$nodeclass MyBehavior : VisualRoutinesStateNode {
  enum proceedInstruction {
  turnLeft,
  turnRight,
  straightAhead
  };

  $nodeclass DecideNode : VisualRoutinesStateNode : {
    virtual void doStart() {
      ...
      if ( x < -10 ) {
        postStateSignal<proceedInstruction>(turnLeft);
      return;
      }
      else ...      ...
    }
  }  

  $setupmachine{
    startnode: DecideNode

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

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

    startnode =S<proceedInstruction>(straightAhead)=> WalkNode($, 200, 0, 0, 1, WalkNode::DISPP) =C=> startnode
} }

The code above corresponds to the following state machine diagram:

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.

When a node's doStart method is invoked via a transition firing, the member variable event is set to the event that triggered the transition. (This is also true when the node's doEvent method is invoked.) In the case of a SignalTrans<T> transition invoking the doStart method, event is set to a DataEvent<T>. User code can examine the DataEvent and retrieve the data it contains. A method for doing this, called extractSignal<T>, is provided by the StateNode class, allowing you to write:

virtual void doStart() {
  const proceedInstruction x = extractSignal(event);
  ...
}
If a node may be entered in one of several ways, you will want to check the event type before calling extractSignal. There is also a tryExtractSignal method that returns a pointer to the data in the DataEvent<T>, or a NULL pointer if event is NULL or is not of type DataEvent<T>.

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


Last modified: Sun Feb 6 20:25:32 EST 2011