1
2 import roslib; roslib.load_manifest('smach_ros')
3 import rospy
4
5 import copy
6 import threading
7 import traceback
8
9 from actionlib.simple_action_server import SimpleActionServer
10 from smach_msgs.msg import *
11 import smach
12
13 __all__ = ['ActionServerWrapper']
14
16 """SMACH container wrapper with actionlib ActionServer.
17
18 Use this class to associate an action server with a smach
19 L{StateMachine<smach.state_machine.StateMachine>}. This allows invocation
20 of the state machine over the actionlib API/protocol.
21
22 This class delegates to a provided SMACH container and associates it with an
23 action server. The user can specify lists of outcomes which correspond to
24 different action result statuses (SUCCEEDED, ABORTED, PREEMPTED). Once the
25 delegate state machine leaves one of these outcomes, this wrapper class will
26 cause the state machine to terminate, and cause the action server to return
27 a result.
28
29 Note that this class does not inherit from L{smach.State<smach.State>} and
30 can only be used as a top-level container.
31 """
32
33 - def __init__(self,
34 server_name, action_spec,
35 wrapped_container,
36 succeeded_outcomes = [],
37 aborted_outcomes = [],
38 preempted_outcomes = [],
39 goal_key = 'action_goal',
40 feedback_key = 'action_feedback',
41 result_key = 'action_result',
42 goal_slots_map = {},
43 feedback_slots_map = {},
44 result_slots_map = {},
45 expand_goal_slots = False,
46 pack_result_slots = False
47 ):
48 """Constructor.
49
50 @type server_name: string
51 @param server_name: The name of the action server that this container will
52 present.
53
54 @type action_spec: actionlib action msg
55 @param action_spec: The type of action this server will present
56
57 @type wrapped_container: L{StateMachine}
58 @param wrapped_container: The state machine to manipulate
59
60 @type succeeded_outcomes: array of strings
61 @param succeeded_outcomes: Array of terminal state labels which, when left,
62 should cause the action server to return SUCCEEDED as a result status.
63
64 @type aborted_outcomes: array of strings
65 @param aborted_outcomes: Array of terminal state labels which, when left,
66 should cause the action server to return ABORTED as a result status.
67
68 @type preempted_outcomes: array of strings
69 @param preempted_outcomes: Array of terminal state labels which, when left,
70 should cause the action server to return PREEMPTED as a result status.
71
72 @type goal_key: string
73 @param goal_key: The userdata key into which the action goal should be
74 stuffed when the action server receives one.
75
76 @type feedback_key: string
77 @param feedback_key: The userdata key into which the SMACH container
78 can put feedback information relevant to the action.
79
80 @type result_key: string
81 @param result_key: The userdata key into which the SMACH container
82 can put result information from this action.
83 """
84
85
86 self.wrapped_container = wrapped_container
87 """State machine that this wrapper talks to."""
88
89
90 self.wrapped_container.register_transition_cb(self.transition_cb)
91 self.wrapped_container.register_termination_cb(self.termination_cb)
92
93
94
95 self.userdata = smach.UserData()
96
97
98 self._goal_key = goal_key
99 self._feedback_key = feedback_key
100 self._result_key = result_key
101
102 self._goal_slots_map = goal_slots_map
103 self._feedback_slots_map = feedback_slots_map
104 self._result_slots_map = result_slots_map
105
106 self._expand_goal_slots = expand_goal_slots
107 self._pack_result_slots = pack_result_slots
108
109
110 self.userdata[self._goal_key] = copy.copy(action_spec().action_goal.goal)
111 self.userdata[self._result_key] = copy.copy(action_spec().action_result.result)
112 self.userdata[self._feedback_key] = copy.copy(action_spec().action_feedback.feedback)
113
114
115 self._server_name = server_name
116 self._action_spec = action_spec
117
118
119 self._action_server = SimpleActionServer(
120 self._server_name,
121 self._action_spec,
122 execute_cb = self.execute_cb,
123 auto_start = False)
124
125
126 self._succeeded_outcomes = set(succeeded_outcomes)
127 self._aborted_outcomes = set(aborted_outcomes)
128 self._preempted_outcomes = set(preempted_outcomes)
129
130
131 card_of_unions = len(self._succeeded_outcomes | self._aborted_outcomes | self._preempted_outcomes)
132 sum_of_cards = (len(self._succeeded_outcomes) + len(self._aborted_outcomes) + len(self._preempted_outcomes))
133 if card_of_unions != sum_of_cards:
134 rospy.logerr("Succeeded, aborted, and preempted outcome lists were not mutually disjoint... expect undefined behavior.")
135
137 """Run the state machine as an action server.
138 Note that this method does not block.
139 """
140
141
142
143 self._action_server.register_preempt_callback(self.preempt_cb)
144
145
146 self._action_server.start()
147
148 rospy.loginfo("Started SMACH action server wrapper, adversiting as '%s'" % self._server_name)
149
150
152 """Transition callback passed to state machine.
153 This method is called each time the state machine transitions.
154 """
155 rospy.logdebug("Publishing action feedback.")
156
157 self.publish_feedback(userdata)
158
159 - def termination_cb(self, userdata, terminal_states, container_outcome):
160 """Termination callback passed to state machine.
161 This callback receives the final state and state machine outcome as
162 specified by the state-outcome map given to the delegate container
163 on construction (see L{ActionServerWrapper.__init__}).
164
165 Remember that in this context, the SMACH container is just a single state
166 object, which has an outcome like any other state; it is this outcome on
167 which we switch here. This method will determine from the state machine
168 outcome which result should be returned to the action client for this goal.
169 """
170 rospy.logdebug("Wrapped state machine has terminated with final state: "+str(terminal_states)+" and container outcome: "+str(container_outcome))
171
173 """Publish the feedback message in the userdata db.
174 Note that this feedback is independent of smach.
175 """
176 self._action_server.publish_feedback(userdata[self._feedback_key])
177
178
180 """Action server goal callback
181 This method is called when the action server associated with this state
182 machine receives a goal. This puts the goal into the userdata,
183 which is the userdata of the contained state.
184 """
185
186
187
188 rospy.logdebug("Starting wrapped SMACH container")
189
190
191
192
193
194 if self._expand_goal_slots:
195 for slot in goal.__slots__:
196 self.userdata[slot] = getattr(goal, slot)
197
198
199 self.userdata[self._goal_key] = goal
200
201
202 for from_key,to_key in self._goal_slots_map.iteritems():
203 self.userdata[to_key] = getattr(goal,from_key)
204
205
206 try:
207 container_outcome = self.wrapped_container.execute(
208 smach.Remapper(
209 self.userdata,
210 self.wrapped_container.get_registered_input_keys(),
211 self.wrapped_container.get_registered_output_keys(),
212 {}))
213
214 except smach.InvalidUserCodeError as ex:
215 rospy.logerr("Exception thrown while executing wrapped container.")
216 self._action_server.set_aborted()
217 return
218 except:
219 rospy.logerr("Exception thrown:while executing wrapped container: " + traceback.format_exc())
220 self._action_server.set_aborted()
221 return
222
223
224 result = self.userdata[self._result_key]
225
226
227 for from_key,to_key in self._result_slots_map.iteritems():
228 setattr(result,from_key,self.userdata[to_key])
229
230
231
232 if self._pack_result_slots:
233 for slot in result.__slots__:
234 if slot in self.userdata:
235 setattr(result,slot,self.userdata[slot])
236
237
238 if container_outcome in self._succeeded_outcomes:
239 rospy.loginfo('SUCCEEDED')
240 self._action_server.set_succeeded(result)
241 elif container_outcome in self._preempted_outcomes:
242 rospy.loginfo('PREEMPTED')
243 self._action_server.set_preempted(result)
244 else:
245 rospy.loginfo('ABORTED')
246 self._action_server.set_aborted(result)
247
248
250 """Action server preempt callback.
251 This method is called when the action client preempts an active goal.
252
253 In this case, the StateMachine needs to propagate said preemption to
254 the currently active delegate action (the current state).
255 """
256 rospy.loginfo("Preempt on state machine requested!")
257 self.wrapped_container.request_preempt()
258