jumpable_state_machine.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 import rospy
00003 import smach
00004 import time
00005 
00006 from flexbe_msgs.msg import OutcomeRequest
00007 from ..proxy import ProxySubscriberCached
00008 
00009 from .silent_state_machine import SilentStateMachine
00010 
00011 
00012 class JumpableStateMachine(SilentStateMachine):
00013     """
00014     A state machine that runs in background and does not report any transition.
00015     It can jump to any of its states, executing all states on a random way to the target
00016     (so make sure its states have no side effects).
00017     """
00018     refresh = False
00019     
00020     def __init__(self, *args, **kwargs):
00021         super(JumpableStateMachine, self).__init__(*args, **kwargs)
00022         
00023         self._sub = ProxySubscriberCached()
00024     
00025         
00026     def jump_to(self, target_state_label):
00027         """
00028         Jumps to the specified state.
00029         
00030         @type target_state_label: string
00031         @param target_state_label: The name of the state to jump to.
00032             Should be unique. If not, the first occurrence will be chosen.
00033         """
00034         # set current state and current container
00035         container = self
00036         while isinstance(container._current_state, smach.StateMachine):
00037             container = container._current_state
00038         current_state = container._current_state
00039         
00040         # determine path
00041         path = self._search_state(current_state, target_state_label, container, [], '')
00042         if path is None:
00043             rospy.logerr('No path found from '+current_state.name+' to '+target_state_label)
00044             return
00045         
00046         rospy.loginfo('Path: '+path)
00047         if path == '': 
00048             JumpableStateMachine.refresh = True
00049             return
00050         
00051         # traverse path
00052         outcomes = path.split('/')
00053         for i in range(len(outcomes)-1):
00054             current_state = self._get_deep_state()
00055             current_state._requested_outcome = outcomes[i]
00056             if not self._wait_for_next_state(current_state, 1):
00057                 rospy.logerr('Unable to jump to '+target_state_label+': Stuck in state '+current_state.name)
00058                 return
00059         
00060         
00061     def _search_state(self, origin_state, target_state_label, container, seen_states, path):
00062         """
00063         Searches the given state to create an outcome path to it.
00064         This is implemented by a hierarchical depth-first search.
00065         
00066         @type origin_state: State
00067         @param origin_state: The current origin for this search.
00068         
00069         @type target_state_label: string
00070         @param target_state_label: The name of the state to search.
00071         
00072         @type container: StateMachine
00073         @param container: The current container inside which the search is currently done.
00074         
00075         @type seen_states: list of State
00076         @param seen_states: A list of all states that already have been traversed.
00077         
00078         @type path: string
00079         @param path: The outcome path to the current origin_state
00080         
00081         @return: The whole outcome path to the searched state.
00082         """
00083         # saw this state
00084         seen_states.append(origin_state)
00085 
00086         # look if we found our target
00087         if origin_state.name == target_state_label:
00088             return path
00089         target_path = None
00090         
00091         # enter the state machine (next lower hierarchy level)
00092         if isinstance(origin_state, smach.StateMachine):
00093             # do not need to enter, inital state is implicitly active
00094             if origin_state._initial_state_label == target_state_label:
00095                 return path
00096             # search inside this state machine
00097             child_state = origin_state._states[origin_state._initial_state_label]
00098             if seen_states.count(child_state) == 0:
00099                 target_path = self._search_state(child_state, target_state_label, origin_state, seen_states, path)
00100             # found target inside, search finished
00101             if target_path is not None:
00102                 return target_path
00103         
00104         # proceed with the neighbors
00105         else:   
00106             for outcome in origin_state._outcomes:
00107                 next_state_label = origin_state.transitions[outcome]
00108                 if next_state_label == 'preempted' or next_state_label == 'preempted_mirror': continue  # skip preempt outcomes
00109                 next_container = container
00110                 reached_exit = False
00111                 
00112                 # determine the next state
00113                 # (consider that it might be necessary to go to the next higher hierarchy level)
00114                 while not next_container._states.has_key(next_state_label): 
00115                     if next_container._parent is None:
00116                         reached_exit = True
00117                         break
00118                     next_state_label = next_container.transitions[next_state_label]
00119                     next_container = next_container._parent
00120                 # reached one outcome of the whole state machine (no higher hierarchy level)
00121                 if reached_exit: break
00122                 
00123                 # search the neighbor if not already seen
00124                 next_state = next_container._states[next_state_label]
00125                 if seen_states.count(next_state) == 0:
00126                     target_path = self._search_state(next_state, target_state_label, next_container, seen_states, path+outcome+'/')
00127                 # found target in neighbors
00128                 if target_path is not None:
00129                     return target_path
00130                 
00131         # unfortunately, no success
00132         return None
00133             
00134             
00135     def _wait_for_next_state(self, current_state, timeout, period=0.05):
00136         """
00137         Sleeps until the state machine proceeded with the next state.
00138         
00139         @type current_state: State
00140         @param current_state: The current state when this method is called.
00141         
00142         @type timeout: float
00143         @param timeout: The time in seconds when this method should stop waiting when no transition is done.
00144         
00145         @type period: float
00146         @param period: The time in seconds how often this method should check the current state.
00147         
00148         @return: True if waiting was successful, False if current_state is still the same (timeout).
00149         """
00150         end_time = time.time() + timeout
00151         while time.time() < end_time:
00152             new_state = self._get_deep_state()
00153             if new_state != current_state: 
00154                 return True
00155             time.sleep(period)
00156         return False
00157 
00158 
00159     def _transition_callback(self, msg):
00160         """
00161         Forces the state machine to do a transition with a specified outcome.
00162         
00163         @type msg: OutcomeRequest
00164         @param msg: A message containing all relevant information.
00165         """
00166         state = self._get_deep_state()
00167         
00168         # wait if state machine is currently not active
00169         if state is None:
00170             if not self._wait_for_next_state(None, 5):
00171                 rospy.logwarn('Updating mirror while it is not running!')
00172                 return # not running
00173             state = self._get_deep_state()
00174             rospy.logwarn('Had to wait for mirror starting to request transition of state %s!', msg.target)
00175             
00176         # wait for the target state to not trigger a wrong transition
00177         if msg.target != state.name:
00178             self._wait_for_next_state(state, 2)
00179             state = self._get_deep_state()
00180             if msg.target != state.name: # waited for next state, so test current state again (but only once to not block anything)
00181                 rospy.logwarn('Wrong state name: requested %s but is %s!', msg.target, state.name)
00182                 return
00183             rospy.logwarn('Had to wait for state %s to request transition!', msg.target)
00184             
00185         # trigger desired outcome or preempt
00186         if msg.outcome == 255:
00187             state._requested_outcome = 'preempted'
00188         else:
00189             state._requested_outcome = state._outcome_list[msg.outcome]
00190         
00191     def destroy(self):
00192         pass


flexbe_core
Author(s): Philipp Schillinger
autogenerated on Thu Jun 6 2019 19:32:27