Go to the documentation of this file.00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 import re
00016 import xmlrpclib
00017
00018 import rosgraph
00019 import rospy
00020 import rosnode
00021
00022 from ros_topology_msgs.msg import Connection
00023 from ros_topology_msgs.msg import Graph
00024 from ros_topology_msgs.msg import Node
00025 from ros_topology_msgs.msg import Service
00026 from ros_topology_msgs.msg import Topic
00027
00028
00029 class Grapher(object):
00030 """ Publishes a latched ros_topology_msgs/Graph message describing the system state.
00031 Grapher uses the xmlrpc api to query information from the master and from individual nodes.
00032 Only one instance of this node should be run at a time.
00033 Information is only published when a change is detected.
00034 """
00035 def __init__(self, name=None):
00036 self._NAME = name or "rosgrapher"
00037
00038
00039 if not re.findall("^/*(.+)$", rospy.get_name())[0] == self._NAME:
00040 raise rospy.ROSInitException(
00041 "Node '%s' of type rosprofiler/rosgrapher should only use the name '%s' to avoid being run multiple times." % (rospy.get_name(), self._NAME))
00042
00043 self._master = rosgraph.Master(self._NAME)
00044 self._publisher = rospy.Publisher('/topology', Graph, queue_size=10, latch=True)
00045 self._poller_timer = None
00046
00047
00048 self._seq = 1
00049
00050
00051 self._old_nodes = dict()
00052 self._old_topics = dict()
00053
00054 def start(self):
00055 self._poller_timer = rospy.Timer(rospy.Duration(2), self._poller_callback)
00056
00057 def stop(self):
00058 self._poller_timer.shutdown()
00059 self._poller_timer.join()
00060
00061 def _poller_callback(self, event):
00062 """ Queries the state of the system using xmlrpc calls, and checks for changes """
00063 nodes = {}
00064 topics = {}
00065
00066
00067
00068
00069
00070 node_uris = {}
00071
00072
00073 allCurrentTopics = self._master.getTopicTypes()
00074 for topic, type_ in allCurrentTopics:
00075 topics[topic] = Topic(name=topic, type=type_)
00076
00077
00078 allCurrentNodes = rosnode.get_node_names()
00079 for name in allCurrentNodes:
00080 node = Node(name=name)
00081 try:
00082 node.uri = self._master.lookupNode(name)
00083 node_uris[node.uri] = name
00084 except rosgraph.masterapi.MasterError:
00085 rospy.logerr("WARNING: MasterAPI Error trying to contact '%s', skipping" % name)
00086 continue
00087 else:
00088 nodes[name] = node
00089
00090
00091 systemstate = self._master.getSystemState()
00092 for topic_name, publisher_list in systemstate[0]:
00093 if topic_name not in topics.keys():
00094 rospy.logerr("Topic %s not found, skipping" % topic_name)
00095 for publishername in publisher_list:
00096 if publishername not in nodes:
00097 rospy.logwarn("Node '%s' was not previously reported, "
00098 "but is listed as publisher" % publishername)
00099 continue
00100 nodes[publishername].publishes.append(topic_name)
00101 for topic_name, subscriber_list in systemstate[1]:
00102 if topic_name not in topics.keys():
00103 rospy.logerr("Topic %s not found, skipping" % topic_name)
00104 for subscribername in subscriber_list:
00105 if subscribername not in nodes:
00106 rospy.logwarn("Node '%s' was not previously reported, "
00107 "but is listed as subscriber" % subscribername)
00108 continue
00109 nodes[subscribername].subscribes.append(topic_name)
00110 for service_name, provider_list in systemstate[2]:
00111 for providername in provider_list:
00112 if providername not in nodes:
00113 rospy.logwarn("Node '%s' was not previously reported, but "
00114 "is listed as service provider" % providername)
00115 continue
00116 service = Service(name=service_name)
00117 try:
00118 service.uri = self._master.lookupService(service_name)
00119 except rosgraph.masterapi.MasterError:
00120 rospy.logerr("WARNING: MasterAPI Error trying to lookup service '%s', skipping" % service_name)
00121 continue
00122 else:
00123 nodes[providername].provides.append(service)
00124
00125
00126 for node in nodes.values():
00127 try:
00128 node_proxy = xmlrpclib.ServerProxy(node.uri)
00129 bus_info = node_proxy.getBusInfo(self._NAME)[2]
00130 for bus in bus_info:
00131 c = Connection()
00132 dest_id = bus[1]
00133 if len(dest_id) > 7 and dest_id[:7] == "http://":
00134 if dest_id in node_uris:
00135 c.destination = node_uris[dest_id]
00136 else:
00137 c.destination = "unknown (%s)" % dest_id
00138 else:
00139 c.destination = dest_id
00140 c.direction = {'o': Connection.OUT, 'i': Connection.IN, 'b': Connection.BOTH}[bus[2]]
00141 c.transport = bus[3]
00142 c.topic = bus[4]
00143 node.connections.append(c)
00144 except xmlrpclib.socket.error:
00145 rospy.logerr("WANRING: XML RPC ERROR contacting '%s', skipping" % node.name)
00146 continue
00147
00148
00149 if not set(nodes.keys()) == set(self._old_nodes.keys()):
00150 return self._update(nodes, topics)
00151 if not set(topics.keys()) == set(self._old_topics.keys()):
00152 return self._update(nodes, topics)
00153 for name in nodes.keys():
00154 if not nodes[name] == self._old_nodes[name]:
00155 return self._update(nodes, topics)
00156
00157 def _update(self, nodes, topics):
00158 """ Publishes updated topology information """
00159 rospy.loginfo("Update detected, publishing revised topology")
00160
00161 self._old_nodes = nodes
00162 self._old_topics = topics
00163
00164 graph = Graph()
00165 graph.header.seq = self._seq
00166 self._seq += 1
00167 graph.header.stamp = rospy.get_rostime()
00168 graph.header.frame_id = "/"
00169 graph.master = self._master.master_uri
00170 graph.nodes.extend(nodes.values())
00171 graph.topics.extend(topics.values())
00172 self._publisher.publish(graph)