1
2 import traceback
3 import threading
4 from contextlib import contextmanager
5
6 import asmach as 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 """Constructor.
36 Initializes callback lists (empty).
37 """
38 smach.state.State.__init__(self, outcomes, input_keys, output_keys)
39
40 self.userdata = smach.UserData()
41 """Userdata to be passed to child states."""
42
43
44 self._start_cbs = []
45 self._transition_cbs = []
46 self._termination_cbs = []
47
49 """Access child state by key.
50 @rtype: L{smach.State}
51 @returns: Child state with label equal to key
52 """
53 raise NotImplementedError()
54
56 """Get the children of this container.
57 This is empty for leaf states.
58
59 @rtype: dict of string: State
60 @return: The sub-states of this container.
61 """
62 raise NotImplementedError()
63
65 """Set initial active state of a container.
66
67 @type initial_states: list of string
68 @param initial_states: A description of the initial active state of this
69 container.
70
71 @type userdata: L{UserData}
72 @param userdata: Initial userdata for this container.
73 """
74 raise NotImplementedError()
75
77 """Get the initial state description.
78
79 @rtype: list of string
80 """
81 raise NotImplementedError()
82
84 """Get a description of the current state.
85 Note that this is specific to container implementation.
86
87 @rtype: list of string
88 """
89 raise NotImplementedError()
90
92 """Get the internal outcome edges of this container.
93 Get a list of 3-tuples (OUTCOME, LABEL_FROM, LABEL_TO) which coresspond
94 to transitions inside this container.
95
96 @rtype: list of 3-tuple
97 """
98 raise NotImplementedError()
99
101 """Check constistency of this container."""
102 raise NotImplementedError()
103
105 """Stores reference to parent userdata if share flage is set."""
106 return
107
108
117
119 if parent_ud is not None:
120 output_keys = self.get_registered_output_keys()
121 for ok in output_keys:
122 try:
123 parent_ud[ok] = ud[ok]
124 except KeyError:
125 smach.logwarn("Attempting to copy output key '%s', but this key does not exist.")
126
127
129 """Adds a start callback to this container.
130 Start callbacks receive arguments:
131 - userdata
132 - local_userdata
133 - initial_states
134 - *cb_args
135 """
136 self._start_cbs.append((start_cb,cb_args))
137
139 """Adds a transition callback to this container.
140 Transition callbacks receive arguments:
141 - userdata
142 - local_userdata
143 - active_states
144 - *cb_args
145 """
146 self._transition_cbs.append((transition_cb,cb_args))
147
149 """Adds a termination callback to this state machine.
150 Termination callbacks receive arguments:
151 - userdata
152 - local_userdata
153 - terminal_states
154 - container_outcome
155 - *cb_args
156 """
157 self._termination_cbs.append((termination_cb, cb_args))
158
160 """Calls the registered start callbacks.
161 Callback functions are called with two arguments in addition to any
162 user-supplied arguments:
163 - userdata
164 - a list of initial states
165 """
166 try:
167 for (cb,args) in self._start_cbs:
168 cb(self.userdata, self.get_initial_states(), *args)
169 except:
170 smach.logerr("Could not execute start callback: "+traceback.format_exc())
171
173 """Calls the registered transition callbacks.
174 Callback functions are called with two arguments in addition to any
175 user-supplied arguments:
176 - userdata
177 - a list of active states
178 """
179 try:
180 for (cb,args) in self._transition_cbs:
181 cb(self.userdata, self.get_active_states(), *args)
182 except:
183 smach.logerr("Could not execute transition callback: "+traceback.format_exc())
184
186 """Calls the registered termination callbacks.
187 Callback functions are called with three arguments in addition to any
188 user-supplied arguments:
189 - userdata
190 - a list of terminal states
191 - the outcome of this container
192 """
193 try:
194 for (cb,args) in self._termination_cbs:
195 cb(self.userdata, terminal_states, outcome, *args)
196 except:
197 smach.logerr("Could not execute termination callback: "+traceback.format_exc())
198
199
200
203
204 - def __exit__(self, exc_type, exc_val, exc_tb):
205 if exc_type is None:
206 return self.close()
207 else:
208 if exc_type != smach.InvalidStateError and exc_type != smach.InvalidTransitionError:
209 smach.logerr("Error raised during SMACH container construction: \n" + "\n".join(traceback.format_exception(exc_type, exc_val, exc_tb)))
210
211 @contextmanager
222
224 """Opens this container for modification.
225
226 This appends the container to the construction stack and locks the
227 reentrant lock if it is a valid container to open."""
228
229
230 Container._construction_stack.append(self)
231 Container._construction_lock.acquire()
232
249
255
259
260 @staticmethod
266
267 @classmethod
269 """Get the currently opened container.
270
271 This also asserts that the open container is of type cls.
272 """
273 if Container._any_containers_opened():
274 opened_container = Container._construction_stack[-1]
275 if not isinstance(opened_container, cls):
276 raise smach.InvalidStateError('Attempting to call a %s construction method without having opened a %s.' % (cls, cls))
277 return opened_container
278 else:
279 raise smach.InvalidStateError('Attempting to access the currently opened container, but no container is opened.')
280