Tutorial: State Machine Intro

From Tekkotsu Wiki

Jump to: navigation, search

Learning Goal: In this tutorial you will learn about state machine diagrams, three kinds of state nodes, four kinds of transitions, and Tekkotsu's state machine shorthand notation. State machines are a popular approach to defining robot behaviors.

Contents

State Machine Diagrams

The figure below shows a simple state machine containing two nodes. Each node describes an action the robot should take. The first node causes the robot to play a sound file named barkmed.wav containing a medium pitch dog bark. The second node causes the robot to speak the phrase "good dog". The arrow pointing to the first node indicates that it is the start node.

File:State-machine-intro-1.png

The arrow from the first node to the second is called a transition. There are several kinds of transitions. The "C" label on the arrow indicates that it is a completion transition: when the sound node completes its action, the transition will fire. This deactivates the sound node and activates the speech node. Since there is no transition leading out of the speech node, when it finishes speaking, the node remains active, but there is nothing more for it to do.

State Machine Shorthand Notation

To write state machines conveniently on the computer, we use a special shorthand notation. The state machine above is written this way:

bark: SoundNode("barkmed.wav")
speak: SpeechNode("good dog")

bark =C=> speak

The first node to be defined is taken to be the start node. (There is a way to override that if necessary.)

When you write a state machine in shorthand notation, it is translated into C++ code by a preprocessor as part of the make process.

Types of Nodes And Transitions

Tekkotsu includes dozens of node and transition types. So far we've seen two node types: SoundNode and SpeechNode. A LedNode can be used to turn the robot's LEDs on or off, or flash them, or produce other effects depending on the type of the robot. A PilotNode can be used to make the robot move. A MapBuilderNode causes the robot to look for certain kinds of objects. All of these node types are C++ classes. Their parent class is called StateNode. If you look at the StateNode class documentation you will see a list of its subclasses.

StateNodeNo action. Parent for all other node classes.
SoundNodePlays a sound, then signals completion.
SpeechNodeSpeaks some text, then signals completion.
LedNodeFlashes the robot's LEDs.
PilotNodeMakes the robot move.
MapBuilderNodeMakes the robot look for things.


There are many kinds of transitions in Tekkotsu; you can find a list in the Transition class documentation. The three most basic transition types are the completion, timeout, and null transitions:

CompletionTrans=C=>Fires when the source node's action is complete.
TimeoutTrans=T(n)=>Fires after n milliseconds have elapsed.
NullTrans=N=>Fires immediately.

Consider the effects of replacing the completion transition in the example above with one of the other transition types. For example, suuppose we wrote:

bark =T(3000)=> speak

This would cause the robot to bark, and then 3 seconds later say "good dog". Instead of the two actions chaining together smoothly, there is a fixed 3 second gap between the start of the first action and the start of the second. (The timeout transition is activated as soon as the SoundNode is entered, and it will fire as soon as 3000 milliseconds have passed, whether or not the node has finished playing the sound.)

If we wanted a fixed gap between the completion of the sound, no matter how long it takes to play, and the beginning of the speech, we could achieve that by using a completion transition to a dummy node that then has a timeout transition leading to the speech node. Here's how to write that in shorthand notation:

bark: SoundNode("barkmed.wav")
wait: StateNode
speak: SpeechNode("good dog")

bark =C=> wait
wait =T(3000)=> speak

What if we used a null transition between the sound and speech nodes, like this?

bark =N=> speak

A null transition fires immediately, deactivating the source node and activating the target node. Deactivating the sound node doesn't normally stop the sound from playing. (There is, however, an option to achieve this, which you can find if you look at the documentation for SoundNode.) So what we would get with a NullTrans is the sound and speech occurring nearly simultaneously.

Chaining

In the examples above, we first defined our nodes and then defined the transitions between them. We assigned a label to each node by writing the label, a colon, and then the node definition. Labels must begin with a lowercase letter. This is the simplest way to write a state machine. But sometimes it's more convenient to intermix the node and transition definitions and avoid having to assign labels to nodes. For example, our first state machine could have been written more compactly by chaining the nodes and transitions, like this:

SoundNode("barkmed.wav") =C=> SpeechNode("good dog")

If a node is not given a label, the state machine parser will make up a label for it, such as soundnode1. If a transition has a node definition preceding it and a node definition following it, the transition is used to chain those nodes together, even if they don't have labels. We could also have chosen to keep the labels but still use chaining to write the definitions on a single line, yielding this version:

bark: SoundNode("barkmed.wav") =C=> speak: SpeechNode("good dog")

Looping

Assigning labels to nodes allows us to form loops in the state machine. The example below repeats the bark-and-speak sequence every five seconds.

bark: SoundNode("barkmed.wav")  =C=>  SpeechNode("good dog")  =T(5000)=>  bark

The graphical version of this state machine is shown below:

File:State-machine-intro-2.png

Test Your Understanding

1. Suppose you are given a LedNode that flashes an LED at a rate of two beats per second. This type of LED action can go on forever; it never completes. If you wanted the robot to flash the LED exactly three times and then stop, what kind of transition could you use, and how would you use it?

2. Suppose you are given a node that plays one of three sounds, chosen at random. The sounds are of different lengths. If you want the robot to flash an LED as soon as the sound has finished playing, what type of transition should you use?

3. What is the bug in the following state machine?

alpha:   SpeechNode("alpha")
bravo:   SpeechNode("bravo")
charlie: SpeechNode("charlie")

bravo =C=> charlie =T(3000)=> alpha

4. What behavior will result from each of the following state machine fragments, and why?

frag1: SpeechNode("ping.wav") =C=> frag1

frag2: SpeechNode("ping.wav") =T(350)=> frag2

frag3: SpeechNode("ping.wav") =N=> frag3

Button Events

Every time you press or release a button on the robot, that generates an event. A button press causes an activate event, while a release causes a deactivate event. An event transition can be used to check for many different types of events. Button press events are a common case that have their own special shorthand.

=B=>Fires for any button event.
=B(b)=>Fires for any event involving button b.
=B(b,t)=>Fires for events involving button b that are of type t (activateETID or deactivateETID).

On the Create/ASUS and Calliope robots, the most commonly used buttons are Play, Advance, and the left and right bump switches. Their names are given in the table below:

Play buttonPlayButOffset
Advance buttonAdvanceButOffset
Left bump switchBumpLeftButOffset
Right bump switchBumpRightButOffset

Here is a state machine that plays one sound when the Play button is pressed and another sound when the button is released. When we have multiple transitions coming out of a state node, it is helpful to define the node first, and then define each transition on a separate line.

loop: StateNode
loop =B(PlayButOffset,activateETID)=>  SoundNode("beepdoub.wav")  =N=> loop
loop =B(PlayButOffset,deactivateETID)=>  SoundNode("beeprobo.wav")  =N=> loop

The graphical version of this state machine is easily derived from the shorthand form:

File:State-machine-intro-3.png

The Annoying Dog

We can put together everything we've learned so far to make a robot behavior called the Annoying Dog. Every time the dog barks, you have 5 seconds to pet it (by pressing one of its buttons). If you don't pet it quickly enough, it emits a long pitiful howl. Either way, it waits 15 seconds and then demands to be petted again. Here is the state machine diagram:

File:State-machine-intro-4.png

And here is the code to implement this state machine. This time you are looking at a complete behavior that can be compiled and run on the robot. To learn how to do that, see Lab: State Machines.

#include "Behaviors/StateMachine.h"

// After each bark, howl unless petted, forever
$nodeclass AnnoyingDog : StateNode {
  $setupmachine {
     loop: SoundNode("barkmed.wav")
     loop =B=> wait
     loop =T(5000)=> howl

     wait: StateNode =T(15000)=> loop

     howl: SoundNode("howl.wav") =C=> wait
  }
}

REGISTER_BEHAVIOR(AnnoyingDog);

Exercises

  1. Write a state machine that plays a low bark the first time you press the Play button, then a high bark the second time, then a low bark the third time, and so on. It should only respond to button press events; it should ignore button release events.
  2. Write a state machine that plays a sequence of barks: low, medium, and high, spaced 3 seconds apart. But if the user presses a button, it abandons the sequence and stays silent for 10 seconds, then it begins the sequence again.
  3. Write the shortest infinite loop you can.
  4. Write a state machine that counts from "one" to "five" (speaking each number) each time you press the Advance button. If you press Play instead of advance, it should repeat the current digit. If you press Advance after getting to five, it should say "No more".
  5. Draw graphical versions of the state machines you wrote above.