00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 import copy
00025 import math
00026 import os
00027 import random
00028 import tempfile
00029
00030 import concert_service_utilities
00031 import gateway_msgs.msg as gateway_msgs
00032 import gateway_msgs.srv as gateway_srvs
00033 import rocon_launch
00034 import rospy
00035 import rocon_gateway_utils
00036 import rocon_python_comms
00037 import std_msgs.msg as std_msgs
00038 import turtlesim.srv as turtlesim_srvs
00039
00040
00041
00042
00043
00044
00045 def prepare_launch_configurations(turtles):
00046 """
00047 :param Turtle[] turtles:
00048 """
00049 port = 11
00050 launch_text = '<concert>\n'
00051 for turtle in turtles:
00052 launch_text += ' <launch title="%s:114%s" package="concert_service_turtlesim" name="turtle.launch" port="114%s">\n' % (turtle.unique_name, str(port), str(port))
00053 launch_text += ' <arg name="turtle_name" value="%s"/>\n' % turtle.unique_name
00054 launch_text += ' <arg name="turtle_concert_whitelist" value="%s"/>\n' % str(turtle.concert_whitelist)
00055 launch_text += ' <arg name="turtle_rapp_whitelist" value="%s"/>\n' % str(turtle.rapp_whitelist)
00056 launch_text += ' </launch>\n'
00057 port = port + 1
00058 launch_text += '</concert>\n'
00059 temp = tempfile.NamedTemporaryFile(mode='w+t', delete=False)
00060
00061 temp.write(launch_text)
00062
00063 temp.close()
00064 launch_configurations = rocon_launch.parse_rocon_launcher(temp.name, "--screen")
00065 try:
00066 os.unlink(temp.name)
00067 except OSError:
00068 rospy.logerr("Turtle Herder : failed to unlink the rocon launcher.")
00069 return launch_configurations
00070
00071
00072
00073
00074
00075
00076 class Turtle(object):
00077 """
00078 Holds parameterised information used for customising a turtle spawning.
00079 """
00080 __slots__ = [
00081 'name',
00082 'unique_name',
00083 'rapp_whitelist',
00084 'concert_whitelist'
00085 ]
00086
00087 def __init__(self, name, rapp_whitelist, concert_whitelist):
00088 self.name = name
00089 self.unique_name = name
00090 self.rapp_whitelist = rapp_whitelist
00091 self.concert_whitelist = concert_whitelist
00092
00093
00094
00095
00096
00097
00098 class TurtleHerder(object):
00099 '''
00100 Shepherds the turtles!
00101
00102 @todo get alised names from the concert client list if the topic is available
00103
00104 @todo watchdog for killing turtles that are no longer connected.
00105 '''
00106 __slots__ = [
00107 'turtles',
00108 '_kill_turtle_service_client',
00109 '_spawn_turtle_service_client',
00110 '_gateway_flip_service',
00111 '_processes',
00112 '_temporary_files',
00113 'is_disabled',
00114 '_terminal',
00115 '_shutdown_subscriber'
00116 ]
00117
00118 def __init__(self):
00119 self.turtles = []
00120 self._processes = []
00121 self._temporary_files = []
00122 self.is_disabled = False
00123
00124 rospy.wait_for_service('kill')
00125 rospy.wait_for_service('spawn')
00126 self._kill_turtle_service_client = rospy.ServiceProxy('kill', turtlesim_srvs.Kill, persistent=True)
00127 self._spawn_turtle_service_client = rospy.ServiceProxy('spawn', turtlesim_srvs.Spawn, persistent=True)
00128
00129 try:
00130 unused_response = self._kill_turtle_service_client("turtle1")
00131 except rospy.ServiceException:
00132 rospy.logerr("Turtle Herder : failed to contact the internal kill turtle service")
00133 except rospy.ROSInterruptException:
00134 rospy.loginfo("Turtle Herder : shutdown while contacting the internal kill turtle service")
00135 return
00136 self._shutdown_subscriber = rospy.Subscriber('shutdown', std_msgs.Empty, self.shutdown)
00137
00138 gateway_namespace = rocon_gateway_utils.resolve_local_gateway()
00139 rospy.wait_for_service(gateway_namespace + '/flip')
00140 self._gateway_flip_service = rospy.ServiceProxy(gateway_namespace + '/flip', gateway_srvs.Remote)
00141
00142 try:
00143 self._terminal = rocon_launch.create_terminal()
00144 except (rocon_launch.UnsupportedTerminal, rocon_python_comms.NotFoundException) as e:
00145 rospy.logwarn("Turtle Herder : cannot find a suitable terminal, falling back to spawning inside the current one [%s]" % str(e))
00146 self._terminal = rocon_launch.create_terminal(rocon_launch.terminals.active)
00147
00148 def _spawn_simulated_turtles(self, turtles):
00149 """
00150 Very important to have checked that the turtle names are unique
00151 before calling this method.
00152
00153 :param Turtle[] turtles:
00154 """
00155 for turtle in turtles:
00156 internal_service_request = turtlesim_srvs.SpawnRequest(
00157 random.uniform(3.5, 6.5),
00158 random.uniform(3.5, 6.5),
00159 random.uniform(0.0, 2.0 * math.pi),
00160 turtle.unique_name)
00161 try:
00162 unused_internal_service_response = self._spawn_turtle_service_client(internal_service_request)
00163 self.turtles.append(turtle)
00164 except rospy.ServiceException:
00165 rospy.logerr("TurtleHerder : failed to contact the internal spawn turtle service")
00166 continue
00167 except rospy.ROSInterruptException:
00168 rospy.loginfo("TurtleHerder : shutdown while contacting the internal spawn turtle service")
00169 continue
00170
00171 def _launch_turtle_clients(self, turtles):
00172 """
00173 :param Turtle[] turtles:
00174 """
00175
00176 launch_configurations = prepare_launch_configurations(turtles)
00177 for launch_configuration in launch_configurations:
00178 rospy.loginfo("Turtle Herder : launching turtle concert client %s on port %s" %
00179 (launch_configuration.name, launch_configuration.port))
00180
00181 process, meta_roslauncher = self._terminal.spawn_roslaunch_window(launch_configuration)
00182 self._processes.append(process)
00183 self._temporary_files.append(meta_roslauncher)
00184
00185 def spawn_turtles(self, turtles):
00186 """
00187 :param Turtle[] turtles:
00188 """
00189 uniquely_named_turtles = self._establish_unique_names(turtles)
00190 self._spawn_simulated_turtles(uniquely_named_turtles)
00191 self._launch_turtle_clients(uniquely_named_turtles)
00192 self._send_flip_rules(uniquely_named_turtles, cancel=False)
00193
00194 def _establish_unique_names(self, turtles):
00195 """
00196 Make sure the turtle names don't clash with currently spawned turtles.
00197 If they do, postfix them with an incrementing counter.
00198
00199 :param Turtle[] turtles: list of new turtle names to uniquify.
00200 :returns: uniquified names for the turtles.
00201 :rtype Turtle[]: updated turtles
00202 """
00203 for turtle in turtles:
00204 name_extension = ''
00205 count = 0
00206 while turtle.name + name_extension in [turtle.name for turtle in self.turtles]:
00207 name_extension = '_' + str(count)
00208 count = count + 1
00209 turtle.unique_name = turtle.name + name_extension
00210 return turtles
00211
00212 def _send_flip_rules(self, turtles, cancel):
00213 """
00214 :param Turtle[] turtles:
00215 """
00216 for turtle in turtles:
00217 rules = []
00218 rule = gateway_msgs.Rule()
00219 rule.node = ''
00220 rule.type = gateway_msgs.ConnectionType.SUBSCRIBER
00221
00222 rule.name = "/services/turtlesim/%s/cmd_vel" % turtle.unique_name
00223 rules.append(copy.deepcopy(rule))
00224 rule.type = gateway_msgs.ConnectionType.PUBLISHER
00225 rule.name = "/services/turtlesim/%s/pose" % turtle.unique_name
00226 rules.append(copy.deepcopy(rule))
00227
00228 request = gateway_srvs.RemoteRequest()
00229 request.cancel = cancel
00230 remote_rule = gateway_msgs.RemoteRule()
00231 remote_rule.gateway = turtle.unique_name
00232 for rule in rules:
00233 remote_rule.rule = rule
00234 request.remotes.append(copy.deepcopy(remote_rule))
00235 try:
00236 self._gateway_flip_service(request)
00237 except rospy.ServiceException:
00238 rospy.logerr("TurtleHerder : failed to send flip rules")
00239 return
00240 except rospy.ROSInterruptException:
00241 rospy.loginfo("TurtleHerder : shutdown while contacting the gateway flip service")
00242 return
00243
00244 def _ros_service_manager_disable_callback(self, msg):
00245 self.is_disabled = True
00246
00247 def shutdown(self, msg=None):
00248 """
00249 - Send unflip requests
00250 - Cleanup turtles on the turtlesim canvas.
00251 - Shutdown spawned terminals
00252
00253 :todo: this should go in a service manager callable ros callback where we can
00254 call disable on this service and bring it down without having to SIGINT it.
00255 """
00256
00257
00258
00259
00260
00261
00262
00263
00264
00265
00266 self._terminal.shutdown_roslaunch_windows(processes=self._processes,
00267 hold=False)
00268 for temporary_file in self._temporary_files:
00269
00270 try:
00271 os.unlink(temporary_file.name)
00272 except OSError as e:
00273 rospy.logerr("Turtle Herder : failed to unlink temporary file [%s]" % str(e))
00274
00275
00276
00277
00278
00279 if __name__ == '__main__':
00280
00281 rospy.init_node('turtle_herder')
00282 (service_name, unused_service_description, service_priority, unused_service_id) = concert_service_utilities.get_service_info()
00283 turtles = []
00284 turtle_parameters = rospy.get_param('/services/' + service_name + '/turtles', {})
00285
00286 for name, parameters in turtle_parameters.items():
00287 try:
00288 turtles.append(Turtle(name, parameters['rapp_whitelist'], parameters['concert_whitelist']))
00289 except KeyError as e:
00290 rospy.logerr("TurtleHerder : not all turtle parameters found for %s (req'd even if empty)[%s]" % (name, str(e)))
00291 rospy.loginfo("TurtleHerder : spawning turtles: %s" % [turtle.name for turtle in turtles])
00292
00293 turtle_herder = TurtleHerder()
00294 turtle_herder.spawn_turtles(turtles)
00295 while not rospy.is_shutdown() and not turtle_herder.is_disabled:
00296 rospy.sleep(0.3)
00297 turtle_herder.shutdown()