Package smach :: Module container

Source Code for Module smach.container

  1   
  2  import traceback 
  3  import threading 
  4  from contextlib import contextmanager 
  5   
  6  import smach 
  7   
  8  __all__ = ['Container'] 
9 10 -class Container(smach.state.State):
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 ### Class members 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 # Callback lists 42 self._start_cbs = [] 43 self._transition_cbs = [] 44 self._termination_cbs = []
45
46 - def __getitem__(self, key):
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
53 - def get_children(self):
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
62 - def set_initial_state(self, initial_states, userdata):
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
74 - def get_initial_states(self):
75 """Get the initial states description. 76 77 @rtype: list of string 78 """ 79 raise NotImplementedError()
80
81 - def get_active_states(self):
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
89 - def get_internal_edges(self):
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
98 - def check_consistency(self):
99 """Check consistency of this container.""" 100 raise NotImplementedError()
101 102 ### Automatic Data passing
103 - def _copy_input_keys(self, parent_ud, ud):
104 if parent_ud is not None: 105 input_keys = self.get_registered_input_keys() 106 for ik in input_keys: 107 try: 108 ud[ik] = parent_ud[ik] 109 except KeyError: 110 smach.logwarn("Attempting to copy input key '%s', but this key does not exist." % ik)
111
112 - def _copy_output_keys(self, ud, parent_ud):
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 ### Callback registreation methods
122 - def register_start_cb(self, start_cb, cb_args=[]):
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
132 - def register_transition_cb(self, transition_cb, cb_args=[]):
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
142 - def register_termination_cb(self, termination_cb, cb_args=[]):
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
153 - def call_start_cbs(self):
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
166 - def call_transition_cbs(self):
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
179 - def call_termination_cbs(self, terminal_states, outcome):
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 # Context manager methods
195 - def __enter__(self):
196 return self.open()
197
198 - def __exit__(self, exc_type, exc_val, exc_tb):
199 if exc_type is None: 200 return self.close() 201 else: 202 if exc_type != smach.InvalidStateError and exc_type != smach.InvalidTransitionError: 203 smach.logerr("Error raised during SMACH container construction: \n" + "\n".join(traceback.format_exception(exc_type, exc_val, exc_tb)))
204 205 @contextmanager
206 - def opened(self, **kwargs):
207 """Context manager method for opening a smach container.""" 208 self.open() 209 prev_kwargs = Container._context_kwargs 210 Container._context_kwargs = kwargs 211 try: 212 yield self 213 finally: 214 Container._context_kwargs = prev_kwargs 215 self.close()
216
217 - def open(self):
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 # Push this container onto the construction stack 224 Container._construction_stack.append(self) 225 Container._construction_lock.acquire()
226
227 - def close(self):
228 """Close the container.""" 229 # Make sure this container is the currently open container 230 if len(Container._construction_stack) > 0: 231 if self != Container._construction_stack[-1]: 232 raise smach.InvalidStateError('Attempting to close a container that is not currently open.') 233 234 # Pop this container off the construction stack 235 Container._construction_stack.pop() 236 Container._construction_lock.release() 237 238 # Check consistency of container, post-construction 239 try: 240 self.check_consistency() 241 except (smach.InvalidStateError, smach.InvalidTransitionError): 242 smach.logerr("Container consistency check failed.")
243
244 - def is_opened(self):
245 """Returns True if this container is currently opened for construction. 246 @rtype: bool 247 """ 248 return len(Container._construction_stack) > 0 and self == Container._construction_stack[-1]
249
250 - def assert_opened(self,msg=''):
251 if not self.is_opened(): 252 raise smach.InvalidConstructionError(msg)
253 254 @staticmethod
256 """Returns True if any containers are opened.""" 257 if len(Container._construction_stack) > 0: 258 return True 259 return False
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