1
2 import traceback
3 import threading
4 from contextlib import contextmanager
5
6 import smach
7
8 __all__ = ['Container']
11 """Smach container interface.
12
13 This provides an interface for hooking into smach containers. This includes
14 methods to get and set state, as well as provide start / transition /
15 termination callback storage and registration utilities.
16
17 Note that it is up to the implementation of the containers both when the
18 callbacks are called as well as what arguments are given to them.
19
20 Callback semantics:
21 - Start: Called when a container is entered
22 - Transition: Called when a container's state changes
23 - Termination: Called when a container is left
24 """
25
26
27 _construction_stack = []
28 _construction_lock = threading.RLock()
29 _context_kwargs = []
30
31 - def __init__(self,
32 outcomes=[],
33 input_keys=[],
34 output_keys=[]):
35 """Initializes callback lists as empty lists."""
36 smach.state.State.__init__(self, outcomes, input_keys, output_keys)
37
38 self.userdata = smach.UserData()
39 """Userdata to be passed to child states."""
40
41
42 self._start_cbs = []
43 self._transition_cbs = []
44 self._termination_cbs = []
45
47 """Access child state by key.
48 @rtype: L{smach.State}
49 @returns: Child state with label equal to key
50 """
51 raise NotImplementedError()
52
54 """Get the children of this container.
55 This is empty for leaf states.
56
57 @rtype: dict of string: State
58 @return: The sub-states of this container.
59 """
60 raise NotImplementedError()
61
63 """Set initial active states of a container.
64
65 @type initial_states: list of string
66 @param initial_states: A description of the initial active state of this
67 container.
68
69 @type userdata: L{UserData}
70 @param userdata: Initial userdata for this container.
71 """
72 raise NotImplementedError()
73
75 """Get the initial states description.
76
77 @rtype: list of string
78 """
79 raise NotImplementedError()
80
82 """Get a description of the current states.
83 Note that this is specific to container implementation.
84
85 @rtype: list of string
86 """
87 raise NotImplementedError()
88
90 """Get the internal outcome edges of this container.
91 Get a list of 3-tuples (OUTCOME, LABEL_FROM, LABEL_TO) which correspond
92 to transitions inside this container.
93
94 @rtype: list of 3-tuple
95 """
96 raise NotImplementedError()
97
99 """Check consistency of this container."""
100 raise NotImplementedError()
101
102
111
113 if parent_ud is not None:
114 output_keys = self.get_registered_output_keys()
115 for ok in output_keys:
116 try:
117 parent_ud[ok] = ud[ok]
118 except KeyError:
119 smach.logwarn("Attempting to copy output key '%s', but this key does not exist." % ok)
120
121
123 """Adds a start callback to this container.
124 Start callbacks receive arguments:
125 - userdata
126 - local_userdata
127 - initial_states
128 - *cb_args
129 """
130 self._start_cbs.append((start_cb,cb_args))
131
133 """Adds a transition callback to this container.
134 Transition callbacks receive arguments:
135 - userdata
136 - local_userdata
137 - active_states
138 - *cb_args
139 """
140 self._transition_cbs.append((transition_cb,cb_args))
141
143 """Adds a termination callback to this state machine.
144 Termination callbacks receive arguments:
145 - userdata
146 - local_userdata
147 - terminal_states
148 - container_outcome
149 - *cb_args
150 """
151 self._termination_cbs.append((termination_cb, cb_args))
152
154 """Calls the registered start callbacks.
155 Callback functions are called with two arguments in addition to any
156 user-supplied arguments:
157 - userdata
158 - a list of initial states
159 """
160 try:
161 for (cb,args) in self._start_cbs:
162 cb(self.userdata, self.get_initial_states(), *args)
163 except:
164 smach.logerr("Could not execute start callback: "+traceback.format_exc())
165
167 """Calls the registered transition callbacks.
168 Callback functions are called with two arguments in addition to any
169 user-supplied arguments:
170 - userdata
171 - a list of active states
172 """
173 try:
174 for (cb,args) in self._transition_cbs:
175 cb(self.userdata, self.get_active_states(), *args)
176 except:
177 smach.logerr("Could not execute transition callback: "+traceback.format_exc())
178
180 """Calls the registered termination callbacks.
181 Callback functions are called with three arguments in addition to any
182 user-supplied arguments:
183 - userdata
184 - a list of terminal states
185 - the outcome of this container
186 """
187 try:
188 for (cb,args) in self._termination_cbs:
189 cb(self.userdata, terminal_states, outcome, *args)
190 except:
191 smach.logerr("Could not execute termination callback: "+traceback.format_exc())
192
193
194
197
198 - def __exit__(self, exc_type, exc_val, exc_tb):
204
205 @contextmanager
216
218 """Opens this container for modification.
219
220 This appends the container to the construction stack and locks the
221 reentrant lock if it is a valid container to open."""
222
223
224 Container._construction_stack.append(self)
225 Container._construction_lock.acquire()
226
243
249
253
254 @staticmethod
260
261 @classmethod
263 """Get the currently opened container.
264
265 This also asserts that the open container is of type cls.
266 """
267 if Container._any_containers_opened():
268 opened_container = Container._construction_stack[-1]
269 if not isinstance(opened_container, cls):
270 raise smach.InvalidStateError('Attempting to call a %s construction method without having opened a %s.' % (cls, cls))
271 return opened_container
272 else:
273 raise smach.InvalidStateError('Attempting to access the currently opened container, but no container is opened.')
274