PageRenderTime 47ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/tools/rosnode/src/rosnode/__init__.py

https://gitlab.com/F34140r/ros_comm
Python | 805 lines | 613 code | 42 blank | 150 comment | 87 complexity | 32023c1d3df98d2a755de7ee8a44dc43 MD5 | raw file
Possible License(s): LGPL-2.1
  1. #!/usr/bin/env python
  2. # Software License Agreement (BSD License)
  3. #
  4. # Copyright (c) 2008, Willow Garage, Inc.
  5. # All rights reserved.
  6. #
  7. # Redistribution and use in source and binary forms, with or without
  8. # modification, are permitted provided that the following conditions
  9. # are met:
  10. #
  11. # * Redistributions of source code must retain the above copyright
  12. # notice, this list of conditions and the following disclaimer.
  13. # * Redistributions in binary form must reproduce the above
  14. # copyright notice, this list of conditions and the following
  15. # disclaimer in the documentation and/or other materials provided
  16. # with the distribution.
  17. # * Neither the name of Willow Garage, Inc. nor the names of its
  18. # contributors may be used to endorse or promote products derived
  19. # from this software without specific prior written permission.
  20. #
  21. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  22. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  23. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  24. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  25. # COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  26. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  27. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  28. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  29. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  30. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  31. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32. # POSSIBILITY OF SUCH DAMAGE.
  33. #
  34. # Revision $Id$
  35. """
  36. rosnode implements the rosnode command-line tool and also provides a
  37. library for retrieving ROS Node information.
  38. """
  39. from __future__ import print_function
  40. import os
  41. import errno
  42. import sys
  43. import socket
  44. import time
  45. import xmlrpclib
  46. try: #py3k
  47. import urllib.parse as urlparse
  48. except ImportError:
  49. import urlparse
  50. from optparse import OptionParser
  51. import rosgraph
  52. import rosgraph.names
  53. import rostopic
  54. NAME='rosnode'
  55. ID = '/rosnode'
  56. class ROSNodeException(Exception):
  57. """
  58. rosnode base exception type
  59. """
  60. pass
  61. class ROSNodeIOException(ROSNodeException):
  62. """
  63. Exceptions for communication-related (i/o) errors, generally due to Master or Node network communication issues.
  64. """
  65. pass
  66. # need for calling node APIs
  67. def _succeed(args):
  68. code, msg, val = args
  69. if code != 1:
  70. raise ROSNodeException("remote call failed: %s"%msg)
  71. return val
  72. _caller_apis = {}
  73. def get_api_uri(master, caller_id, skip_cache=False):
  74. """
  75. @param master: XMLRPC handle to ROS Master
  76. @type master: rosgraph.Master
  77. @param caller_id: node name
  78. @type caller_id: str
  79. @param skip_cache: flag to skip cached data and force to lookup node from master
  80. @type skip_cache: bool
  81. @return: xmlrpc URI of caller_id
  82. @rtype: str
  83. @raise ROSNodeIOException: if unable to communicate with master
  84. """
  85. caller_api = _caller_apis.get(caller_id, None)
  86. if not caller_api or skip_cache:
  87. try:
  88. caller_api = master.lookupNode(caller_id)
  89. _caller_apis[caller_id] = caller_api
  90. except rosgraph.MasterError:
  91. return None
  92. except socket.error:
  93. raise ROSNodeIOException("Unable to communicate with master!")
  94. return caller_api
  95. def lookup_uri(master, system_state, topic, uri):
  96. for l in system_state[0:2]:
  97. for entry in l:
  98. if entry[0] == topic:
  99. for n in entry[1]:
  100. if rostopic.get_api(master, n) == uri:
  101. return '%s (%s)' % (n, uri)
  102. return uri
  103. def get_node_names(namespace=None):
  104. """
  105. @param namespace: optional namespace to scope return values by. Namespace must already be resolved.
  106. @type namespace: str
  107. @return: list of node caller IDs
  108. @rtype: [str]
  109. @raise ROSNodeIOException: if unable to communicate with master
  110. """
  111. master = rosgraph.Master(ID)
  112. try:
  113. state = master.getSystemState()
  114. except socket.error:
  115. raise ROSNodeIOException("Unable to communicate with master!")
  116. nodes = []
  117. if namespace:
  118. # canonicalize namespace with leading/trailing slash
  119. g_ns = rosgraph.names.make_global_ns(namespace)
  120. for s in state:
  121. for t, l in s:
  122. nodes.extend([n for n in l if n.startswith(g_ns) or n == namespace])
  123. else:
  124. for s in state:
  125. for t, l in s:
  126. nodes.extend(l)
  127. return list(set(nodes))
  128. def get_machines_by_nodes():
  129. """
  130. Find machines connected to nodes. This is a very costly procedure as it
  131. must do N lookups with the Master, where N is the number of nodes.
  132. @return: list of machines connected
  133. @rtype: [str]
  134. @raise ROSNodeIOException: if unable to communicate with master
  135. """
  136. master = rosgraph.Master(ID)
  137. # get all the node names, lookup their uris, parse the hostname
  138. # from the uris, and then compare the resolved hostname against
  139. # the requested machine name.
  140. machines = []
  141. node_names = get_node_names()
  142. for n in node_names:
  143. try:
  144. uri = master.lookupNode(n)
  145. h = urlparse.urlparse(uri).hostname
  146. if h not in machines:
  147. machines.append(h)
  148. except socket.error:
  149. raise ROSNodeIOException("Unable to communicate with master!")
  150. except rosgraph.MasterError:
  151. # it's possible that the state changes as we are doing lookups. this is a soft-fail
  152. continue
  153. return machines
  154. def get_nodes_by_machine(machine):
  155. """
  156. Find nodes by machine name. This is a very costly procedure as it
  157. must do N lookups with the Master, where N is the number of nodes.
  158. @return: list of nodes on the specified machine
  159. @rtype: [str]
  160. @raise ROSNodeException: if machine name cannot be resolved to an address
  161. @raise ROSNodeIOException: if unable to communicate with master
  162. """
  163. import urlparse
  164. master = rosgraph.Master(ID)
  165. try:
  166. machine_actual = [host[4][0] for host in socket.getaddrinfo(machine, 0, 0, 0, socket.SOL_TCP)]
  167. except:
  168. raise ROSNodeException("cannot resolve machine name [%s] to address"%machine)
  169. # get all the node names, lookup their uris, parse the hostname
  170. # from the uris, and then compare the resolved hostname against
  171. # the requested machine name.
  172. matches = [machine] + machine_actual
  173. not_matches = [] # cache lookups
  174. node_names = get_node_names()
  175. retval = []
  176. for n in node_names:
  177. try:
  178. try:
  179. uri = master.lookupNode(n)
  180. except rosgraph.MasterError:
  181. # it's possible that the state changes as we are doing lookups. this is a soft-fail
  182. continue
  183. h = urlparse.urlparse(uri).hostname
  184. if h in matches:
  185. retval.append(n)
  186. elif h in not_matches:
  187. continue
  188. else:
  189. r = [host[4][0] for host in socket.getaddrinfo(h, 0, 0, 0, socket.SOL_TCP)]
  190. if set(r) & set(machine_actual):
  191. matches.append(r)
  192. retval.append(n)
  193. else:
  194. not_matches.append(r)
  195. except socket.error:
  196. raise ROSNodeIOException("Unable to communicate with master!")
  197. return retval
  198. def kill_nodes(node_names):
  199. """
  200. Call shutdown on the specified nodes
  201. @return: list of nodes that shutdown was called on successfully and list of failures
  202. @rtype: ([str], [str])
  203. """
  204. master = rosgraph.Master(ID)
  205. success = []
  206. fail = []
  207. tocall = []
  208. try:
  209. # lookup all nodes keeping track of lookup failures for return value
  210. for n in node_names:
  211. try:
  212. uri = master.lookupNode(n)
  213. tocall.append([n, uri])
  214. except:
  215. fail.append(n)
  216. except socket.error:
  217. raise ROSNodeIOException("Unable to communicate with master!")
  218. for n, uri in tocall:
  219. # the shutdown call can sometimes fail to succeed if the node
  220. # tears down during the request handling, so we assume success
  221. try:
  222. p = xmlrpclib.ServerProxy(uri)
  223. _succeed(p.shutdown(ID, 'user request'))
  224. except:
  225. pass
  226. success.append(n)
  227. return success, fail
  228. def _sub_rosnode_listnodes(namespace=None, list_uri=False, list_all=False):
  229. """
  230. Subroutine for rosnode_listnodes(). Composes list of strings to print to screen.
  231. @param namespace: (default None) namespace to scope list to.
  232. @type namespace: str
  233. @param list_uri: (default False) return uris of nodes instead of names.
  234. @type list_uri: bool
  235. @param list_all: (default False) return names and uris of nodes as combined strings
  236. @type list_all: bool
  237. @return: new-line separated string containing list of all nodes
  238. @rtype: str
  239. """
  240. master = rosgraph.Master(ID)
  241. nodes = get_node_names(namespace)
  242. nodes.sort()
  243. if list_all:
  244. return '\n'.join(["%s \t%s"%(get_api_uri(master, n) or 'unknown address', n) for n in nodes])
  245. elif list_uri:
  246. return '\n'.join([(get_api_uri(master, n) or 'unknown address') for n in nodes])
  247. else:
  248. return '\n'.join(nodes)
  249. def rosnode_listnodes(namespace=None, list_uri=False, list_all=False):
  250. """
  251. Print list of all ROS nodes to screen.
  252. @param namespace: namespace to scope list to
  253. @type namespace: str
  254. @param list_uri: print uris of nodes instead of names
  255. @type list_uri: bool
  256. @param list_all: print node names and uris
  257. @param list_all: bool
  258. """
  259. print(_sub_rosnode_listnodes(namespace=namespace, list_uri=list_uri, list_all=list_all))
  260. def rosnode_ping(node_name, max_count=None, verbose=False):
  261. """
  262. Test connectivity to node by calling its XMLRPC API
  263. @param node_name: name of node to ping
  264. @type node_name: str
  265. @param max_count: number of ping requests to make
  266. @type max_count: int
  267. @param verbose: print ping information to screen
  268. @type verbose: bool
  269. @return: True if node pinged
  270. @rtype: bool
  271. @raise ROSNodeIOException: if unable to communicate with master
  272. """
  273. master = rosgraph.Master(ID)
  274. node_api = get_api_uri(master,node_name)
  275. if not node_api:
  276. print("cannot ping [%s]: unknown node"%node_name, file=sys.stderr)
  277. return False
  278. timeout = 3.
  279. if verbose:
  280. print("pinging %s with a timeout of %ss"%(node_name, timeout))
  281. socket.setdefaulttimeout(timeout)
  282. node = xmlrpclib.ServerProxy(node_api)
  283. lastcall = 0.
  284. count = 0
  285. acc = 0.
  286. try:
  287. while True:
  288. try:
  289. start = time.time()
  290. pid = _succeed(node.getPid(ID))
  291. end = time.time()
  292. dur = (end-start)*1000.
  293. acc += dur
  294. count += 1
  295. if verbose:
  296. print("xmlrpc reply from %s\ttime=%fms"%(node_api, dur))
  297. # 1s between pings
  298. except socket.error as e:
  299. # 3786: catch ValueError on unpack as socket.error is not always a tuple
  300. try:
  301. # #3659
  302. errnum, msg = e
  303. if errnum == -2: #name/service unknown
  304. p = urlparse.urlparse(node_api)
  305. print("ERROR: Unknown host [%s] for node [%s]"%(p.hostname, node_name), file=sys.stderr)
  306. elif errnum == errno.ECONNREFUSED:
  307. # check if node url has changed
  308. new_node_api = get_api_uri(master,node_name, skip_cache=True)
  309. if new_node_api != node_api:
  310. if verbose:
  311. print("node url has changed from [%s] to [%s], retrying to ping"%(node_api, new_node_api))
  312. node_api = new_node_api
  313. node = xmlrpclib.ServerProxy(node_api)
  314. continue
  315. print("ERROR: connection refused to [%s]"%(node_api), file=sys.stderr)
  316. else:
  317. print("connection to [%s] timed out"%node_name, file=sys.stderr)
  318. return False
  319. except ValueError:
  320. print("unknown network error contacting node: %s"%(str(e)))
  321. if max_count and count >= max_count:
  322. break
  323. time.sleep(1.0)
  324. except KeyboardInterrupt:
  325. pass
  326. if verbose and count > 1:
  327. print("ping average: %fms"%(acc/count))
  328. return True
  329. def rosnode_ping_all(verbose=False):
  330. """
  331. Ping all running nodes
  332. @return [str], [str]: pinged nodes, un-pingable nodes
  333. @raise ROSNodeIOException: if unable to communicate with master
  334. """
  335. master = rosgraph.Master(ID)
  336. try:
  337. state = master.getSystemState()
  338. except socket.error:
  339. raise ROSNodeIOException("Unable to communicate with master!")
  340. nodes = []
  341. for s in state:
  342. for t, l in s:
  343. nodes.extend(l)
  344. nodes = list(set(nodes)) #uniq
  345. if verbose:
  346. print("Will ping the following nodes: \n"+''.join([" * %s\n"%n for n in nodes]))
  347. pinged = []
  348. unpinged = []
  349. for node in nodes:
  350. if rosnode_ping(node, max_count=1, verbose=verbose):
  351. pinged.append(node)
  352. else:
  353. unpinged.append(node)
  354. return pinged, unpinged
  355. def cleanup_master_blacklist(master, blacklist):
  356. """
  357. Remove registrations from ROS Master that do not match blacklist.
  358. @param master: XMLRPC handle to ROS Master
  359. @type master: xmlrpclib.ServerProxy
  360. @param blacklist: list of nodes to scrub
  361. @type blacklist: [str]
  362. """
  363. pubs, subs, srvs = master.getSystemState()
  364. for n in blacklist:
  365. print("Unregistering", n)
  366. node_api = get_api_uri(master, n)
  367. for t, l in pubs:
  368. if n in l:
  369. master_n = rosgraph.Master(n)
  370. master_n.unregisterPublisher(t, node_api)
  371. for t, l in subs:
  372. if n in l:
  373. master_n = rosgraph.Master(n)
  374. master_n.unregisterSubscriber(t, node_api)
  375. for s, l in srvs:
  376. if n in l:
  377. service_api = master.lookupService(s)
  378. master_n = rosgraph.Master(n)
  379. master_n.unregisterService(s, service_api)
  380. def cleanup_master_whitelist(master, whitelist):
  381. """
  382. Remove registrations from ROS Master that do not match whitelist.
  383. @param master: XMLRPC handle to ROS Master
  384. @type master: xmlrpclib.ServerProxy
  385. @param whitelist: list of nodes to keep
  386. @type whitelist: list of nodes to keep
  387. """
  388. pubs, subs, srvs = master.getSystemState()
  389. for t, l in pubs:
  390. for n in l:
  391. if n not in whitelist:
  392. node_api = get_api_uri(master, n)
  393. master_n = rosgraph.Master(n)
  394. master_n.unregisterPublisher(t, node_api)
  395. for t, l in subs:
  396. for n in l:
  397. if n not in whitelist:
  398. node_api = get_api_uri(master, n)
  399. master_n = rosgraph.Master(n)
  400. master_n.unregisterSubscriber(t, node_api)
  401. for s, l in srvs:
  402. for n in l:
  403. if n not in whitelist:
  404. service_api = master.lookupService(s)
  405. master_n = rosgraph.Master(n)
  406. master_n.unregisterService(s, service_api)
  407. def rosnode_cleanup():
  408. """
  409. This is a semi-hidden routine for cleaning up stale node
  410. registration information on the ROS Master. The intent is to
  411. remove this method once Master TTLs are properly implemented.
  412. """
  413. pinged, unpinged = rosnode_ping_all()
  414. if unpinged:
  415. master = rosgraph.Master(ID)
  416. print("Unable to contact the following nodes:")
  417. print('\n'.join(' * %s'%n for n in unpinged))
  418. print("Warning: these might include alive and functioning nodes, e.g. in unstable networks.")
  419. print("Cleanup will purge all information about these nodes from the master.")
  420. print("Please type y or n to continue:")
  421. input = sys.stdin.readline()
  422. while not input.strip() in ['y', 'n']:
  423. input = sys.stdin.readline()
  424. if input.strip() == 'n':
  425. print('aborting')
  426. return
  427. cleanup_master_blacklist(master, unpinged)
  428. print("done")
  429. def get_node_info_description(node_name):
  430. def topic_type(t, pub_topics):
  431. matches = [t_type for t_name, t_type in pub_topics if t_name == t]
  432. if matches:
  433. return matches[0]
  434. return 'unknown type'
  435. master = rosgraph.Master(ID)
  436. # go through the master system state first
  437. try:
  438. state = master.getSystemState()
  439. pub_topics = master.getPublishedTopics('/')
  440. except socket.error:
  441. raise ROSNodeIOException("Unable to communicate with master!")
  442. pubs = [t for t, l in state[0] if node_name in l]
  443. subs = [t for t, l in state[1] if node_name in l]
  444. srvs = [t for t, l in state[2] if node_name in l]
  445. buff = "Node [%s]"%node_name
  446. if pubs:
  447. buff += "\nPublications: \n"
  448. buff += '\n'.join([" * %s [%s]"%(l, topic_type(l, pub_topics)) for l in pubs]) + '\n'
  449. else:
  450. buff += "\nPublications: None\n"
  451. if subs:
  452. buff += "\nSubscriptions: \n"
  453. buff += '\n'.join([" * %s [%s]"%(l, topic_type(l, pub_topics)) for l in subs]) + '\n'
  454. else:
  455. buff += "\nSubscriptions: None\n"
  456. if srvs:
  457. buff += "\nServices: \n"
  458. buff += '\n'.join([" * %s"%l for l in srvs]) + '\n'
  459. else:
  460. buff += "\nServices: None\n"
  461. return buff
  462. def get_node_connection_info_description(node_api, master):
  463. #turn down timeout on socket library
  464. socket.setdefaulttimeout(5.0)
  465. node = xmlrpclib.ServerProxy(node_api)
  466. system_state = master.getSystemState()
  467. try:
  468. pid = _succeed(node.getPid(ID))
  469. buff = "Pid: %s\n"%pid
  470. #master_uri = _succeed(node.getMasterUri(ID))
  471. businfo = _succeed(node.getBusInfo(ID))
  472. if businfo:
  473. buff += "Connections:\n"
  474. for info in businfo:
  475. dest_id = info[1]
  476. direction = info[2]
  477. transport = info[3]
  478. topic = info[4]
  479. if len(info) > 5:
  480. connected = info[5]
  481. else:
  482. connected = True #backwards compatibility
  483. if connected:
  484. buff += " * topic: %s\n"%topic
  485. # older ros publisher implementations don't report a URI
  486. buff += " * to: %s\n"%lookup_uri(master, system_state, topic, dest_id)
  487. if direction == 'i':
  488. buff += " * direction: inbound\n"
  489. elif direction == 'o':
  490. buff += " * direction: outbound\n"
  491. else:
  492. buff += " * direction: unknown\n"
  493. buff += " * transport: %s\n"%transport
  494. except socket.error:
  495. raise ROSNodeIOException("Communication with node[%s] failed!"%(node_api))
  496. return buff
  497. def rosnode_info(node_name):
  498. """
  499. Print information about node, including subscriptions and other debugging information. This will query the node over the network.
  500. @param node_name: name of ROS node
  501. @type node_name: str
  502. @raise ROSNodeIOException: if unable to communicate with master
  503. """
  504. def topic_type(t, pub_topics):
  505. matches = [t_type for t_name, t_type in pub_topics if t_name == t]
  506. if matches:
  507. return matches[0]
  508. return 'unknown type'
  509. master = rosgraph.Master(ID)
  510. node_name = rosgraph.names.script_resolve_name('rosnode', node_name)
  511. print('-'*80)
  512. print(get_node_info_description(node_name))
  513. node_api = get_api_uri(master, node_name)
  514. if not node_api:
  515. print("cannot contact [%s]: unknown node"%node_name, file=sys.stderr)
  516. return
  517. print("\ncontacting node %s ..."%node_api)
  518. print(get_node_connection_info_description(node_api, master))
  519. # backwards compatibility (deprecated)
  520. rosnode_debugnode = rosnode_info
  521. def _rosnode_cmd_list(argv):
  522. """
  523. Implements rosnode 'list' command.
  524. """
  525. args = argv[2:]
  526. parser = OptionParser(usage="usage: %prog list", prog=NAME)
  527. parser.add_option("-u",
  528. dest="list_uri", default=False,
  529. action="store_true",
  530. help="list XML-RPC URIs")
  531. parser.add_option("-a","--all",
  532. dest="list_all", default=False,
  533. action="store_true",
  534. help="list all information")
  535. (options, args) = parser.parse_args(args)
  536. namespace = None
  537. if len(args) > 1:
  538. parser.error("invalid args: you may only specify one namespace")
  539. elif len(args) == 1:
  540. namespace = rosgraph.names.script_resolve_name('rostopic', args[0])
  541. rosnode_listnodes(namespace=namespace, list_uri=options.list_uri, list_all=options.list_all)
  542. def _rosnode_cmd_info(argv):
  543. """
  544. Implements rosnode 'info' command.
  545. """
  546. args = argv[2:]
  547. parser = OptionParser(usage="usage: %prog info node1 [node2...]", prog=NAME)
  548. (options, args) = parser.parse_args(args)
  549. if not args:
  550. parser.error("You must specify at least one node name")
  551. for node in args:
  552. rosnode_info(node)
  553. def _rosnode_cmd_machine(argv):
  554. """
  555. Implements rosnode 'machine' command.
  556. @raise ROSNodeException: if user enters in unrecognized machine name
  557. """
  558. args = argv[2:]
  559. parser = OptionParser(usage="usage: %prog machine [machine-name]", prog=NAME)
  560. (options, args) = parser.parse_args(args)
  561. if len(args) > 1:
  562. parser.error("please enter only one machine name")
  563. elif len(args) == 0:
  564. machines = get_machines_by_nodes()
  565. machines.sort()
  566. print('\n'.join(machines))
  567. else:
  568. nodes = get_nodes_by_machine(args[0])
  569. nodes.sort()
  570. print('\n'.join(nodes))
  571. def _rosnode_cmd_kill(argv):
  572. """
  573. Implements rosnode 'kill' command.
  574. @raise ROSNodeException: if user enters in unrecognized nodes
  575. """
  576. args = argv[2:]
  577. parser = OptionParser(usage="usage: %prog kill [node]...", prog=NAME)
  578. parser.add_option("-a","--all",
  579. dest="kill_all", default=False,
  580. action="store_true",
  581. help="kill all nodes")
  582. (options, args) = parser.parse_args(args)
  583. if options.kill_all:
  584. if args:
  585. parser.error("invalid arguments with kill all (-a) option")
  586. args = get_node_names()
  587. args.sort()
  588. elif not args:
  589. node_list = get_node_names()
  590. node_list.sort()
  591. if not node_list:
  592. print("No nodes running", file=sys.stderr)
  593. return 0
  594. sys.stdout.write('\n'.join(["%s. %s"%(i+1, n) for i,n in enumerate(node_list)]))
  595. sys.stdout.write("\n\nPlease enter the number of the node you wish to kill.\n> ")
  596. selection = ''
  597. while not selection:
  598. selection = sys.stdin.readline().strip()
  599. try:
  600. selection = int(selection)
  601. if selection <= 0:
  602. print("ERROR: invalid selection. Please enter a number (ctrl-C to cancel)")
  603. except:
  604. print("ERROR: please enter a number (ctrl-C to cancel)")
  605. sys.stdout.flush()
  606. selection = ''
  607. args = [node_list[selection - 1]]
  608. else:
  609. # validate args
  610. args = [rosgraph.names.script_resolve_name(ID, n) for n in args]
  611. node_list = get_node_names()
  612. unknown = [n for n in args if not n in node_list]
  613. if unknown:
  614. raise ROSNodeException("Unknown node(s):\n"+'\n'.join([" * %s"%n for n in unknown]))
  615. if len(args) > 1:
  616. print("killing:\n"+'\n'.join([" * %s"%n for n in args]))
  617. else:
  618. print("killing %s"%(args[0]))
  619. success, fail = kill_nodes(args)
  620. if fail:
  621. print("ERROR: Failed to kill:\n"+'\n'.join([" * %s"%n for n in fail]), file=sys.stderr)
  622. return 1
  623. print("killed")
  624. return 0
  625. def _rosnode_cmd_cleanup(argv):
  626. """
  627. Implements rosnode 'cleanup' command.
  628. @param argv: command-line args
  629. @type argv: [str]
  630. """
  631. args = argv[2:]
  632. parser = OptionParser(usage="usage: %prog cleanup", prog=NAME)
  633. (options, args) = parser.parse_args(args)
  634. rosnode_cleanup()
  635. def _rosnode_cmd_ping(argv):
  636. """
  637. Implements rosnode 'ping' command.
  638. @param argv: command-line args
  639. @type argv: [str]
  640. """
  641. args = argv[2:]
  642. parser = OptionParser(usage="usage: %prog ping [options] <node>", prog=NAME)
  643. parser.add_option("--all", "-a",
  644. dest="ping_all", default=False,
  645. action="store_true",
  646. help="ping all nodes")
  647. parser.add_option("-c",
  648. dest="ping_count", default=None, metavar="COUNT",type="int",
  649. help="number of pings to send. Not available with --all")
  650. (options, args) = parser.parse_args(args)
  651. node_name = None
  652. if not options.ping_all:
  653. if not args:
  654. try:
  655. parser.error("Please enter a node to ping. Available nodes are:\n"+_sub_rosnode_listnodes())
  656. except:
  657. # master is offline, but user did have invalid usage, so display correct usage first
  658. parser.error("Please enter a node to ping")
  659. elif len(args) > 1:
  660. parser.error("you may only specify one input node")
  661. elif len(args) == 1:
  662. node_name = rosgraph.names.script_resolve_name('rosnode', args[0])
  663. print("rosnode: node is [%s]"%node_name)
  664. else:
  665. if args:
  666. parser.error("Invalid arguments '%s' used with --all"%(' '.join(args)))
  667. elif options.ping_count:
  668. parser.error("-c may not be used with --all")
  669. if options.ping_all:
  670. rosnode_ping_all(verbose=True)
  671. else:
  672. rosnode_ping(node_name, verbose=True, max_count=options.ping_count)
  673. def _fullusage(return_error=True):
  674. """
  675. Prints rosnode usage information.
  676. @param return_error whether to exit with error code os.EX_USAGE
  677. """
  678. print("""rosnode is a command-line tool for printing information about ROS Nodes.
  679. Commands:
  680. \trosnode ping\ttest connectivity to node
  681. \trosnode list\tlist active nodes
  682. \trosnode info\tprint information about node
  683. \trosnode machine\tlist nodes running on a particular machine or list machines
  684. \trosnode kill\tkill a running node
  685. \trosnode cleanup\tpurge registration information of unreachable nodes
  686. Type rosnode <command> -h for more detailed usage, e.g. 'rosnode ping -h'
  687. """)
  688. if return_error:
  689. sys.exit(getattr(os, 'EX_USAGE', 1))
  690. else:
  691. sys.exit(0)
  692. def rosnodemain(argv=None):
  693. """
  694. Prints rosnode main entrypoint.
  695. @param argv: override sys.argv
  696. @param argv: [str]
  697. """
  698. if argv == None:
  699. argv = sys.argv
  700. if len(argv) == 1:
  701. _fullusage()
  702. try:
  703. command = argv[1]
  704. if command == 'ping':
  705. sys.exit(_rosnode_cmd_ping(argv) or 0)
  706. elif command == 'list':
  707. sys.exit(_rosnode_cmd_list(argv) or 0)
  708. elif command == 'info':
  709. sys.exit(_rosnode_cmd_info(argv) or 0)
  710. elif command == 'machine':
  711. sys.exit(_rosnode_cmd_machine(argv) or 0)
  712. elif command == 'cleanup':
  713. sys.exit(_rosnode_cmd_cleanup(argv) or 0)
  714. elif command == 'kill':
  715. sys.exit(_rosnode_cmd_kill(argv) or 0)
  716. elif command == '--help':
  717. _fullusage(False)
  718. else:
  719. _fullusage()
  720. except socket.error:
  721. print("Network communication failed. Most likely failed to communicate with master.", file=sys.stderr)
  722. except rosgraph.MasterError as e:
  723. print("ERROR: "+str(e), file=sys.stderr)
  724. except ROSNodeException as e:
  725. print("ERROR: "+str(e), file=sys.stderr)
  726. except KeyboardInterrupt:
  727. pass