grapher.py
Go to the documentation of this file.
00001 # Copyright 2014 Open Source Robotics Foundation, Inc.
00002 #
00003 # Licensed under the Apache License, Version 2.0 (the "License");
00004 # you may not use this file except in compliance with the License.
00005 # You may obtain a copy of the License at
00006 #
00007 #     http://www.apache.org/licenses/LICENSE-2.0
00008 #
00009 # Unless required by applicable law or agreed to in writing, software
00010 # distributed under the License is distributed on an "AS IS" BASIS,
00011 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
00012 # See the License for the specific language governing permissions and
00013 # limitations under the License.
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         # Force singleton by not allowing name to be remapped
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         # Message Sequence Number
00048         self._seq = 1
00049 
00050         # Previous values for comparison
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 = {}  # name: Node()
00064         topics = {}  # name: Topic()
00065 
00066         # This is an extra lookup table. The API at http://wiki.ros.org/ROS/Slave_API is
00067         # lying to us and the protocol is inconsistent! Sometimes the destination in
00068         # getBusInfo() returns a URI, sometimes it is a node name :( So we keep
00069         # a list of URIs that we can translate back into node names using this.
00070         node_uris = {}
00071 
00072         # Query master and compile a list of all published topics and their types
00073         allCurrentTopics = self._master.getTopicTypes()
00074         for topic, type_ in allCurrentTopics:
00075             topics[topic] = Topic(name=topic, type=type_)
00076 
00077         # Compile a list of nodes names and uris
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         # Add lists of subscribers, publishers, and services for each topic
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         # Add connection information reported by nodes
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         # If any nodes or topics are added removed or changed, publish update
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         # Save old information
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)


rosprofiler
Author(s): Dan Brooks
autogenerated on Thu Jun 6 2019 18:15:05