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
Contents
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.
Installing Smach
$ sudo apt-get install ros-noetic-smach-ros
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:
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.
The resulting state machine looks like this:
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.
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.
The next tutorial will teach you how to pass user data between different states and state machines.