1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33 import time
34
35 import roslib; roslib.load_manifest('master_discovery_fkie')
36 import rospy
39 '''
40 The NodeInfo class stores informations about a ROS node.
41 '''
43 '''
44 Creates a new NodeInfo for a node with given name.
45 @param name: the name of the node
46 @type name: C{str}
47 @param masteruri: the URI of the ROS master, where the node is registered.
48 This masteruri will be used to determine, whether the ROS master and the
49 node are running on the same machine.
50 @type masteruri: C{str}
51 '''
52 self.__name = name
53 self.__masteruri = masteruri
54 self.__org_masteruri = masteruri
55 self.__uri = None
56 self.pid = None
57 '''@ivar: the process id of the node. Invalid id has a C{None} value'''
58 self.__local = False
59 self._publishedTopics = []
60 self._subscribedTopics = []
61 self._services = []
62
63 @property
65 '''
66 Returns the name of the node.
67 @rtype: C{str}
68 '''
69 return self.__name
70
71 @property
73 '''
74 Returns the URI of the RPC API of the node.
75 @rtype: C{str}
76 '''
77 return self.__uri
78
79 @uri.setter
81 '''
82 Sets the URI of the RPC API of the node.
83 '''
84 self.__uri = uri
85 self.__local = NodeInfo.local_(self.__masteruri, self.__org_masteruri, self.__uri)
86
87 @property
89 '''
90 Returns the URI of the ROS master where the node is registered.
91 @rtype: C{str}
92 '''
93 return self.__org_masteruri
94
95 @masteruri.setter
97 '''
98 Sets the ROS master URI.
99 '''
100 self.__org_masteruri = uri
101 self.__local = NodeInfo.local_(self.__masteruri, self.__org_masteruri, self.__uri)
102
103 @property
105 '''
106 Returns C{True} if the node and the ROS master are running on the same machine.
107 @rtype: C{boolean}
108 '''
109 return self.__local
110
111 @property
113 '''
114 Returns the list of all published topics by this node.
115 @rtype: C{[str, ...]}
116 '''
117 return self._publishedTopics
118
119 @publishedTopics.setter
121 '''
122 Append a new published topic to this node.
123 @param name: the name of the topic
124 @type name: C{str}
125 '''
126 try:
127 if isinstance(name, list):
128 del self._publishedTopics
129 self._publishedTopics = name
130 else:
131 self._publishedTopics.index(name)
132 except ValueError:
133 self._publishedTopics.append(name)
134
135
136
137
138
139 @property
141 '''
142 Returns the list of all subscribed topics by this node.
143 @rtype: C{[str, ...]}
144 '''
145 return self._subscribedTopics
146
147 @subscribedTopics.setter
149 '''
150 Append a new subscribed topic to this node.
151 @param name: the name of the topic
152 @type name: C{str}
153 '''
154 try:
155 if isinstance(name, list):
156 del self._subscribedTopics
157 self._subscribedTopics = name
158 else:
159 self._subscribedTopics.index(name)
160 except ValueError:
161 self._subscribedTopics.append(name)
162
163
164
165
166
167 @property
169 '''
170 Returns the list of all services provided by this node.
171 @rtype: C{[str, ...]}
172 '''
173 return self._services
174
175 @services.setter
177 '''
178 Append a new service to this node.
179 @param name: the name of the topic
180 @type name: C{str}
181 '''
182 try:
183 if isinstance(name, list):
184 del self._services
185 self._services = name
186 else:
187 self._services.index(name)
188 except ValueError:
189 self._services.append(name)
190
191
192
193
194
196 '''
197 Creates a copy this object and returns it.
198 @rtype: L{NodeInfo}
199 '''
200 result = NodeInfo(self.name, self.masteruri)
201 result.uri = ''.join([self.uri]) if not self.uri is None else None
202 result.pid = self.pid
203 result._publishedTopics = list(self._publishedTopics)
204 result._subscribedTopics = list(self._subscribedTopics)
205 result._services = list(self._services)
206 return result
207
208 @staticmethod
209 - def local_(masteruri, org_masteruri, uri):
210 result = False
211 try:
212 from urlparse import urlparse
213 om = urlparse(masteruri)
214 on = urlparse(uri)
215 result = (om.hostname == on.hostname) and (masteruri == org_masteruri)
216 except:
217 pass
218 return result
219
222 '''
223 The TopicInfo class stores informations about a ROS topic.
224 '''
226 '''
227 Creates a new TopicInfo for a topic with given name.
228 @param name: the name of the topic
229 @type name: C{str}
230 '''
231 self.__name = name
232 self.type = None
233 '''@ivar: the type of the topic. (Default: None)'''
234 self._publisherNodes = []
235 self._subscriberNodes = []
236
237 @property
239 '''
240 Returns the name of the topic.
241 @rtype: C{str}
242 '''
243 return self.__name
244
245 @property
247 '''
248 Returns the list with node names witch are publishing to this topic.
249 @rtype: C{[str,...]}
250 '''
251 return list(self._publisherNodes)
252
253 @publisherNodes.setter
255 '''
256 Append a new publishing node to this topic.
257 '''
258 try:
259 if isinstance(name, list):
260 del self._publisherNodes
261 self._publisherNodes = name
262 else:
263 self._publisherNodes.index(name)
264 except ValueError:
265 self._publisherNodes.append(name)
266
267
268
269
270
271 @property
273 '''
274 Returns the list with node names witch are subscribed to this topic.
275 @rtype: C{[str,...]}
276 '''
277 return list(self._subscriberNodes)
278
279 @subscriberNodes.setter
281 '''
282 Append a new subscribing node to this topic.
283 '''
284 try:
285 if isinstance(name, list):
286 del self._subscriberNodes
287 self._subscriberNodes = name
288 else:
289 self._subscriberNodes.index(name)
290 except ValueError:
291 self._subscriberNodes.append(name)
292
293
294
295
296
298 '''
299 Creates a copy this object and returns it.
300 @rtype: L{TopicInfo}
301 '''
302 result = TopicInfo(self.name)
303 result.type = self.type
304 result._publisherNodes = list(self._publisherNodes)
305 result._subscriberNodes = list(self._subscriberNodes)
306 return result
307
310 '''
311 The ServiceInfo class stores informations about a ROS service.
312 '''
314 '''
315 Creates a new instance of the ServiceInfo.
316 @param name: the name of the service
317 @type name: C{str}
318 @param masteruri: the URI of the ROS master, where the service is registered.
319 This masteruri will be used to determine, whether the ROS master and the
320 service are running on the same machine.
321 @type masteruri: C{str}
322 '''
323 self.__name = name
324 self.__masteruri = masteruri
325 self.__org_masteruri = masteruri
326 self.__uri = None
327 self.__local = False
328 self.type = None
329 '''@ivar: the type of the service. (Default: None)'''
330 self.__service_class = None
331 self.args = None
332 self.__serviceProvider = []
333
334 @property
336 '''
337 Returns the name of the service.
338 @rtype: C{str}
339 '''
340 return self.__name
341
342 @property
344 '''
345 Returns the URI of the RPC API of the service
346 @rtype: C{str}
347 '''
348 return self.__uri
349
350 @uri.setter
351 - def uri(self, uri):
352 '''
353 Sets the uri of the service RPC interface and determine whether this service
354 and the ROS master are running on the same machine.
355 @param uri: The URI of the service RPC interface
356 @type uri: C{str}
357 '''
358 self.__uri = uri
359 self.__local = NodeInfo.local_(self.__masteruri, self.__org_masteruri, self.__uri)
360
361 @property
363 '''
364 Returns the URI of the ROS master of the service
365 @rtype: C{str}
366 '''
367 return self.__org_masteruri
368
369 @masteruri.setter
371 '''
372 Sets the uri of the origin ROS master and determine whether this service
373 and the ROS master are running on the same machine.
374 @param uri: The URI of the ROS master
375 @type uri: C{str}
376 '''
377 self.__org_masteruri = uri
378 self.__local = NodeInfo.local_(self.__masteruri, self.__org_masteruri, self.__uri)
379
380 @property
382 '''
383 Returns C{True}, if this service and the master are on the same machine. This
384 will be determine on setting the uri-parameter.
385 @rtype: C{boolean}
386 '''
387 return self.__local
388
389 @property
391 '''
392 Return the list of the node names, which provide this service.
393 @rtype: C{[str, ...]}
394 '''
395 return self.__serviceProvider
396
397 @serviceProvider.setter
399 '''
400 Adds a new service provider, if no one with given name exists.
401 @param name: name of the new service provider
402 @type name: C{str}
403 '''
404 try:
405 self.__serviceProvider.index(name)
406 except ValueError:
407 self.__serviceProvider.append(name)
408
409 @serviceProvider.deleter
411 del self.__serviceProvider
412
413
415 '''
416 Get the service class using the type of the service. NOTE: this
417 method is from 'rosservice' and changed to avoid a probe call to the service.
418 @param allow_get_type: allow to connect to service and get the type if the
419 type is not valid (in case of other host e.g.)
420 @type allow_get_type: C{boolean}
421 @return: service class
422 @rtype: ServiceDefinition: service class
423 @raise ROSServiceException: if service class cannot be retrieved
424 '''
425 if not self.__service_class is None:
426 return self.__service_class
427
428 type = self.type
429
430 if not type and allow_get_type and self.uri:
431 dest_addr = dest_port = None
432 try:
433 dest_addr, dest_port = rospy.parse_rosrpc_uri(self.uri)
434 except:
435 pass
436 else:
437 import socket
438 import cStringIO
439 s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
440 try:
441
442 s.settimeout(0.5)
443 s.connect((dest_addr, dest_port))
444 header = { 'probe':'1', 'md5sum':'*',
445 'callerid':rospy.get_name(), 'service':self.name}
446 roslib.network.write_ros_handshake_header(s, header)
447 type = roslib.network.read_ros_handshake_header(s, cStringIO.StringIO(), 2048)
448 type = type['type']
449 except socket.error:
450 pass
451 except:
452 pass
453 finally:
454 if s is not None:
455 s.close()
456
457 import rosservice
458 if not type:
459 raise rosservice.ROSServiceException("Not valid type of service [%s]."%str(type))
460
461
462 service_class = roslib.message.get_service_class(type)
463
464
465 if service_class and self.type.endswith('Request') and \
466 not hasattr(service_class, "_request_class"):
467 type = type[:-7]
468 service_class = roslib.message.get_service_class(type)
469
470 if service_class is None:
471 pkg = roslib.names.resource_name_package(self.type)
472 raise rosservice.ROSServiceException("Unable to load type [%s].\n"%self.type+
473 "Have you typed 'make' in [%s]?"%pkg)
474 self.__service_class = service_class
475 return service_class
476
479 '''
480 The MasterInfo class stores informations about a ROS master.
481 Not thread safe!
482 '''
483 - def __init__(self, masteruri, mastername = None):
484 '''
485 Creates a new instance of the MasterInfo. The mastername will be extracted
486 from the masterui, if no name is given.
487 @param masteruri: The URI of the corresponding master
488 @type masteruri: str
489 @param mastername: The name of the ROS master. If no one is given, it will be
490 extracted from the masteruri.
491 @type mastername: str or None (Default: None)
492 '''
493 self.__masteruri = masteruri
494 self.__mastername = mastername
495 if mastername is None:
496 from urlparse import urlparse
497 o = urlparse(self.__masteruri)
498 self.__mastername = o.hostname
499 self.__nodelist = {}
500 self.__topiclist = {}
501 self.__servicelist = {}
502 self.__timestamp = 0
503 self.__timestamp_local = 0
504 self.check_ts = 0
505 '''@ivar: the last time, when the state of the ROS master retrieved'''
506
507 @staticmethod
509 '''
510 Creates a new instance of the MasterInfo from given list.
511 @see: L{listedState()}
512 @param l: the list returned by listedState()
513 @type l: list
514 @return: the new instance of the MasterInfo filled from list.
515 @rtype: MasterInfo
516 '''
517 if l is None:
518 return None
519 result = MasterInfo(l[2], l[3])
520 result.timestamp = float(l[0])
521 result.timestamp_local = float(l[1])
522 publishers = l[4]
523 subscribers = l[5]
524 services = l[6]
525 topicTypes = l[7]
526 nodes = l[8]
527 serviceProvider = l[9]
528
529 for pub, nodelist in publishers:
530 result.topics = pub
531 for n in nodelist:
532 result.nodes = n
533 result.getNode(n).publishedTopics = pub
534 result.getTopic(pub).publisherNodes = n
535
536 for sub, nodelist in subscribers:
537 result.topics = sub
538 for n in nodelist:
539 result.nodes = n
540 result.getNode(n).subscribedTopics = sub
541 result.getTopic(sub).subscriberNodes = n
542
543 for s, provider in services:
544 result.services = s
545 for n in provider:
546 result.nodes = n
547 result.getNode(n).services = s
548 result.getService(s).serviceProvider = n
549
550 for topic, type in topicTypes:
551 result.topics = topic
552 result.getTopic(topic).type = type
553
554 for nodename, uri, masteruri, pid, local in nodes:
555 result.nodes = nodename
556 result.getNode(nodename).uri = uri
557 result.getNode(nodename).masteruri = masteruri
558 result.getNode(nodename).pid = pid
559
560 for servicename, uri, masteruri, type, local in serviceProvider:
561 result.services = servicename
562 result.getService(servicename).uri = uri
563 result.getService(servicename).masteruri = masteruri
564 result.getService(servicename).type = type
565 return result
566
567 @property
569 '''
570 Returns the name of the ROS master. In most cases the ROS master name is the
571 name of the host, where the ROS master running. Although it can differ.
572 @rtype: C{str}
573 '''
574 return self.__mastername
575
576 @property
578 '''
579 Returns the URI of the ROS master.
580 @rtype: C{str}
581 '''
582 return self.__masteruri
583
584 @property
586 '''
587 The timestamp when this MasterInfo was first time filled with the
588 information. See L{self.check_ts} to get the time, when the information was
589 compared with the data of ROS Master.
590 @rtype: C{float}
591 '''
592 return self.__timestamp
593
594 @timestamp.setter
596 '''
597 Sets the timestamp of this instance
598 @param ts: the new timestamp
599 @type ts: C{float}
600 '''
601 self.__timestamp = ts
602 self.check_ts = ts
603 self.__timestamp_local = ts
604
605 @property
607 '''
608 The timestamp when this MasterInfo was first time filled with the
609 information. See L{self.check_ts} to get the time, when the information was
610 compared with the data of ROS Master.
611 @rtype: C{float}
612 '''
613 return self.__timestamp_local
614
615 @timestamp_local.setter
617 '''
618 Sets the timestamp of this instance
619 @param ts: the new timestamp
620 @type ts: C{float}
621 '''
622 self.__timestamp_local = ts
623
624 @property
626 '''
627 Returns the dictionary with node names and corresponding instances of L{NodeInfo}.
628 @rtype: C{dict(str:L{NodeInfo}, ...)}
629 '''
630 return self.__nodelist
631
632 @nodes.setter
634 '''
635 Adds a new L{NodeInfo} with given name.
636 @note: If the NodeInfo already exists, do nothing.
637 @param name: the name of new L{NodeInfo}
638 @type name: C{str}
639 '''
640 if (name is None) or not name:
641 return None
642 if not (name in self.__nodelist):
643 self.__nodelist[name] = NodeInfo(name, self.__masteruri)
644
645 @property
647 '''
648 Returns the list with node names
649 @rtype: C{[str, ...]}
650 '''
651
652 return self.__nodelist.keys()
653
654 @property
656 '''
657 Returns the list with node URI's.
658 @rtype: C{[str, ...]}
659 '''
660 uris = []
661 for node in self.__nodelist.itervalues():
662 uris.append(node.uri)
663 return uris
664
665 @property
667 '''
668 Returns the dictionary with topic names and corresponding L{TopicInfo} instances.
669 @rtype: C{dict(str:L{TopicInfo}, ...)}
670 '''
671 return self.__topiclist
672
673 @topics.setter
675 '''
676 Adds a new TopicInfo with given name. If the L{TopicInfo} already exists, do
677 nothing.
678 @param name: the name of new L{TopicInfo}
679 @type name: C{str}
680 '''
681 if (name is None) or not name:
682 return None
683 if not (name in self.__topiclist):
684 self.__topiclist[name] = TopicInfo(name)
685
686 @property
688 '''
689 Returns the list with topic names.
690 @rtype: C{[str, ...]}
691 '''
692 return self.__topiclist.keys()
693
694 @property
696 '''
697 Returns the dictionary with service names and corresponding L{ServiceInfo} instances.
698 @rtype: C{dict(str:L{ServiceInfo}, ...)}
699 '''
700 return self.__servicelist
701
702 @services.setter
704 '''
705 Adds a new L{ServiceInfo} with given name. If the L{ServiceInfo} already exists, do
706 nothing.
707 @param name: the name of new L{ServiceInfo}
708 @type name: C{str}
709 '''
710 if (name is None) or not name:
711 return None
712 if not (name in self.__servicelist):
713 self.__servicelist[name] = ServiceInfo(name, self.__masteruri)
714
715 @property
717 '''
718 Returns the list with service names.
719 @rtype: C{[str, ...]}
720 '''
721 return self.__servicelist.keys()
722
723 @property
725 '''
726 Returns the list with service URI's.
727 @rtype: C{[str, ...]}
728 '''
729 uris = []
730 for service in self.__servicelist.itervalues():
731 uris.append(service.uri)
732 return uris
733
735 '''
736 @param name: the name of the node
737 @type name: str
738 @return: the instance of the L{NodeInfo} with given name
739 @rtype: L{NodeInfo} or C{None}
740 '''
741 if (name is None) or not name:
742 return None
743 return self.__nodelist.get(name, None)
744
746 '''
747 Returns the node, which name ends with given suffix
748 @param suffix: the end of the name
749 @type suffix: C{str}
750 @return: the instance of the L{NodeInfo} with with given suffix
751 @rtype: L{NodeInfo} or C{None}
752 '''
753 if (suffix is None) or not suffix:
754 return None
755 for name, node in self.__nodelist.items():
756 if name.endswith(suffix):
757 return node
758 return None
759
761 '''
762 @param name: the name of the topic
763 @type name: C{str}
764 @return: the instance of the L{TopicInfo} with given name
765 @rtype: L{NodeInfo} or C{None}
766 '''
767 if (name is None) or not name:
768 return None
769 return self.__topiclist.get(name, None)
770
772 '''
773 @param name: the name of the service
774 @type name: C{str}
775 @return: the instance of the L{ServiceInfo} with given name
776 @rtype: L{ServiceInfo} or C{None}
777 '''
778 if (name is None) or not name:
779 return None
780 return self.__servicelist.get(name, None)
781
783 '''
784 Compares the master state with other master state. The timestamp will not be
785 compared.
786 @param other: the another L{MasterInfo} instance.
787 @type other: L{MasterInfo}
788 @return: True, if the states are equal.
789 @rtype: C{boolean}
790 '''
791
792
793
794
795 if (other is None):
796 return False
797 if (self.masteruri != other.masteruri):
798 return False
799 if (set(self.node_uris) ^ set(other.node_uris)):
800 return False
801
802
803
804
805 if (set(self.service_uris) ^ set(other.service_uris)):
806 return False
807 if (set(self.topic_names) ^ set(other.topic_names)):
808 return False
809
810 for name in self.node_names:
811 n1 = self.getNode(name)
812 n2 = other.getNode(name)
813 if not n1 is None and not n2 is None:
814 if n1.pid != n2.pid:
815 return False
816
817
818 if set(n1.publishedTopics) ^ set(n2.publishedTopics):
819 return False
820 if set(n1.subscribedTopics) ^ set(n2.subscribedTopics):
821 return False
822 if set(n1.services) ^ set(n2.services):
823 return False
824 return True
825
826
827
828
829
831 return not self.__eq__(other)
832
891
892
893
894
896 '''
897 Returns a extended roscore state.
898 @return: complete roscore state as
899
900 C{(stamp, stamp_local, masteruri, name, publishers, subscribers, services, topicTypes, nodes, serviceProvider)}
901
902 - C{publishers} is of the form
903
904 C{[ [topic1, [topic1Publisher1...topic1PublisherN]] ... ]}
905
906 - C{subscribers} is of the form
907
908 C{[ [topic1, [topic1Subscriber1...topic1SubscriberN]] ... ]}
909
910 - C{services} is of the form
911
912 C{[ [service1, [service1Provider1...service1ProviderN]] ... ]}
913
914 - C{topicTypes} is a list of
915
916 C{[ [topicName1, topicType1], ... ]}
917
918 - C{nodes} is a list of (the pid of remote Nodes will not be resolved)
919
920 C{[nodename, XML-RPC URI, origin ROS_MASTER_URI, pid, E{lb} local, remote E{rb}]}
921
922 - C{serviceProvider} is a list of (the type, serviceClass and args of remote Services will not be resolved)
923
924 C{[service, XML-RPC URI, origin ROS_MASTER_URI, type, E{lb} local, remote E{rb}]}
925
926 @rtype: C{(float,
927 float,
928 str,
929 str,
930 [ [str,[str] ] ],
931 [ [str,[str] ] ],
932 [ [str,[str] ] ],
933 [ [str,str] ],
934 [ [str,str,str,int,str] ],
935 [ [str,str,str,str,str] ])}
936 '''
937 stamp = str(self.timestamp)
938 stamp_local = str(self.timestamp_local)
939 publishers = []
940 subscribers = []
941 services = []
942 topicTypes = []
943 nodes = []
944 serviceProvider = []
945 for name, topic in self.topics.items():
946 pn = topic.publisherNodes
947 if pn:
948 publishers.append((name, pn))
949 sn = topic.subscriberNodes
950 if sn:
951 subscribers.append((name, sn))
952 topicTypes.append((name, topic.type))
953 for name, service in self.services.items():
954 services.append((name, service.serviceProvider))
955 serviceProvider.append((name, service.uri, str(service.masteruri), service.type if not service.type is None else '', 'local' if service.isLocal else 'remote'))
956 for name, node in self.nodes.items():
957 nodes.append((name, node.uri, str(node.masteruri), node.pid, 'local' if node.isLocal else 'remote'))
958
959 return (stamp, stamp_local, self.masteruri, self.mastername, publishers, subscribers, services, topicTypes, nodes, serviceProvider)
960
961
962
963