(!) Please ask about problems and questions regarding this tutorial on answers.ros.org. Don't forget to include in your question the link to this page, the versions of your OS & ROS, and also add appropriate tags.

Getting Started with smach

Description: This tutorial guides you through the very first steps of using smach.

Tutorial Level: BEGINNER

Next Tutorial: Passing User Data between States

Why learn Smach?

Smach, which stands for "State Machine", is a powerful and scalable Python-based library for hierarchical state machines. The Smach library does not depend on ROS, and can be used in any Python project. The executive_smach stack however provides very nice integration with ROS, including smooth actionlib integration and a powerful Smach viewer to visualize and introspect state machines.

It is very easy to write a simple Smach state machine, while at the same time Smach allows you to design, maintain and debug large, complex hierarchical state machines. The image below shows an example state machine used to coordinate actionlib actions that allow the PR2 robot to charge itself at a standard outlet.

pr2_plugs_executive/smach.png

Creating a State Machine

To create a Smach state machine, you first create a number of states, and then add those states to a State Machine container.

Creating a state

To create a state, you simply inherit from the State base class, and implement the State.execute(userdata) method:

   1   class Foo(smach.State):
   2      def __init__(self, outcomes=['outcome1', 'outcome2']):
   3        # Your state initialization goes here
   4 
   5      def execute(self, userdata):
   6         # Your state execution goes here
   7         if xxxx:
   8             return 'outcome1'
   9         else:
  10             return 'outcome2'
  • In the init method you initialize your state class. Make sure to never block in the init method! If you need to wait for other parts of your system to start up, do this from a separate thread.

  • In the execute method of a state the actual work is done. Here you can execute any code you want. It is okay to block in this method as long as you like. Once you return from this method, the current state is finished.

  • When a state finishes, it returns an outcome. Each state has a number of possible outcomes associated with it. An outcome is a user-defined string that describes how a state finishes. A set of possible outcomes could for example be ['succeeded', 'failed', 'awesome']. The transition to the next state will be specified based on the outcome of the previous state.

Adding states to a state machine

A state machine is a container that holds a number of states. When adding a state to a state machine container, you specify the transitions between the states.

   1   sm = smach.StateMachine(outcomes=['outcome4','outcome5'])
   2   with sm:
   3      smach.StateMachine.add('FOO', Foo(),
   4                             transitions={'outcome1':'BAR',
   5                                          'outcome2':'outcome4'})
   6      smach.StateMachine.add('BAR', Bar(),
   7                             transitions={'outcome2':'FOO'})

The resulting state machine looks like this:

simple.png

  • The red boxes show the possible outcomes of the state machine container: outcome4 and outcome5, as specified in line 1.

  • In line 3-5 we add the first state to the container, and call it FOO. The convention is to name states with all caps. If the outcome of state FOO is 'outcome1', then we transition to state BAR. If the outcome of state FOO is 'outcome2', then the whole state machine will exit with 'outcome4'.

  • Every state machine container is also a state. So you can nest state machines by adding a state machine container to another state machine container.

Example

This is a complete runnable example you can find in the executive_smach_tutorials package.

https://raw.githubusercontent.com/rhaschke/executive_smach_tutorials/indigo-devel/examples/state_machine_simple.py

   1 #!/usr/bin/env python
   2 
   3 import rospy
   4 import smach
   5 
   6 # define state Foo
   7 class Foo(smach.State):
   8     def __init__(self):
   9         smach.State.__init__(self, outcomes=['outcome1','outcome2'])
  10         self.counter = 0
  11 
  12     def execute(self, userdata):
  13         rospy.loginfo('Executing state FOO')
  14         if self.counter < 3:
  15             self.counter += 1
  16             return 'outcome1'
  17         else:
  18             return 'outcome2'
  19 
  20 
  21 # define state Bar
  22 class Bar(smach.State):
  23     def __init__(self):
  24         smach.State.__init__(self, outcomes=['outcome2'])
  25 
  26     def execute(self, userdata):
  27         rospy.loginfo('Executing state BAR')
  28         return 'outcome2'
  29         
  30 
  31 
  32 
  33 # main
  34 def main():
  35     rospy.init_node('smach_example_state_machine')
  36 
  37     # Create a SMACH state machine
  38     sm = smach.StateMachine(outcomes=['outcome4', 'outcome5'])
  39 
  40     # Open the container
  41     with sm:
  42         # Add states to the container
  43         smach.StateMachine.add('FOO', Foo(), 
  44                                transitions={'outcome1':'BAR', 
  45                                             'outcome2':'outcome4'})
  46         smach.StateMachine.add('BAR', Bar(), 
  47                                transitions={'outcome2':'FOO'})
  48 
  49     # Execute SMACH plan
  50     outcome = sm.execute()
  51 
  52 
  53 if __name__ == '__main__':
  54     main()

Running the example:

For ROS Kinetic (and newer) versions just clone the git repository, cd into the folder "executive_smach_tutorials" and run the example (inside your ros environment)

$ git clone https://github.com/eacousineau/executive_smach_tutorials.git
$ cd executive_smach_tutorials
$ ./examples/state_machine_simple.py

For older ROS-versions you can install the smach_tutorials package using rosdep.

$ roscd smach_tutorials
$ ./examples/state_machine_simple.py

This should give you the following output:

[INFO] 1279835117.234563: Executing state FOO
[INFO] 1279835117.234849: State machine transitioning 'FOO':'outcome1'-->'BAR'
[INFO] 1279835117.235114: Executing state BAR
[INFO] 1279835117.235360: State machine transitioning 'BAR':'outcome2'-->'FOO'
[INFO] 1279835117.235633: Executing state FOO
[INFO] 1279835117.235884: State machine transitioning 'FOO':'outcome1'-->'BAR'
[INFO] 1279835117.236143: Executing state BAR
[INFO] 1279835117.236387: State machine transitioning 'BAR':'outcome2'-->'FOO'
[INFO] 1279835117.236644: Executing state FOO
[INFO] 1279835117.236891: State machine transitioning 'FOO':'outcome1'-->'BAR'
[INFO] 1279835117.237149: Executing state BAR
[INFO] 1279835117.237394: State machine transitioning 'BAR':'outcome2'-->'FOO'
[INFO] 1279835117.237652: Executing state FOO
[INFO] 1279835117.237898: State machine terminating 'FOO':'outcome2':'outcome4'

Pre-defined States and Containers

State library

The example above shows how you can implement your own states. However, Smach comes with a whole library of pre-implemented states that cover many common usecases:

  • SimpleActionState: automatically call actionlib actions.

  • ServiceState: automatically call ROS services

  • MonitorState

  • ...

The 'Smach States' section on the tutorials page gives an overview of all available states.

Container library

Similarly, Smach also comes with a set of useful containers:

  • StateMachine: the generic state machine container

  • Concurrence: a state machine that can run multiple states in parallel.

  • Sequence: a state machine that makes it easy to execute a set of states in sequence. The 'Smach Containers' section on the tutorials page gives an overview of all available containers.

width="350"

The next tutorial will teach you how to pass user data between different states and state machines.

Wiki: smach/Tutorials/Getting Started (last edited 2018-11-15 17:37:08 by JosephHirschfeld)