(!) 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.

Writing Custom State Classes with User-defined Callbacks

Description: Sometimes you want to create a state class like smach.SimpleActionState which receives user callbacks as arguments. If these callbacks change the SMACH interface of a state (outcomes, userdata keys), then the callbacks need to be annotated with this information.

Tutorial Level: ADVANCED

SMACH provides a decorator which can attach a SMACH interface to a function. This includes user data input keys, output keys, and state outcomes. A common pattern for extensible state design is to use callback functions that are called on various events. For example, SimpleActionState can call callbacks for generating goal messages or processing result messages, and MonitorState calls a callback each time a message comes in on a specified topic.

If a state defines an interface that can take callback functions, it should support this decorator model. Doing so is as simple as checking for the existence of the SMACH interface methods. A single function is provided bu the smach.util module.

Single User Callback

The following code shows how to integrate the SMACH interface provided by a callback function into the interface of a state. This state simply receives a SINGLE callback function and calls it when the state is executing.

   1 class MyState(smach.State):
   2     def __init__(self, cb):
   3         smach.State.__init__(self, outcomes=['done'])
   5         self._cb = cb
   6         if cb and smach.has_smach_interface(cb):
   7             self.register_input_keys(cb.get_registered_input_keys())
   8             self.register_output_keys(cb.get_registered_output_keys())
   9             self.register_outcomes(cb.get_registered_outcomes())
  10     ...
  11     def execute(self, ud):
  12         # Call callback
  13         cb_outcome = self._cb(ud)
  15         # If callback returned an outcome, return it as the state outcome
  16         if cb_outcome:
  17             return cb_outcome 
  19         return 'done'

Multiple User Callbacks

If more than one callback is supplied, the state class implementer should ensure that no side-effects are caused by adding callbacks with different interfaces. This is done by using a userdata remapper when passing userdata to the callbacks.

The following state takes a list of callbacks as an argument, and stores each callback's input and output keys. Then, when each callback is called while this state is executing, they will receive access to only their declared input and output keys. This prevents accidental access to be granted to one callback by another callback.

   1 class MyState(smach.State):
   2     def __init__(self, cb_list):
   3         smach.State.__init__(self, outcomes=['done'])
   5         self._cbs = cb_list
   6         for cb in cb_list:
   7             if cb and smach.has_smach_interface(cb):
   8                 self._cb_input_keys.append(cb.get_registered_input_keys())
   9                 self._cb_output_keys.append(cb.get_registered_output_keys())
  11                 self.register_input_keys(self._cb_input_keys[-1])
  12                 self.register_output_keys(self._cb_output_keys[-1])
  13                 self.register_outcomes(self._cb_input_keys[-1])
  14     ...
  15     def execute(self, ud):
  16         # Call callbacks
  17         for (cb, ik, ok) in zip(self._cbs,
  18                                 self._cb_input_keys,
  19                                 self._cb_output_keys):
  21             # Call callback with limited userdata
  22             cb_outcome = self._cb(smach.Remapper(ud,ik,ok,{}))
  24             # If callback returned an outcome, return it as the state outcome
  25             if cb_outcome:
  26                 return cb_outcome 
  28         return 'done'

Wiki: mysmach/Tutorials/Writing Custom State Classes With User-defined Callbacks (last edited 2015-01-31 08:33:54 by hongming wang)