00001
00002
00003 import roslib; roslib.load_manifest('flexbe_onboard')
00004 import rospy
00005 import rospkg
00006 import os
00007 import sys
00008 import inspect
00009 import threading
00010 import time
00011 import smach
00012 import random
00013 import yaml
00014 import zlib
00015 import xml.etree.ElementTree as ET
00016 from ast import literal_eval as cast
00017
00018 from flexbe_core import Logger, BehaviorLibrary
00019
00020 from flexbe_msgs.msg import BehaviorSelection, BEStatus, ContainerStructure, CommandFeedback
00021 from flexbe_core.proxy import ProxyPublisher, ProxySubscriberCached
00022
00023 from std_msgs.msg import Int32, Empty
00024
00025
00026 '''
00027 Created on 20.05.2013
00028
00029 @author: Philipp Schillinger
00030 '''
00031
00032 class VigirBeOnboard(object):
00033 '''
00034 Implements an idle state where the robot waits for a behavior to be started.
00035 '''
00036
00037 def __init__(self):
00038 '''
00039 Constructor
00040 '''
00041 self.be = None
00042 Logger.initialize()
00043 smach.set_loggers (
00044 rospy.logdebug,
00045 rospy.logwarn,
00046 rospy.logdebug,
00047 rospy.logerr
00048 )
00049
00050
00051
00052
00053
00054 rp = rospkg.RosPack()
00055 self._tmp_folder = "/tmp/flexbe_onboard"
00056 if not os.path.exists(self._tmp_folder):
00057 os.makedirs(self._tmp_folder)
00058 sys.path.append(self._tmp_folder)
00059
00060
00061 self._behavior_lib = BehaviorLibrary()
00062
00063 self._pub = ProxyPublisher()
00064 self._sub = ProxySubscriberCached()
00065
00066 self.status_topic = 'flexbe/status'
00067 self.feedback_topic = 'flexbe/command_feedback'
00068
00069 self._pub.createPublisher(self.status_topic, BEStatus, _latch = True)
00070 self._pub.createPublisher(self.feedback_topic, CommandFeedback)
00071
00072
00073 self._running = False
00074 self._switching = False
00075 self._sub.subscribe('flexbe/start_behavior', BehaviorSelection, self._behavior_callback)
00076
00077
00078 self._pub.createPublisher('flexbe/heartbeat', Empty)
00079 self._execute_heartbeat()
00080
00081 rospy.sleep(0.5)
00082 self._pub.publish(self.status_topic, BEStatus(code=BEStatus.READY))
00083 rospy.loginfo('\033[92m--- Behavior Engine ready! ---\033[0m')
00084
00085
00086 def _behavior_callback(self, msg):
00087 thread = threading.Thread(target=self._behavior_execution, args=[msg])
00088 thread.daemon = True
00089 thread.start()
00090
00091 def _behavior_execution(self, msg):
00092 if self._running:
00093 Logger.loginfo('--> Initiating behavior switch...')
00094 self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['received']))
00095 else:
00096 Logger.loginfo('--> Starting new behavior...')
00097
00098 be = self._prepare_behavior(msg)
00099 if be is None:
00100 Logger.logerr('Dropped behavior start request because preparation failed.')
00101 if self._running:
00102 self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['failed']))
00103 else:
00104 rospy.loginfo('\033[92m--- Behavior Engine ready! ---\033[0m')
00105 return
00106
00107 if self._running:
00108 if self._switching:
00109 Logger.logwarn('Already switching, dropped new start request.')
00110 return
00111 self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['start']))
00112 if not self._is_switchable(be):
00113 Logger.logerr('Dropped behavior start request because switching is not possible.')
00114 self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['not_switchable']))
00115 return
00116 self._switching = True
00117 active_state = self.be.get_current_state()
00118 rospy.loginfo("Current state %s is kept active.", active_state.name)
00119 try:
00120 be.prepare_for_switch(active_state)
00121 self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['prepared']))
00122 except Exception as e:
00123 Logger.logerr('Failed to prepare behavior switch:\n%s' % str(e))
00124 self._switching = False
00125 self._pub.publish(self.feedback_topic, CommandFeedback(command="switch", args=['failed']))
00126 return
00127 rospy.loginfo('Preempting current behavior version...')
00128 self.be.preempt_for_switch()
00129 rate = rospy.Rate(10)
00130 while self._running:
00131 rate.sleep()
00132 self._switching = False
00133
00134 self._running = True
00135 self.be = be
00136
00137 result = ""
00138 try:
00139 rospy.loginfo('Behavior ready, execution starts now.')
00140 rospy.loginfo('[%s : %s]', be.name, msg.behavior_checksum)
00141 self.be.confirm()
00142 args = [self.be.requested_state_path] if self.be.requested_state_path is not None else []
00143 self._pub.publish(self.status_topic, BEStatus(behavior_id=self.be.id, code=BEStatus.STARTED, args=args))
00144 result = self.be.execute()
00145 if self._switching:
00146 self._pub.publish(self.status_topic, BEStatus(behavior_id=self.be.id, code=BEStatus.SWITCHING))
00147 else:
00148 self._pub.publish(self.status_topic, BEStatus(behavior_id=self.be.id, code=BEStatus.FINISHED, args=[str(result)]))
00149 except Exception as e:
00150 self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.FAILED))
00151 Logger.logerr('Behavior execution failed!\n%s' % str(e))
00152 import traceback
00153 Logger.loginfo(traceback.format_exc())
00154 result = "failed"
00155
00156 try:
00157 self._cleanup_behavior(msg.behavior_checksum)
00158 except Exception as e:
00159 rospy.logerr('Failed to clean up behavior:\n%s' % str(e))
00160
00161 self.be = None
00162 if not self._switching:
00163 rospy.loginfo('Behavior execution finished with result %s.', str(result))
00164 rospy.loginfo('\033[92m--- Behavior Engine ready! ---\033[0m')
00165 self._running = False
00166
00167
00168 def _prepare_behavior(self, msg):
00169
00170 try:
00171 rp = rospkg.RosPack()
00172 behavior = self._behavior_lib.get_behavior(msg.behavior_id)
00173 if behavior is None:
00174 raise ValueError(msg.behavior_id)
00175 be_filepath = os.path.join(rp.get_path(behavior["package"]), 'src/' + behavior["package"] + '/' + behavior["file"] + '_tmp.py')
00176 if os.path.isfile(be_filepath):
00177 be_file = open(be_filepath, "r")
00178 rospy.logwarn("Found a tmp version of the referred behavior! Assuming local test run.")
00179 else:
00180 be_filepath = os.path.join(rp.get_path(behavior["package"]), 'src/' + behavior["package"] + '/' + behavior["file"] + '.py')
00181 be_file = open(be_filepath, "r")
00182 be_content = be_file.read()
00183 be_file.close()
00184 except Exception as e:
00185 Logger.logerr('Failed to retrieve behavior from library:\n%s' % str(e))
00186 self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
00187 return
00188
00189
00190 try:
00191 file_content = ""
00192 last_index = 0
00193 for mod in msg.modifications:
00194 file_content += be_content[last_index:mod.index_begin] + mod.new_content
00195 last_index = mod.index_end
00196 file_content += be_content[last_index:]
00197 if zlib.adler32(file_content) != msg.behavior_checksum:
00198 mismatch_msg = ("Checksum mismatch of behavior versions! \n"
00199 "Attempted to load behavior: %s\n"
00200 "Make sure that all computers are on the same version a.\n"
00201 "Also try: rosrun flexbe_widget clear_cache" % str(be_filepath))
00202 raise Exception(mismatch_msg)
00203 else:
00204 rospy.loginfo("Successfully applied %d modifications." % len(msg.modifications))
00205 except Exception as e:
00206 Logger.logerr('Failed to apply behavior modifications:\n%s' % str(e))
00207 self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
00208 return
00209
00210
00211 try:
00212 file_path = os.path.join(self._tmp_folder, 'tmp_%d.py' % msg.behavior_checksum)
00213 sc_file = open(file_path, "w")
00214 sc_file.write(file_content)
00215 sc_file.close()
00216 except Exception as e:
00217 Logger.logerr('Failed to create temporary file for behavior class:\n%s' % str(e))
00218 self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
00219 return
00220
00221
00222 try:
00223 package = __import__("tmp_%d" % msg.behavior_checksum, fromlist=["tmp_%d" % msg.behavior_checksum])
00224 clsmembers = inspect.getmembers(package, lambda member: inspect.isclass(member) and member.__module__ == package.__name__)
00225 beclass = clsmembers[0][1]
00226 be = beclass()
00227 rospy.loginfo('Behavior ' + be.name + ' created.')
00228 except Exception as e:
00229 Logger.logerr('Exception caught in behavior definition:\n%s' % str(e))
00230 self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
00231 return
00232
00233
00234 contain_list = {}
00235 try:
00236 contain_list = self._build_contains(be, "")
00237 except Exception as e:
00238 Logger.logerr('Failed to load contained behaviors:\n%s' % str(e))
00239 return
00240
00241
00242 if len(msg.arg_keys) > 0:
00243 rospy.loginfo('The following parameters will be used:')
00244 try:
00245 for i in range(len(msg.arg_keys)):
00246 if msg.arg_keys[i] == '':
00247
00248 continue
00249 key_splitted = msg.arg_keys[i].rsplit('/', 1)
00250 if len(key_splitted) == 1:
00251 behavior = ''
00252 key = key_splitted[0]
00253 rospy.logwarn('Parameter key %s has no path specification, assuming: /%s' % (key, key))
00254 else:
00255 behavior = key_splitted[0]
00256 key = key_splitted[1]
00257 found = False
00258
00259 if behavior == '' and hasattr(be, key):
00260 self._set_typed_attribute(be, key, msg.arg_values[i])
00261
00262 for b in contain_list:
00263 if hasattr(contain_list[b], key):
00264 self._set_typed_attribute(contain_list[b], key, msg.arg_values[i], b)
00265 found = True
00266
00267 for b in contain_list:
00268 if b == behavior and hasattr(contain_list[b], key):
00269 self._set_typed_attribute(contain_list[b], key, msg.arg_values[i], b)
00270 found = True
00271
00272 if not found:
00273 rospy.logwarn('Parameter ' + msg.arg_keys[i] + ' (set to ' + msg.arg_values[i] + ') not implemented')
00274
00275 except Exception as e:
00276 Logger.logerr('Failed to initialize parameters:\n%s' % str(e))
00277 self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
00278 return
00279
00280
00281 try:
00282 be.set_up(id=msg.behavior_checksum, autonomy_level=msg.autonomy_level, debug=False)
00283 be.prepare_for_execution(self._convert_input_data(msg.input_keys, msg.input_values))
00284 rospy.loginfo('State machine built.')
00285 except Exception as e:
00286 Logger.logerr('Behavior construction failed!\n%s' % str(e))
00287 self._pub.publish(self.status_topic, BEStatus(behavior_id=msg.behavior_checksum, code=BEStatus.ERROR))
00288 return
00289
00290 return be
00291
00292
00293 def _is_switchable(self, be):
00294 if self.be.name != be.name:
00295 Logger.logerr('Unable to switch behavior, names do not match:\ncurrent: %s <--> new: %s' % (self.be.name, be.name))
00296 return False
00297
00298
00299
00300
00301
00302
00303
00304 return True
00305
00306
00307 def _cleanup_behavior(self, behavior_checksum):
00308 del(sys.modules["tmp_%d" % behavior_checksum])
00309 file_path = os.path.join(self._tmp_folder, 'tmp_%d.pyc' % behavior_checksum)
00310 try:
00311 os.remove(file_path)
00312 except OSError:
00313 pass
00314 try:
00315 os.remove(file_path + 'c')
00316 except OSError:
00317 pass
00318
00319
00320 def _set_typed_attribute(self, obj, name, value, behavior=''):
00321 attr = getattr(obj, name)
00322 if type(attr) is int:
00323 value = int(value)
00324 elif type(attr) is long:
00325 value = long(value)
00326 elif type(attr) is float:
00327 value = float(value)
00328 elif type(attr) is bool:
00329 value = (value != "0")
00330 elif type(attr) is dict:
00331 value = yaml.load(value)
00332 setattr(obj, name, value)
00333 suffix = ' (' + behavior + ')' if behavior != '' else ''
00334 rospy.loginfo(name + ' = ' + str(value) + suffix)
00335
00336
00337 def _convert_input_data(self, keys, values):
00338 result = dict()
00339
00340 for k, v in zip(keys, values):
00341 try:
00342 result[k] = self._convert_dict(cast(v))
00343 except ValueError:
00344
00345 result[k] = str(v)
00346 except SyntaxError as se:
00347 Logger.logdebug('Unable to parse input value for key "%s", assuming string:\n%s\n%s' % (k, str(v), str(se)))
00348 result[k] = str(v)
00349
00350 return result
00351
00352
00353 def _build_contains(self, obj, path):
00354 contain_list = dict((path+"/"+key,value) for (key,value) in getattr(obj, 'contains', {}).items())
00355 add_to_list = {}
00356 for b_id, b_inst in contain_list.items():
00357 add_to_list.update(self._build_contains(b_inst, b_id))
00358 contain_list.update(add_to_list)
00359 return contain_list
00360
00361
00362 def _execute_heartbeat(self):
00363 thread = threading.Thread(target=self._heartbeat_worker)
00364 thread.daemon = True
00365 thread.start()
00366
00367 def _heartbeat_worker(self):
00368 while True:
00369 self._pub.publish('flexbe/heartbeat', Empty())
00370 time.sleep(1)
00371
00372 class _attr_dict(dict): __getattr__ = dict.__getitem__
00373 def _convert_dict(self, o):
00374 if isinstance(o, list):
00375 return [self._convert_dict(e) for e in o]
00376 elif isinstance(o, dict):
00377 return self._attr_dict((k, self._convert_dict(v)) for k, v in o.items())
00378 else:
00379 return o