PageRenderTime 33ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/tools/rostopic/src/rostopic/__init__.py

https://gitlab.com/F34140r/ros_comm
Python | 1675 lines | 1381 code | 96 blank | 198 comment | 187 complexity | 81d1099697704b01387efa2d632869ca 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. # make sure we aren't using floor division
  36. from __future__ import division, print_function
  37. NAME='rostopic'
  38. import os
  39. import sys
  40. import math
  41. import socket
  42. import time
  43. import traceback
  44. import yaml
  45. import xmlrpclib
  46. from operator import itemgetter
  47. from urlparse import urlparse
  48. import genpy
  49. import roslib.message
  50. import rosgraph
  51. #TODO: lazy-import rospy or move rospy-dependent routines to separate location
  52. import rospy
  53. class ROSTopicException(Exception):
  54. """
  55. Base exception class of rostopic-related errors
  56. """
  57. pass
  58. class ROSTopicIOException(ROSTopicException):
  59. """
  60. rostopic errors related to network I/O failures
  61. """
  62. pass
  63. def _check_master():
  64. """
  65. Make sure that master is available
  66. :raises: :exc:`ROSTopicException` If unable to successfully communicate with master
  67. """
  68. try:
  69. rosgraph.Master('/rostopic').getPid()
  70. except socket.error:
  71. raise ROSTopicIOException("Unable to communicate with master!")
  72. def _master_get_topic_types(master):
  73. try:
  74. val = master.getTopicTypes()
  75. except xmlrpclib.Fault:
  76. #TODO: remove, this is for 1.1
  77. sys.stderr.write("WARNING: rostopic is being used against an older version of ROS/roscore\n")
  78. val = master.getPublishedTopics('/')
  79. return val
  80. class ROSTopicHz(object):
  81. """
  82. ROSTopicHz receives messages for a topic and computes frequency stats
  83. """
  84. def __init__(self, window_size, filter_expr=None):
  85. import threading
  86. self.lock = threading.Lock()
  87. self.last_printed_tn = 0
  88. self.msg_t0 = -1.
  89. self.msg_tn = 0
  90. self.times =[]
  91. self.filter_expr = filter_expr
  92. # can't have infinite window size due to memory restrictions
  93. if window_size < 0:
  94. window_size = 50000
  95. self.window_size = window_size
  96. def callback_hz(self, m):
  97. """
  98. ros sub callback
  99. :param m: Message instance
  100. """
  101. # #694: ignore messages that don't match filter
  102. if self.filter_expr is not None and not self.filter_expr(m):
  103. return
  104. with self.lock:
  105. curr_rostime = rospy.get_rostime()
  106. # time reset
  107. if curr_rostime.is_zero():
  108. if len(self.times) > 0:
  109. print("time has reset, resetting counters")
  110. self.times = []
  111. return
  112. curr = curr_rostime.to_sec()
  113. if self.msg_t0 < 0 or self.msg_t0 > curr:
  114. self.msg_t0 = curr
  115. self.msg_tn = curr
  116. self.times = []
  117. else:
  118. self.times.append(curr - self.msg_tn)
  119. self.msg_tn = curr
  120. #only keep statistics for the last 10000 messages so as not to run out of memory
  121. if len(self.times) > self.window_size - 1:
  122. self.times.pop(0)
  123. def print_hz(self):
  124. """
  125. print the average publishing rate to screen
  126. """
  127. if not self.times:
  128. return
  129. elif self.msg_tn == self.last_printed_tn:
  130. print("no new messages")
  131. return
  132. with self.lock:
  133. #frequency
  134. # kwc: In the past, the rate decayed when a publisher
  135. # dies. Now, we use the last received message to perform
  136. # the calculation. This change was made because we now
  137. # report a count and keep track of last_printed_tn. This
  138. # makes it easier for users to see when a publisher dies,
  139. # so the decay is no longer necessary.
  140. n = len(self.times)
  141. #rate = (n - 1) / (rospy.get_time() - self.msg_t0)
  142. mean = sum(self.times) / n
  143. rate = 1./mean if mean > 0. else 0
  144. #std dev
  145. std_dev = math.sqrt(sum((x - mean)**2 for x in self.times) /n)
  146. # min and max
  147. max_delta = max(self.times)
  148. min_delta = min(self.times)
  149. self.last_printed_tn = self.msg_tn
  150. print("average rate: %.3f\n\tmin: %.3fs max: %.3fs std dev: %.5fs window: %s"%(rate, min_delta, max_delta, std_dev, n+1))
  151. def _sleep(duration):
  152. rospy.rostime.wallsleep(duration)
  153. def _rostopic_hz(topic, window_size=-1, filter_expr=None):
  154. """
  155. Periodically print the publishing rate of a topic to console until
  156. shutdown
  157. :param topic: topic name, ``str``
  158. :param window_size: number of messages to average over, -1 for infinite, ``int``
  159. :param filter_expr: Python filter expression that is called with m, the message instance
  160. """
  161. msg_class, real_topic, _ = get_topic_class(topic, blocking=True) #pause hz until topic is published
  162. if rospy.is_shutdown():
  163. return
  164. rospy.init_node(NAME, anonymous=True)
  165. rt = ROSTopicHz(window_size, filter_expr=filter_expr)
  166. # we use a large buffer size as we don't know what sort of messages we're dealing with.
  167. # may parameterize this in the future
  168. if filter_expr is not None:
  169. # have to subscribe with topic_type
  170. sub = rospy.Subscriber(real_topic, msg_class, rt.callback_hz)
  171. else:
  172. sub = rospy.Subscriber(real_topic, rospy.AnyMsg, rt.callback_hz)
  173. print("subscribed to [%s]"%real_topic)
  174. while not rospy.is_shutdown():
  175. _sleep(1.0)
  176. rt.print_hz()
  177. class ROSTopicBandwidth(object):
  178. def __init__(self, window_size=100):
  179. import threading
  180. self.lock = threading.Lock()
  181. self.last_printed_tn = 0
  182. self.sizes =[]
  183. self.times =[]
  184. self.window_size = window_size or 100
  185. def callback(self, data):
  186. """ros sub callback"""
  187. with self.lock:
  188. try:
  189. t = time.time()
  190. self.times.append(t)
  191. self.sizes.append(len(data._buff)) #AnyMsg instance
  192. assert(len(self.times) == len(self.sizes))
  193. if len(self.times) > self.window_size:
  194. self.times.pop(0)
  195. self.sizes.pop(0)
  196. except:
  197. traceback.print_exc()
  198. def print_bw(self):
  199. """print the average publishing rate to screen"""
  200. if len(self.times) < 2:
  201. return
  202. with self.lock:
  203. n = len(self.times)
  204. tn = time.time()
  205. t0 = self.times[0]
  206. total = sum(self.sizes)
  207. bytes_per_s = total / (tn - t0)
  208. mean = total / n
  209. #std_dev = math.sqrt(sum((x - mean)**2 for x in self.sizes) /n)
  210. # min and max
  211. max_s = max(self.sizes)
  212. min_s = min(self.sizes)
  213. #min/max and even mean are likely to be much smaller, but for now I prefer unit consistency
  214. if bytes_per_s < 1000:
  215. bw, mean, min_s, max_s = ["%.2fB"%v for v in [bytes_per_s, mean, min_s, max_s]]
  216. elif bytes_per_s < 1000000:
  217. bw, mean, min_s, max_s = ["%.2fKB"%(v/1000) for v in [bytes_per_s, mean, min_s, max_s]]
  218. else:
  219. bw, mean, min_s, max_s = ["%.2fMB"%(v/1000000) for v in [bytes_per_s, mean, min_s, max_s]]
  220. print("average: %s/s\n\tmean: %s min: %s max: %s window: %s"%(bw, mean, min_s, max_s, n))
  221. def _rostopic_bw(topic, window_size=-1):
  222. """
  223. periodically print the received bandwidth of a topic to console until
  224. shutdown
  225. """
  226. _check_master()
  227. _, real_topic, _ = get_topic_type(topic, blocking=True) #pause hz until topic is published
  228. if rospy.is_shutdown():
  229. return
  230. # #3543 disable all auto-subscriptions to /clock
  231. rospy.init_node(NAME, anonymous=True, disable_rostime=True)
  232. rt = ROSTopicBandwidth(window_size)
  233. # we use a large buffer size as we don't know what sort of messages we're dealing with.
  234. # may parameterize this in the future
  235. sub = rospy.Subscriber(real_topic, rospy.AnyMsg, rt.callback)
  236. print("subscribed to [%s]"%real_topic)
  237. while not rospy.is_shutdown():
  238. _sleep(1.0)
  239. rt.print_bw()
  240. # TODO: port to the version I wrote for rxplot instead as it should be more efficient
  241. def msgevalgen(pattern):
  242. """
  243. Generates a function that returns the relevant field (aka 'subtopic') of a Message object
  244. :param pattern: subtopic, e.g. /x. Must have a leading '/' if specified, ``str``
  245. :returns: function that converts a message into the desired value, ``fn(Message) -> value``
  246. """
  247. if not pattern or pattern == '/':
  248. return None
  249. def msgeval(msg):
  250. # I will probably replace this with some less beautiful but more efficient
  251. try:
  252. return eval('msg'+'.'.join(pattern.split('/')))
  253. except AttributeError as e:
  254. sys.stdout.write("no field named [%s]"%pattern+"\n")
  255. return None
  256. return msgeval
  257. def _get_topic_type(topic):
  258. """
  259. subroutine for getting the topic type
  260. :returns: topic type, real topic name and fn to evaluate the message instance
  261. if the topic points to a field within a topic, e.g. /rosout/msg, ``(str, str, fn)``
  262. """
  263. try:
  264. val = _master_get_topic_types(rosgraph.Master('/rostopic'))
  265. except socket.error:
  266. raise ROSTopicIOException("Unable to communicate with master!")
  267. # exact match first, followed by prefix match
  268. matches = [(t, t_type) for t, t_type in val if t == topic]
  269. if not matches:
  270. matches = [(t, t_type) for t, t_type in val if topic.startswith(t+'/')]
  271. # choose longest match
  272. matches.sort(key=itemgetter(0), reverse=True)
  273. if matches:
  274. t, t_type = matches[0]
  275. if t_type == rosgraph.names.ANYTYPE:
  276. return None, None, None
  277. return t_type, t, msgevalgen(topic[len(t):])
  278. else:
  279. return None, None, None
  280. # NOTE: this is used externally by rxplot
  281. def get_topic_type(topic, blocking=False):
  282. """
  283. Get the topic type.
  284. :param topic: topic name, ``str``
  285. :param blocking: (default False) block until topic becomes available, ``bool``
  286. :returns: topic type, real topic name and fn to evaluate the message instance
  287. if the topic points to a field within a topic, e.g. /rosout/msg. fn is None otherwise. ``(str, str, fn)``
  288. :raises: :exc:`ROSTopicException` If master cannot be contacted
  289. """
  290. topic_type, real_topic, msg_eval = _get_topic_type(topic)
  291. if topic_type:
  292. return topic_type, real_topic, msg_eval
  293. elif blocking:
  294. sys.stderr.write("WARNING: topic [%s] does not appear to be published yet\n"%topic)
  295. while not rospy.is_shutdown():
  296. topic_type, real_topic, msg_eval = _get_topic_type(topic)
  297. if topic_type:
  298. return topic_type, real_topic, msg_eval
  299. else:
  300. _sleep(0.1)
  301. return None, None, None
  302. def get_topic_class(topic, blocking=False):
  303. """
  304. Get the topic message class
  305. :returns: message class for topic, real topic
  306. name, and function for evaluating message objects into the subtopic
  307. (or ``None``). ``(Message, str, str)``
  308. :raises: :exc:`ROSTopicException` If topic type cannot be determined or loaded
  309. """
  310. topic_type, real_topic, msg_eval = get_topic_type(topic, blocking=blocking)
  311. if topic_type is None:
  312. return None, None, None
  313. msg_class = roslib.message.get_message_class(topic_type)
  314. if not msg_class:
  315. raise ROSTopicException("Cannot load message class for [%s]. Are your messages built?"%topic_type)
  316. return msg_class, real_topic, msg_eval
  317. def _str_plot_fields(val, f, field_filter):
  318. """
  319. get CSV representation of fields used by _str_plot
  320. :returns: list of fields as a CSV string, ``str``
  321. """
  322. s = _sub_str_plot_fields(val, f, field_filter)
  323. if s is not None:
  324. return "time,"+s
  325. else:
  326. return 'time,'
  327. def _sub_str_plot_fields(val, f, field_filter):
  328. """recursive helper function for _str_plot_fields"""
  329. # CSV
  330. type_ = type(val)
  331. if type_ in (bool, int, float) or \
  332. isinstance(val, genpy.TVal):
  333. return f
  334. # duck-type check for messages
  335. elif hasattr(val, "_slot_types"):
  336. if field_filter is not None:
  337. fields = list(field_filter(val))
  338. else:
  339. fields = val.__slots__
  340. sub = (_sub_str_plot_fields(_convert_getattr(val, a, t), f+"."+a, field_filter) for a,t in zip(val.__slots__, val._slot_types) if a in fields)
  341. sub = [s for s in sub if s is not None]
  342. if sub:
  343. return ','.join([s for s in sub])
  344. elif type_ in (str, unicode):
  345. return f
  346. elif type_ in (list, tuple):
  347. if len(val) == 0:
  348. return None
  349. val0 = val[0]
  350. type0 = type(val0)
  351. # no arrays of arrays
  352. if type0 in (bool, int, float) or \
  353. isinstance(val0, genpy.TVal):
  354. return ','.join(["%s%s"%(f,x) for x in xrange(0,len(val))])
  355. elif type0 in (str, unicode):
  356. return ','.join(["%s%s"%(f,x) for x in xrange(0,len(val))])
  357. elif hasattr(val0, "_slot_types"):
  358. labels = ["%s%s"%(f,x) for x in xrange(0,len(val))]
  359. sub = [s for s in [_sub_str_plot_fields(v, sf, field_filter) for v,sf in zip(val, labels)] if s]
  360. if sub:
  361. return ','.join([s for s in sub])
  362. return None
  363. def _str_plot(val, time_offset=None, current_time=None, field_filter=None):
  364. """
  365. Convert value to matlab/octave-friendly CSV string representation.
  366. :param val: message
  367. :param current_time: current :class:`genpy.Time` to use if message does not contain its own timestamp.
  368. :param time_offset: (optional) for time printed for message, print as offset against this :class:`genpy.Time`
  369. :param field_filter: filter the fields that are strified for Messages, ``fn(Message)->iter(str)``
  370. :returns: comma-separated list of field values in val, ``str``
  371. """
  372. s = _sub_str_plot(val, time_offset, field_filter)
  373. if s is None:
  374. s = ''
  375. if time_offset is not None:
  376. time_offset = time_offset.to_nsec()
  377. else:
  378. time_offset = 0
  379. if getattr(val, "_has_header", False):
  380. return "%s,%s"%(val.header.stamp.to_nsec()-time_offset, s)
  381. elif current_time is not None:
  382. return "%s,%s"%(current_time.to_nsec()-time_offset, s)
  383. else:
  384. return "%s,%s"%(rospy.get_rostime().to_nsec()-time_offset, s)
  385. def _sub_str_plot(val, time_offset, field_filter):
  386. """Helper routine for _str_plot."""
  387. # CSV
  388. type_ = type(val)
  389. if type_ == bool:
  390. return '1' if val else '0'
  391. elif type_ in (int, float) or \
  392. isinstance(val, genpy.TVal):
  393. if time_offset is not None and isinstance(val, genpy.Time):
  394. return str(val-time_offset)
  395. else:
  396. return str(val)
  397. elif hasattr(val, "_slot_types"):
  398. if field_filter is not None:
  399. fields = list(field_filter(val))
  400. else:
  401. fields = val.__slots__
  402. sub = (_sub_str_plot(_convert_getattr(val, f, t), time_offset, field_filter) for f,t in zip(val.__slots__, val._slot_types) if f in fields)
  403. sub = [s for s in sub if s is not None]
  404. if sub:
  405. return ','.join(sub)
  406. elif type_ in (str, unicode):
  407. return val
  408. elif type_ in (list, tuple):
  409. if len(val) == 0:
  410. return None
  411. val0 = val[0]
  412. # no arrays of arrays
  413. type0 = type(val0)
  414. if type0 == bool:
  415. return ','.join([('1' if v else '0') for v in val])
  416. elif type0 in (int, float) or \
  417. isinstance(val0, genpy.TVal):
  418. return ','.join([str(v) for v in val])
  419. elif type0 in (str, unicode):
  420. return ','.join([v for v in val])
  421. elif hasattr(val0, "_slot_types"):
  422. sub = [s for s in [_sub_str_plot(v, time_offset, field_filter) for v in val] if s is not None]
  423. if sub:
  424. return ','.join([s for s in sub])
  425. return None
  426. # copied from roslib.message
  427. def _convert_getattr(val, f, t):
  428. """
  429. Convert atttribute types on the fly, if necessary. This is mainly
  430. to convert uint8[] fields back to an array type.
  431. """
  432. attr = getattr(val, f)
  433. if type(attr) in (str, unicode) and 'uint8[' in t:
  434. return [ord(x) for x in attr]
  435. else:
  436. return attr
  437. class CallbackEcho(object):
  438. """
  439. Callback instance that can print callback data in a variety of
  440. formats. Used for all variants of rostopic echo
  441. """
  442. def __init__(self, topic, msg_eval, plot=False, filter_fn=None,
  443. echo_clear=False, echo_all_topics=False,
  444. offset_time=False, count=None,
  445. field_filter_fn=None):
  446. """
  447. :param plot: if ``True``, echo in plotting-friendly format, ``bool``
  448. :param filter_fn: function that evaluates to ``True`` if message is to be echo'd, ``fn(topic, msg)``
  449. :param echo_all_topics: (optional) if ``True``, echo all messages in bag, ``bool``
  450. :param offset_time: (optional) if ``True``, display time as offset from current time, ``bool``
  451. :param count: number of messages to echo, ``None`` for infinite, ``int``
  452. :param field_filter_fn: filter the fields that are strified for Messages, ``fn(Message)->iter(str)``
  453. """
  454. if topic and topic[-1] == '/':
  455. topic = topic[:-1]
  456. self.topic = topic
  457. self.msg_eval = msg_eval
  458. self.plot = plot
  459. self.filter_fn = filter_fn
  460. self.prefix = ''
  461. self.suffix = '\n---' if not plot else ''# same as YAML document separator, bug #3291
  462. self.echo_all_topics = echo_all_topics
  463. self.offset_time = offset_time
  464. # done tracks when we've exceeded the count
  465. self.done = False
  466. self.max_count = count
  467. self.count = 0
  468. # determine which strifying function to use
  469. if plot:
  470. #TODOXXX: need to pass in filter function
  471. self.str_fn = _str_plot
  472. self.sep = ''
  473. else:
  474. #TODOXXX: need to pass in filter function
  475. self.str_fn = genpy.message.strify_message
  476. if echo_clear:
  477. self.prefix = '\033[2J\033[;H'
  478. self.field_filter=field_filter_fn
  479. # first tracks whether or not we've printed anything yet. Need this for printing plot fields.
  480. self.first = True
  481. # cache
  482. self.last_topic = None
  483. self.last_msg_eval = None
  484. def callback(self, data, topic, current_time=None):
  485. """
  486. Callback to pass to rospy.Subscriber or to call
  487. manually. rospy.Subscriber constructor must also pass in the
  488. topic name as an additional arg
  489. :param data: Message
  490. :param topic: topic name, ``str``
  491. :param current_time: override calculation of current time, :class:`genpy.Time`
  492. """
  493. if self.filter_fn is not None and not self.filter_fn(data):
  494. return
  495. if self.max_count is not None and self.count >= self.max_count:
  496. self.done = True
  497. return
  498. try:
  499. msg_eval = self.msg_eval
  500. if topic == self.topic:
  501. pass
  502. elif self.topic.startswith(topic + '/'):
  503. # self.topic is actually a reference to topic field, generate msgeval
  504. if topic == self.last_topic:
  505. # use cached eval
  506. msg_eval = self.last_msg_eval
  507. else:
  508. # generate msg_eval and cache
  509. self.last_msg_eval = msg_eval = msgevalgen(self.topic[len(topic):])
  510. self.last_topic = topic
  511. elif not self.echo_all_topics:
  512. return
  513. if msg_eval is not None:
  514. data = msg_eval(data)
  515. else:
  516. val = data
  517. # data can be None if msg_eval returns None
  518. if data is not None:
  519. # NOTE: we do all prints using direct writes to sys.stdout, which works better with piping
  520. self.count += 1
  521. # print fields header for plot
  522. if self.plot and self.first:
  523. sys.stdout.write("%"+_str_plot_fields(data, 'field', self.field_filter)+'\n')
  524. self.first = False
  525. if self.offset_time:
  526. sys.stdout.write(self.prefix+\
  527. self.str_fn(data, time_offset=rospy.get_rostime(),
  528. current_time=current_time, field_filter=self.field_filter) + \
  529. self.suffix + '\n')
  530. else:
  531. sys.stdout.write(self.prefix+\
  532. self.str_fn(data,
  533. current_time=current_time, field_filter=self.field_filter) + \
  534. self.suffix + '\n')
  535. # we have to flush in order before piping to work
  536. sys.stdout.flush()
  537. # #2778 : have to check count after incr to set done flag
  538. if self.max_count is not None and self.count >= self.max_count:
  539. self.done = True
  540. except IOError:
  541. self.done = True
  542. except:
  543. # set done flag so we exit
  544. self.done = True
  545. traceback.print_exc()
  546. def _rostopic_type(topic):
  547. """
  548. Print ROS message type of topic to screen
  549. :param topic: topic name, ``str``
  550. """
  551. t, _, _ = get_topic_type(topic, blocking=False)
  552. if t:
  553. print(t)
  554. else:
  555. sys.stderr.write('unknown topic type [%s]\n'%topic)
  556. sys.exit(1)
  557. def _rostopic_echo_bag(callback_echo, bag_file):
  558. """
  559. :param callback_echo: :class:`CallbackEcho` instance to invoke on new messages in bag file
  560. :param bag_file: name of bag file to echo messages from or ``None``, ``str``
  561. """
  562. if not os.path.exists(bag_file):
  563. raise ROSTopicException("bag file [%s] does not exist"%bag_file)
  564. first = True
  565. import rosbag
  566. with rosbag.Bag(bag_file) as b:
  567. for t, msg, timestamp in b.read_messages():
  568. # bag files can have relative paths in them, this respects any
  569. # dynamic renaming
  570. if t[0] != '/':
  571. t = rosgraph.names.script_resolve_name('rostopic', t)
  572. callback_echo.callback(msg, t, current_time=timestamp)
  573. # done is set if there is a max echo count
  574. if callback_echo.done:
  575. break
  576. def _rostopic_echo(topic, callback_echo, bag_file=None, echo_all_topics=False):
  577. """
  578. Print new messages on topic to screen.
  579. :param topic: topic name, ``str``
  580. :param bag_file: name of bag file to echo messages from or ``None``, ``str``
  581. """
  582. # we have to init a node regardless and bag echoing can print timestamps
  583. if bag_file:
  584. # initialize rospy time due to potential timestamp printing
  585. rospy.rostime.set_rostime_initialized(True)
  586. _rostopic_echo_bag(callback_echo, bag_file)
  587. else:
  588. _check_master()
  589. rospy.init_node(NAME, anonymous=True)
  590. msg_class, real_topic, msg_eval = get_topic_class(topic, blocking=True)
  591. if msg_class is None:
  592. # occurs on ctrl-C
  593. return
  594. callback_echo.msg_eval = msg_eval
  595. use_sim_time = rospy.get_param('/use_sim_time', False)
  596. sub = rospy.Subscriber(real_topic, msg_class, callback_echo.callback, topic)
  597. if use_sim_time:
  598. # #2950: print warning if nothing received for two seconds
  599. timeout_t = time.time() + 2.
  600. while time.time() < timeout_t and \
  601. callback_echo.count == 0 and \
  602. not rospy.is_shutdown() and \
  603. not callback_echo.done:
  604. _sleep(0.1)
  605. if callback_echo.count == 0 and \
  606. not rospy.is_shutdown() and \
  607. not callback_echo.done:
  608. sys.stderr.write("WARNING: no messages received and simulated time is active.\nIs /clock being published?\n")
  609. while not rospy.is_shutdown() and not callback_echo.done:
  610. _sleep(0.1)
  611. _caller_apis = {}
  612. def get_api(master, caller_id):
  613. """
  614. Get XML-RPC API of node
  615. :param master: XML-RPC handle to ROS Master, :class:`xmlrpclib.ServerProxy`
  616. :param caller_id: node name, ``str``
  617. :returns: XML-RPC URI of node, ``str``
  618. :raises: :exc:`ROSTopicIOException` If unable to communicate with master
  619. """
  620. caller_api = _caller_apis.get(caller_id, None)
  621. if not caller_api:
  622. try:
  623. caller_api = master.lookupNode(caller_id)
  624. _caller_apis[caller_id] = caller_api
  625. except socket.error:
  626. raise ROSTopicIOException("Unable to communicate with master!")
  627. except rosgraph.MasterError:
  628. caller_api = 'unknown address %s'%caller_id
  629. return caller_api
  630. def _rostopic_list_bag(bag_file, topic=None):
  631. """
  632. Prints topics in bag file to screen
  633. :param bag_file: path to bag file, ``str``
  634. :param topic: optional topic name to match. Will print additional information just about messagese in this topic, ``str``
  635. """
  636. import rosbag
  637. if not os.path.exists(bag_file):
  638. raise ROSTopicException("bag file [%s] does not exist"%bag_file)
  639. with rosbag.Bag(bag_file) as b:
  640. if topic:
  641. # create string for namespace comparison
  642. topic_ns = rosgraph.names.make_global_ns(topic)
  643. count = 0
  644. earliest = None
  645. latest = None
  646. for top, msg, t in b.read_messages(raw=True):
  647. if top == topic or top.startswith(topic_ns):
  648. count += 1
  649. if earliest == None:
  650. earliest = t
  651. latest = t
  652. if rospy.is_shutdown():
  653. break
  654. import time
  655. earliest, latest = [time.strftime("%d %b %Y %H:%M:%S", time.localtime(t.to_time())) for t in (earliest, latest)]
  656. print("%s message(s) from %s to %s"%(count, earliest, latest))
  657. else:
  658. topics = set()
  659. for top, msg, _ in b.read_messages(raw=True):
  660. if top not in topics:
  661. print(top)
  662. topics.add(top)
  663. if rospy.is_shutdown():
  664. break
  665. def _sub_rostopic_list(master, pubs, subs, publishers_only, subscribers_only, verbose, indent=''):
  666. def topic_type(t, topic_types):
  667. matches = [t_type for t_name, t_type in topic_types if t_name == t]
  668. if matches:
  669. return matches[0]
  670. return 'unknown type'
  671. if verbose:
  672. topic_types = _master_get_topic_types(master)
  673. if not subscribers_only:
  674. print("\n%sPublished topics:"%indent)
  675. for t, l in pubs:
  676. if len(l) > 1:
  677. print(indent+" * %s [%s] %s publishers"%(t, topic_type(t, topic_types), len(l)))
  678. else:
  679. print(indent+" * %s [%s] 1 publisher"%(t, topic_type(t, topic_types)))
  680. if not publishers_only:
  681. print(indent)
  682. print(indent+"Subscribed topics:")
  683. for t,l in subs:
  684. if len(l) > 1:
  685. print(indent+" * %s [%s] %s subscribers"%(t, topic_type(t, topic_types), len(l)))
  686. else:
  687. print(indent+" * %s [%s] 1 subscriber"%(t, topic_type(t, topic_types)))
  688. print('')
  689. else:
  690. if publishers_only:
  691. topics = [t for t,_ in pubs]
  692. elif subscribers_only:
  693. topics = [t for t,_ in subs]
  694. else:
  695. topics = list(set([t for t,_ in pubs] + [t for t,_ in subs]))
  696. topics.sort()
  697. print('\n'.join(["%s%s"%(indent, t) for t in topics]))
  698. # #3145
  699. def _rostopic_list_group_by_host(master, pubs, subs):
  700. """
  701. Build up maps for hostname to topic list per hostname
  702. :returns: publishers host map, subscribers host map, ``{str: set(str)}, {str: set(str)}``
  703. """
  704. def build_map(master, state, uricache):
  705. tmap = {}
  706. for topic, tnodes in state:
  707. for p in tnodes:
  708. if not p in uricache:
  709. uricache[p] = master.lookupNode(p)
  710. uri = uricache[p]
  711. puri = urlparse(uri)
  712. if not puri.hostname in tmap:
  713. tmap[puri.hostname] = []
  714. # recreate the system state data structure, but for a single host
  715. matches = [l for x, l in tmap[puri.hostname] if x == topic]
  716. if matches:
  717. matches[0].append(p)
  718. else:
  719. tmap[puri.hostname].append((topic, [p]))
  720. return tmap
  721. uricache = {}
  722. host_pub_topics = build_map(master, pubs, uricache)
  723. host_sub_topics = build_map(master, subs, uricache)
  724. return host_pub_topics, host_sub_topics
  725. def _rostopic_list(topic, verbose=False,
  726. subscribers_only=False, publishers_only=False,
  727. group_by_host=False):
  728. """
  729. Print topics to screen
  730. :param topic: topic name to list information or None to match all topics, ``str``
  731. :param verbose: print additional debugging information, ``bool``
  732. :param subscribers_only: print information about subscriptions only, ``bool``
  733. :param publishers_only: print information about subscriptions only, ``bool``
  734. :param group_by_host: group topic list by hostname, ``bool``
  735. """
  736. # #1563
  737. if subscribers_only and publishers_only:
  738. raise ROSTopicException("cannot specify both subscribers- and publishers-only")
  739. master = rosgraph.Master('/rostopic')
  740. try:
  741. state = master.getSystemState()
  742. pubs, subs, _ = state
  743. if topic:
  744. # filter based on topic
  745. topic_ns = rosgraph.names.make_global_ns(topic)
  746. subs = (x for x in subs if x[0] == topic or x[0].startswith(topic_ns))
  747. pubs = (x for x in pubs if x[0] == topic or x[0].startswith(topic_ns))
  748. except socket.error:
  749. raise ROSTopicIOException("Unable to communicate with master!")
  750. if group_by_host:
  751. # #3145
  752. host_pub_topics, host_sub_topics = _rostopic_list_group_by_host(master, pubs, subs)
  753. for hostname in set(list(host_pub_topics.keys()) + list(host_sub_topics.keys())): #py3k
  754. pubs, subs = host_pub_topics.get(hostname,[]), host_sub_topics.get(hostname, []),
  755. if (pubs and not subscribers_only) or (subs and not publishers_only):
  756. print("Host [%s]:" % hostname)
  757. _sub_rostopic_list(master, pubs, subs,
  758. publishers_only, subscribers_only,
  759. verbose, indent=' ')
  760. else:
  761. _sub_rostopic_list(master, pubs, subs,
  762. publishers_only, subscribers_only,
  763. verbose)
  764. def get_info_text(topic):
  765. """
  766. Get human-readable topic description
  767. :param topic: topic name, ``str``
  768. """
  769. import cStringIO, itertools
  770. buff = cStringIO.StringIO()
  771. def topic_type(t, topic_types):
  772. matches = [t_type for t_name, t_type in topic_types if t_name == t]
  773. if matches:
  774. return matches[0]
  775. return 'unknown type'
  776. master = rosgraph.Master('/rostopic')
  777. try:
  778. state = master.getSystemState()
  779. pubs, subs, _ = state
  780. # filter based on topic
  781. subs = [x for x in subs if x[0] == topic]
  782. pubs = [x for x in pubs if x[0] == topic]
  783. topic_types = _master_get_topic_types(master)
  784. except socket.error:
  785. raise ROSTopicIOException("Unable to communicate with master!")
  786. if not pubs and not subs:
  787. raise ROSTopicException("Unknown topic %s"%topic)
  788. buff.write("Type: %s\n\n"%topic_type(topic, topic_types))
  789. if pubs:
  790. buff.write("Publishers: \n")
  791. for p in itertools.chain(*[l for x, l in pubs]):
  792. buff.write(" * %s (%s)\n"%(p, get_api(master, p)))
  793. else:
  794. buff.write("Publishers: None\n")
  795. buff.write('\n')
  796. if subs:
  797. buff.write("Subscribers: \n")
  798. for p in itertools.chain(*[l for x, l in subs]):
  799. buff.write(" * %s (%s)\n"%(p, get_api(master, p)))
  800. else:
  801. buff.write("Subscribers: None\n")
  802. buff.write('\n')
  803. return buff.getvalue()
  804. def _rostopic_info(topic):
  805. """
  806. Print topic information to screen.
  807. :param topic: topic name, ``str``
  808. """
  809. print(get_info_text(topic))
  810. ##########################################################################################
  811. # COMMAND PROCESSING #####################################################################
  812. def _rostopic_cmd_echo(argv):
  813. def expr_eval(expr):
  814. def eval_fn(m):
  815. return eval(expr)
  816. return eval_fn
  817. args = argv[2:]
  818. from optparse import OptionParser
  819. parser = OptionParser(usage="usage: %prog echo [options] /topic", prog=NAME)
  820. parser.add_option("-b", "--bag",
  821. dest="bag", default=None,
  822. help="echo messages from .bag file", metavar="BAGFILE")
  823. parser.add_option("-p",
  824. dest="plot", default=False,
  825. action="store_true",
  826. help="echo in a plotting friendly format")
  827. parser.add_option("--filter",
  828. dest="filter_expr", default=None,
  829. metavar="FILTER-EXPRESSION",
  830. help="Python expression to filter messages that are printed. Expression can use Python builtins as well as m (the message) and topic (the topic name).")
  831. parser.add_option("--nostr",
  832. dest="nostr", default=False,
  833. action="store_true",
  834. help="exclude string fields")
  835. parser.add_option("--noarr",
  836. dest="noarr", default=False,
  837. action="store_true",
  838. help="exclude arrays")
  839. parser.add_option("-c", "--clear",
  840. dest="clear", default=False,
  841. action="store_true",
  842. help="clear screen before printing next message")
  843. parser.add_option("-a", "--all",
  844. dest="all_topics", default=False,
  845. action="store_true",
  846. help="display all message in bag, only valid with -b option")
  847. parser.add_option("-n",
  848. dest="msg_count", default=None, metavar="COUNT",
  849. help="number of messages to echo")
  850. parser.add_option("--offset",
  851. dest="offset_time", default=False,
  852. action="store_true",
  853. help="display time as offsets from current time (in seconds)")
  854. (options, args) = parser.parse_args(args)
  855. if len(args) > 1:
  856. parser.error("you may only specify one input topic")
  857. if options.all_topics and not options.bag:
  858. parser.error("Display all option is only valid when echoing from bag files")
  859. if options.offset_time and options.bag:
  860. parser.error("offset time option is not valid with bag files")
  861. if options.all_topics:
  862. topic = ''
  863. else:
  864. if len(args) == 0:
  865. parser.error("topic must be specified")
  866. topic = rosgraph.names.script_resolve_name('rostopic', args[0])
  867. # suppressing output to keep it clean
  868. #if not options.plot:
  869. # print "rostopic: topic is [%s]"%topic
  870. filter_fn = None
  871. if options.filter_expr:
  872. filter_fn = expr_eval(options.filter_expr)
  873. try:
  874. msg_count = int(options.msg_count) if options.msg_count else None
  875. except ValueError:
  876. parser.error("COUNT must be an integer")
  877. field_filter_fn = create_field_filter(options.nostr, options.noarr)
  878. callback_echo = CallbackEcho(topic, None, plot=options.plot,
  879. filter_fn=filter_fn,
  880. echo_clear=options.clear, echo_all_topics=options.all_topics,
  881. offset_time=options.offset_time, count=msg_count,
  882. field_filter_fn=field_filter_fn)
  883. try:
  884. _rostopic_echo(topic, callback_echo, bag_file=options.bag)
  885. except socket.error:
  886. sys.stderr.write("Network communication failed. Most likely failed to communicate with master.\n")
  887. def create_field_filter(echo_nostr, echo_noarr):
  888. def field_filter(val):
  889. fields = val.__slots__
  890. field_types = val._slot_types
  891. for f, t in zip(val.__slots__, val._slot_types):
  892. if echo_noarr and '[' in t:
  893. continue
  894. elif echo_nostr and 'string' in t:
  895. continue
  896. yield f
  897. return field_filter
  898. def _optparse_topic_only(cmd, argv):
  899. args = argv[2:]
  900. from optparse import OptionParser
  901. parser = OptionParser(usage="usage: %%prog %s /topic"%cmd, prog=NAME)
  902. (options, args) = parser.parse_args(args)
  903. if len(args) == 0:
  904. parser.error("topic must be specified")
  905. if len(args) > 1:
  906. parser.error("you may only specify one input topic")
  907. return rosgraph.names.script_resolve_name('rostopic', args[0])
  908. def _rostopic_cmd_type(argv):
  909. _rostopic_type(_optparse_topic_only('type', argv))
  910. def _rostopic_cmd_hz(argv):
  911. args = argv[2:]
  912. from optparse import OptionParser
  913. parser = OptionParser(usage="usage: %prog hz /topic", prog=NAME)
  914. parser.add_option("-w", "--window",
  915. dest="window_size", default=-1,
  916. help="window size, in # of messages, for calculating rate", metavar="WINDOW")
  917. parser.add_option("--filter",
  918. dest="filter_expr", default=None,
  919. help="only measure messages matching the specified Python expression", metavar="EXPR")
  920. (options, args) = parser.parse_args(args)
  921. if len(args) == 0:
  922. parser.error("topic must be specified")
  923. if len(args) > 1:
  924. parser.error("you may only specify one input topic")
  925. try:
  926. if options.window_size != -1:
  927. import string
  928. window_size = string.atoi(options.window_size)
  929. else:
  930. window_size = options.window_size
  931. except:
  932. parser.error("window size must be an integer")
  933. topic = rosgraph.names.script_resolve_name('rostopic', args[0])
  934. # #694
  935. if options.filter_expr:
  936. def expr_eval(expr):
  937. def eval_fn(m):
  938. return eval(expr)
  939. return eval_fn
  940. filter_expr = expr_eval(options.filter_expr)
  941. else:
  942. filter_expr = None
  943. _rostopic_hz(topic, window_size=window_size, filter_expr=filter_expr)
  944. def _rostopic_cmd_bw(argv=sys.argv):
  945. args = argv[2:]
  946. from optparse import OptionParser
  947. parser = OptionParser(usage="usage: %prog bw /topic", prog=NAME)
  948. parser.add_option("-w", "--window",
  949. dest="window_size", default=None,
  950. help="window size, in # of messages, for calculating rate", metavar="WINDOW")
  951. options, args = parser.parse_args(args)
  952. if len(args) == 0:
  953. parser.error("topic must be specified")
  954. if len(args) > 1:
  955. parser.error("you may only specify one input topic")
  956. try:
  957. if options.window_size:
  958. import string
  959. window_size = string.atoi(options.window_size)
  960. else:
  961. window_size = options.window_size
  962. except:
  963. parser.error("window size must be an integer")
  964. topic = rosgraph.names.script_resolve_name('rostopic', args[0])
  965. _rostopic_bw(topic, window_size=window_size)
  966. def find_by_type(topic_type):
  967. """
  968. Lookup topics by topic_type
  969. :param topic_type: type of topic to find, ``str``
  970. :returns: list of topic names that use topic_type, ``[str]``
  971. """
  972. master = rosgraph.Master('/rostopic')
  973. try:
  974. t_list = _master_get_topic_types(master)
  975. except socket.error:
  976. raise ROSTopicIOException("Unable to communicate with master!")
  977. return [t_name for t_name, t_type in t_list if t_type == topic_type]
  978. def _rostopic_cmd_find(argv=sys.argv):
  979. """
  980. Implements 'rostopic type'
  981. :param argv: command-line args, ``[str]``
  982. """
  983. args = argv[2:]
  984. from optparse import OptionParser
  985. parser = OptionParser(usage="usage: %prog find msg-type", prog=NAME)
  986. options, args = parser.parse_args(args)
  987. if not len(args):
  988. parser.error("please specify a message type")
  989. if len(args) > 1:
  990. parser.error("you may only specify one message type")
  991. print('\n'.join(find_by_type(args[0])))
  992. def _resource_name_package(name):
  993. """
  994. pkg/typeName -> pkg, typeName -> None
  995. :param name: package resource name, e.g. 'std_msgs/String', ``str``
  996. :returns: package name of resource, ``str``
  997. """
  998. if not '/' in name:
  999. return None
  1000. return name[:name.find('/')]
  1001. def create_publisher(topic_name, topic_type, latch):
  1002. """
  1003. Create rospy.Publisher instance from the string topic name and
  1004. type. This is a powerful method as it allows creation of
  1005. rospy.Publisher and Message instances using the topic and type
  1006. names. This enables more dynamic publishing from Python programs.
  1007. :param topic_name: name of topic, ``str``
  1008. :param topic_type: name of topic type, ``str``
  1009. :param latch: latching topic, ``bool``
  1010. :returns: topic :class:`rospy.Publisher`, :class:`Message` class
  1011. """
  1012. topic_name = rosgraph.names.script_resolve_name('rostopic', topic_name)
  1013. try:
  1014. msg_class = roslib.message.get_message_class(topic_type)
  1015. except:
  1016. raise ROSTopicException("invalid topic type: %s"%topic_type)
  1017. if msg_class is None:
  1018. pkg = _resource_name_package(topic_type)
  1019. raise ROSTopicException("invalid message type: %s.\nIf this is a valid message type, perhaps you need to type 'rosmake %s'"%(topic_type, pkg))
  1020. # disable /rosout and /rostime as this causes blips in the pubsub network due to rostopic pub often exiting quickly
  1021. rospy.init_node('rostopic', anonymous=True, disable_rosout=True, disable_rostime=True)
  1022. pub = rospy.Publisher(topic_name, msg_class, latch=latch)
  1023. return pub, msg_class
  1024. def _publish_at_rate(pub, msg, rate, verbose=False):
  1025. """
  1026. Publish message at specified rate. Subroutine of L{publish_message()}.
  1027. :param pub: :class:rospy.Publisher` instance for topic
  1028. :param msg: message instance to publish
  1029. :param rate: publishing rate (hz) or None for just once, ``int``
  1030. :param verbose: If ``True``, print more verbose output to stdout, ``bool``
  1031. """
  1032. try:
  1033. r = rospy.Rate(float(rate))
  1034. except ValueError:
  1035. raise ROSTopicException("Rate must be a number")
  1036. while not rospy.is_shutdown():
  1037. if verbose:
  1038. print("publishing %s"%msg)
  1039. pub.publish(msg)
  1040. r.sleep()
  1041. _ONCE_DELAY = 3.
  1042. def _publish_latched(pub, msg, once=False, verbose=False):
  1043. """
  1044. Publish and latch message. Subroutine of L{publish_message()}.
  1045. :param pub: :class:`rospy.Publisher` instance for topic
  1046. :param msg: message instance to publish
  1047. :param once: if ``True``, publish message once and then exit after sleep interval, ``bool``
  1048. :param verbose: If ``True``, print more verbose output to stdout, ``bool``
  1049. """
  1050. try:
  1051. pub.publish(msg)
  1052. except TypeError as e:
  1053. raise ROSTopicException(str(e))
  1054. if not once:
  1055. rospy.spin()
  1056. def publish_message(pub, msg_class, pub_args, rate=None, once=False, verbose=False):
  1057. """
  1058. Create new instance of msg_class, populate with pub_args, and publish. This may
  1059. print output to screen.
  1060. :param pub: :class:`rospy.Publisher` instance for topic
  1061. :param msg_class: Message type, ``Class``
  1062. :param pub_args: Arguments to initialize message that is published, ``[val]``
  1063. :param rate: publishing rate (hz) or None for just once, ``int``
  1064. :param once: publish only once and return, ``bool``
  1065. :param verbose: If ``True``, print more verbose output to stdout, ``bool``
  1066. """
  1067. msg = msg_class()
  1068. try:
  1069. # Populate the message and enable substitution keys for 'now'
  1070. # and 'auto'. There is a corner case here: this logic doesn't
  1071. # work if you're publishing a Header only and wish to use
  1072. # 'auto' with it. This isn't a troubling case, but if we start
  1073. # allowing more keys in the future, it could become an actual
  1074. # use case. It greatly complicates logic because we'll have to
  1075. # do more reasoning over types. to avoid ambiguous cases
  1076. # (e.g. a std_msgs/String type, which only has a single string
  1077. # field).
  1078. # allow the use of the 'now' string with timestamps and 'auto' with header
  1079. now = rospy.get_rostime()
  1080. import std_msgs.msg
  1081. keys = { 'now': now, 'auto': std_msgs.msg.Header(stamp=now) }
  1082. genpy.message.fill_message_args(msg, pub_args, keys=keys)
  1083. except genpy.MessageException as e:
  1084. raise ROSTopicException(str(e)+"\n\nArgs are: [%s]"%genpy.message.get_printable_message_args(msg))
  1085. try:
  1086. if rate is None:
  1087. s = "publishing and latching [%s]"%(msg) if verbose else "publishing and latching message"
  1088. if once:
  1089. s = s + " for %s seconds"%_ONCE_DELAY
  1090. else:
  1091. s = s + ". Press ctrl-C to terminate"
  1092. print(s)
  1093. if rate is None:
  1094. _publish_latched(pub, msg, once, verbose)
  1095. else:
  1096. _publish_at_rate(pub, msg, rate, verbose)
  1097. except rospy.ROSSerializationException as e:
  1098. import rosmsg
  1099. # we could just print the message definition, but rosmsg is more readable
  1100. raise ROSTopicException("Unable to publish message. One of the fields has an incorrect type:\n"+\
  1101. " %s\n\nmsg file:\n%s"%(e, rosmsg.get_msg_text(msg_class._type)))
  1102. def _rostopic_cmd_pub(argv):
  1103. """
  1104. Parse 'pub' command arguments and run command. Will cause a system
  1105. exit if command-line argument parsing fails.
  1106. :param argv: command-line arguments
  1107. :param argv: [str]
  1108. :raises: :exc:`ROSTopicException` If call command cannot be executed
  1109. """
  1110. args = argv[2:]
  1111. from optparse import OptionParser
  1112. parser = OptionParser(usage="usage: %prog pub /topic type [args...]", prog=NAME)
  1113. parser.add_option("-v", dest="verbose", default=False,
  1114. action="store_true",
  1115. help="print verbose output")
  1116. parser.add_option("-r", "--rate", dest="rate", default=None,
  1117. help="publishing rate (hz). For -f and stdin input, this defaults to 10. Otherwise it is not set.")
  1118. parser.add_option("-1", "--once", action="store_true", dest="once", default=False,
  1119. help="publish one message and exit")
  1120. parser.add_option("-f", '--file', dest="file", metavar='FILE', default=None,
  1121. help="read args from YAML file (Bagy)")
  1122. parser.add_option("-l", '--latch', dest="latch", default=False, action="store_true",
  1123. help="enable latching for -f, -r and piped input. This latches the first message.")
  1124. #parser.add_option("-p", '--param', dest="parameter", metavar='/PARAM', default=None,
  1125. # help="read args from ROS parameter (Bagy format)")
  1126. (options, args) = parser.parse_args(args)
  1127. if options.rate is not None:
  1128. if options.once:
  1129. parser.error("You cannot select both -r and -1 (--once)")
  1130. try:
  1131. rate = float(options.rate)
  1132. except ValueError:
  1133. parser.error("rate must be a number")
  1134. if rate <= 0:
  1135. parser.error("rate must be greater than zero")
  1136. else:
  1137. # we will default this to 10 for file/stdin later
  1138. rate = None
  1139. # validate args len
  1140. if len(args) == 0:
  1141. parser.error("/topic must be specified")
  1142. if len(args) == 1:
  1143. parser.error("topic type must be specified")
  1144. if 0:
  1145. if len(args) > 2 and options.parameter:
  1146. parser.error("args confict with -p setting")
  1147. if len(args) > 2 and options.file:
  1148. parser.error("args confict with -f setting")
  1149. topic_name, topic_type = args[0], args[1]
  1150. # type-case using YAML
  1151. try:
  1152. pub_args = []
  1153. for arg in args[2:]:
  1154. pub_args.append(yaml.load(arg))
  1155. except Exception as e:
  1156. parser.error("Argument error: "+str(e))
  1157. # make sure master is online. we wait until after we've parsed the
  1158. # args to do this so that syntax errors are reported first
  1159. _check_master()
  1160. # if no rate, or explicit latch, we latch
  1161. latch = (rate == None) or options.latch
  1162. pub, msg_class = create_publisher(topic_name, topic_type, latch)
  1163. if 0 and options.parameter:
  1164. param_name = rosgraph.names.script_resolve_name('rostopic', options.parameter)
  1165. if options.once:
  1166. param_publish_once(pub, msg_class, param_name, rate, options.verbose)
  1167. else:
  1168. param_publish(pub, msg_class, param_name, rate, options.verbose)
  1169. elif not pub_args and len(msg_class.__slots__):
  1170. if not options.file and sys.stdin.isatty():
  1171. parser.error("Please specify message values")
  1172. # stdin/file input has a rate by default
  1173. if rate is None and not options.latch and not options.once:
  1174. rate = 10.
  1175. stdin_publish(pub, msg_class, rate, options.once, options.file, options.verbose)
  1176. else:
  1177. argv_publish(pub, msg_class, pub_args, rate, options.once, options.verbose)
  1178. def file_yaml_arg(filename):
  1179. """
  1180. :param filename: file name, ``str``
  1181. :returns: Iterator that yields pub args (list of args), ``iterator``
  1182. :raises: :exc:`ROSTopicException` If filename is invalid
  1183. """
  1184. if not os.path.isfile(filename):
  1185. raise ROSTopicException("file does not exist: %s"%(filename))
  1186. import yaml
  1187. def bagy_iter():
  1188. try:
  1189. with open(filename, 'r') as f:
  1190. # load all documents
  1191. data = yaml.load_all(f)
  1192. for d in data:
  1193. yield [d]
  1194. except yaml.YAMLError as e:
  1195. raise ROSTopicException("invalid YAML in file: %s"%(str(e)))
  1196. return bagy_iter
  1197. def argv_publish(pub, msg_class, pub_args, rate, once, verbose):
  1198. publish_message(pub, msg_class, pub_args, rate, once, verbose=verbose)
  1199. if once:
  1200. # stick around long enough for others to grab
  1201. timeout_t = time.time() + _ONCE_DELAY
  1202. while not rospy.is_shutdown() and time.time() < timeout_t:
  1203. rospy.sleep(0.2)
  1204. SUBSCRIBER_TIMEOUT = 5.
  1205. def wait_for_subscriber(pub, timeout):
  1206. timeout_t = time.time() + timeout
  1207. while pub.get_num_connections() == 0 and timeout_t > time.time():
  1208. _sleep(0.01)
  1209. def param_publish_once(pub, msg_class, param_name, verbose):
  1210. if not rospy.has_param(param_name):
  1211. raise ROSTopicException("parameter does not exist: %s"%(param_name))
  1212. pub_args = rospy.get_param(param_name)
  1213. argv_publish(pub, msg_class, pub_args, None, True, verbose)
  1214. class _ParamNotifier(object):
  1215. def __init__(self, param_name, value=None):
  1216. import threading
  1217. self.lock = threading.Condition()
  1218. self.param_name = param_name
  1219. self.updates = []
  1220. self.value = None
  1221. def __call__(self, key, value):
  1222. with self.lock:
  1223. # have to address downward if we got notification on sub namespace
  1224. if key != self.param_name:
  1225. subs = [x for x in key[len(self.param_name):].split('/') if x]
  1226. idx = self.value
  1227. for s in subs[:-1]:
  1228. if s in idx:
  1229. idx = idx[s]
  1230. else:
  1231. idx[s] = {}
  1232. idx = idx[s]
  1233. idx[subs[-1]] = value
  1234. else:
  1235. self.value = value
  1236. self.updates.append(self.value)
  1237. self.lock.notify_all()
  1238. def param_publish(pub, msg_class, param_name, rate, verbose):
  1239. """
  1240. :param param_name: ROS parameter name, ``str``
  1241. :returns: List of msg dicts in file, ``[{str: any}]``
  1242. :raises: :exc:`ROSTopicException` If parameter is not set
  1243. """
  1244. import rospy
  1245. import rospy.impl.paramserver
  1246. import rosgraph
  1247. if not rospy.has_param(param_name):
  1248. raise ROSTopicException("parameter does not exist: %s"%(param_name))
  1249. # reach deep into subscription APIs here. Very unstable stuff
  1250. # here, don't copy elsewhere!
  1251. ps_cache = rospy.impl.paramserver.get_param_server_cache()
  1252. notifier = _ParamNotifier(param_name)
  1253. ps_cache.set_notifier(notifier)
  1254. master = rosgraph.Master(rospy.get_name())
  1255. notifier.value = master.subscribeParam(rospy.get_node_uri(), param_name)
  1256. pub_args = notifier.value
  1257. ps_cache.set(param_name, pub_args)
  1258. if type(pub_args) == dict:
  1259. pub_args = [pub_args]
  1260. elif type(pub_args) != list:
  1261. raise ROSTopicException("Parameter [%s] in not a valid type"%(param_name))
  1262. r = rospy.Rate(rate) if rate is not None else None
  1263. publish = True
  1264. while not rospy.is_shutdown():
  1265. try:
  1266. if publish:
  1267. publish_message(pub, msg_class, pub_args, None, True, verbose=verbose)
  1268. except ValueError as e:
  1269. sys.stderr.write("%s\n"%str(e))
  1270. break
  1271. if r is not None:
  1272. r.sleep()
  1273. with notifier.lock:
  1274. if notifier.updates:
  1275. pub_args = notifier.updates.pop(0)
  1276. if type(pub_args) == dict:
  1277. pub_args = [pub_args]
  1278. else:
  1279. publish = False
  1280. with notifier.lock:
  1281. if not notifier.updates:
  1282. notifier.lock.wait(1.)
  1283. if notifier.updates:
  1284. publish = True
  1285. pub_args = notifier.updates.pop(0)
  1286. if type(pub_args) == dict:
  1287. pub_args = [pub_args]
  1288. if rospy.is_shutdown():
  1289. break
  1290. def stdin_publish(pub, msg_class, rate, once, filename, verbose):
  1291. """
  1292. :param filename: name of file to read from instead of stdin, or ``None``, ``str``
  1293. """
  1294. if filename:
  1295. iterator = file_yaml_arg(filename)
  1296. else:
  1297. iterator = stdin_yaml_arg
  1298. r = rospy.Rate(rate) if rate is not None else None
  1299. # stdin publishing can happen really fast, especially if no rate
  1300. # is set, so try to make sure someone is listening before we
  1301. # publish, though we don't wait too long.
  1302. wait_for_subscriber(pub, SUBSCRIBER_TIMEOUT)
  1303. single_arg = None
  1304. for pub_args in iterator():
  1305. if rospy.is_shutdown():
  1306. break
  1307. if pub_args:
  1308. if type(pub_args) != list:
  1309. pub_args = [pub_args]
  1310. try:
  1311. # we use 'bool(r) or once' for the once value, which
  1312. # controls whether or not publish_message blocks and
  1313. # latches until exit. We want to block if the user
  1314. # has enabled latching (i.e. rate is none). It would
  1315. # be good to reorganize this code more conceptually
  1316. # but, for now, this is the best re-use of the
  1317. # underlying methods.
  1318. publish_message(pub, msg_class, pub_args, None, bool(r) or once, verbose=verbose)
  1319. except ValueError as e:
  1320. sys.stderr.write("%s\n"%str(e))
  1321. break
  1322. if r is not None:
  1323. r.sleep()
  1324. if rospy.is_shutdown() or once:
  1325. break
  1326. # Publishing a single message repeatedly
  1327. if single_arg and r and not once:
  1328. while not rospy.is_shutdown():
  1329. try:
  1330. publish_message(pub, msg_class, pub_args, None, True, verbose=verbose)
  1331. if r is not None:
  1332. r.sleep()
  1333. except ValueError as e:
  1334. break
  1335. def stdin_yaml_arg():
  1336. """
  1337. Iterate over YAML documents in stdin
  1338. :returns: for next list of arguments on stdin. Iterator returns a list of args for each call, ``iterator``
  1339. """
  1340. import yaml
  1341. from select import select
  1342. from select import error as select_error
  1343. try:
  1344. arg = 'x'
  1345. rlist = [sys.stdin]
  1346. wlist = xlist = []
  1347. while not rospy.is_shutdown() and arg != '\n':
  1348. buff = ''
  1349. while arg != '' and arg.strip() != '---' and not rospy.is_shutdown():
  1350. val, _, _ = select(rlist, wlist, xlist, 1.0)
  1351. if not val:
  1352. continue
  1353. # sys.stdin.readline() returns empty string on EOF
  1354. arg = sys.stdin.readline()
  1355. if arg != '' and arg.strip() != '---':
  1356. buff = buff + arg
  1357. if arg.strip() == '---': # End of document
  1358. try:
  1359. loaded = yaml.load(buff.rstrip())
  1360. except Exception as e:
  1361. sys.stderr.write("Invalid YAML: %s\n"%str(e))
  1362. if loaded is not None:
  1363. yield loaded
  1364. elif arg == '': #EOF
  1365. # we don't yield the remaining buffer in this case
  1366. # because we don't want to publish partial inputs
  1367. return
  1368. arg = 'x' # reset
  1369. except select_error:
  1370. return # most likely ctrl-c interrupt
  1371. def _rostopic_cmd_list(argv):
  1372. """
  1373. Command-line parsing for 'rostopic list' command.
  1374. """
  1375. args = argv[2:]
  1376. from optparse import OptionParser
  1377. parser = OptionParser(usage="usage: %prog list [/namespace]", prog=NAME)
  1378. parser.add_option("-b", "--bag",
  1379. dest="bag", default=None,
  1380. help="list topics in .bag file", metavar="BAGFILE")
  1381. parser.add_option("-v", "--verbose",
  1382. dest="verbose", default=False,action="store_true",
  1383. help="list full details about each topic")
  1384. parser.add_option("-p",
  1385. dest="publishers", default=False,action="store_true",
  1386. help="list only publishers")
  1387. parser.add_option("-s",
  1388. dest="subscribers", default=False,action="store_true",
  1389. help="list only subscribers")
  1390. parser.add_option("--host", dest="hostname", default=False, action="store_true",
  1391. help="group by host name")
  1392. (options, args) = parser.parse_args(args)
  1393. topic = None
  1394. if len(args) == 1:
  1395. topic = rosgraph.names.script_resolve_name('rostopic', args[0])
  1396. elif len(args) > 1:
  1397. parser.error("you may only specify one input topic")
  1398. if options.bag:
  1399. if options.subscribers:
  1400. parser.error("-s option is not valid with bags")
  1401. elif options.publishers:
  1402. parser.error("-p option is not valid with bags")
  1403. elif options.hostname:
  1404. parser.error("--host option is not valid with bags")
  1405. _rostopic_list_bag(options.bag, topic)
  1406. else:
  1407. if options.subscribers and options.publishers:
  1408. parser.error("you may only specify one of -p, -s")
  1409. exitval = _rostopic_list(topic, verbose=options.verbose, subscribers_only=options.subscribers, publishers_only=options.publishers, group_by_host=options.hostname) or 0
  1410. if exitval != 0:
  1411. sys.exit(exitval)
  1412. def _rostopic_cmd_info(argv):
  1413. """
  1414. Command-line parsing for 'rostopic info' command.
  1415. """
  1416. args = argv[2:]
  1417. from optparse import OptionParser
  1418. parser = OptionParser(usage="usage: %prog info /topic", prog=NAME)
  1419. (options, args) = parser.parse_args(args)
  1420. if len(args) == 0:
  1421. parser.error("you must specify a topic name")
  1422. elif len(args) > 1:
  1423. parser.error("you may only specify one topic name")
  1424. topic = rosgraph.names.script_resolve_name('rostopic', args[0])
  1425. exitval = _rostopic_info(topic) or 0
  1426. if exitval != 0:
  1427. sys.exit(exitval)
  1428. def _fullusage():
  1429. print("""rostopic is a command-line tool for printing information about ROS Topics.
  1430. Commands:
  1431. \trostopic bw\tdisplay bandwidth used by topic
  1432. \trostopic echo\tprint messages to screen
  1433. \trostopic find\tfind topics by type
  1434. \trostopic hz\tdisplay publishing rate of topic
  1435. \trostopic info\tprint information about active topic
  1436. \trostopic list\tlist active topics
  1437. \trostopic pub\tpublish data to topic
  1438. \trostopic type\tprint topic type
  1439. Type rostopic <command> -h for more detailed usage, e.g. 'rostopic echo -h'
  1440. """)
  1441. sys.exit(getattr(os, 'EX_USAGE', 1))
  1442. def rostopicmain(argv=None):
  1443. import rosbag
  1444. if argv is None:
  1445. argv=sys.argv
  1446. # filter out remapping arguments in case we are being invoked via roslaunch
  1447. argv = rospy.myargv(argv)
  1448. # process argv
  1449. if len(argv) == 1:
  1450. _fullusage()
  1451. try:
  1452. command = argv[1]
  1453. if command == 'echo':
  1454. _rostopic_cmd_echo(argv)
  1455. elif command == 'hz':
  1456. _rostopic_cmd_hz(argv)
  1457. elif command == 'type':
  1458. _rostopic_cmd_type(argv)
  1459. elif command in 'list':
  1460. _rostopic_cmd_list(argv)
  1461. elif command == 'info':
  1462. _rostopic_cmd_info(argv)
  1463. elif command == 'pub':
  1464. _rostopic_cmd_pub(argv)
  1465. elif command == 'bw':
  1466. _rostopic_cmd_bw(argv)
  1467. elif command == 'find':
  1468. _rostopic_cmd_find(argv)
  1469. else:
  1470. _fullusage()
  1471. except socket.error:
  1472. sys.stderr.write("Network communication failed. Most likely failed to communicate with master.\n")
  1473. sys.exit(1)
  1474. except rosbag.ROSBagException as e:
  1475. sys.stderr.write("ERROR: unable to use bag file: %s\n"%str(e))
  1476. sys.exit(1)
  1477. except rosgraph.MasterException as e:
  1478. # mainly for invalid master URI/rosgraph.masterapi
  1479. sys.stderr.write("ERROR: %s\n"%str(e))
  1480. sys.exit(1)
  1481. except ROSTopicException as e:
  1482. sys.stderr.write("ERROR: %s\n"%str(e))
  1483. sys.exit(1)
  1484. except KeyboardInterrupt: pass
  1485. except rospy.ROSInterruptException: pass