Go to the documentation of this file.00001
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
00035 container = self
00036 while isinstance(container._current_state, smach.StateMachine):
00037 container = container._current_state
00038 current_state = container._current_state
00039
00040
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
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
00084 seen_states.append(origin_state)
00085
00086
00087 if origin_state.name == target_state_label:
00088 return path
00089 target_path = None
00090
00091
00092 if isinstance(origin_state, smach.StateMachine):
00093
00094 if origin_state._initial_state_label == target_state_label:
00095 return path
00096
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
00101 if target_path is not None:
00102 return target_path
00103
00104
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
00109 next_container = container
00110 reached_exit = False
00111
00112
00113
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
00121 if reached_exit: break
00122
00123
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
00128 if target_path is not None:
00129 return target_path
00130
00131
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
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
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
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:
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
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