flexbe_onboard.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
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, # hide SMACH transition log spamming
00045             rospy.logwarn,
00046             rospy.logdebug,
00047             rospy.logerr
00048         )
00049 
00050         #ProxyPublisher._simulate_delay = True
00051         #ProxySubscriberCached._simulate_delay = True
00052 
00053         # prepare temp folder
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         # prepare manifest folder access
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         # listen for new behavior to start
00073         self._running = False
00074         self._switching = False
00075         self._sub.subscribe('flexbe/start_behavior', BehaviorSelection, self._behavior_callback)
00076 
00077         # heartbeat
00078         self._pub.createPublisher('flexbe/heartbeat', Empty)
00079         self._execute_heartbeat()
00080 
00081         rospy.sleep(0.5) # wait for publishers etc to really be set up
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         # get sourcecode from ros package
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         # apply modifications if any
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         # create temp file for behavior class
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         # import temp class file and initialize behavior
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         # import contained behaviors
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         # initialize behavior parameters
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                     # action call has empty string as default, not a valid param key
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                     # propagate to contained behaviors
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         # build state machine
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         # locked inside
00298         # locked state exists in new behavior
00299         #if self.be.id == be.id:
00300             #Logger.logwarn('Behavior version ID is the same.')
00301         #    Logger.logwarn('Skipping behavior switch, version ID is the same.')
00302         #    return False
00303         # ok, can switch
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                 # unquoted strings will raise a ValueError, so leave it as string in this case
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) # sec
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


flexbe_onboard
Author(s): Philipp Schillinger
autogenerated on Thu Jun 6 2019 19:32:30