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'])
4
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)
14
15 # If callback returned an outcome, return it as the state outcome
16 if cb_outcome:
17 return cb_outcome
18
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'])
4
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())
10
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):
20
21 # Call callback with limited userdata
22 cb_outcome = self._cb(smach.Remapper(ud,ik,ok,{}))
23
24 # If callback returned an outcome, return it as the state outcome
25 if cb_outcome:
26 return cb_outcome
27
28 return 'done'