Package asmach :: Module container

Source Code for Module asmach.container

  1   
  2  import traceback 
  3  import threading 
  4  from contextlib import contextmanager 
  5   
  6  import asmach as 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 """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 # Callback lists 44 self._start_cbs = [] 45 self._transition_cbs = [] 46 self._termination_cbs = []
47
48 - def __getitem__(self, key):
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
55 - def get_children(self):
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
64 - def set_initial_state(self, initial_states, userdata):
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
76 - def get_initial_states(self):
77 """Get the initial state description. 78 79 @rtype: list of string 80 """ 81 raise NotImplementedError()
82
83 - def get_active_states(self):
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
91 - def get_internal_edges(self):
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
100 - def check_consistency(self):
101 """Check constistency of this container.""" 102 raise NotImplementedError()
103
104 - def set_userdata(self, userdata):
105 """Stores reference to parent userdata if share flage is set.""" 106 return
107 108 ### Automatic Data passing
109 - def _copy_input_keys(self, parent_ud, ud):
110 if parent_ud is not None: 111 input_keys = self.get_registered_input_keys() 112 for ik in input_keys: 113 try: 114 ud[ik] = parent_ud[ik] 115 except KeyError: 116 smach.logwarn("Attempting to copy input key '%s', but this key does not exist.")
117
118 - def _copy_output_keys(self, ud, parent_ud):
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 ### Callback registreation methods
128 - def register_start_cb(self, start_cb, cb_args=[]):
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
138 - def register_transition_cb(self, transition_cb, cb_args=[]):
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
148 - def register_termination_cb(self, termination_cb, cb_args=[]):
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
159 - def call_start_cbs(self):
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
172 - def call_transition_cbs(self):
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
185 - def call_termination_cbs(self, terminal_states, outcome):
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 # Context manager methods
201 - def __enter__(self):
202 return self.open()
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
212 - def opened(self, **kwargs):
213 """Context manager method for opening a smach container.""" 214 self.open() 215 prev_kwargs = Container._context_kwargs 216 Container._context_kwargs = kwargs 217 try: 218 yield self 219 finally: 220 Container._context_kwargs = prev_kwargs 221 self.close()
222
223 - def open(self):
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 # Push this container onto the construction stack 230 Container._construction_stack.append(self) 231 Container._construction_lock.acquire()
232
233 - def close(self):
234 """Close the container.""" 235 # Make sure this container is the currently open container 236 if len(Container._construction_stack) > 0: 237 if self != Container._construction_stack[-1]: 238 raise smach.InvalidStateError('Attempting to close a container that is not currently open.') 239 240 # Pop this container off the construction stack 241 Container._construction_stack.pop() 242 Container._construction_lock.release() 243 244 # Check consistency of container, post-construction 245 try: 246 self.check_consistency() 247 except (smach.InvalidStateError, smach.InvalidTransitionError): 248 smach.logerr("Container consistency check failed.")
249
250 - def is_opened(self):
251 """Returns True if this container is currently opened for construction. 252 @rtype: bool 253 """ 254 return len(Container._construction_stack) > 0 and self == Container._construction_stack[-1]
255
256 - def assert_opened(self,msg=''):
257 if not self.is_opened(): 258 raise smach.InvalidConstructionError(msg)
259 260 @staticmethod
262 """Returns True if any containers are opened.""" 263 if len(Container._construction_stack) > 0: 264 return True 265 return False
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