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 ping 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(ChiaraInfo::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");
addNode(barker);
barker->setAutoStop(true);
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")
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 blank lines between declarations since, in this version, we're not chaining nodes and transition declarations together.

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

wait: StateNode

bark >== EventTrans($, $$,
		EventBase::buttonEGID,
		RobotInfo::HeadFrButOffset,
		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($, $$, g, s, t) =E(g,s,t)=>
EventTrans($, $$, buttonEGID, s) =B(s)=>
EventTrans($, $$, buttonEGID, s, t) =B(s,t)=>
TimeOutTrans($, $$, t) =T(t)=>
RandomTrans =RND=>
SignalTrans<T>($, $$) =S<T>=>
SignalTrans<T>($, $$, v) =S<T>(v)=>
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);
                  getMC()->set(RobotInfo::FaceLEDMask,0.0);]

bark: SoundNode($,"barkmed.wav")
  =B(RobotInfo::GreenButOffset)[setSound("ping.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

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.

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 #endstatemachine. The Tekkotsu make file will read the fsm file and create xxx.h or xxx.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:

//-*-c++-*-
#ifndef INCLUDED_BarkHowlBlinkBehavior_h_
#define INCLUDED_BarkHowlBlinkBehavior_h_
 
#include "Behaviors/StateMachine.h"
#include "Events/EventRouter.h"

class BarkHowlBlinkBehavior : public StateNode {

public:
  BarkHowlBlinkBehavior() : StateNode("BarkHowlBlinkBehavior") {}
 
  virtual void setup() {
#statemachine
startnode: StateNode =N=> {noblink, bark}

noblink: LedNode [setPriority(MotionManager::kBackgroundPriority);
	          getMC()->set(RobotInfo::FaceLEDMask,0.0);]

bark: SoundNode($,"barkmed.wav")
  =B(ChiaraInfo::GreenButOffset)[setSound("ping.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
#endstatemachine } // end of setup() }; #endif

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 .h file. This kind of error will only be caught by the gcc compiler, and the error message it generates will reference line numbers in the .h file, not the fsm file. If you cannot figure out the error from looking at your source code, you may need to look at the compiler-specified line in the generated .h file. This file can be found in your project/build directory.

Example: suppose your file is called MyDemo.h.fsm, and you're getting a compiler error on line 73 of MyDemo.h. In an editor, open the file project/build/PLATFORM_LOCAL/TGT_CHIARA/MyDemo.h and examine line 73. 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 tools/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.


../tools/stateparser BarkHowlBlinkBehavior.h.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

If you don't name your start node "startnode", then you must tell the parent state machine what your start node is by assigning a value to 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 a CompletionTrans by calling its setSound() method, which it inherits from Transition:

foo =C[setSound("ping.wav")]=> bar

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


Last modified: Tue Sep 8 03:50:38 EDT 2009