API review

Proposer: Jonathan Bohren

Present at review:

  • List reviewers

Question / concerns / comments

SMACH has now been used in re-implementing two PR2 applications, and has been used in the creation of four novel PR2 applications. In the latter category, SMACH was used by several new users, some of which received very little or no help outside of the API documentation and some examples.


  • The ActionServer allows you to map the action goal/result onto userdata. Can the SimpleActionClient also provide direct mapping between goal/result and userdata?

  • I don't understand the need for "@smach.cb_interface". Can't we specify user data keys when we construct e.g. the SimpleActionState?


For remapping:

I can see the directionality both ways, but the current direction does depend a bit on scope:

  smach.StateMachine.add('FOO', Foo(), 
                               remapping={'foo_counter_in':'sm_counter', 'foo_counter_out':'sm_counter'})

I think this means "From Foo's perspective, map the key foo_counter_in to the sm_counter value from in the current scope". This seems a little weird given that we're calling this as part of the state machine add, where it seems like the semantics should be "map from the current user data variable sm_counter to the internal variable for this container of foo_counter_in".

Hackathon Post-Mortem

After this testing, we have learned a lot about the strengths and weaknesses of SMACH. People desired more features for SimpleActionState, the SMACH state that represents an actionlib action. We also learned that the most confusing aspect of SMACH was how the userdata system worked. In the 0.2.x series, userdata access was confusing for several reasons:

  • It is not immediately obvious where certain user data is coming from
  • It is not immediately obvious where certain user data is being used
  • SMACH states access userdata as a state object's instance member (self.userdata), which implies that that userdata is scoped to the state, as opposed to scoped to the container (which is the case)
  • The correct way to userdata between scopes is not clear from the API or documentation
  • No one really knows where the user data lives

There was also a great deal of positive feedback. Wrapping a SMACH tree in an actionlib action server proved very simple (once the userdata confusion was resolved) as was enabling introspection for the SMACH Viewer. The general construction API was clear, and people had no problem implementing their own custom states.

Meeting agenda

Now that we have several people who have experience with SMACH, we can analyze this more mature iteration of the system, which aims to solve the aforementioned problems.


Preemption in SMACH has been tested very heavily in practice when building the pr2_drinks application. The new preemption mechanisms are easy-to-use, and well-defined.

New Userdata System

The userdata system has been completely re-done. This new design is characterized by the following features:

  • Userdata access is strictly controlled with declared input keys and output keys

  • The API for setting input and output keys is the same for states and containers, alike
  • Userdata is not stored in states, it is passed into their execute() (formerly enter()) method as an argument

Containers Only Have One Type of Context Manager

When using the with syntax for SMACH construction, there are no special contexts that change the behavior of the add() method of a container. If there is such a method, it simply has a different name.

New Inline State Macro

A user can now rapidly create a state class from a single execute() method.

Instead of having to write:

   1 class MyState(smach.State):
   2     def __init__(self):
   3         smach.State.__init__(self,outcomes=['done','preempted'],input_keys=['x'])
   5     def execute(self, ud):
   6         if self.preempt_requested():
   7             self.service_preempt()
   8             return 'preempted'
   9         print "x = "+str(ud.x)
  10         return 'done'

You can just write:

   1 @smach.inline(outcomes=['done','preempted'],input_keys=['x'])
   2 def MyState(self, ud):
   3     if self.preempt_requested():
   4         self.service_preempt()
   5         return 'preempted'
   6     print "x = "+str(ud.x)
   7     return 'done'

Callback Decorators

Making user data access strictly controlled has it's drawbacks; it means that any time you access user data, you need to have declared that you're going to access it. If you have a state class that can be extended through callback functions, you need to document the userdata keys that that callback may access as well as the outcomes that that callback may potentially add to the state.

This is done with a simple decorator:

   1 @smach.cb_interface(input_keys=['x','y'], output_keys=['z'])
   2 def my_cb(ud):
   3     ud.z = ud.x + ud.y

Userdata Remapping

There is a very simple API for remapping user data keys when adding states to containers. It works very similarly to remapping in ROS.


A whole new set of tutorials have been written, and they attempt to be a basis set for everything that you can do with SMACH.


Package status change mark change manifest)

  • /!\ Branch simple action state for outcome modification RETRACTED

  • /!\ strict about which methods the decorator applies to DONE

  • /!\ there's alternate syntax for input/output keys / outcome DONE

  • /!\ get rid of inline, replace with callback state DONE

  • /!\ DOCUMENT remappingDONE

  • /!\ call new smach stack "executive_smach" DONE

Wiki: smach/Reviews/2010-07-20_API_Review (last edited 2010-08-04 17:31:59 by wim)