gazebo_robot_manager.py
Go to the documentation of this file.
00001 #!/usr/bin/env python
00002 #
00003 # License: BSD
00004 #   https://raw.github.com/robotics-in-concert/concert_services/license/LICENSE
00005 #
00006 ##############################################################################
00007 # About
00008 ##############################################################################
00009 
00010 # Simple script to manage spawning and killing of simulated gazebo robots
00011 # for a concert. This node can be requested to trigger a rocon_launch'ed style
00012 # terminal which embeds a standard concert client for each gazebo robot. It
00013 # then flips across the necessary gazebo simulated handles to that concert
00014 # client
00015 
00016 ##############################################################################
00017 # Imports
00018 ##############################################################################
00019 
00020 import copy
00021 import os
00022 import tempfile
00023 import yaml
00024 
00025 import gateway_msgs.msg as gateway_msgs
00026 import gateway_msgs.srv as gateway_srvs
00027 import rocon_std_msgs.msg as rocon_std_msgs
00028 import rocon_launch
00029 import rospy
00030 import rocon_gateway_utils
00031 import rocon_python_utils
00032 
00033 from .robot_manager import RobotManager
00034 
00035 ##############################################################################
00036 # Utility 
00037 ##############################################################################
00038 def prepare_robot_managers(robot_type_locations, world_name):
00039     """
00040     Prepare robot manager objects based on its type
00041     
00042     :param robot_types: The configurations define robots model launcher in gazebo
00043     :type robot_types: [{name: robot_type, launch: <package>/<.launch>}]
00044     
00045     :returns: A dict of robot manager, and a dict of invalid robot manager
00046     :rtype: {robot_type: RobotManager()}, {robot_type: reason}
00047     """
00048     robot_managers = {}
00049     invalid_robot_managers = {}
00050     for name, loc in robot_type_locations.items():
00051         try:
00052             with open(loc) as f:
00053                 loaded_robot_type = yaml.load(f)
00054                 robot_managers[loaded_robot_type['name']] = RobotManager(loaded_robot_type, world_name)
00055         except rospkg.ResourceNotFound as e: 
00056             invalid_robot_managers[name] = str(e) 
00057     return robot_managers, invalid_robot_managers
00058 
00059 ##############################################################################
00060 # Gazebo Robot Manager
00061 ##############################################################################
00062 
00063 class GazeboRobotManager:
00064     '''
00065       This class contains all the robot-independent functionality to launch
00066       robots in gazebo, create concert clients for each robot, and flip
00067       necessary information to each concert client, so each robot can truly
00068       behave as a concert client.
00069     '''
00070 
00071     def __init__(self, world_name, concert_name):
00072         """
00073         :param world_name: Gazebo World name
00074         """
00075         self.is_disabled = False
00076         self._robot_manager = None
00077         self._world_name = world_name
00078         self._concert_name = concert_name
00079         self._processes = []
00080         self._temporary_files = []
00081         self._robots = []
00082 
00083         # Gateway
00084         gateway_namespace = rocon_gateway_utils.resolve_local_gateway()
00085         rospy.wait_for_service(gateway_namespace + '/flip')
00086         self._gateway_flip_service = rospy.ServiceProxy(gateway_namespace + '/flip', gateway_srvs.Remote)
00087 
00088         # extract spawnable robot types from package exports
00089         self._robot_types = self._extract_robot_types()
00090 
00091         # Terminal type for spawning
00092         try:
00093             self._terminal = rocon_launch.create_terminal()
00094         except (rocon_launch.UnsupportedTerminal, rocon_python_comms.NotFoundException) as e:
00095             self.logwarn('cannot find a suitable terminal, falling back to spawning inside the current one [%s]' % str(e))
00096             self._terminal = rocon_launch.create_terminal(rocon_launch.terminals.active)
00097 
00098     def _extract_robot_types(self):
00099         cached_robot_type_information, unused_invalid_robot_types = rocon_python_utils.ros.resource_index_from_package_exports(rocon_std_msgs.Strings.TAG_GAZEBO_ROBOT_TYPE)
00100 
00101         self.loginfo(str(cached_robot_type_information))
00102         self._cached_robot_type_locations = {cached_resource_name: cached_filename for cached_resource_name, (cached_filename, unused_catkin_package) in cached_robot_type_information.iteritems()}
00103         self._robot_managers, self._invalid_robot_managers = prepare_robot_managers(self._cached_robot_type_locations, self._world_name)
00104         self.loginfo(str(self._robot_managers))
00105         self.loginfo(str(self._invalid_robot_managers))
00106 
00107         for name, manager in self._robot_managers.items():
00108             self.loginfo('%s loaded'%name)
00109 
00110 
00111     def _spawn_simulated_robots(self, robots, robot_managers):
00112         """
00113         Names and locations of robots to be spawned, read from a parameter file.
00114 
00115         :param robots list of dicts[]: The parameter file.
00116         The parameter file should read into a list of dictionaries, where each
00117         dict contains a "name" string, and a "location" tuple. For example:
00118             [{'name': 'kobuki', 'location': [0.0, 0.0, 0.0]},
00119              {'name': 'guimul', 'location': [0.0, 2.0, 0.0]}]
00120         For a full definition of the location vector, see
00121         RobotManager.spawn_robot().
00122         """
00123         for robot in robots:
00124             try:
00125                 args = robot['args'] if 'args' in robot else None
00126                 robot_managers[robot['type']].spawn_robot(robot["name"], robot["location"], args)
00127                 self._robots.append(robot["name"])
00128             # TODO add failure exception
00129             except rospy.ROSInterruptException:
00130                 self.loginfo("shutdown while spawning robot")
00131                 continue
00132 
00133     def _launch_robot_clients(self, robots):
00134         """
00135         Spawn concert clients for given named robot.
00136 
00137         :param robot_names str[]: Names of all robots.
00138         """
00139         # spawn the concert clients
00140         rocon_launch_text = self._prepare_rocon_launch_text(robots)
00141         self.loginfo("constructing robot client rocon launcher")
00142         #print("\n" + console.green + rocon_launch_text + console.reset)
00143         temp = tempfile.NamedTemporaryFile(mode='w+t', delete=False)
00144         temp.write(rocon_launch_text)
00145         temp.close()
00146         launch_configurations = rocon_launch.parse_rocon_launcher(temp.name, "--screen")
00147         try:
00148             os.unlink(temp.name)
00149         except OSError:
00150             self.logerr("failed to unlink the rocon launcher.")
00151 
00152         for launch_configuration in launch_configurations:
00153             self.loginfo("launching concert client %s on port %s" %
00154                       (launch_configuration.name, launch_configuration.port))
00155             #print("%s" % launch_configuration)
00156             process, meta_roslauncher = self._terminal.spawn_roslaunch_window(launch_configuration)
00157             self._processes.append(process)
00158             self._temporary_files.append(meta_roslauncher)
00159 
00160     def _prepare_rocon_launch_text(self, robots):
00161         port = 11411
00162         launch_text  = '<concert>\n'
00163         for robot in robots:
00164             launch_text += '  <launch title="%s:%s" package="concert_service_gazebo" name="robot.launch" port="%s">\n'%(robot['name'], str(port), str(port))
00165             launch_text += '    <arg name="robot_name" value="%s"/>\n' % robot['name']
00166             launch_text += '    <arg name="robot_type" value="%s"/>\n' % robot['type'] 
00167             launch_text += '    <arg name="robot_concert_whitelist" value="%s"/>\n' % self._concert_name 
00168             launch_text += '    <arg name="robot_rapp_whitelist" value="%s"/>\n' % str(robot['robot_rapp_whitelist'])
00169             launch_text += '  </launch>'
00170             port = port + 1
00171         launch_text += '</concert>\n'
00172 
00173         return launch_text
00174 
00175     def _establish_unique_names(self, robots):
00176         """
00177         Make sure robot names don't clash with currently spawned robots, or
00178         with other robots in the same list itself. If they do, postfix them
00179         with an incrementing counter.
00180 
00181         :param robots list of dicts[]: The parameter file defining robots and
00182             start locations. For a full description, see
00183             _spawn_simulated_robots().
00184         :return [dict]: uniquified names for the concert clients.
00185         """
00186         unique_robots = []
00187         unique_robot_names = []
00188         for robot in robots:
00189             robot_name = robot["name"]
00190             name_extension = ''
00191             count = 0
00192             while (robot_name + name_extension in unique_robot_names or
00193                    robot_name + name_extension in self._robots):
00194                 name_extension = str(count)
00195                 count = count + 1
00196             unique_robot_names.append(robot_name + name_extension)
00197             robot_copy = copy.deepcopy(robot)
00198             robot_copy["name"] = robot_name + name_extension
00199             unique_robots.append(robot_copy)
00200         return unique_robots
00201 
00202     def _send_flip_rules(self, robots, cancel):
00203         """
00204         Flip rules from Gazebo to the robot's concert client.
00205 
00206         :param robot_names str[]: Names of robots to whom information needs to
00207             be flipped.
00208         :param cancel bool: Cancel existing flips. Used during shutdown.
00209         """
00210         for robot in robots:
00211             rules = self._robot_managers[robot['type']].get_flip_rule_list(robot['name'])
00212             # send the request
00213             request = gateway_srvs.RemoteRequest()
00214             request.cancel = cancel
00215             remote_rule = gateway_msgs.RemoteRule()
00216             remote_rule.gateway = robot['name'] 
00217             for rule in rules:
00218                 remote_rule.rule = rule
00219                 request.remotes.append(copy.deepcopy(remote_rule))
00220             try:
00221                 self._gateway_flip_service(request)
00222             except rospy.ServiceException:  # communication failed
00223                 self.logerr('failed to send flip rules')
00224                 return
00225             except rospy.ROSInterruptException:
00226                 self.loginfo('shutdown while contacting the gateway flip service')
00227                 return
00228 
00229     def spawn_robots(self, robots):
00230         """
00231         Ensure all robots have existing names, spawn robots in gazebo, launch
00232         concert clients, and flip necessary information from gazebo to each
00233         concert client.
00234 
00235         :param robots list of dicts[]: The parameter file defining robots, type and
00236             start locations. For a full description, see
00237             _spawn_simulated_robots().
00238         :type robots: [{name: str, type: str, location: (x,y,theta)}]
00239         """
00240         unique_robots = self._establish_unique_names(robots)
00241         self._spawn_simulated_robots(unique_robots, self._robot_managers)
00242         self._launch_robot_clients(unique_robots)
00243         self._send_flip_rules(unique_robots, cancel=False)
00244         self._robots = unique_robots
00245 
00246     def spin(self):
00247         try:
00248             while not rospy.is_shutdown() and not self.is_disabled:
00249                 rospy.sleep(0.3)
00250         except rospy.ROSInterruptException:
00251             pass
00252         self.shutdown()
00253 
00254     def shutdown(self):
00255         """
00256           - Send unflip requests.
00257           - Cleanup robots in gazebo.
00258           - Shutdown spawned terminals.
00259         """
00260         for robot in self._robots:
00261             try:
00262                 self._robot_managers[robot['type']].delete_robot(robot['name'])
00263                 #TODO quitely fail exception here
00264             except rospy.ROSInterruptException:
00265                 break  # quietly fail
00266 
00267         self._terminal.shutdown_roslaunch_windows(processes=self._processes,
00268                                                   hold=False)
00269         for temporary_file in self._temporary_files:
00270             #print("Unlinking %s" % temporary_file.name)
00271             try:
00272                 os.unlink(temporary_file.name)
00273             except OSError as e:
00274                 self.logerr('failed to unlink temporary file [%s]' % str(e))
00275 
00276     def loginfo(self, msg):
00277         rospy.loginfo('GazeboRobotManager : %s'%str(msg))
00278 
00279     def logerr(self, msg):
00280         rospy.logerr('GazeboRobotManager : %s'%str(msg))
00281 
00282     def logwarn(self, msg):
00283         rospy.logwarn('GazeboRobotManager : %s'%str(msg))


concert_service_gazebo
Author(s): Daniel Stonier, Piyush Khandelwal
autogenerated on Thu Jun 6 2019 21:35:16