flexbe_onboard.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import roslib; roslib.load_manifest('flexbe_onboard')
4 import rospy
5 import rospkg
6 import os
7 import sys
8 import inspect
9 import threading
10 import time
11 import smach
12 import random
13 import yaml
14 import zlib
15 import xml.etree.ElementTree as ET
16 from ast import literal_eval as cast
17 
18 from flexbe_core import Logger, BehaviorLibrary
19 
20 from flexbe_msgs.msg import BehaviorSelection, BEStatus, ContainerStructure, CommandFeedback
21 from flexbe_core.proxy import ProxyPublisher, ProxySubscriberCached
22 
23 from std_msgs.msg import Int32, Empty
24 
25 
26 '''
27 Created on 20.05.2013
28 
29 @author: Philipp Schillinger
30 '''
31 
32 class VigirBeOnboard(object):
33  '''
34  Implements an idle state where the robot waits for a behavior to be started.
35  '''
36 
37  def __init__(self):
38  '''
39  Constructor
40  '''
41  self.be = None
42  Logger.initialize()
43  smach.set_loggers (
44  rospy.logdebug, # hide SMACH transition log spamming
45  rospy.logwarn,
46  rospy.logdebug,
47  rospy.logerr
48  )
49 
50  #ProxyPublisher._simulate_delay = True
51  #ProxySubscriberCached._simulate_delay = True
52 
53  # prepare temp folder
54  rp = rospkg.RosPack()
55  self._tmp_folder = "/tmp/flexbe_onboard"
56  if not os.path.exists(self._tmp_folder):
57  os.makedirs(self._tmp_folder)
58  sys.path.append(self._tmp_folder)
59 
60  # prepare manifest folder access
61  self._behavior_lib = BehaviorLibrary()
62 
63  self._pub = ProxyPublisher()
64  self._sub = ProxySubscriberCached()
65 
66  self.status_topic = 'flexbe/status'
67  self.feedback_topic = 'flexbe/command_feedback'
68 
69  self._pub.createPublisher(self.status_topic, BEStatus, _latch = True)
70  self._pub.createPublisher(self.feedback_topic, CommandFeedback)
71 
72  # listen for new behavior to start
73  self._running = False
74  self._switching = False
75  self._sub.subscribe('flexbe/start_behavior', BehaviorSelection, self._behavior_callback)
76 
77  # heartbeat
78  self._pub.createPublisher('flexbe/heartbeat', Empty)
79  self._execute_heartbeat()
80 
81  rospy.sleep(0.5) # wait for publishers etc to really be set up
82  self._pub.publish(self.status_topic, BEStatus(code=BEStatus.READY))
83  rospy.loginfo('\033[92m--- Behavior Engine ready! ---\033[0m')
84 
85 
86  def _behavior_callback(self, msg):
87  thread = threading.Thread(target=self._behavior_execution, args=[msg])
88  thread.daemon = True
89  thread.start()
90 
91  def _behavior_execution(self, msg):
92  if self._running:
93  Logger.loginfo('--> Initiating behavior switch...')
94  self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['received']))
95  else:
96  Logger.loginfo('--> Starting new behavior...')
97 
98  be = self._prepare_behavior(msg)
99  if be is None:
100  Logger.logerr('Dropped behavior start request because preparation failed.')
101  if self._running:
102  self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['failed']))
103  else:
104  rospy.loginfo('\033[92m--- Behavior Engine ready! ---\033[0m')
105  return
106 
107  if self._running:
108  if self._switching:
109  Logger.logwarn('Already switching, dropped new start request.')
110  return
111  self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['start']))
112  if not self._is_switchable(be):
113  Logger.logerr('Dropped behavior start request because switching is not possible.')
114  self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['not_switchable']))
115  return
116  self._switching = True
117  active_state = self.be.get_current_state()
118  rospy.loginfo("Current state %s is kept active.", active_state.name)
119  try:
120  be.prepare_for_switch(active_state)
121  self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['prepared']))
122  except Exception as e:
123  Logger.logerr('Failed to prepare behavior switch:\n%s' % str(e))
124  self._switching = False
125  self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['failed']))
126  return
127  rospy.loginfo('Preempting current behavior version...')
128  self.be.preempt_for_switch()
129  rate = rospy.Rate(10)
130  while self._running:
131  rate.sleep()
132  self._switching = False
133 
134  self._running = True
135  self.be = be
136 
137  result = ""
138  try:
139  rospy.loginfo('Behavior ready, execution starts now.')
140  rospy.loginfo('[%s : %s]', be.name, msg.behavior_checksum)
141  self.be.confirm()
142  args = [self.be.requested_state_path] if self.be.requested_state_path is not None else []
143  self._pub.publish(self.status_topic, BEStatus(behavior_id=self.be.id, code=BEStatus.STARTED, args=args))
144  result = self.be.execute()
145  if self._switching:
146  self._pub.publish(self.status_topic, BEStatus(behavior_id=self.be.id, code=BEStatus.SWITCHING))
147  else:
148  self._pub.publish(self.status_topic, BEStatus(behavior_id=self.be.id, code=BEStatus.FINISHED, args=[str(result)]))
149  except Exception as e:
150  self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.FAILED))
151  Logger.logerr('Behavior execution failed!\n%s' % str(e))
152  import traceback
153  Logger.loginfo(traceback.format_exc())
154  result = "failed"
155 
156  try:
157  self._cleanup_behavior(msg.behavior_checksum)
158  except Exception as e:
159  rospy.logerr('Failed to clean up behavior:\n%s' % str(e))
160 
161  self.be = None
162  if not self._switching:
163  rospy.loginfo('Behavior execution finished with result %s.', str(result))
164  rospy.loginfo('\033[92m--- Behavior Engine ready! ---\033[0m')
165  self._running = False
166 
167 
168  def _prepare_behavior(self, msg):
169  # get sourcecode from ros package
170  try:
171  rp = rospkg.RosPack()
172  behavior = self._behavior_lib.get_behavior(msg.behavior_id)
173  if behavior is None:
174  raise ValueError(msg.behavior_id)
175  be_filepath = os.path.join(rp.get_path(behavior["package"]), 'src/' + behavior["package"] + '/' + behavior["file"] + '_tmp.py')
176  if os.path.isfile(be_filepath):
177  be_file = open(be_filepath, "r")
178  rospy.logwarn("Found a tmp version of the referred behavior! Assuming local test run.")
179  else:
180  be_filepath = os.path.join(rp.get_path(behavior["package"]), 'src/' + behavior["package"] + '/' + behavior["file"] + '.py')
181  be_file = open(be_filepath, "r")
182  be_content = be_file.read()
183  be_file.close()
184  except Exception as e:
185  Logger.logerr('Failed to retrieve behavior from library:\n%s' % str(e))
186  self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
187  return
188 
189  # apply modifications if any
190  try:
191  file_content = ""
192  last_index = 0
193  for mod in msg.modifications:
194  file_content += be_content[last_index:mod.index_begin] + mod.new_content
195  last_index = mod.index_end
196  file_content += be_content[last_index:]
197  if zlib.adler32(file_content) != msg.behavior_checksum:
198  mismatch_msg = ("Checksum mismatch of behavior versions! \n"
199  "Attempted to load behavior: %s\n"
200  "Make sure that all computers are on the same version a.\n"
201  "Also try: rosrun flexbe_widget clear_cache" % str(be_filepath))
202  raise Exception(mismatch_msg)
203  else:
204  rospy.loginfo("Successfully applied %d modifications." % len(msg.modifications))
205  except Exception as e:
206  Logger.logerr('Failed to apply behavior modifications:\n%s' % str(e))
207  self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
208  return
209 
210  # create temp file for behavior class
211  try:
212  file_path = os.path.join(self._tmp_folder, 'tmp_%d.py' % msg.behavior_checksum)
213  sc_file = open(file_path, "w")
214  sc_file.write(file_content)
215  sc_file.close()
216  except Exception as e:
217  Logger.logerr('Failed to create temporary file for behavior class:\n%s' % str(e))
218  self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
219  return
220 
221  # import temp class file and initialize behavior
222  try:
223  package = __import__("tmp_%d" % msg.behavior_checksum, fromlist=["tmp_%d" % msg.behavior_checksum])
224  clsmembers = inspect.getmembers(package, lambda member: inspect.isclass(member) and member.__module__ == package.__name__)
225  beclass = clsmembers[0][1]
226  be = beclass()
227  rospy.loginfo('Behavior ' + be.name + ' created.')
228  except Exception as e:
229  Logger.logerr('Exception caught in behavior definition:\n%s' % str(e))
230  self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
231  return
232 
233  # import contained behaviors
234  contain_list = {}
235  try:
236  contain_list = self._build_contains(be, "")
237  except Exception as e:
238  Logger.logerr('Failed to load contained behaviors:\n%s' % str(e))
239  return
240 
241  # initialize behavior parameters
242  if len(msg.arg_keys) > 0:
243  rospy.loginfo('The following parameters will be used:')
244  try:
245  for i in range(len(msg.arg_keys)):
246  if msg.arg_keys[i] == '':
247  # action call has empty string as default, not a valid param key
248  continue
249  key_splitted = msg.arg_keys[i].rsplit('/', 1)
250  if len(key_splitted) == 1:
251  behavior = ''
252  key = key_splitted[0]
253  rospy.logwarn('Parameter key %s has no path specification, assuming: /%s' % (key, key))
254  else:
255  behavior = key_splitted[0]
256  key = key_splitted[1]
257  found = False
258 
259  if behavior == '' and hasattr(be, key):
260  self._set_typed_attribute(be, key, msg.arg_values[i])
261  # propagate to contained behaviors
262  for b in contain_list:
263  if hasattr(contain_list[b], key):
264  self._set_typed_attribute(contain_list[b], key, msg.arg_values[i], b)
265  found = True
266 
267  for b in contain_list:
268  if b == behavior and hasattr(contain_list[b], key):
269  self._set_typed_attribute(contain_list[b], key, msg.arg_values[i], b)
270  found = True
271 
272  if not found:
273  rospy.logwarn('Parameter ' + msg.arg_keys[i] + ' (set to ' + msg.arg_values[i] + ') not implemented')
274 
275  except Exception as e:
276  Logger.logerr('Failed to initialize parameters:\n%s' % str(e))
277  self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
278  return
279 
280  # build state machine
281  try:
282  be.set_up(id=msg.behavior_checksum, autonomy_level=msg.autonomy_level, debug=False)
283  be.prepare_for_execution(self._convert_input_data(msg.input_keys, msg.input_values))
284  rospy.loginfo('State machine built.')
285  except Exception as e:
286  Logger.logerr('Behavior construction failed!\n%s' % str(e))
287  self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
288  return
289 
290  return be
291 
292 
293  def _is_switchable(self, be):
294  if self.be.name != be.name:
295  Logger.logerr('Unable to switch behavior, names do not match:\ncurrent: %s <--> new: %s' % (self.be.name, be.name))
296  return False
297  # locked inside
298  # locked state exists in new behavior
299  #if self.be.id == be.id:
300  #Logger.logwarn('Behavior version ID is the same.')
301  # Logger.logwarn('Skipping behavior switch, version ID is the same.')
302  # return False
303  # ok, can switch
304  return True
305 
306 
307  def _cleanup_behavior(self, behavior_checksum):
308  del(sys.modules["tmp_%d" % behavior_checksum])
309  file_path = os.path.join(self._tmp_folder, 'tmp_%d.pyc' % behavior_checksum)
310  try:
311  os.remove(file_path)
312  except OSError:
313  pass
314  try:
315  os.remove(file_path + 'c')
316  except OSError:
317  pass
318 
319 
320  def _set_typed_attribute(self, obj, name, value, behavior=''):
321  attr = getattr(obj, name)
322  if type(attr) is int:
323  value = int(value)
324  elif type(attr) is long:
325  value = long(value)
326  elif type(attr) is float:
327  value = float(value)
328  elif type(attr) is bool:
329  value = (value != "0")
330  elif type(attr) is dict:
331  value = yaml.load(value)
332  setattr(obj, name, value)
333  suffix = ' (' + behavior + ')' if behavior != '' else ''
334  rospy.loginfo(name + ' = ' + str(value) + suffix)
335 
336 
337  def _convert_input_data(self, keys, values):
338  result = dict()
339 
340  for k, v in zip(keys, values):
341  try:
342  result[k] = self._convert_dict(cast(v))
343  except ValueError:
344  # unquoted strings will raise a ValueError, so leave it as string in this case
345  result[k] = str(v)
346  except SyntaxError as se:
347  Logger.logdebug('Unable to parse input value for key "%s", assuming string:\n%s\n%s' % (k, str(v), str(se)))
348  result[k] = str(v)
349 
350  return result
351 
352 
353  def _build_contains(self, obj, path):
354  contain_list = dict((path+"/"+key,value) for (key,value) in getattr(obj, 'contains', {}).items())
355  add_to_list = {}
356  for b_id, b_inst in contain_list.items():
357  add_to_list.update(self._build_contains(b_inst, b_id))
358  contain_list.update(add_to_list)
359  return contain_list
360 
361 
363  thread = threading.Thread(target=self._heartbeat_worker)
364  thread.daemon = True
365  thread.start()
366 
367  def _heartbeat_worker(self):
368  while True:
369  self._pub.publish('flexbe/heartbeat', Empty())
370  time.sleep(1) # sec
371 
372  class _attr_dict(dict): __getattr__ = dict.__getitem__
373  def _convert_dict(self, o):
374  if isinstance(o, list):
375  return [self._convert_dict(e) for e in o]
376  elif isinstance(o, dict):
377  return self._attr_dict((k, self._convert_dict(v)) for k, v in o.items())
378  else:
379  return o
def _set_typed_attribute(self, obj, name, value, behavior='')
def _cleanup_behavior(self, behavior_checksum)


flexbe_onboard
Author(s): Philipp Schillinger
autogenerated on Wed Jun 5 2019 21:52:03