00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
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
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
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
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
00089 self._robot_types = self._extract_robot_types()
00090
00091
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
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
00140 rocon_launch_text = self._prepare_rocon_launch_text(robots)
00141 self.loginfo("constructing robot client rocon launcher")
00142
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
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
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:
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
00264 except rospy.ROSInterruptException:
00265 break
00266
00267 self._terminal.shutdown_roslaunch_windows(processes=self._processes,
00268 hold=False)
00269 for temporary_file in self._temporary_files:
00270
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))