Exploring Tekkotsu Programming on Mobile Robots:

State Machine Shorthand Notation

Prev: Nodes and transitions
Up: Contents
Next: Defining new node classes

Contents: Shorthand notation, Node declarations, Transition declarations, Transition abbreviations, Creating an FSM file, Diagnosing syntax errors, Running stateparser directly, Advanced concepts

Shorthand Notation

From inspecting the examples in the previous section you may conclude that constructing state machines by hand is tedious, especially when there are many nodes and transitions. Fortunately, Tekkotsu provides a tool called the stateparser that lets you write state machine descriptions in a convenient shorthand form. The stateparser automatically translates this notation into executable code that goes inside your setup function. In the following sections we will explore this notation in detail. But first, here's a sample of what it looks like: a one-node state machine that barks every 5 seconds.

startnode: SoundNode($,"barkhigh.wav") =T(5000)=> startnode

If we also want the robot to play a ping sound whenever the green button is pressed, so the two processes run in parallel, we can do this:

startnode: StateNode =N=> {barker, buttonwatcher}

barker: SoundNode($,"barkhigh.wav") =T(5000)=> barker

buttonwatcher: StateNode
   =B(RobotInfo::GreenButOffset)[setSound("ping.wav")]=> buttonwatcher

This should give you an appreciation for how easy it is to put state machines together using the shorthand. Now let's take a closer look at its syntax.

Node Declarations

The shorthand for a node declaration looks like this:
nodename: NodeClass<template_args>(constructor_args) [initializers]
All the elements except the NodeClass are optional. Node classes must begin with a capital letter; node names should begin with a lowercase letter.

State node constructors normally take a string as the initial argument, specifying the node name. In the shorthand notation, you can leave out the constructor arguments, in which case the argument list defaults to ($). The dollar sign is a special token that expands into a string that is the name of the node. So, for example:

buttonwatcher: StateNode
expands into
buttonwatcher: StateNode($)
which in turn expands into
buttonwatcher: StateNode("buttonwatcher")
When you specify arguments to the node constructor, the $ notation is used so you don't have to explicitly repeat the name of the node. This way, if you need to rename a node, you only have to change it in one place. Example:
barker: SoundNode($, "barkhigh.wav")
expands into
barker: SoundNode("barker", "barkhigh.wav")
If you don't assign a name to a node, the stateparser will generate one for you, e.g., SoundNode($,"barkhigh.wav") expands into SoundNode("soundnode1", "barkhigh.wav").

Initializers are used to call methods that set parameters of a node or transition. The initializer expressions for a state node must be methods of the node being initialized. For example, SoundNode has a method called setAutoStop, so if you want to call this method as part of your initialization of this node, you can write:

barker: SoundNode($, "barkhigh.wav") [setAutoStop(true);]
which expands into the following code:
SoundNode *barker = new SoundNode("barker","barkhigh.wav");
A very common use of initializers is to set parameters for motion command nodes such as LedNode or WalkNode.

Transition Declarations

Transitions have long forms and, in some cases, abbreviated forms. The examples we saw at the beginning of this chapter all used abbreviated forms. The long form syntax for a transition looks like this:
sourcenode >== transname: TransitionClass<template_args>(constructor_args) [initializers] ==> targetnode
All components except the source node and transition class are optional, as are the spaces between them.

A "source node" or "target node" can be either a node name, or an entire node declaration. Node and transition declarations can therefore be chained together, forming a sequence, like this:

bark: SoundNode($,"barkmed.wav") >== EventTrans($$,EventBase::buttonEGID) ==> howl: SoundNode($,"howl.wav")
Chaining node declarations and transitions together like this is permitted but not required. Some people prefer to write all their node declarations first, and then all their transition declarations.

The special token $$ expands into the name of the destination node, which is the first argument to the transition constructor. Transitions also have a constructor that takes a string first argument that is the name of the transition; the destination node is then the second argument. If no constructor arguments are specified, defaults are supplied. Thus,

howl >== CompletionTrans ==> wait: StateNode
expands into
howl >== CompletionTrans($,$$) ==> wait: StateNode($)
which in turn expands into
howl >== CompletionTrans("completiontrans1",wait) ==> wait: StateNode("wait")
Initializers can be used to set parameters of a transition, such as telling it which sound file to play.

A list notation is used when a transition has multiple sources or multiple destinations. In that case, each of the sources or destinations must be referenced by name and declared elsewhere. For multiple source nodes, use:

{srcname1, srcname2, ...} >== transition ==>
Similarly, for multiple targets use:
>== transition ==> {targname1, targname2, ...}

Here is an example of the bark/howl state machine from the previous section written as simply as possible using the long form for transitions. Notice the use of optional blank lines between declarations since, in this version, we're not chaining nodes and transition declarations together. Breaking up definitions by adding blank lines can make it easier to find syntax errors.

bark: SoundNode($,"barkmed.wav")

wait: StateNode

bark >== EventTrans($, $$,
		EventBase::activateETID) [setSound("ping.wav");] ==> wait

wait >==TimeOutTrans($,$$,15000)==> bark

howl: SoundNode($,"howl.wav") 

bark >==TimeOutTrans($,$$,500)==> howl

howl >==CompletionTrans==> wait

Transition Abbreviations

Certain transition types are so common that they have an abbreviated notation:
NullTrans =N=>
CompletionTrans =C=>
CompletionTrans($, $$, n) =C(n)=>
TextMsgTrans($, $$, str) =TM(str)=>
EventTrans($, $$, EventBase::g, s, EventBase::t) =E(g,s,t)=>
EventTrans($, $$, EventBase::buttonEGID, s, EventBase::activateETID) =B(s)=>
EventTrans($, $$, EventBase::buttonEGID, s, EventBase::t) =B(s,t)=>
TimeOutTrans($, $$, t) =T(t)=>
RandomTrans =RND=>
Notice that we do not have to apply the EventBase:: qualifier to the arguments of the EventTrans transition when we write it in abbreviated form, e.g., we can write =E(buttonEGID,ChiaraInfo::GreenButOffset,activateETID)=>. For button press events there is an even more abbreviated form that assumes an event type of "activate" by default: =B(ChiaraInfo::GreenButOffSet)=>.

Here is an example of the full bark/howl/blink state machine defined as compactly as possible, using chaining and transition abbreviations.

startnode: StateNode =N=> {noblink, bark}

noblink: LedNode [setPriority(MotionManager::kBackgroundPriority);

bark: SoundNode($,"barkmed.wav")
     wait: StateNode =T(15000)=> bark

bark =T(5000)=> {howl, blink}

howl: SoundNode($,"howl.wav")

blink: LedNode [getMC()->cycle(RobotInfo::FaceLEDMask, 1500, 1.0);]

{howl, blink} =C(1)=> wait

Creating an FSM File

To create a state machine using shorthand notation, you must name your file xxx.h.fsm or xxx.cc.fsm. The state machine definition goes inside your definition for setup(), and must be preceded by $statemachine{ and terminated by a }. Alternatively, you can use $setupmachine{...} which takes care of defining setup() for you.

The Tekkotsu make file will read the fsm file and create xxx-fsm.h or xxx-fsm.cc for you, but this derived file will be hidden away in the build directory so that you don't accidentally edit it instead of the fsm file.

Here is an example of the full implementation of the bark/howl/blink state machine:

#include "Behaviors/StateMachine.h"

class BarkHowlBlinkBehavior : public StateNode {

  BarkHowlBlinkBehavior() : StateNode("BarkHowlBlinkBehavior") {}
    startnode: StateNode =N=> {noblink, bark}

    noblink: LedNode [setPriority(MotionManager::kBackgroundPriority);

    bark: SoundNode($,"barkmed.wav")
	 wait: StateNode =T(15000)=> bark

    bark =T(5000)=> {howl, blink}

    howl: SoundNode($,"howl.wav")

    blink: LedNode [getMC()->cycle(RobotInfo::AllLEDMask, 1500, 1.0);]

    {howl, blink} =C(1)=> wait
}; REGISTER_BEHAVIOR(BarkHowlBlinkBehavior);

Diagnosing Syntax Errors

There are two kinds of errors you may encounter with the state machine shorthand. First, a syntax error in your use of the shorthand notation will generate an error from the state parser tool. Error messages include the line number in the fsm file, so they are usually easy to track down. If you are chaining a sequence of states together, it may be helpful to break up the chain into individual declarations separated by blank lines, to make the error message more specific and help you narrow down the problem.

The second kind of error is an error in the generated .cc file. This kind of error will only be caught by the gcc compiler. If you cannot figure out the error from looking at your source code, you may need to look at the corresponding line in the generated .cc file. This file can be found in your project/build directory.

Example: suppose your file is called MyDemo.cc.fsm, and you're getting a compiler error on line 73. In an editor, open the file project/build/PLATFORM_LOCAL/TGT_CHIARA/MyDemo-fsm.cc and search for #line compiler directives that reference source line 73. There may be more than one of them. The code immediately following one of these directives is the source of the error. Of course, if you're compiling for a different target robot, use that robot name instead of TGT_CHIARA.

Running the State Parser Tool Directly

Normally the state parser tool is invoked automatically by the Makefile when you compile your project, if your directory contains fsm files. However, you can also run the tool directly if you want to see whether your state machine description is valid, or look at the generated code. Simply run the script Tekkotsu/tools/sbin/stateparser, and give it an input filename as the first command line argument. You can specify an output filename as the second argument; if omitted, a default filename will be used.

Tekkotsu/tools/sbin/stateparser BarkHowlBlinkBehavior.cc.fsm -

If you supply a second argument of "-" the output will be written to the terminal instead of to a file.

Advanced Concepts

Here is the shortest possible infinite loop:
startnode: StateNode =N=> startnode

The first node defined in the state machine is assumed to be the start node unless there is actually a node with the name "startnode". Alternatively, you can explictly tell the parent state machine what your start node is by assigning a value to the variable startnode at the end of your setup() function.

You can assign names to transitions as well as to nodes. If you don't do this, they will be assigned generated names like nulltrans1 or eventtrans3. Assigning your own names can make the code more readable, and it can make traces (either from the Event Logger or the Storyboard tool) more understandable. Exanple:

{howl, blink} >== howldone: CompletionTrans($,$$,1) ==> wait

One caveat: to assign a name to a transition written in abbreviated form, there must be no spaces between the = sign, the name, the colon, and the transition symbol:

{howl, blink} =howldone:C(1)=> wait

You can also use initialization expressions with abbreviated transitions. The initializer must come just before the => with no space before the left square bracket or after the right square bracket. For example, you could use an initializer to assign a sound to an EventTrans by calling its setSound() method, which it inherits from Transition, as in the example program above.

Prev: Nodes and transitions
Up: Contents
Next: Defining new node classes

Last modified: Fri Jan 21 16:06:34 EST 2011