ssm_state_machine.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # Copyright 2015 Airbus
00004 # Copyright 2017 Fraunhofer Institute for Manufacturing Engineering and Automation (IPA)
00005 #
00006 # Licensed under the Apache License, Version 2.0 (the "License");
00007 # you may not use this file except in compliance with the License.
00008 # You may obtain a copy of the License at
00009 #
00010 #   http://www.apache.org/licenses/LICENSE-2.0
00011 #
00012 # Unless required by applicable law or agreed to in writing, software
00013 # distributed under the License is distributed on an "AS IS" BASIS,
00014 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00015 # See the License for the specific language governing permissions and
00016 # limitations under the License.
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 ### State Machine class
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         # Make sure the state exists
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         # Check if a preempt was requested before or while the last state was running
00111         if self.preempt_requested():
00112             smach.loginfo("Preempt requested on state machine before executing the next state.")
00113             # We were preempted
00114             if self._preempted_state is not None:
00115                 # We were preempted while the last state was running
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                     # The flag was not reset, so we need to keep preempting 
00119                     # (this will reset the current preempt)
00120                     self._preempt_current_state()
00121                 else:
00122                     # The flag was reset, so the container can reset
00123                     self._preempt_requested = False
00124                     self._preempted_state = None
00125             else:
00126                 # We were preempted after the last state was running
00127                 # So we sho                 if(self._preempt_requested):uld preempt this state before we execute it
00128                 self._preempt_current_state()
00129 
00130         # Execute the state
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         # Check if outcome was a potential outcome for this type of state
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         # Check if this outcome is actually mapped to any target
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         # Set the transition target
00167         transition_target = self._current_transitions[outcome]
00168 
00169         # Check if the transition target is a state in this state machine, or an outcome of this state machine
00170         if transition_target in self._states:
00171             # Set the new state 
00172             self._set_current_state(transition_target)
00173 
00174             # Spew some info
00175             smach.loginfo("State machine transitioning '%s':'%s'-->'%s'" %
00176                           (last_state_label, outcome, transition_target))
00177 
00178             # Call transition callbacks
00179             self.call_transition_cbs()
00180         else:
00181             # This is a terminal state
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                 # This is a container outcome that will fall through
00189                 transition_target = outcome
00190 
00191             if transition_target in self.get_registered_outcomes():
00192                 # The transition target is an outcome of the state machine
00193                 self._set_current_state(None)
00194 
00195                 # Spew some info
00196                 smach.loginfo("State machine terminating '%s':'%s':'%s'" %
00197                               (last_state_label, outcome, transition_target))
00198 
00199                 # Call termination callbacks
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         # This will prevent preempts from getting propagated to non-existent children
00216 
00217         with self._state_transitioning_lock:
00218             # Check state consistency
00219             try:
00220                 self.check_consistency()
00221             except (smach.InvalidStateError, smach.InvalidTransitionError):
00222                 smach.logerr("Container consistency check failed.")
00223                 return None
00224 
00225             # Set running flag
00226             self._is_running = True
00227 
00228             # Initialize preempt state
00229             self._preempted_label = None
00230             self._preempted_state = None
00231 
00232             # Set initial state 
00233             self._set_current_state(self._initial_state_label)
00234 
00235             # Copy input keys
00236             self._copy_input_keys(parent_ud, self.userdata)
00237 
00238             # Spew some info
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             # Call start callbacks
00244             self.call_start_cbs()
00245 
00246             # Initialize container outcome
00247             container_outcome = None
00248             
00249             ## Copy the datamodel's value into the userData
00250             for data in self._datamodel:
00251                 if(self._datamodel[data] != ""):
00252                     self.userdata[data] = self._datamodel[data]
00253             
00254             ## Do the <onentry>
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             # Step through state machine
00264             while container_outcome is None and self._is_running and not smach.is_shutdown(): 
00265                 # Update the state machine
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             ## Do the <onexit>    
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             # Copy output keys
00293             self._copy_output_keys(self.userdata, parent_ud)    
00294 
00295             # We're no longer running
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         ##Create a the log file if ssm_enable_log is True
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        


airbus_ssm_core
Author(s): Ludovic Delval
autogenerated on Thu Jun 6 2019 17:59:28