00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019 import threading
00020 import datetime
00021 import traceback
00022
00023 from std_msgs.msg import Empty, Bool
00024
00025 import rospy
00026 import smach
00027
00028 __all__ = ['StateMachine']
00029
00030
00031
00032 def StrTimeStamped():
00033 return datetime.datetime.now().strftime("%Y_%m_%d_%H:%M:%S.%f - ")
00034
00035
00036 class ssmStateMachine(smach.StateMachine):
00037 """StateMachine
00038
00039 This is a finite state machine smach container. Note that though this is
00040 a state machine, it also implements the L{smach.State}
00041 interface, so these can be composed hierarchically, if such a pattern is
00042 desired.
00043
00044 States are added to the state machine as 3-tuple specifications:
00045 - label
00046 - state instance
00047 - transitions
00048
00049 The label is a string, the state instance is any class that implements the
00050 L{smach.State} interface, and transitions is a dictionary mapping strings onto
00051 strings which represent the transitions out of this new state. Transit_preempt_pauseions
00052 can take one of three forms:
00053 - OUTCOME -> STATE_LABEL
00054 - OUTCOME -> None (or unspecified)
00055 - OUTCOME -> SM_OUTCOME
00056 """
00057 def __init__(self, outcomes,input_keys=[], output_keys=[]):
00058
00059 input_keys.append("logfile")
00060 output_keys.append("logfile")
00061 smach.StateMachine.__init__(self,outcomes, input_keys, output_keys)
00062 self.register_outcomes(["preempt"])
00063 self._datamodel = {}
00064 self._onEntry = None
00065 self._onExit = None
00066 self._pause = False
00067 self._preempt_pause = False
00068 self._tree_view = None
00069 self._tree_view_cb = None
00070 self._server_name = rospy.get_param('ssm_server_name', '/ssm')
00071 self._pause_sub = rospy.Subscriber(self._server_name + '/pause',
00072 Bool,
00073 self._request_pause_cb, queue_size=1)
00074
00075 self._preempt_sub = rospy.Subscriber(self._server_name + '/preempt',
00076 Empty,
00077 self._request_preempt_cb, queue_size=1)
00078
00079 def _request_preempt_cb(self, msg):
00080 """Callback empty message for propagate preempt
00081 to currently active state.
00082 """
00083 if(self._is_running):
00084 self._preempt_pause = True
00085
00086
00087 def _request_pause_cb(self, msg):
00088 """Callback empty message for propagate preempt
00089 to currently active state.
00090 """
00091 if(self._is_running):
00092 self._pause = msg.data
00093
00094 def _update_once(self):
00095 """Method that updates the state machine once.
00096 This checks if the current state is ready to transition, if so, it
00097 requests the outcome of the current state, and then extracts the next state
00098 label from the current state's transition dictionary, and then transitions
00099 to the next state.
00100 """
00101 outcome = None
00102 transition_target = None
00103 last_state_label = self._current_label
00104
00105
00106 if self._current_label not in self._states:
00107 raise smach.InvalidStateError("State '%s' does not exist. Available states are: %s" %
00108 (self._current_label, list(self._states.keys())))
00109
00110
00111 if self.preempt_requested():
00112 smach.loginfo("Preempt requested on state machine before executing the next state.")
00113
00114 if self._preempted_state is not None:
00115
00116 if self._preempted_state.preempt_requested():
00117 smach.loginfo("Last state '%s' did not service preempt. Preempting next state '%s' before executing..." % (self._preempted_label, self._current_label))
00118
00119
00120 self._preempt_current_state()
00121 else:
00122
00123 self._preempt_requested = False
00124 self._preempted_state = None
00125 else:
00126
00127
00128 self._preempt_current_state()
00129
00130
00131 self._tree_view_enable_state(self._current_label)
00132 try:
00133 self._state_transitioning_lock.release()
00134 outcome = self._current_state.execute(
00135 smach.Remapper(
00136 self.userdata,
00137 self._current_state.get_registered_input_keys(),
00138 self._current_state.get_registered_output_keys(),
00139 self._remappings[self._current_label]))
00140 except smach.InvalidUserCodeError as ex:
00141 smach.logerr("State '%s' failed to execute." % self._current_label)
00142 raise ex
00143 except:
00144 raise smach.InvalidUserCodeError("Could not execute state '%s' of type '%s': " %
00145 (self._current_label, self._current_state)
00146 + traceback.format_exc())
00147 finally:
00148 self._state_transitioning_lock.acquire()
00149
00150 self._tree_view_disable_state(self._current_label)
00151
00152 if outcome not in self._current_state.get_registered_outcomes():
00153 raise smach.InvalidTransitionError(
00154 "Attempted to return outcome '%s' preempt_requestedfrom state '%s' of"
00155 " type '%s' which only has registered outcomes: %s" %
00156 (outcome,
00157 self._current_label,
00158 self._current_state,
00159 self._current_state.get_registered_outcomes()))
00160
00161
00162 if outcome not in self._current_transitions:
00163 raise smach.InvalidTransitionError("Outcome '%s' of state '%s' is not bound to any transition target. Bound transitions include: %s" %
00164 (str(outcome), str(self._current_label), str(self._current_transitions)))
00165
00166
00167 transition_target = self._current_transitions[outcome]
00168
00169
00170 if transition_target in self._states:
00171
00172 self._set_current_state(transition_target)
00173
00174
00175 smach.loginfo("State machine transitioning '%s':'%s'-->'%s'" %
00176 (last_state_label, outcome, transition_target))
00177
00178
00179 self.call_transition_cbs()
00180 else:
00181
00182
00183 if self._preempt_requested and self._preempted_state is not None:
00184 if not self._current_state.preempt_requested():
00185 self.service_preempt()
00186
00187 if transition_target not in self.get_registered_outcomes():
00188
00189 transition_target = outcome
00190
00191 if transition_target in self.get_registered_outcomes():
00192
00193 self._set_current_state(None)
00194
00195
00196 smach.loginfo("State machine terminating '%s':'%s':'%s'" %
00197 (last_state_label, outcome, transition_target))
00198
00199
00200 self.call_termination_cbs([last_state_label],transition_target)
00201
00202 return transition_target
00203 else:
00204 raise smach.InvalidTransitionError("Outcome '%s' of state '%s' with transition target '%s' is neither a registered state nor a registered container outcome." %
00205 (outcome, self._current_label, transition_target))
00206 return None
00207
00208 def execute(self, parent_ud = smach.UserData()):
00209 """Run the state machine on entry to this state.
00210 This will set the "closed" flag and spin up the execute thread. Once
00211 this flag has been set, it will prevent more states from being added to
00212 the state machine.
00213 """
00214
00215
00216
00217 with self._state_transitioning_lock:
00218
00219 try:
00220 self.check_consistency()
00221 except (smach.InvalidStateError, smach.InvalidTransitionError):
00222 smach.logerr("Container consistency check failed.")
00223 return None
00224
00225
00226 self._is_running = True
00227
00228
00229 self._preempted_label = None
00230 self._preempted_state = None
00231
00232
00233 self._set_current_state(self._initial_state_label)
00234
00235
00236 self._copy_input_keys(parent_ud, self.userdata)
00237
00238
00239 smach.loginfo("State machine starting in initial state '%s' with userdata: \n\t%s" %
00240 (self._current_label, list(self.userdata.keys())))
00241
00242
00243
00244 self.call_start_cbs()
00245
00246
00247 container_outcome = None
00248
00249
00250 for data in self._datamodel:
00251 if(self._datamodel[data] != ""):
00252 self.userdata[data] = self._datamodel[data]
00253
00254
00255 if(self._onEntry is not None):
00256 try:
00257 self._onEntry.execute(self.userdata)
00258 except Exception as ex:
00259 rospy.logerr('%s::onEntry::execute() raised | %s'
00260 %(self.__class__.__name__,str(ex)))
00261 return "preempt"
00262
00263
00264 while container_outcome is None and self._is_running and not smach.is_shutdown():
00265
00266 if(self._pause):
00267 self._tree_view_pause_state(self._current_label)
00268 cpt = 30
00269 while(self._pause):
00270 if(cpt >= 30):
00271 cpt = 0
00272 rospy.logwarn("[SSM] : Smart State Machine is paused")
00273 rospy.logwarn("[SSM] : Next state executed is : " +self._current_label)
00274 cpt = cpt+1
00275 rospy.sleep(0.1)
00276 if(self._preempt_pause):
00277 self._tree_view_disable_state(self._current_label)
00278 return "preempt"
00279
00280 container_outcome = self._update_once()
00281
00282
00283
00284 if(self._onExit is not None):
00285 try:
00286 container_outcome = self._onExit.execute(self.userdata,container_outcome)
00287 except Exception as ex:
00288 rospy.logerr('%s::onExit::execute() raised | %s'
00289 %(self.__class__.__name__,str(ex)))
00290 return "preempt"
00291
00292
00293 self._copy_output_keys(self.userdata, parent_ud)
00294
00295
00296 self._is_running = False
00297
00298 return container_outcome
00299
00300
00301 def _create_tree_view(self):
00302 self._tree_view = {}
00303 for child in self.get_children():
00304 self._tree_view[child] = 0
00305
00306 def _tree_view_enable_state(self, label):
00307 if(self._tree_view is not None):
00308 self._tree_view[label] = 1
00309 self.call_update_tree_view_cb()
00310
00311 def _tree_view_disable_state(self, label):
00312 if(self._tree_view is not None):
00313 self._tree_view[label] = 0
00314 self.call_update_tree_view_cb()
00315
00316 def _tree_view_pause_state(self, label):
00317 if(self._tree_view is not None):
00318 self._tree_view[label] = -1
00319 self.call_update_tree_view_cb()
00320
00321 def get_tree_view(self):
00322 return self._tree_view
00323
00324 def register_tree_view_cb(self, callback):
00325 self._tree_view_cb = callback
00326
00327 def call_update_tree_view_cb(self):
00328 if(self._tree_view_cb is not None):
00329 try:
00330 self._tree_view_cb()
00331 except:
00332 smach.logerr("Could not execute treeview callback: "+traceback.format_exc())
00333
00334 class ssmMainStateMachine(ssmStateMachine):
00335 """StateMachine
00336
00337 This is a finite state machine smach container. Note that though this is
00338 a state machine, it also implements the L{smach.State}
00339 interface, so these can be composed hierarchically, if such a pattern is
00340 desired.
00341
00342 States are added to the state machine as 3-tuple specifications:
00343 - label
00344 - state instance
00345 - transitions
00346
00347 The label is a string, the state instance is any class that implements the
00348 L{smach.State} interface, and transitions is a dictionary mapping strings onto
00349 strings which represent the transitions out of this new state. Transitions
00350 can take one of three forms:
00351 - OUTCOME -> STATE_LABEL
00352 - OUTCOME -> None (or unspecified)
00353 - OUTCOME -> SM_OUTCOME
00354 """
00355
00356 def __init__(self, outcomes):
00357 ssmStateMachine.__init__(self,outcomes)
00358 self._input_keys=[]
00359 self._output_keys=[]
00360
00361 def _request_preempt_cb(self, msg):
00362 """Callback empty message for propagate preempt
00363 to currently active state.
00364 """
00365 if(self._pause):
00366 self._preempt_pause = True
00367 else:
00368 self.request_preempt()
00369
00370 def execute(self, parent_ud = smach.UserData()):
00371 """Run the state machine on entry to this state.
00372 This will set the "closed" flag and spin up the execute thread. Once
00373 this flag has been set, it will prevent more states from being added to
00374 the state machine.
00375 """
00376
00377 if(rospy.get_param("ssm_enable_log", False) == True):
00378 date_str = datetime.datetime.now().strftime("%Y_%m_%d_%H_%M_")
00379 self.userdata.logfile = rospy.get_param("ssm_log_path", default="/tmp/") + date_str +"log.txt"
00380 try:
00381 file = open(self.userdata.logfile, "a")
00382 file.write(StrTimeStamped() +"SMART STATE MACHINE Started \n")
00383 file.close()
00384 except:
00385 rospy.logerr("Log Fail !")
00386 return 'preempt'
00387
00388 container_outcome = super(ssmMainStateMachine,self).execute(parent_ud)
00389
00390 if(rospy.get_param("ssm_enable_log", False) == True):
00391 file = open(self.userdata.logfile, "a")
00392 file.write(StrTimeStamped() +"SMART STATE MACHINE Finished \n")
00393 file.close()
00394
00395 return container_outcome