├── doc ├── changelog.rst ├── images │ ├── mix.png │ ├── cmd_vel.png │ └── joint_states.png ├── rqt_ez_publisher.rst ├── rqt_ez_publisher.publisher.rst ├── rqt_ez_publisher.quaternion_module.rst ├── index.rst ├── rqt_ez_publisher.widget.rst ├── Makefile └── conf.py ├── rosdoc.yaml ├── src └── rqt_ez_publisher │ ├── __init__.py │ ├── quaternion_module │ ├── __init__.py │ ├── quaternion_module.py │ ├── rpy_widget.py │ └── rpy_value_widget.py │ ├── publisher │ ├── __init__.py │ ├── topic_publisher.py │ ├── topic_fill_header_publisher.py │ └── topic_publisher_with_timer.py │ ├── widget │ ├── __init__.py │ ├── uint_value_widget.py │ ├── string_value_widget.py │ ├── bool_value_widget.py │ ├── base_widget.py │ ├── int_value_widget.py │ ├── double_value_widget.py │ └── value_widget.py │ ├── base_module.py │ ├── config_dialog.py │ ├── ez_publisher_plugin.py │ ├── ez_publisher_widget.py │ └── ez_publisher_model.py ├── README.md ├── test ├── ros.test ├── joint.py ├── polygon.py ├── function_test.py └── model_ros_test.py ├── setting_sample.yaml ├── scripts └── rqt_ez_publisher ├── setup.py ├── CMakeLists.txt ├── plugin.xml ├── .gitignore ├── package.xml ├── LICENSE ├── CHANGELOG.rst └── .travis.yml /doc/changelog.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../CHANGELOG.rst 2 | -------------------------------------------------------------------------------- /rosdoc.yaml: -------------------------------------------------------------------------------- 1 | - builder: sphinx 2 | sphinx_root_dir: doc 3 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/__init__.py: -------------------------------------------------------------------------------- 1 | from ez_publisher_plugin import EzPublisherPlugin 2 | -------------------------------------------------------------------------------- /doc/images/mix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OTL/rqt_ez_publisher/HEAD/doc/images/mix.png -------------------------------------------------------------------------------- /doc/images/cmd_vel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OTL/rqt_ez_publisher/HEAD/doc/images/cmd_vel.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | rqt_ez_publisher 2 | ================ 3 | 4 | read http://wiki.ros.org/rqt_ez_publisher 5 | 6 | -------------------------------------------------------------------------------- /doc/images/joint_states.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OTL/rqt_ez_publisher/HEAD/doc/images/joint_states.png -------------------------------------------------------------------------------- /src/rqt_ez_publisher/quaternion_module/__init__.py: -------------------------------------------------------------------------------- 1 | from .quaternion_module import QuaternionModule 2 | from .rpy_value_widget import RPYValueWidget 3 | from .rpy_widget import RPYWidget 4 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/publisher/__init__.py: -------------------------------------------------------------------------------- 1 | from topic_publisher import TopicPublisher 2 | from topic_fill_header_publisher import TopicFillHeaderPublisher 3 | from topic_publisher_with_timer import TopicPublisherWithTimer 4 | -------------------------------------------------------------------------------- /test/ros.test: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/joint.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import rospy 4 | from sensor_msgs.msg import JointState 5 | 6 | def hoge(msg): 7 | print msg.position 8 | 9 | rospy.init_node('joint') 10 | rospy.Subscriber('/joint_states', JointState, hoge) 11 | rospy.spin() 12 | -------------------------------------------------------------------------------- /setting_sample.yaml: -------------------------------------------------------------------------------- 1 | publish_interval: 100 2 | texts: 3 | - /cmd_vel/linear/x 4 | - /cmd_vel/linear/z 5 | settings: 6 | - /cmd_vel/linear/x: 7 | min: -1.0 8 | max: 1.0 9 | is_repeat: true 10 | - /cmd_vel/linear/z: 11 | min: -1.0 12 | max: 1.0 13 | is_repeat: true 14 | -------------------------------------------------------------------------------- /scripts/rqt_ez_publisher: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | 5 | from rqt_gui.main import Main 6 | from rqt_ez_publisher import EzPublisherPlugin 7 | 8 | main = Main() 9 | sys.exit(main.main(sys.argv, standalone='rqt_ez_publisher', 10 | plugin_argument_provider=EzPublisherPlugin.add_arguments)) 11 | -------------------------------------------------------------------------------- /test/polygon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import rospy 4 | from geometry_msgs.msg import Polygon 5 | from geometry_msgs.msg import Pose 6 | 7 | def hoge(msg): 8 | print msg 9 | 10 | rospy.init_node('polygon') 11 | rospy.Subscriber('/polygon', Polygon, hoge) 12 | rospy.Subscriber('/pose', Pose, hoge) 13 | rospy.spin() 14 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/quaternion_module/quaternion_module.py: -------------------------------------------------------------------------------- 1 | from .. import base_module 2 | from . import rpy_widget 3 | 4 | 5 | class QuaternionModule(base_module.BaseModule): 6 | 7 | def get_msg_string(self): 8 | return 'geometry_msgs/Quaternion' 9 | 10 | def get_widget_class(self): 11 | return rpy_widget.RPYWidget 12 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from catkin_pkg.python_setup import generate_distutils_setup 3 | 4 | d = generate_distutils_setup( 5 | packages=['rqt_ez_publisher', 'rqt_ez_publisher.widget', 6 | 'rqt_ez_publisher.publisher', 'rqt_ez_publisher.quaternion_module'], 7 | package_dir={'': 'src'}, 8 | ) 9 | 10 | setup(**d) 11 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/__init__.py: -------------------------------------------------------------------------------- 1 | from .base_widget import BaseWidget 2 | from .bool_value_widget import BoolValueWidget 3 | from .double_value_widget import DoubleValueWidget 4 | from .int_value_widget import IntValueWidget 5 | from .string_value_widget import StringValueWidget 6 | from .uint_value_widget import UIntValueWidget 7 | from .value_widget import ValueWidget 8 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/base_module.py: -------------------------------------------------------------------------------- 1 | import roslib.message 2 | 3 | 4 | class BaseModule(object): 5 | 6 | '''Base class for modules for specific message type''' 7 | 8 | def get_msg_string(self): 9 | return '' 10 | 11 | def get_msg_class(self): 12 | return roslib.message.get_message_class(self.get_msg_string()) 13 | 14 | def get_widget_class(self): 15 | return None 16 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/uint_value_widget.py: -------------------------------------------------------------------------------- 1 | import int_value_widget 2 | 3 | 4 | class UIntValueWidget(int_value_widget.IntValueWidget): 5 | 6 | def __init__(self, topic_name, attributes, array_index, publisher, parent): 7 | super(UIntValueWidget, self).__init__( 8 | topic_name, attributes, array_index, publisher, parent) 9 | 10 | def setup_ui(self, name): 11 | super(UIntValueWidget, self).setup_ui( 12 | name, min_value=0, default_min_value=0) 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8.3) 2 | project(rqt_ez_publisher) 3 | 4 | find_package(catkin REQUIRED COMPONENTS rostest) 5 | 6 | catkin_python_setup() 7 | 8 | catkin_package() 9 | 10 | if(CATKIN_ENABLE_TESTING) 11 | catkin_add_nosetests(test/function_test.py) 12 | add_rostest(test/ros.test) 13 | endif() 14 | 15 | install(FILES 16 | plugin.xml 17 | DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION} 18 | ) 19 | 20 | install(PROGRAMS scripts/rqt_ez_publisher 21 | DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION} 22 | ) 23 | -------------------------------------------------------------------------------- /plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | An example Python GUI plugin to create a great user interface. 5 | 6 | 7 | 8 | 9 | folder 10 | Plugins related to ROS topics. 11 | 12 | 13 | stock_up 14 | GUI plugin for easy publishing ROS messages. 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/publisher/topic_publisher.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | 3 | 4 | class TopicPublisher(object): 5 | 6 | def __init__(self, topic_name, message_class): 7 | self._name = topic_name 8 | try: 9 | self._publisher = rospy.Publisher( 10 | topic_name, message_class, queue_size=100) 11 | self._message = message_class() 12 | except ValueError as e: 13 | rospy.logfatal('msg file for %s not found' % topic_name) 14 | raise e 15 | 16 | def get_topic_name(self): 17 | return self._name 18 | 19 | def publish(self): 20 | self._publisher.publish(self._message) 21 | 22 | def get_message(self): 23 | return self._message 24 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/string_value_widget.py: -------------------------------------------------------------------------------- 1 | from python_qt_binding import QtWidgets 2 | import value_widget 3 | 4 | 5 | class StringValueWidget(value_widget.ValueWidget): 6 | 7 | def __init__(self, topic_name, attributes, array_index, publisher, parent): 8 | self._type = str 9 | super(StringValueWidget, self).__init__( 10 | topic_name, attributes, array_index, publisher, parent) 11 | 12 | def input_text(self): 13 | self.publish_value(str(self._line_edit.text())) 14 | 15 | def setup_ui(self, name): 16 | self._line_edit = QtWidgets.QLineEdit() 17 | self._line_edit.returnPressed.connect(self.input_text) 18 | self._horizontal_layout.addWidget(self._line_edit) 19 | self.setLayout(self._horizontal_layout) 20 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/bool_value_widget.py: -------------------------------------------------------------------------------- 1 | from python_qt_binding import QtWidgets 2 | import value_widget 3 | 4 | 5 | class BoolValueWidget(value_widget.ValueWidget): 6 | 7 | def __init__(self, topic_name, attributes, array_index, publisher, parent): 8 | self._type = bool 9 | super(BoolValueWidget, self).__init__( 10 | topic_name, attributes, array_index, publisher, parent) 11 | 12 | def state_changed(self, state): 13 | self.publish_value(self._check_box.isChecked()) 14 | 15 | def setup_ui(self, name): 16 | self._check_box = QtWidgets.QCheckBox() 17 | self._check_box.stateChanged.connect(self.state_changed) 18 | self._horizontal_layout.addWidget(self._check_box) 19 | self.setLayout(self._horizontal_layout) 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | *~ 56 | -------------------------------------------------------------------------------- /doc/rqt_ez_publisher.rst: -------------------------------------------------------------------------------- 1 | rqt_ez_publisher package 2 | ======================== 3 | 4 | config_dialog module 5 | ------------------------------------- 6 | 7 | .. automodule:: rqt_ez_publisher.config_dialog 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | ez_publisher_plugin module 13 | ------------------------------------ 14 | 15 | .. automodule:: rqt_ez_publisher.ez_publisher_plugin 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | ez_publisher_model module 21 | ------------------------------------------ 22 | 23 | .. automodule:: rqt_ez_publisher.ez_publisher_model 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | ez_publisher_widget module 29 | ------------------------------------------- 30 | 31 | .. automodule:: rqt_ez_publisher.ez_publisher_widget 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | -------------------------------------------------------------------------------- /doc/rqt_ez_publisher.publisher.rst: -------------------------------------------------------------------------------- 1 | publisher Package 2 | ================= 3 | 4 | :mod:`publisher` Package 5 | ------------------------ 6 | 7 | .. automodule:: rqt_ez_publisher.publisher 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`topic_fill_header_publisher` Module 13 | ----------------------------------------- 14 | 15 | .. automodule:: rqt_ez_publisher.publisher.topic_fill_header_publisher 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`topic_publisher` Module 21 | ----------------------------- 22 | 23 | .. automodule:: rqt_ez_publisher.publisher.topic_publisher 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`topic_publisher_with_timer` Module 29 | ---------------------------------------- 30 | 31 | .. automodule:: rqt_ez_publisher.publisher.topic_publisher_with_timer 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /doc/rqt_ez_publisher.quaternion_module.rst: -------------------------------------------------------------------------------- 1 | quaternion_module Package 2 | ========================= 3 | 4 | :mod:`quaternion_module` Package 5 | -------------------------------- 6 | 7 | .. automodule:: rqt_ez_publisher.quaternion_module 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`quaternion_module` Module 13 | ------------------------------- 14 | 15 | .. automodule:: rqt_ez_publisher.quaternion_module.quaternion_module 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`rpy_value_widget` Module 21 | ------------------------------ 22 | 23 | .. automodule:: rqt_ez_publisher.quaternion_module.rpy_value_widget 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`rpy_widget` Module 29 | ------------------------ 30 | 31 | .. automodule:: rqt_ez_publisher.quaternion_module.rpy_widget 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/base_widget.py: -------------------------------------------------------------------------------- 1 | from python_qt_binding.QtWidgets import QWidget 2 | 3 | 4 | class BaseWidget(QWidget): 5 | 6 | def __init__(self, topic_name, publisher, parent=None): 7 | super(BaseWidget, self).__init__(parent=None) 8 | self._topic_name = topic_name 9 | self._publisher = publisher 10 | 11 | def get_text(self): 12 | return '' 13 | 14 | def get_range(self): 15 | return (0, 0) 16 | 17 | def set_range(self, r): 18 | pass 19 | 20 | def is_repeat(self): 21 | return self._publisher.is_repeating() 22 | 23 | def set_is_repeat(self, repeat_on): 24 | if repeat_on: 25 | self._publisher.set_timer() 26 | else: 27 | self._publisher.stop_timer() 28 | self._publisher.request_update() 29 | 30 | def get_topic_name(self): 31 | return self._topic_name 32 | 33 | def update(self): 34 | pass 35 | 36 | def set_configurable(self, value): 37 | pass 38 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/publisher/topic_fill_header_publisher.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | import topic_publisher 3 | import tf2_msgs.msg 4 | 5 | 6 | class TopicFillHeaderPublisher(topic_publisher.TopicPublisher): 7 | 8 | def __init__(self, topic_name, message_class): 9 | super(TopicFillHeaderPublisher, self).__init__( 10 | topic_name, message_class) 11 | self._is_tf = False 12 | if message_class == tf2_msgs.msg.TFMessage: 13 | self._is_tf = True 14 | self._has_header = False 15 | if hasattr(self._message, 'header'): 16 | if hasattr(self._message.header, 'stamp'): 17 | self._has_header = True 18 | 19 | def publish(self): 20 | if self._is_tf: 21 | now = rospy.Time.now() 22 | for transform in self._message.transforms: 23 | transform.header.stamp = now 24 | if self._has_header: 25 | self._message.header.stamp = rospy.Time.now() 26 | super(TopicFillHeaderPublisher, self).publish() 27 | -------------------------------------------------------------------------------- /package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | rqt_ez_publisher 4 | 0.5.0 5 | The rqt_ez_publisher package 6 | 7 | Takashi Ogura 8 | 9 | BSD 10 | 11 | http://wiki.ros.org/rqt_ez_publisher 12 | Takashi Ogura 13 | 14 | 15 | catkin 16 | rostest 17 | python-catkin-pkg 18 | 19 | rospy 20 | rqt_gui 21 | rqt_gui_py 22 | rqt_py_common 23 | tf 24 | tf2_msgs 25 | geometry_msgs 26 | 27 | sensor_msgs 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /doc/index.rst: -------------------------------------------------------------------------------- 1 | .. rqt_ez_publisher documentation master file, created by 2 | sphinx-quickstart on Sat Jul 5 22:29:35 2014. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | rqt_ez_publisher 7 | ============================================ 8 | 9 | rqt_ez_publisher is a plugin for rqt. 10 | This creates GUI for publishing topics. 11 | hoge 12 | 13 | .. figure:: images/joint_states.png 14 | :align: center 15 | 16 | for /joint_states (sensor_msgs/JointState) 17 | 18 | .. figure:: images/cmd_vel.png 19 | :align: center 20 | 21 | for /cmd_vel (goemetry_msgs/Twist) 22 | 23 | .. figure:: images/mix.png 24 | :align: center 25 | 26 | for everything 27 | 28 | 29 | Contents: 30 | 31 | .. toctree:: 32 | :maxdepth: 2 33 | 34 | rqt_ez_publisher 35 | rqt_ez_publisher.widget 36 | rqt_ez_publisher.publisher 37 | rqt_ez_publisher.quaternion_module 38 | changelog 39 | 40 | Indices and tables 41 | ================== 42 | 43 | * :ref:`genindex` 44 | * :ref:`modindex` 45 | * :ref:`search` 46 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, Takashi Ogura 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of the {organization} nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /src/rqt_ez_publisher/publisher/topic_publisher_with_timer.py: -------------------------------------------------------------------------------- 1 | import topic_fill_header_publisher 2 | from python_qt_binding import QtCore 3 | 4 | DEFAULT_PUBLISH_INTERVAL = 100 5 | 6 | 7 | class TopicPublisherWithTimer(topic_fill_header_publisher.TopicFillHeaderPublisher): 8 | 9 | publish_interval = DEFAULT_PUBLISH_INTERVAL 10 | 11 | def __init__(self, topic_name, message_class): 12 | super(TopicPublisherWithTimer, self).__init__( 13 | topic_name, message_class) 14 | self._timer = None 15 | self._manager = None 16 | 17 | def update_timer_interval(self, interval): 18 | if self._timer: 19 | self._timer.setInterval(interval) 20 | 21 | def set_timer(self, parent=None): 22 | interval = self.publish_interval 23 | if not self._timer: 24 | self._timer = QtCore.QTimer(parent=parent) 25 | self._timer.timeout.connect(self.publish) 26 | self._timer.setInterval(interval) 27 | self._timer.start() 28 | 29 | def stop_timer(self): 30 | if self._timer: 31 | self._timer.stop() 32 | self._timer = None 33 | 34 | def is_repeating(self): 35 | if self._timer and self._timer.isActive(): 36 | return True 37 | else: 38 | return False 39 | 40 | def set_manager(self, manager): 41 | self._manager = manager 42 | 43 | def get_manager(self): 44 | return self._manager 45 | 46 | def request_update(self): 47 | for slider in self._manager.get_sliders_for_topic( 48 | self.get_topic_name()): 49 | slider.update() 50 | -------------------------------------------------------------------------------- /doc/rqt_ez_publisher.widget.rst: -------------------------------------------------------------------------------- 1 | widget Package 2 | ============== 3 | 4 | :mod:`widget` Package 5 | --------------------- 6 | 7 | .. automodule:: rqt_ez_publisher.widget 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | :mod:`bool_value_widget` Module 13 | ------------------------------- 14 | 15 | .. automodule:: rqt_ez_publisher.widget.bool_value_widget 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | :mod:`double_value_widget` Module 21 | --------------------------------- 22 | 23 | .. automodule:: rqt_ez_publisher.widget.double_value_widget 24 | :members: 25 | :undoc-members: 26 | :show-inheritance: 27 | 28 | :mod:`int_value_widget` Module 29 | ------------------------------ 30 | 31 | .. automodule:: rqt_ez_publisher.widget.int_value_widget 32 | :members: 33 | :undoc-members: 34 | :show-inheritance: 35 | 36 | :mod:`rpy_value_widget` Module 37 | ------------------------------ 38 | 39 | .. automodule:: rqt_ez_publisher.widget.rpy_value_widget 40 | :members: 41 | :undoc-members: 42 | :show-inheritance: 43 | 44 | :mod:`rpy_widget` Module 45 | ------------------------ 46 | 47 | .. automodule:: rqt_ez_publisher.widget.rpy_widget 48 | :members: 49 | :undoc-members: 50 | :show-inheritance: 51 | 52 | :mod:`string_value_widget` Module 53 | --------------------------------- 54 | 55 | .. automodule:: rqt_ez_publisher.widget.string_value_widget 56 | :members: 57 | :undoc-members: 58 | :show-inheritance: 59 | 60 | :mod:`uint_value_widget` Module 61 | ------------------------------- 62 | 63 | .. automodule:: rqt_ez_publisher.widget.uint_value_widget 64 | :members: 65 | :undoc-members: 66 | :show-inheritance: 67 | 68 | :mod:`value_widget` Module 69 | -------------------------- 70 | 71 | .. automodule:: rqt_ez_publisher.widget.value_widget 72 | :members: 73 | :undoc-members: 74 | :show-inheritance: 75 | 76 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/quaternion_module/rpy_widget.py: -------------------------------------------------------------------------------- 1 | from .. import ez_publisher_model as ez_model 2 | import math 3 | from python_qt_binding import QtWidgets 4 | from ..widget import base_widget 5 | from . import rpy_value_widget 6 | 7 | 8 | class RPYWidget(base_widget.BaseWidget): 9 | 10 | def __init__(self, topic_name, attributes, array_index, publisher, 11 | parent=None): 12 | super(RPYWidget, self).__init__(topic_name, publisher, parent=parent) 13 | self._attributes = attributes 14 | self._publisher = publisher 15 | self._array_index = array_index 16 | self._topic_name = topic_name 17 | self._text = ez_model.make_text(topic_name, attributes, array_index) 18 | self._vertical_layout = QtWidgets.QVBoxLayout() 19 | self._widgets = [] 20 | self._parent = parent 21 | for i in range(3): 22 | widget = rpy_value_widget.RPYValueWidget( 23 | topic_name, attributes, array_index, publisher, i, self) 24 | self._widgets.append(widget) 25 | self._vertical_layout.addWidget(widget) 26 | 27 | self.set_range([-math.pi, math.pi]) 28 | self.setLayout(self._vertical_layout) 29 | self.add_button = None 30 | 31 | def get_text(self): 32 | return self._text 33 | 34 | def get_range(self): 35 | return self._widgets[0].get_range() 36 | 37 | def set_range(self, r): 38 | for widget in self._widgets: 39 | widget.set_range(r) 40 | 41 | def set_is_repeat(self, is_repeat): 42 | for widget in self._widgets: 43 | widget.set_is_repeat(is_repeat) 44 | 45 | def update(self): 46 | for widget in self._widgets: 47 | widget.update() 48 | 49 | def close_slider(self, widget, remove=True): 50 | widget.hide() 51 | if remove: 52 | self._widgets.remove(widget) 53 | self._vertical_layout.removeWidget(widget) 54 | if not self._widgets: 55 | self._parent.close_slider(self) 56 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/quaternion_module/rpy_value_widget.py: -------------------------------------------------------------------------------- 1 | import tf.transformations 2 | import rospy 3 | from ..widget import double_value_widget 4 | from .. import ez_publisher_model as ez_model 5 | 6 | 7 | class RPYValueWidget(double_value_widget.DoubleValueWidget): 8 | 9 | def __init__(self, topic_name, attributes, array_index, publisher, rpy_index, parent): 10 | self._type = 'RPY' 11 | self._rpy_index = rpy_index 12 | 13 | if self._rpy_index == 0: 14 | title = ez_model.make_text( 15 | topic_name, attributes + ['roll'], array_index) 16 | elif self._rpy_index == 1: 17 | title = ez_model.make_text( 18 | topic_name, attributes + ['pitch'], array_index) 19 | elif self._rpy_index == 2: 20 | title = ez_model.make_text( 21 | topic_name, attributes + ['yaw'], array_index) 22 | else: 23 | rospy.logerr('this is impossible, rpy[%d]' % rpy_index) 24 | title = ez_model.make_text( 25 | topic_name, attributes + ['???'], array_index) 26 | super(RPYValueWidget, self).__init__( 27 | topic_name, attributes, array_index, publisher, parent, 28 | label_text=title) 29 | 30 | def publish_value(self, value): 31 | q_msg = ez_model.get_msg_attribute_value(self._publisher.get_message(), 32 | self._topic_name, self._attributes) 33 | rpy = tf.transformations.euler_from_quaternion( 34 | [q_msg.x, q_msg.y, q_msg.z, q_msg.w]) 35 | rpy_list = list(rpy) 36 | rpy_list[self._rpy_index] = value 37 | new_q = tf.transformations.quaternion_from_euler( 38 | rpy_list[0], rpy_list[1], rpy_list[2]) 39 | xyzw = 'xyzw' 40 | for i in range(4): 41 | ez_model.set_msg_attribute_value( 42 | self._publisher.get_message(), self._topic_name, self._type, 43 | self._attributes + [xyzw[i]], self._array_index, new_q[i]) 44 | self._publisher.publish() 45 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/int_value_widget.py: -------------------------------------------------------------------------------- 1 | from python_qt_binding import QtCore 2 | from python_qt_binding import QtWidgets 3 | import value_widget 4 | 5 | 6 | class IntValueWidget(value_widget.ValueWidget): 7 | 8 | LCD_HEIGHT = 35 9 | 10 | def __init__(self, topic_name, attributes, array_index, publisher, parent): 11 | self._type = int 12 | super(IntValueWidget, self).__init__( 13 | topic_name, attributes, array_index, publisher, parent) 14 | 15 | def slider_changed(self, value): 16 | self._lcd.display(value) 17 | self.publish_value(value) 18 | 19 | def setup_ui(self, name, max_value=100000, min_value=-100000, 20 | default_max_value=100, default_min_value=-100, 21 | initial_value=0): 22 | self._min_spin_box = QtWidgets.QSpinBox() 23 | self._min_spin_box.setMaximum(max_value) 24 | self._min_spin_box.setMinimum(min_value) 25 | self._slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) 26 | self._slider.setTickPosition(QtWidgets.QSlider.TicksBelow) 27 | self._slider.valueChanged.connect(self.slider_changed) 28 | self._max_spin_box = QtWidgets.QSpinBox() 29 | self._max_spin_box.setMaximum(max_value) 30 | self._max_spin_box.setMinimum(min_value) 31 | self._lcd = QtWidgets.QLCDNumber() 32 | self._lcd.setMaximumHeight(self.LCD_HEIGHT) 33 | self._min_spin_box.valueChanged.connect(self._slider.setMinimum) 34 | self._max_spin_box.valueChanged.connect(self._slider.setMaximum) 35 | self._min_spin_box.setValue(default_min_value) 36 | self._max_spin_box.setValue(default_max_value) 37 | self._slider.setValue(initial_value) 38 | zero_button = QtWidgets.QPushButton('reset') 39 | zero_button.clicked.connect(lambda x: self._slider.setValue(0)) 40 | self._horizontal_layout.addWidget(self._min_spin_box) 41 | self._horizontal_layout.addWidget(self._slider) 42 | self._horizontal_layout.addWidget(self._max_spin_box) 43 | self._horizontal_layout.addWidget(self._lcd) 44 | self._horizontal_layout.addWidget(zero_button) 45 | 46 | self.setLayout(self._horizontal_layout) 47 | 48 | def get_range(self): 49 | return (self._min_spin_box.value(), self._max_spin_box.value()) 50 | 51 | def set_range(self, r): 52 | self._min_spin_box.setValue(r[0]) 53 | self._max_spin_box.setValue(r[1]) 54 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 2 | Changelog for package rqt_ez_publisher 3 | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 4 | 5 | 0.5.0 (2017-03-03) 6 | ------------------ 7 | * Add congigure checkbox and publish button by rein, thank you 8 | 9 | 0.4.0 (2016-10-26) 10 | ------------------ 11 | * Update config dialog for Qt5 12 | * Migrate to Qt5 13 | * Merge pull request `#23 `_ from felixduvallet/travis_fix 14 | Fix travis script 15 | * Fix python import path by sourcing devel/setup.bash. 16 | * Contributors: Felix Duvallet, Takashi Ogura 17 | 18 | 0.3.2 (2016-05-06) 19 | ------------------ 20 | * Fix JointTrajectory issue #22 21 | * Apply pep8 22 | * Merge pull request #21 from felixduvallet/travis_ci 23 | It seems nice! 24 | * Merge pull request #20 from felixduvallet/load_from_file_fix 25 | fix problem with loading yaml slider config file 26 | * Add travis CI for package. 27 | * fix problem with loading yaml slider config file. 28 | * remove trailing whitespace. 29 | * Contributors: Felix Duvallet, Takashi Ogura 30 | 31 | 0.3.1 (2016-04-11) 32 | ------------------ 33 | * Fix save/load dialog 34 | * Search from deeper topic name 35 | * Contributors: Takashi Ogura 36 | 37 | 0.3.0 (2014-08-25) 38 | ------------------ 39 | * Add save/load button in config dialog 40 | * Support yaml setting file 41 | * Support action in devel environment 42 | * update some log messages 43 | 44 | 0.2.0 (2014-07-19) 45 | ------------------ 46 | * Add tf for dependency 47 | * Refactoring 48 | - Use base_widget 49 | - divided widgets into widget/ 50 | - moved publishers into publisher/ 51 | - rpy to quaternion_module/ 52 | * do not create header/seq 53 | * Convert Quaternion to RPY 54 | 55 | 0.1.0 (2014-07-13) 56 | ------------------ 57 | * Change default for double 1.0 58 | * Support tf and header 59 | * fix bug with array 60 | * fix type bug 61 | 62 | 0.0.5 (2014-07-11) 63 | ------------------ 64 | * Fix test wait 65 | 66 | 0.0.4 (2014-07-11) 67 | ------------------ 68 | * Update for indigo 69 | * Fix NoneType error for make_topic_string 70 | * Clean codes (applied pep8) 71 | * Support byte 72 | * Add sphinx doc 73 | 74 | 0.0.3 (2014-07-05) 75 | ------------------ 76 | * Add tests that uses ros 77 | * Support array of non builtin type 78 | * add build status in README.md 79 | * Add config dialog, reload button 80 | 81 | 0.0.2 (2014-06-29) 82 | ------------------ 83 | * MoveUP/Down and use icon 84 | * Fix cmake (remove python packages from find_package) 85 | * Support repeat publish 86 | * support Header 87 | * add queue_size for publisher for indigo 88 | * for python3 89 | 90 | 0.0.1 (2014-06-29) 91 | ------------------ 92 | * more easy topic publisher 93 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/double_value_widget.py: -------------------------------------------------------------------------------- 1 | from python_qt_binding import QtCore 2 | from python_qt_binding import QtWidgets 3 | import value_widget 4 | 5 | 6 | class DoubleValueWidget(value_widget.ValueWidget): 7 | 8 | LCD_HEIGHT = 35 9 | DEFAULT_MAX_VALUE = 1.0 10 | DEFAULT_MIN_VALUE = -1.0 11 | 12 | def __init__(self, topic_name, attributes, array_index, publisher, parent, 13 | label_text=None): 14 | self._type = float 15 | super(DoubleValueWidget, self).__init__( 16 | topic_name, attributes, array_index, publisher, parent, 17 | label_text=label_text) 18 | 19 | def set_value(self, value): 20 | self._lcd.display(value) 21 | self.publish_value(value) 22 | 23 | def value_to_slider(self, value): 24 | return (value - self._min_spin_box.value()) / ( 25 | (self._max_spin_box.value() - self._min_spin_box.value())) * 100 26 | 27 | def slider_to_value(self, val): 28 | return self._min_spin_box.value() + ( 29 | (self._max_spin_box.value() - 30 | self._min_spin_box.value()) / 100.0 * val) 31 | 32 | def slider_changed(self, val): 33 | self.set_value(self.slider_to_value(val)) 34 | 35 | def setup_ui(self, name): 36 | self._min_spin_box = QtWidgets.QDoubleSpinBox() 37 | self._min_spin_box.setMaximum(10000) 38 | self._min_spin_box.setMinimum(-10000) 39 | self._min_spin_box.setValue(self.DEFAULT_MIN_VALUE) 40 | self._slider = QtWidgets.QSlider(QtCore.Qt.Horizontal) 41 | self._slider.setTickPosition(QtWidgets.QSlider.TicksBelow) 42 | self._slider.valueChanged.connect(self.slider_changed) 43 | self._max_spin_box = QtWidgets.QDoubleSpinBox() 44 | self._max_spin_box.setMaximum(10000) 45 | self._max_spin_box.setMinimum(-10000) 46 | self._max_spin_box.setValue(self.DEFAULT_MAX_VALUE) 47 | self._lcd = QtWidgets.QLCDNumber() 48 | self._lcd.setMaximumHeight(self.LCD_HEIGHT) 49 | self._slider.setValue(50) 50 | zero_button = QtWidgets.QPushButton('reset') 51 | zero_button.clicked.connect( 52 | lambda x: self._slider.setValue(self.value_to_slider(0.0))) 53 | self._horizontal_layout.addWidget(self._min_spin_box) 54 | self._horizontal_layout.addWidget(self._slider) 55 | self._horizontal_layout.addWidget(self._max_spin_box) 56 | self._horizontal_layout.addWidget(self._lcd) 57 | self._horizontal_layout.addWidget(zero_button) 58 | 59 | self.setLayout(self._horizontal_layout) 60 | 61 | def get_range(self): 62 | return (self._min_spin_box.value(), self._max_spin_box.value()) 63 | 64 | def set_range(self, min_max): 65 | self._min_spin_box.setValue(min_max[0]) 66 | self._max_spin_box.setValue(min_max[1]) 67 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/config_dialog.py: -------------------------------------------------------------------------------- 1 | from python_qt_binding import QtWidgets 2 | from python_qt_binding.QtWidgets import QDialog 3 | from python_qt_binding.QtWidgets import QDialogButtonBox 4 | from . import publisher 5 | 6 | 7 | class ConfigDialog(QDialog): 8 | 9 | '''Dialog for configure button of rqt system 10 | 11 | - set time interval for repeated publishing''' 12 | 13 | def __init__(self, plugin): 14 | super(ConfigDialog, self).__init__() 15 | self._plugin = plugin 16 | self._interval_spin_box = QtWidgets.QSpinBox() 17 | self._interval_spin_box.setMaximum(10000) 18 | self._interval_spin_box.setMinimum(1) 19 | self._interval_spin_box.setValue( 20 | publisher.TopicPublisherWithTimer.publish_interval) 21 | self._interval_spin_box.valueChanged.connect(self.update_interval) 22 | self._vertical_layout = QtWidgets.QVBoxLayout() 23 | self.configurable_checkbox = QtWidgets.QCheckBox() 24 | self.configurable_checkbox.setChecked(plugin.configurable) 25 | configurable_label = QtWidgets.QLabel('Configurable') 26 | self._configurable_horizontal_layout = QtWidgets.QHBoxLayout() 27 | self._configurable_horizontal_layout.addWidget(configurable_label) 28 | self._configurable_horizontal_layout.addWidget(self.configurable_checkbox) 29 | self._vertical_layout.addLayout(self._configurable_horizontal_layout) 30 | self._horizontal_layout = QtWidgets.QHBoxLayout() 31 | spin_label = QtWidgets.QLabel('Publish Interval for repeat [ms]') 32 | self._horizontal_layout.addWidget(spin_label) 33 | self._horizontal_layout.addWidget(self._interval_spin_box) 34 | self._vertical_layout.addLayout(self._horizontal_layout) 35 | save_button = QtWidgets.QPushButton(parent=self) 36 | save_button.setIcon( 37 | self.style().standardIcon(QtWidgets.QStyle.SP_DialogSaveButton)) 38 | save_button.setText('Save to file') 39 | save_button.clicked.connect(self.save_to_file) 40 | 41 | load_button = QtWidgets.QPushButton(parent=self) 42 | load_button.setIcon( 43 | self.style().standardIcon(QtWidgets.QStyle.SP_DialogOpenButton)) 44 | load_button.setText('Load from file') 45 | load_button.clicked.connect(self.load_from_file) 46 | 47 | self._vertical_layout.addWidget(save_button) 48 | self._vertical_layout.addWidget(load_button) 49 | self.setLayout(self._vertical_layout) 50 | self.adjustSize() 51 | 52 | def save_to_file(self): 53 | file_path, _ = QtWidgets.QFileDialog.getSaveFileName( 54 | self, 'Open file to save', filter="Setting File (*.yaml)") 55 | if file_path: 56 | if '.' not in file_path: 57 | file_path += ".yaml" 58 | self._plugin.save_to_file(file_path) 59 | self.close() 60 | 61 | def load_from_file(self): 62 | file_path, _ = QtWidgets.QFileDialog.getOpenFileName( 63 | self, 'Open file to load', filter="Setting File (*.yaml)") 64 | if file_path: 65 | self._plugin.load_from_file(file_path) 66 | self.close() 67 | 68 | def update_interval(self, interval): 69 | publisher.TopicPublisherWithTimer.publish_interval = interval 70 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/widget/value_widget.py: -------------------------------------------------------------------------------- 1 | from python_qt_binding import QtGui 2 | from python_qt_binding import QtWidgets 3 | from python_qt_binding.QtWidgets import QWidget 4 | from . import base_widget 5 | from .. import ez_publisher_model as ez_model 6 | 7 | 8 | class ValueWidget(base_widget.BaseWidget): 9 | 10 | def __init__(self, topic_name, attributes, array_index, publisher, parent, 11 | label_text=None): 12 | super(ValueWidget, self).__init__(topic_name, publisher, parent=parent) 13 | self._parent = parent 14 | self._attributes = attributes 15 | self._array_index = array_index 16 | self._text = ez_model.make_text(topic_name, attributes, array_index) 17 | self._horizontal_layout = QtWidgets.QHBoxLayout() 18 | if label_text is None: 19 | self._topic_label = QtWidgets.QLabel(self._text) 20 | else: 21 | self._topic_label = QtWidgets.QLabel(label_text) 22 | self.close_button = QtWidgets.QPushButton() 23 | self.close_button.setMaximumWidth(30) 24 | self.close_button.setIcon( 25 | self.style().standardIcon(QtWidgets.QStyle.SP_TitleBarCloseButton)) 26 | self.up_button = QtWidgets.QPushButton() 27 | self.up_button.setIcon( 28 | self.style().standardIcon(QtWidgets.QStyle.SP_ArrowUp)) 29 | self.up_button.setMaximumWidth(30) 30 | self.down_button = QtWidgets.QPushButton() 31 | self.down_button.setMaximumWidth(30) 32 | self.down_button.setIcon( 33 | self.style().standardIcon(QtWidgets.QStyle.SP_ArrowDown)) 34 | repeat_label = QtWidgets.QLabel('repeat') 35 | self._repeat_box = QtWidgets.QCheckBox() 36 | self._repeat_box.stateChanged.connect(self.repeat_changed) 37 | self._repeat_box.setChecked(publisher.is_repeating()) 38 | self._publish_button = QtWidgets.QPushButton('Publish') 39 | self._publish_button.clicked.connect(publisher.publish) 40 | self._horizontal_layout.addWidget(self._topic_label) 41 | self._horizontal_layout.addWidget(self.close_button) 42 | self._horizontal_layout.addWidget(self.up_button) 43 | self._horizontal_layout.addWidget(self.down_button) 44 | if self._array_index is not None: 45 | self.add_button = QtWidgets.QPushButton('+') 46 | self.add_button.setMaximumWidth(30) 47 | self._horizontal_layout.addWidget(self.add_button) 48 | else: 49 | self.add_button = None 50 | self.close_button.clicked.connect( 51 | lambda x: self._parent.close_slider(self)) 52 | self.up_button.clicked.connect( 53 | lambda x: self._parent.move_up_widget(self)) 54 | self.down_button.clicked.connect( 55 | lambda x: self._parent.move_down_widget(self)) 56 | self.setup_ui(self._text) 57 | self._horizontal_layout.addWidget(self._publish_button) 58 | self._horizontal_layout.addWidget(repeat_label) 59 | self._horizontal_layout.addWidget(self._repeat_box) 60 | 61 | def repeat_changed(self, state): 62 | self.set_is_repeat(state == 2) 63 | 64 | def update(self): 65 | self._repeat_box.setChecked(self.is_repeat()) 66 | 67 | def get_text(self): 68 | return self._text 69 | 70 | def publish_value(self, value): 71 | ez_model.set_msg_attribute_value(self._publisher.get_message(), 72 | self._topic_name, self._type, self._attributes, 73 | self._array_index, value) 74 | self._publisher.publish() 75 | 76 | def setup_ui(self, name): 77 | pass 78 | 79 | def get_range(self): 80 | return (0, 0) 81 | 82 | def set_range(self, range): 83 | pass 84 | 85 | def set_configurable(self, value): 86 | self.down_button.setVisible(value) 87 | self.up_button.setVisible(value) 88 | self.close_button.setVisible(value) 89 | if self.add_button: 90 | self.add_button.setVisible(value) 91 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Generic .travis.yml file for running continuous integration on Travis-CI for 2 | # any ROS package. 3 | # 4 | # Available here: 5 | # - http://felixduvallet.github.io/ros-travis-integration 6 | # - https://github.com/felixduvallet/ros-travis-integration 7 | # 8 | # This installs ROS on a clean Travis-CI virtual machine, creates a ROS 9 | # workspace, resolves all listed dependencies, and sets environment variables 10 | # (setup.bash). Then, it compiles the entire ROS workspace (ensuring there are 11 | # no compilation errors), and runs all the tests. If any of the compilation/test 12 | # phases fail, the build is marked as a failure. 13 | # 14 | # We handle two types of package dependencies: 15 | # - packages (ros and otherwise) available through apt-get. These are installed 16 | # using rosdep, based on the information in the ROS package.xml. 17 | # - dependencies that must be checked out from source. These are handled by 18 | # 'wstool', and should be listed in a file named dependencies.rosinstall. 19 | # 20 | # There are two variables you may want to change: 21 | # - ROS_DISTRO (default is indigo). Note that packages must be available for 22 | # ubuntu 14.04 trusty. 23 | # - ROSINSTALL_FILE (default is dependencies.rosinstall inside the repo 24 | # root). This should list all necessary repositories in wstool format (see 25 | # the ros wiki). If the file does not exists then nothing happens. 26 | # 27 | # See the README.md for more information. 28 | # 29 | # Author: Felix Duvallet 30 | 31 | # NOTE: The build lifecycle on Travis.ci is something like this: 32 | # before_install 33 | # install 34 | # before_script 35 | # script 36 | # after_success or after_failure 37 | # after_script 38 | # OPTIONAL before_deploy 39 | # OPTIONAL deploy 40 | # OPTIONAL after_deploy 41 | 42 | ################################################################################ 43 | 44 | # Use ubuntu trusty (14.04) with sudo privileges. 45 | dist: bionic 46 | sudo: required 47 | language: 48 | - generic 49 | cache: 50 | - apt 51 | 52 | # Configuration variables. All variables are global now, but this can be used to 53 | # trigger a build matrix for different ROS distributions if desired. 54 | env: 55 | global: 56 | - ROS_DISTRO=melodic 57 | - ROS_CI_DESKTOP="`lsb_release -cs`" # e.g. [precise|trusty|...] 58 | - CI_SOURCE_PATH=$(pwd) 59 | - ROSINSTALL_FILE=$CI_SOURCE_PATH/dependencies.rosinstall 60 | - CATKIN_OPTIONS=$CI_SOURCE_PATH/catkin.options 61 | - ROS_PARALLEL_JOBS='-j8 -l6' 62 | 63 | ################################################################################ 64 | 65 | # Install system dependencies, namely a very barebones ROS setup. 66 | before_install: 67 | - sudo sh -c "echo \"deb http://packages.ros.org/ros/ubuntu $ROS_CI_DESKTOP main\" > /etc/apt/sources.list.d/ros-latest.list" 68 | - wget http://packages.ros.org/ros.key -O - | sudo apt-key add - 69 | - sudo apt-get update -qq 70 | - sudo apt-get install -y python-catkin-pkg python-rosdep python-wstool ros-$ROS_DISTRO-catkin 71 | - source /opt/ros/$ROS_DISTRO/setup.bash 72 | # Prepare rosdep to install dependencies. 73 | - sudo rosdep init 74 | - rosdep update 75 | 76 | # Create a catkin workspace with the package under integration. 77 | install: 78 | - mkdir -p ~/catkin_ws/src 79 | - cd ~/catkin_ws/src 80 | - catkin_init_workspace 81 | # Create the devel/setup.bash (run catkin_make with an empty workspace) and 82 | # source it to set the path variables. 83 | - cd ~/catkin_ws 84 | - catkin_make 85 | - source devel/setup.bash 86 | # Add the package under integration to the workspace using a symlink. 87 | - cd ~/catkin_ws/src 88 | - ln -s $CI_SOURCE_PATH . 89 | 90 | # Install all dependencies, using wstool and rosdep. 91 | # wstool looks for a ROSINSTALL_FILE defined in the environment variables. 92 | before_script: 93 | # source dependencies: install using wstool. 94 | - cd ~/catkin_ws/src 95 | - wstool init 96 | - if [[ -f $ROSINSTALL_FILE ]] ; then wstool merge $ROSINSTALL_FILE ; fi 97 | - wstool up 98 | # package depdencies: install using rosdep. 99 | - cd ~/catkin_ws 100 | - rosdep install -y --from-paths src --ignore-src --rosdistro $ROS_DISTRO 101 | 102 | # Compile and test (fail if any step fails). If the CATKIN_OPTIONS file exists, 103 | # use it as an argument to catkin_make, for example to blacklist certain 104 | # packages. 105 | # 106 | # NOTE on testing: `catkin_make run_tests` will show the output of the tests 107 | # (gtest, nosetest, etc..) but always returns 0 (success) even if a test 108 | # fails. On the other hand, `catkin_make test` aggregates all results, but 109 | # returns non-zero when a test fails (which notifies Travis the build 110 | # failed). This is why we run both. 111 | script: 112 | - source /opt/ros/$ROS_DISTRO/setup.bash 113 | - cd ~/catkin_ws 114 | - catkin_make $( [ -f $CATKIN_OPTIONS ] && cat $CATKIN_OPTIONS ) 115 | # Run the tests, ensuring the path is set correctly. 116 | - source devel/setup.bash 117 | - catkin_make run_tests && catkin_make test 118 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/ez_publisher_plugin.py: -------------------------------------------------------------------------------- 1 | import os 2 | import rospy 3 | import yaml 4 | from . import ez_publisher_widget 5 | from . import publisher 6 | from . import config_dialog 7 | from . import quaternion_module 8 | from rqt_py_common.plugin_container_widget import PluginContainerWidget 9 | from qt_gui.plugin import Plugin 10 | 11 | 12 | class EzPublisherPlugin(Plugin): 13 | 14 | '''Plugin top class for rqt_ez_publisher''' 15 | 16 | def __init__(self, context): 17 | super(EzPublisherPlugin, self).__init__(context) 18 | self.setObjectName('EzPublisher') 19 | modules = [quaternion_module.QuaternionModule()] 20 | self._widget = ez_publisher_widget.EzPublisherWidget(modules=modules) 21 | self._widget.setObjectName('EzPublisherPluginUi') 22 | self.mainwidget = PluginContainerWidget(self._widget, True, False) 23 | if context.serial_number() > 1: 24 | self._widget.setWindowTitle(self._widget.windowTitle() + 25 | (' (%d)' % context.serial_number())) 26 | context.add_widget(self._widget) 27 | from argparse import ArgumentParser 28 | parser = ArgumentParser(prog='rqt_ez_publisher') 29 | EzPublisherPlugin.add_arguments(parser) 30 | args, unknowns = parser.parse_known_args(context.argv()) 31 | self._loaded_settings = None 32 | self.configurable = True 33 | if args.slider_file is not None: 34 | self.load_from_file(args.slider_file) 35 | 36 | def shutdown_plugin(self): 37 | pass 38 | 39 | def save_to_file(self, file_path): 40 | try: 41 | f = open(file_path, 'w') 42 | f.write(yaml.safe_dump(self.save_to_dict(), 43 | encoding='utf-8', allow_unicode=True)) 44 | f.close() 45 | rospy.loginfo('saved as %s' % file_path) 46 | except IOError as e: 47 | rospy.logerr('%s: Failed to save as %s' % (e, file_path)) 48 | 49 | def load_from_file(self, file_path): 50 | if os.path.exists(file_path): 51 | self._widget.clear_sliders() 52 | self._loaded_settings = yaml.load(open(file_path).read()) 53 | self.restore_from_dict(self._loaded_settings) 54 | else: 55 | rospy.logerr('%s is not found' % file_path) 56 | 57 | def save_settings(self, plugin_settings, instance_settings): 58 | instance_settings.set_value( 59 | 'texts', [x.get_text() for x in self._widget.get_sliders()]) 60 | instance_settings.set_value( 61 | 'publish_interval', publisher.TopicPublisherWithTimer.publish_interval) 62 | for slider in self._widget.get_sliders(): 63 | instance_settings.set_value( 64 | slider.get_text() + '_range', slider.get_range()) 65 | instance_settings.set_value( 66 | slider.get_text() + '_is_repeat', slider.is_repeat()) 67 | instance_settings.set_value('configurable', self.configurable) 68 | 69 | def restore_settings(self, plugin_settings, instance_settings): 70 | if self._loaded_settings is not None: 71 | return 72 | texts = instance_settings.value('texts') 73 | if texts: 74 | for text in texts: 75 | self._widget.add_slider_by_text(text) 76 | for slider in self._widget.get_sliders(): 77 | r = instance_settings.value(slider.get_text() + '_range') 78 | slider.set_range(r) 79 | is_repeat = instance_settings.value( 80 | slider.get_text() + '_is_repeat') 81 | slider.set_is_repeat(is_repeat == 'true') 82 | interval = instance_settings.value('publish_interval') 83 | if interval: 84 | publisher.TopicPublisherWithTimer.publish_interval = int(interval) 85 | configurable = instance_settings.value('configurable') 86 | if configurable is not None: 87 | self.configurable = bool(configurable) 88 | self._widget.set_configurable(self.configurable) 89 | 90 | def restore_from_dict(self, settings): 91 | for text in settings['texts']: 92 | self._widget.add_slider_by_text(text) 93 | for slider in self._widget.get_sliders(): 94 | try: 95 | slider_setting = settings['settings'][slider.get_text()] 96 | slider.set_range( 97 | [slider_setting['min'], slider_setting['max']]) 98 | 99 | slider.set_is_repeat(slider_setting['is_repeat']) 100 | except KeyError as e: 101 | pass 102 | publisher.TopicPublisherWithTimer.publish_interval = ( 103 | settings['publish_interval']) 104 | 105 | def save_to_dict(self): 106 | save_dict = {} 107 | save_dict['texts'] = [x.get_text() for x in self._widget.get_sliders()] 108 | save_dict['publish_interval'] = ( 109 | publisher.TopicPublisherWithTimer.publish_interval) 110 | save_dict['settings'] = {} 111 | for slider in self._widget.get_sliders(): 112 | save_dict['settings'][slider.get_text()] = {} 113 | range_min, range_max = slider.get_range() 114 | save_dict['settings'][slider.get_text()]['min'] = range_min 115 | save_dict['settings'][slider.get_text()]['max'] = range_max 116 | save_dict['settings'][slider.get_text()]['is_repeat'] = ( 117 | slider.is_repeat()) 118 | return save_dict 119 | 120 | def trigger_configuration(self): 121 | dialog = config_dialog.ConfigDialog(self) 122 | dialog.exec_() 123 | self.configurable = dialog.configurable_checkbox.isChecked() 124 | self._widget.set_configurable(self.configurable) 125 | 126 | @staticmethod 127 | def _isfile(parser, arg): 128 | if os.path.isfile(arg): 129 | return arg 130 | else: 131 | parser.error("Setting file %s does not exist" % arg) 132 | 133 | @staticmethod 134 | def add_arguments(parser): 135 | group = parser.add_argument_group( 136 | 'Options for rqt_ez_publisher plugin') 137 | group.add_argument('--slider-file', 138 | type=lambda x: EzPublisherPlugin._isfile(parser, x), 139 | help="YAML setting file") 140 | -------------------------------------------------------------------------------- /test/function_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import unittest 4 | import geometry_msgs.msg as geo_msgs 5 | 6 | import rqt_ez_publisher.ez_publisher_model as ez_model 7 | from rqt_ez_publisher import quaternion_module 8 | 9 | PKG='rqt_ez_publisher' 10 | 11 | class FunctionTest(unittest.TestCase): 12 | 13 | def test_make_topic_strings(self): 14 | strings = ez_model.make_topic_strings(geo_msgs.Twist(), '/cmd_vel') 15 | self.assertEqual(len(strings), 6) 16 | self.assertTrue('/cmd_vel/linear/x' in strings) 17 | self.assertTrue('/cmd_vel/linear/y' in strings) 18 | self.assertTrue('/cmd_vel/linear/z' in strings) 19 | self.assertTrue('/cmd_vel/angular/x' in strings) 20 | self.assertTrue('/cmd_vel/angular/y' in strings) 21 | self.assertTrue('/cmd_vel/angular/z' in strings) 22 | 23 | def test_make_topic_strings_with_header(self): 24 | strings = ez_model.make_topic_strings(geo_msgs.PointStamped(), 25 | '/cmd_vel') 26 | self.assertEqual(len(strings), 7) 27 | self.assertTrue('/cmd_vel/header/seq' in strings) 28 | self.assertTrue('/cmd_vel/header/stamp/secs' in strings) 29 | self.assertTrue('/cmd_vel/header/stamp/nsecs' in strings) 30 | self.assertTrue('/cmd_vel/header/frame_id' in strings) 31 | self.assertTrue('/cmd_vel/point/x' in strings) 32 | self.assertTrue('/cmd_vel/point/y' in strings) 33 | self.assertTrue('/cmd_vel/point/z' in strings) 34 | 35 | def test_flatten(self): 36 | flattened = ez_model.flatten([0, [[1, 2], 3, 4], [5, 6], [7], 8]) 37 | self.assertEqual(len(flattened), 9) 38 | 39 | def test_find_topic_name_found(self): 40 | topic, attr, index = ez_model.find_topic_name( 41 | '/hoge/data', {'/hoge': 'type_a', '/hoga': 'type_b'}) 42 | self.assertEqual(topic, '/hoge') 43 | self.assertEqual(attr, ['data']) 44 | self.assertEqual(index, None) 45 | 46 | def test_find_topic_name_found_topic_only(self): 47 | topic, attr, index = ez_model.find_topic_name( 48 | '/hoge', {'/hoge': 'type_a', '/hoga': 'type_b'}) 49 | self.assertEqual(topic, '/hoge') 50 | self.assertEqual(attr, None) 51 | self.assertEqual(index, None) 52 | 53 | def test_find_topic_name_found_topic_only_nested(self): 54 | topic, attr, index = ez_model.find_topic_name( 55 | '/hoge', {'/hoge': 'type_a', '/hoga': 'type_b', '/hoge/nested': 'type_c'}) 56 | self.assertEqual(topic, '/hoge') 57 | self.assertEqual(attr, None) 58 | self.assertEqual(index, None) 59 | 60 | def test_find_topic_name_found_topic_only_nested_by_nested(self): 61 | topic, attr, index = ez_model.find_topic_name( 62 | '/some/topic/again/data', 63 | {'/some/topic/again': 'std_msgs/Float32', '/some/topic': 'std_msgs/Float32', '/this/works': 'std_msgs/Float32'}) 64 | self.assertEqual(topic, '/some/topic/again') 65 | # self.assertEqual(attr, None) 66 | self.assertEqual(index, None) 67 | 68 | def test_find_topic_name_found_topic_only_nested_by_nested2(self): 69 | topic, attr, index = ez_model.find_topic_name( 70 | '/some/topic/again', 71 | {'/some/topic/again': 'std_msgs/Float32', '/some/topic': 'std_msgs/Float32', '/this/works': 'std_msgs/Float32'}) 72 | self.assertEqual(topic, '/some/topic/again') 73 | self.assertEqual(attr, None) 74 | self.assertEqual(index, None) 75 | 76 | def test_find_topic_name_found_with_index(self): 77 | topic, attr, index = ez_model.find_topic_name( 78 | '/hoge/data[2]', {'/hoge': 'type_a', '/hoga': 'type_b'}) 79 | self.assertEqual(topic, '/hoge') 80 | self.assertEqual(attr, ['data']) 81 | self.assertEqual(index, 2) 82 | 83 | def test_find_topic_name_not_found(self): 84 | topic, attr, index = ez_model.find_topic_name( 85 | '/hoge/data', {'/hogi': 'type_a', '/hoga': 'type_b'}) 86 | self.assertEqual(topic, None) 87 | self.assertEqual(attr, None) 88 | self.assertEqual(index, None) 89 | 90 | def test_find_topic_name_repeated(self): 91 | topic, attr, index = ez_model.find_topic_name( 92 | '/hoge/goal/goal', {'/hoge/goal': 'type_a', '/hoga': 'type_b'}) 93 | self.assertEqual(topic, '/hoge/goal') 94 | self.assertEqual(attr, ['goal']) 95 | self.assertEqual(index, None) 96 | 97 | def test_get_value_type(self): 98 | type, is_array = ez_model.get_value_type( 99 | 'geometry_msgs/Twist', ['linear', 'x']) 100 | self.assertEqual(type, float) 101 | self.assertEqual(is_array, False) 102 | 103 | def test_get_value_type_header(self): 104 | type, is_array = ez_model.get_value_type( 105 | 'geometry_msgs/PointStamped', ['header', 'frame_id']) 106 | self.assertEqual(type, str) 107 | self.assertEqual(is_array, False) 108 | 109 | def test_get_value_type_not_found(self): 110 | type, is_array = ez_model.get_value_type( 111 | 'geometry_msgs/Twist', ['linear']) 112 | self.assertEqual(type, None) 113 | self.assertEqual(is_array, False) 114 | 115 | def test_get_value_type_array(self): 116 | type, is_array = ez_model.get_value_type( 117 | 'geometry_msgs/TwistWithCovariance', ['covariance']) 118 | self.assertEqual(type, float) 119 | self.assertEqual(is_array, True) 120 | 121 | def test_get_value_type_non_builtin_array(self): 122 | type, is_array = ez_model.get_value_type( 123 | 'geometry_msgs/Polygon', ['points[0]', 'x']) 124 | self.assertEqual(type, float) 125 | self.assertEqual(is_array, False) 126 | 127 | def test_make_test(self): 128 | text = ez_model.make_text('/cmd_vel', ['linear', 'x'], None) 129 | self.assertEqual(text, '/cmd_vel/linear/x') 130 | 131 | def test_make_test_array(self): 132 | text = ez_model.make_text('/cmd_vel', ['linear', 'x'], 2) 133 | self.assertEqual(text, '/cmd_vel/linear/x[2]') 134 | 135 | def test_get_value_type_quaternion(self): 136 | msg_type, is_array = ez_model.get_value_type('geometry_msgs/Pose', ['orientation']) 137 | self.assertEqual(msg_type, None) 138 | self.assertEqual(is_array, False) 139 | msg_type, is_array = ez_model.get_value_type( 140 | 'geometry_msgs/Pose', ['orientation'], 141 | modules=[quaternion_module.QuaternionModule()]) 142 | self.assertEqual(msg_type, 'geometry_msgs/Quaternion') 143 | self.assertEqual(is_array, False) 144 | 145 | 146 | if __name__ == '__main__': 147 | import rosunit 148 | rosunit.unitrun(PKG, 'function_test', FunctionTest) 149 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/ez_publisher_widget.py: -------------------------------------------------------------------------------- 1 | import rospy 2 | from python_qt_binding import QtCore 3 | from python_qt_binding import QtGui 4 | from python_qt_binding import QtWidgets 5 | from python_qt_binding.QtWidgets import QWidget 6 | from rqt_ez_publisher import ez_publisher_model as ez_model 7 | from rqt_ez_publisher import widget as ez_widget 8 | from rqt_ez_publisher import publisher 9 | 10 | 11 | class EzPublisherWidget(QWidget): 12 | 13 | '''Main widget of this GUI''' 14 | 15 | sig_sysmsg = QtCore.Signal(str) 16 | 17 | def __init__(self, parent=None, modules=[]): 18 | self._model = ez_model.EzPublisherModel( 19 | publisher.TopicPublisherWithTimer, modules=modules) 20 | self._sliders = [] 21 | QWidget.__init__(self, parent=parent) 22 | self.setup_ui() 23 | 24 | def add_slider_from_combo(self): 25 | return self.add_slider_by_text(str(self._combo.currentText())) 26 | 27 | def close_slider(self, widget, remove=True): 28 | widget.hide() 29 | if remove: 30 | self._sliders.remove(widget) 31 | self._main_vertical_layout.removeWidget(widget) 32 | 33 | def get_next_index(self, topic_name, attributes): 34 | array_index = 0 35 | text = ez_model.make_text(topic_name, attributes, array_index) 36 | while text in [x.get_text() for x in self._sliders]: 37 | array_index += 1 38 | text = ez_model.make_text(topic_name, attributes, array_index) 39 | return array_index 40 | 41 | def add_widget(self, output_type, topic_name, attributes, array_index, 42 | position=None): 43 | widget_class = None 44 | type_class_dict = {float: ez_widget.DoubleValueWidget, 45 | int: ez_widget.IntValueWidget, 46 | 'uint': ez_widget.UIntValueWidget, 47 | bool: ez_widget.BoolValueWidget, 48 | str: ez_widget.StringValueWidget} 49 | for module in self._model.get_modules(): 50 | type_class_dict[ 51 | module.get_msg_string()] = module.get_widget_class() 52 | if output_type in type_class_dict: 53 | widget_class = type_class_dict[output_type] 54 | else: 55 | self.sig_sysmsg.emit('not supported type %s' % output_type) 56 | return False 57 | widget = widget_class(topic_name, attributes, array_index, 58 | self._model.get_publisher(topic_name), self) 59 | self._model.get_publisher(topic_name).set_manager(self) 60 | self._sliders.append(widget) 61 | if widget.add_button: 62 | widget.add_button.clicked.connect( 63 | lambda: self.add_widget( 64 | output_type, topic_name, attributes, 65 | self.get_next_index(topic_name, attributes), 66 | self._main_vertical_layout.indexOf(widget) + 1)) 67 | if position: 68 | self._main_vertical_layout.insertWidget(position, widget) 69 | else: 70 | self._main_vertical_layout.addWidget(widget) 71 | return True 72 | 73 | def move_down_widget(self, widget): 74 | index = self._main_vertical_layout.indexOf(widget) 75 | if index < self._main_vertical_layout.count() - 1: 76 | self._main_vertical_layout.removeWidget(widget) 77 | self._main_vertical_layout.insertWidget(index + 1, widget) 78 | 79 | def move_up_widget(self, widget): 80 | index = self._main_vertical_layout.indexOf(widget) 81 | if index > 1: 82 | self._main_vertical_layout.removeWidget(widget) 83 | self._main_vertical_layout.insertWidget(index - 1, widget) 84 | 85 | def add_slider_by_text(self, text): 86 | if text.endswith('/header/seq'): 87 | rospy.loginfo('header/seq is not created') 88 | return 89 | if text in [x.get_text() for x in self._sliders]: 90 | self.sig_sysmsg.emit('%s is already exists' % text) 91 | return 92 | results = self._model.register_topic_by_text(text) 93 | if not results: 94 | self.sig_sysmsg.emit('%s does not exists' % text) 95 | return 96 | topic_name, attributes, builtin_type, is_array, array_index = results 97 | if builtin_type: 98 | if is_array and array_index is None: 99 | # use index 0 100 | array_index = 0 101 | self.add_widget(builtin_type, topic_name, attributes, array_index) 102 | else: 103 | for string in self._model.expand_attribute(text, array_index): 104 | self.add_slider_by_text(string) 105 | 106 | def get_sliders_for_topic(self, topic): 107 | return [x for x in self._sliders if x.get_topic_name() == topic] 108 | 109 | def get_sliders(self): 110 | return self._sliders 111 | 112 | def clear_sliders(self): 113 | for widget in self._sliders: 114 | self.close_slider(widget, False) 115 | self._sliders = [] 116 | 117 | def update_combo_items(self): 118 | self._combo.clear() 119 | for topic in self._model.get_topic_names(): 120 | self._combo.addItem(topic) 121 | 122 | def set_configurable(self, value): 123 | self._reload_button.setVisible(value) 124 | self._topic_label.setVisible(value) 125 | self._clear_button.setVisible(value) 126 | self._combo.setVisible(value) 127 | for slider in self._sliders: 128 | slider.set_configurable(value) 129 | 130 | def setup_ui(self): 131 | self._horizontal_layout = QtWidgets.QHBoxLayout() 132 | self._reload_button = QtWidgets.QPushButton(parent=self) 133 | self._reload_button.setMaximumWidth(30) 134 | self._reload_button.setIcon( 135 | self.style().standardIcon(QtWidgets.QStyle.SP_BrowserReload)) 136 | self._reload_button.clicked.connect(self.update_combo_items) 137 | self._topic_label = QtWidgets.QLabel('topic(+data member) name') 138 | self._clear_button = QtWidgets.QPushButton('all clear') 139 | self._clear_button.setMaximumWidth(200) 140 | self._clear_button.clicked.connect(self.clear_sliders) 141 | self._combo = QtWidgets.QComboBox() 142 | self._combo.setEditable(True) 143 | self.update_combo_items() 144 | self._combo.activated.connect(self.add_slider_from_combo) 145 | self._horizontal_layout.addWidget(self._reload_button) 146 | self._horizontal_layout.addWidget(self._topic_label) 147 | self._horizontal_layout.addWidget(self._combo) 148 | self._horizontal_layout.addWidget(self._clear_button) 149 | self._main_vertical_layout = QtWidgets.QVBoxLayout() 150 | self._main_vertical_layout.addLayout(self._horizontal_layout) 151 | self._main_vertical_layout.setAlignment( 152 | self._horizontal_layout, QtCore.Qt.AlignTop) 153 | self.setLayout(self._main_vertical_layout) 154 | 155 | def shutdown(self): 156 | self._model.shutdown() 157 | 158 | 159 | def main(): 160 | import sys 161 | app = QtWidgets.QApplication(sys.argv) 162 | main_window = QtWidgets.QMainWindow() 163 | main_widget = EzPublisherWidget() 164 | main_window.setCentralWidget(main_widget) 165 | main_window.show() 166 | app.exec_() 167 | 168 | if __name__ == '__main__': 169 | rospy.init_node('ez_publisher') 170 | main() 171 | -------------------------------------------------------------------------------- /doc/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = .build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " devhelp to make HTML files and a Devhelp project" 34 | @echo " epub to make an epub" 35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 36 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 38 | @echo " text to make text files" 39 | @echo " man to make manual pages" 40 | @echo " texinfo to make Texinfo files" 41 | @echo " info to make Texinfo files and run them through makeinfo" 42 | @echo " gettext to make PO message catalogs" 43 | @echo " changes to make an overview of all changed/added/deprecated items" 44 | @echo " xml to make Docutils-native XML files" 45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 46 | @echo " linkcheck to check all external links for integrity" 47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 48 | 49 | clean: 50 | rm -rf $(BUILDDIR)/* 51 | 52 | html: 53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 54 | @echo 55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 56 | 57 | dirhtml: 58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 59 | @echo 60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 61 | 62 | singlehtml: 63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 64 | @echo 65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 66 | 67 | pickle: 68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 69 | @echo 70 | @echo "Build finished; now you can process the pickle files." 71 | 72 | json: 73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 74 | @echo 75 | @echo "Build finished; now you can process the JSON files." 76 | 77 | htmlhelp: 78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 79 | @echo 80 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 81 | ".hhp project file in $(BUILDDIR)/htmlhelp." 82 | 83 | qthelp: 84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 85 | @echo 86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/rqt_ez_publisher.qhcp" 89 | @echo "To view the help file:" 90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/rqt_ez_publisher.qhc" 91 | 92 | devhelp: 93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 94 | @echo 95 | @echo "Build finished." 96 | @echo "To view the help file:" 97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/rqt_ez_publisher" 98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/rqt_ez_publisher" 99 | @echo "# devhelp" 100 | 101 | epub: 102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 103 | @echo 104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 105 | 106 | latex: 107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 108 | @echo 109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 111 | "(use \`make latexpdf' here to do that automatically)." 112 | 113 | latexpdf: 114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 115 | @echo "Running LaTeX files through pdflatex..." 116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 118 | 119 | latexpdfja: 120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 121 | @echo "Running LaTeX files through platex and dvipdfmx..." 122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 124 | 125 | text: 126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 127 | @echo 128 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 129 | 130 | man: 131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 132 | @echo 133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 134 | 135 | texinfo: 136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 137 | @echo 138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 139 | @echo "Run \`make' in that directory to run these through makeinfo" \ 140 | "(use \`make info' here to do that automatically)." 141 | 142 | info: 143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 144 | @echo "Running Texinfo files through makeinfo..." 145 | make -C $(BUILDDIR)/texinfo info 146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 147 | 148 | gettext: 149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 150 | @echo 151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 152 | 153 | changes: 154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 155 | @echo 156 | @echo "The overview file is in $(BUILDDIR)/changes." 157 | 158 | linkcheck: 159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 160 | @echo 161 | @echo "Link check complete; look for any errors in the above output " \ 162 | "or in $(BUILDDIR)/linkcheck/output.txt." 163 | 164 | doctest: 165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 166 | @echo "Testing of doctests in the sources finished, look at the " \ 167 | "results in $(BUILDDIR)/doctest/output.txt." 168 | 169 | xml: 170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 171 | @echo 172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 173 | 174 | pseudoxml: 175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 176 | @echo 177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 178 | -------------------------------------------------------------------------------- /test/model_ros_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import rostest 4 | import unittest 5 | import rospy 6 | import geometry_msgs.msg as geo_msgs 7 | import sensor_msgs.msg as sen_msgs 8 | 9 | import rqt_ez_publisher.ez_publisher_model as ez_model 10 | from rqt_ez_publisher import quaternion_module 11 | 12 | PKG='rqt_ez_publisher' 13 | 14 | def wait_topics(): 15 | _, _, topic_types = rospy.get_master().getTopicTypes() 16 | topic_dict = dict(topic_types) 17 | while '/joint_states' not in topic_dict or '/polygon' not in topic_dict: 18 | rospy.sleep(0.1) 19 | 20 | 21 | # check /joint_states and /polygon 22 | class RosFunctionTest(unittest.TestCase): 23 | 24 | def callback(self, msg): 25 | pass 26 | 27 | def setUp(self): 28 | wait_topics() 29 | 30 | def test_make_topic_strings_with_array_non_builtin(self): 31 | strings = ez_model.make_topic_strings(geo_msgs.Polygon(), '/polygon') 32 | self.assertEqual(len(strings), 3) 33 | self.assertEqual(strings[0], '/polygon/points[0]/x') 34 | self.assertEqual(strings[1], '/polygon/points[0]/y') 35 | self.assertEqual(strings[2], '/polygon/points[0]/z') 36 | 37 | def test_make_topic_strings_quaterion(self): 38 | strings = ez_model.make_topic_strings(geo_msgs.Quaternion(), 39 | '/pose/orientation') 40 | self.assertEqual(len(strings), 4) 41 | strings = ez_model.make_topic_strings(geo_msgs.Quaternion(), 42 | '/pose/orientation', 43 | modules=[quaternion_module.QuaternionModule()]) 44 | self.assertEqual(len(strings), 1) 45 | self.assertEqual(strings[0], '/pose/orientation') 46 | 47 | 48 | 49 | def test_make_topic_strings_with_array_builtin(self): 50 | strings = ez_model.make_topic_strings(sen_msgs.JointState(), 51 | '/joint_states') 52 | self.assertEqual(len(strings), 8) 53 | self.assertEqual('/joint_states/name[0]' in strings, True) 54 | self.assertEqual('/joint_states/position[0]' in strings, True) 55 | 56 | 57 | 58 | class TopicPublisherTest(unittest.TestCase): 59 | 60 | def callback(self, msg): 61 | self._msg = msg 62 | 63 | def setUp(self): 64 | self.publiser = ez_model.publisher.TopicPublisher('/topic', geo_msgs.Vector3) 65 | self.subscriber = rospy.Subscriber('/topic', geo_msgs.Vector3, self.callback) 66 | while self.subscriber.get_num_connections() == 0: 67 | rospy.sleep(0.1) 68 | self._msg = None 69 | 70 | def test_get_topic_name(self): 71 | self.assertEqual(self.publisher.get_topic_name(), '/topic') 72 | 73 | def test_publish(self): 74 | msg = self.publisher.get_message() 75 | msg.x = 1.0 76 | msg.y = 2.0 77 | msg.z = 3.0 78 | self.publisher.publish() 79 | while not self._msg: 80 | rospy.sleep(0.1) 81 | self.assertEqual(self._msg.x, 1.0) 82 | self.assertEqual(self._msg.y, 2.0) 83 | self.assertEqual(self._msg.z, 3.0) 84 | 85 | 86 | class ModelTest(unittest.TestCase): 87 | 88 | def setUp(self): 89 | wait_topics() 90 | self.model = ez_model.EzPublisherModel(None) 91 | 92 | def test_get_publisher_not_exists(self): 93 | self.assertEqual(self.model.get_publisher('not_exists'), None) 94 | 95 | def test_expand_no_attribute_polygon(self): 96 | strings = self.model.expand_attribute('/polygon/xxx', None) 97 | self.assertEqual(strings, []) 98 | 99 | def test_expand_no_attribute_array_polygon(self): 100 | strings = self.model.expand_attribute('/polygon/xxx', 0) 101 | self.assertEqual(strings, []) 102 | 103 | def test_expand_no_attribute_array_polygon(self): 104 | strings = self.model.expand_attribute('/polygon/xxx[0]', None) 105 | self.assertEqual(strings, []) 106 | 107 | def test_expand_attribute_polygon(self): 108 | strings = self.model.expand_attribute('/polygon/points', None) 109 | self.assertEqual(len(strings), 3) 110 | self.assertEqual(strings[0], '/polygon/points[0]/x') 111 | self.assertEqual(strings[1], '/polygon/points[0]/y') 112 | self.assertEqual(strings[2], '/polygon/points[0]/z') 113 | 114 | def test_expand_attribute_with_index(self): 115 | strings = self.model.expand_attribute('/polygon/points', 1) 116 | self.assertEqual(len(strings), 3) 117 | self.assertEqual(strings[0], '/polygon/points[1]/x') 118 | self.assertEqual(strings[1], '/polygon/points[1]/y') 119 | self.assertEqual(strings[2], '/polygon/points[1]/z') 120 | 121 | def test_expand_attribute_polygon(self): 122 | strings = self.model.expand_attribute('/polygon') 123 | self.assertEqual(len(strings), 3) 124 | self.assertEqual(strings[0], '/polygon/points[0]/x') 125 | self.assertEqual(strings[1], '/polygon/points[0]/y') 126 | self.assertEqual(strings[2], '/polygon/points[0]/z') 127 | 128 | def test_expand_attribute_array(self): 129 | strings = self.model.expand_attribute('/joint_states/position', 2) 130 | self.assertEqual(len(strings), 1) 131 | self.assertEqual(strings[0], '/joint_states/position[2]') 132 | 133 | def test_expand_attribute_joint_states_header(self): 134 | strings = self.model.expand_attribute('/joint_states/header/stamp/secs') 135 | self.assertEqual(len(strings), 0) 136 | 137 | def test_expand_attribute_joint_states(self): 138 | strings = self.model.expand_attribute('/joint_states') 139 | self.assertEqual(len(strings), 8) 140 | self.assertEqual(strings[0], '/joint_states/header/seq') 141 | self.assertEqual(strings[1], '/joint_states/header/stamp/secs') 142 | self.assertEqual(strings[2], '/joint_states/header/stamp/nsecs') 143 | self.assertEqual(strings[3], '/joint_states/header/frame_id') 144 | self.assertEqual(strings[4], '/joint_states/name[0]') 145 | self.assertEqual(strings[5], '/joint_states/position[0]') 146 | self.assertEqual(strings[6], '/joint_states/velocity[0]') 147 | self.assertEqual(strings[7], '/joint_states/effort[0]') 148 | 149 | def test_expand_pose_for_plugin(self): 150 | self.model.add_module(quaternion_module.QuaternionModule()) 151 | strings = self.model.expand_attribute('/pose') 152 | self.assertEqual(len(strings), 4) 153 | self.assertEqual(strings[0], '/pose/position/x') 154 | self.assertEqual(strings[1], '/pose/position/y') 155 | self.assertEqual(strings[2], '/pose/position/z') 156 | self.assertEqual(strings[3], '/pose/orientation') 157 | 158 | def test_set_msg_attribute_value_uint8_array(self): 159 | img = sen_msgs.CompressedImage() 160 | ez_model.set_msg_attribute_value(img, '/compressed_image', int, [u'data'], 1, 3) 161 | self.assertEqual(len(img.data), 2) 162 | self.assertEqual(img.data[0], chr(0)) 163 | self.assertEqual(img.data[1], chr(3)) 164 | 165 | def test_set_msg_attribute_value(self): 166 | twi = geo_msgs.Twist() 167 | ez_model.set_msg_attribute_value(twi, '/twist', float, [u'linear', u'x'], None, 0.1) 168 | self.assertEqual(twi.linear.x, 0.1) 169 | self.assertEqual(ez_model.get_msg_attribute_value(twi, '/twist', [u'linear', u'x']), 0.1) 170 | 171 | 172 | if __name__ == '__main__': 173 | import rostest 174 | rospy.init_node('ez_publisher_model_test') 175 | rostest.rosrun(PKG, 'ros_function_test', RosFunctionTest) 176 | rostest.rosrun(PKG, 'model_test', ModelTest) 177 | -------------------------------------------------------------------------------- /doc/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # rqt_ez_publisher documentation build configuration file, created by 4 | # sphinx-quickstart on Sat Jul 5 22:29:35 2014. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import sys 16 | import os 17 | import catkin_pkg.package 18 | catkin_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) 19 | catkin_package = catkin_pkg.package.parse_package(os.path.join(catkin_dir, catkin_pkg.package.PACKAGE_MANIFEST_FILENAME)) 20 | 21 | # If extensions (or modules to document with autodoc) are in another directory, 22 | # add these directories to sys.path here. If the directory is relative to the 23 | # documentation root, use os.path.abspath to make it absolute, like shown here. 24 | #sys.path.insert(0, os.path.abspath('.')) 25 | 26 | # -- General configuration ------------------------------------------------ 27 | 28 | # If your documentation needs a minimal Sphinx version, state it here. 29 | #needs_sphinx = '1.0' 30 | 31 | # Add any Sphinx extension module names here, as strings. They can be 32 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 33 | # ones. 34 | extensions = [ 35 | 'sphinx.ext.autodoc', 36 | 'sphinx.ext.doctest', 37 | 'sphinx.ext.intersphinx', 38 | 'sphinx.ext.todo', 39 | 'sphinx.ext.coverage', 40 | 'sphinx.ext.mathjax', 41 | 'sphinx.ext.viewcode', 42 | ] 43 | 44 | # Add any paths that contain templates here, relative to this directory. 45 | templates_path = ['.templates'] 46 | 47 | # The suffix of source filenames. 48 | source_suffix = '.rst' 49 | 50 | # The encoding of source files. 51 | #source_encoding = 'utf-8-sig' 52 | 53 | # The master toctree document. 54 | master_doc = 'index' 55 | 56 | # General information about the project. 57 | project = u'rqt_ez_publisher' 58 | copyright = u'2014, Takashi Ogura' 59 | 60 | # The version info for the project you're documenting, acts as replacement for 61 | # |version| and |release|, also used in various other places throughout the 62 | # built documents. 63 | # 64 | # The short X.Y version. 65 | version = catkin_package.version 66 | # The full version, including alpha/beta/rc tags. 67 | release = catkin_package.version 68 | 69 | # The language for content autogenerated by Sphinx. Refer to documentation 70 | # for a list of supported languages. 71 | #language = None 72 | 73 | # There are two options for replacing |today|: either, you set today to some 74 | # non-false value, then it is used: 75 | #today = '' 76 | # Else, today_fmt is used as the format for a strftime call. 77 | #today_fmt = '%B %d, %Y' 78 | 79 | # List of patterns, relative to source directory, that match files and 80 | # directories to ignore when looking for source files. 81 | exclude_patterns = ['.build'] 82 | 83 | # The reST default role (used for this markup: `text`) to use for all 84 | # documents. 85 | #default_role = None 86 | 87 | # If true, '()' will be appended to :func: etc. cross-reference text. 88 | #add_function_parentheses = True 89 | 90 | # If true, the current module name will be prepended to all description 91 | # unit titles (such as .. function::). 92 | #add_module_names = True 93 | 94 | # If true, sectionauthor and moduleauthor directives will be shown in the 95 | # output. They are ignored by default. 96 | #show_authors = False 97 | 98 | # The name of the Pygments (syntax highlighting) style to use. 99 | pygments_style = 'sphinx' 100 | 101 | # A list of ignored prefixes for module index sorting. 102 | #modindex_common_prefix = [] 103 | 104 | # If true, keep warnings as "system message" paragraphs in the built documents. 105 | #keep_warnings = False 106 | 107 | 108 | # -- Options for HTML output ---------------------------------------------- 109 | 110 | # The theme to use for HTML and HTML Help pages. See the documentation for 111 | # a list of builtin themes. 112 | html_theme = 'nature' 113 | 114 | # Theme options are theme-specific and customize the look and feel of a theme 115 | # further. For a list of options available for each theme, see the 116 | # documentation. 117 | #html_theme_options = {} 118 | 119 | # Add any paths that contain custom themes here, relative to this directory. 120 | #html_theme_path = [] 121 | 122 | # The name for this set of Sphinx documents. If None, it defaults to 123 | # " v documentation". 124 | #html_title = None 125 | 126 | # A shorter title for the navigation bar. Default is the same as html_title. 127 | #html_short_title = None 128 | 129 | # The name of an image file (relative to this directory) to place at the top 130 | # of the sidebar. 131 | #html_logo = None 132 | 133 | # The name of an image file (within the static path) to use as favicon of the 134 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 135 | # pixels large. 136 | #html_favicon = None 137 | 138 | # Add any paths that contain custom static files (such as style sheets) here, 139 | # relative to this directory. They are copied after the builtin static files, 140 | # so a file named "default.css" will overwrite the builtin "default.css". 141 | html_static_path = ['.static'] 142 | 143 | # Add any extra paths that contain custom files (such as robots.txt or 144 | # .htaccess) here, relative to this directory. These files are copied 145 | # directly to the root of the documentation. 146 | #html_extra_path = [] 147 | 148 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 149 | # using the given strftime format. 150 | #html_last_updated_fmt = '%b %d, %Y' 151 | 152 | # If true, SmartyPants will be used to convert quotes and dashes to 153 | # typographically correct entities. 154 | #html_use_smartypants = True 155 | 156 | # Custom sidebar templates, maps document names to template names. 157 | #html_sidebars = {} 158 | 159 | # Additional templates that should be rendered to pages, maps page names to 160 | # template names. 161 | #html_additional_pages = {} 162 | 163 | # If false, no module index is generated. 164 | #html_domain_indices = True 165 | 166 | # If false, no index is generated. 167 | #html_use_index = True 168 | 169 | # If true, the index is split into individual pages for each letter. 170 | #html_split_index = False 171 | 172 | # If true, links to the reST sources are added to the pages. 173 | #html_show_sourcelink = True 174 | 175 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 176 | #html_show_sphinx = True 177 | 178 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 179 | #html_show_copyright = True 180 | 181 | # If true, an OpenSearch description file will be output, and all pages will 182 | # contain a tag referring to it. The value of this option must be the 183 | # base URL from which the finished HTML is served. 184 | #html_use_opensearch = '' 185 | 186 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 187 | #html_file_suffix = None 188 | 189 | # Output file base name for HTML help builder. 190 | htmlhelp_basename = 'rqt_ez_publisherdoc' 191 | 192 | 193 | # -- Options for LaTeX output --------------------------------------------- 194 | 195 | latex_elements = { 196 | # The paper size ('letterpaper' or 'a4paper'). 197 | #'papersize': 'letterpaper', 198 | 199 | # The font size ('10pt', '11pt' or '12pt'). 200 | #'pointsize': '10pt', 201 | 202 | # Additional stuff for the LaTeX preamble. 203 | #'preamble': '', 204 | } 205 | 206 | # Grouping the document tree into LaTeX files. List of tuples 207 | # (source start file, target name, title, 208 | # author, documentclass [howto, manual, or own class]). 209 | latex_documents = [ 210 | ('index', 'rqt_ez_publisher.tex', u'rqt\\_ez\\_publisher Documentation', 211 | u'Takashi Ogura', 'manual'), 212 | ] 213 | 214 | # The name of an image file (relative to this directory) to place at the top of 215 | # the title page. 216 | #latex_logo = None 217 | 218 | # For "manual" documents, if this is true, then toplevel headings are parts, 219 | # not chapters. 220 | #latex_use_parts = False 221 | 222 | # If true, show page references after internal links. 223 | #latex_show_pagerefs = False 224 | 225 | # If true, show URL addresses after external links. 226 | #latex_show_urls = False 227 | 228 | # Documents to append as an appendix to all manuals. 229 | #latex_appendices = [] 230 | 231 | # If false, no module index is generated. 232 | #latex_domain_indices = True 233 | 234 | 235 | # -- Options for manual page output --------------------------------------- 236 | 237 | # One entry per manual page. List of tuples 238 | # (source start file, name, description, authors, manual section). 239 | man_pages = [ 240 | ('index', 'rqt_ez_publisher', u'rqt_ez_publisher Documentation', 241 | [u'Takashi Ogura'], 1) 242 | ] 243 | 244 | # If true, show URL addresses after external links. 245 | #man_show_urls = False 246 | 247 | 248 | # -- Options for Texinfo output ------------------------------------------- 249 | 250 | # Grouping the document tree into Texinfo files. List of tuples 251 | # (source start file, target name, title, author, 252 | # dir menu entry, description, category) 253 | texinfo_documents = [ 254 | ('index', 'rqt_ez_publisher', u'rqt_ez_publisher Documentation', 255 | u'Takashi Ogura', 'rqt_ez_publisher', 'One line description of project.', 256 | 'Miscellaneous'), 257 | ] 258 | 259 | # Documents to append as an appendix to all manuals. 260 | #texinfo_appendices = [] 261 | 262 | # If false, no module index is generated. 263 | #texinfo_domain_indices = True 264 | 265 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 266 | #texinfo_show_urls = 'footnote' 267 | 268 | # If true, do not generate a @detailmenu in the "Top" node's menu. 269 | #texinfo_no_detailmenu = False 270 | 271 | 272 | # Example configuration for intersphinx: refer to the Python standard library. 273 | intersphinx_mapping = {'http://docs.python.org/': None} 274 | -------------------------------------------------------------------------------- /src/rqt_ez_publisher/ez_publisher_model.py: -------------------------------------------------------------------------------- 1 | import copy 2 | import functools 3 | import re 4 | import roslib.message 5 | import roslib.msgs 6 | import rospy 7 | from rqt_py_common import topic_helpers as helpers 8 | 9 | 10 | def get_field_type_capable_with_index(field_string): 11 | '''get type even if it contains [] for array''' 12 | 13 | m = re.search('(.+)(\[[0-9]+\])$', field_string) 14 | if m: 15 | return helpers.get_field_type(m.group(1)) 16 | else: 17 | return helpers.get_field_type(field_string) 18 | 19 | 20 | def make_topic_strings_internal(msg_instance, string='', modules=[]): 21 | '''returns break down strings''' 22 | 23 | if msg_instance is None: 24 | return [string] 25 | if isinstance(msg_instance, list): 26 | msg_type = get_field_type_capable_with_index(string)[0] 27 | if msg_type is not None: 28 | array_instance = msg_type() 29 | return make_topic_strings_internal(array_instance, string + '[0]', modules=modules) 30 | else: 31 | rospy.logwarn('not found type of %s' % string) 32 | return [] 33 | # this should be replaced by plugin system 34 | for module in modules: 35 | if isinstance(msg_instance, module.get_msg_class()): 36 | return [string] 37 | try: 38 | return [make_topic_strings_internal(msg_instance.__getattribute__(slot), 39 | string + '/' + slot, modules=modules) 40 | for slot in msg_instance.__slots__] 41 | except AttributeError: 42 | return [string] 43 | 44 | 45 | def set_msg_attribute_value(msg_instance, topic_name, msg_type, attributes, 46 | array_index, value): 47 | '''set value to the attribute of topic''' 48 | message_target = msg_instance 49 | if len(attributes) >= 2: 50 | message_target = get_msg_attribute_value( 51 | message_target, topic_name, attributes[:-1]) 52 | if array_index is not None: 53 | array = message_target.__getattribute__(attributes[-1]) 54 | # when the array is uint8, it becomes str instead of array. 55 | if isinstance(array, str): 56 | if len(array) <= array_index: 57 | array += chr(0) * (array_index - len(array) + 1) 58 | array = array[0:array_index] + chr(value) + array[array_index + 1:] 59 | else: 60 | while len(array) <= array_index: 61 | array.append(msg_type()) 62 | array[array_index] = value 63 | message_target.__setattr__(attributes[-1], array) 64 | else: 65 | message_target.__setattr__(attributes[-1], value) 66 | message_target = value 67 | 68 | 69 | def get_msg_attribute_value(msg_instance, topic_name, attributes): 70 | message_target = msg_instance 71 | full_string = topic_name 72 | for attr in attributes: 73 | full_string += '/' + attr 74 | m = re.search('(\w+)\[([0-9]+)\]$', attr) 75 | if m: 76 | index = int(m.group(2)) 77 | attr = m.group(1) 78 | array_type = get_field_type_capable_with_index(full_string)[0] 79 | while len(message_target.__getattribute__(attr)) <= index: 80 | message_target.__getattribute__(attr).append(array_type()) 81 | message_target = message_target.__getattribute__(attr)[index] 82 | elif get_field_type_capable_with_index(full_string)[1]: 83 | array_type = get_field_type_capable_with_index(full_string)[0] 84 | if len(message_target.__getattribute__(attr)) == 0: 85 | message_target.__getattribute__(attr).append(array_type()) 86 | message_target = message_target.__getattribute__(attr)[0] 87 | else: 88 | message_target = message_target.__getattribute__(attr) 89 | return message_target 90 | 91 | 92 | def flatten(complicated_list): 93 | if isinstance(complicated_list, list): 94 | return functools.reduce(lambda a, b: 95 | a + flatten(b), complicated_list, []) 96 | else: 97 | return [complicated_list] 98 | 99 | 100 | def make_topic_strings(msg_instance, string='', modules=[]): 101 | return flatten(make_topic_strings_internal(msg_instance, string=string, modules=modules)) 102 | 103 | 104 | def find_topic_name(full_text, topic_dict): 105 | if full_text == '': 106 | return (None, None, None) 107 | if full_text[0] != '/': 108 | full_text = '/' + full_text 109 | # This is topic 110 | if full_text in topic_dict: 111 | return (full_text, None, None) 112 | splited_text = full_text.split('/')[1:] 113 | topic_name = full_text 114 | topic_inside_variable_list = [] 115 | while topic_name not in topic_dict and splited_text: 116 | topic_name = '/' + '/'.join(splited_text[:-1]) 117 | topic_inside_variable_list.append(splited_text[-1]) 118 | splited_text = splited_text[:-1] 119 | topic_inside_variable_list.reverse() 120 | if topic_name != '/' and topic_inside_variable_list: 121 | m = re.search('(\w+)\[([0-9]+)\]', topic_inside_variable_list[-1]) 122 | if m: 123 | topic_inside_variable_list[-1] = m.group(1) 124 | return (topic_name, topic_inside_variable_list, int(m.group(2))) 125 | else: 126 | return (topic_name, topic_inside_variable_list, None) 127 | else: 128 | return (None, None, None) 129 | 130 | 131 | def get_value_type(topic_type_str, attributes, modules=[]): 132 | # for Header -> std_msgs/Header 133 | topic_type_str = roslib.msgs.resolve_type(topic_type_str, '') 134 | spec = None 135 | if not attributes: 136 | return (None, False) 137 | try: 138 | _, spec = roslib.msgs.load_by_type(topic_type_str) 139 | except roslib.msgs.MsgSpecException: 140 | return (None, False) 141 | except IOError as e: # not found 142 | # for devel environment 143 | import os 144 | cmake_prefix_list = os.environ.get('CMAKE_PREFIX_PATH') 145 | if cmake_prefix_list is not None: 146 | package, msg = topic_type_str.split('/') 147 | for path in cmake_prefix_list.split(':'): 148 | msg_path = "%s/share/%s/msg/%s.msg" % (path, package, msg) 149 | if os.path.exists(msg_path): 150 | _, spec = roslib.msgs.load_from_file(msg_path, package) 151 | rospy.logdebug( 152 | 'loaded %s/%s for devel environment' % (package, msg)) 153 | break 154 | try: 155 | head_attribute = attributes[0].split('[')[0] 156 | index = spec.names.index(head_attribute) 157 | field = spec.parsed_fields()[index] 158 | attr_type = field.base_type 159 | if field.is_builtin: 160 | if attr_type in ['int8', 'int16', 'int32', 'int64']: 161 | return_type = int 162 | elif attr_type in ['byte', 'uint8', 'uint16', 'uint32', 'uint64']: 163 | return_type = 'uint' 164 | elif attr_type in ['float32', 'float64']: 165 | return_type = float 166 | elif attr_type == 'string': 167 | return_type = str 168 | elif attr_type == 'bool': 169 | return_type = bool 170 | else: 171 | rospy.logwarn('%s is not supported' % attr_type) 172 | return (None, False) 173 | return (return_type, field.is_array) 174 | else: 175 | for module in modules: 176 | if field.base_type == module.get_msg_string(): 177 | return (module.get_msg_string(), field.is_array) 178 | return get_value_type(field.base_type, attributes[1:], modules=modules) 179 | except ValueError as e: 180 | rospy.logwarn(e) 181 | return (None, False) 182 | return (None, False) 183 | 184 | 185 | def make_text(topic_name, attributes, array_index): 186 | text = topic_name + '/' + '/'.join(attributes) 187 | if array_index is not None: 188 | text += '[%d]' % array_index 189 | return text 190 | 191 | 192 | class EzPublisherModel(object): 193 | 194 | '''Model for rqt_ez_publisher''' 195 | 196 | def __init__(self, publisher_class, modules=[]): 197 | self._publishers = {} 198 | self._publisher_class = publisher_class 199 | self._modules = modules 200 | 201 | def get_modules(self): 202 | return self._modules 203 | 204 | def set_modules(self, modules): 205 | self._modules = modules 206 | 207 | def add_module(self, module): 208 | self._modules.append(module) 209 | 210 | def publish_topic(self, topic_name): 211 | if topic_name in self._publishers: 212 | self._publishers[topic_name].publish() 213 | 214 | def get_publisher(self, topic_name): 215 | if topic_name in self._publishers: 216 | return self._publishers[topic_name] 217 | else: 218 | return None 219 | 220 | def _add_publisher_if_not_exists(self, topic_name, message_class): 221 | if topic_name not in self._publishers: 222 | self._publishers[topic_name] = self._publisher_class( 223 | topic_name, message_class) 224 | 225 | def get_topic_names(self): 226 | _, _, topic_types = rospy.get_master().getTopicTypes() 227 | return sorted([x[0] for x in topic_types]) 228 | 229 | def expand_attribute(self, input_text, array_index=None): 230 | text = copy.copy(input_text) 231 | try: 232 | msg_type, is_array = get_field_type_capable_with_index(text) 233 | if is_array: 234 | if array_index is None: 235 | text += '[0]' 236 | else: 237 | array_string = '[%d]' % array_index 238 | if not text.endswith(array_string): 239 | text += array_string 240 | if msg_type == int: # for time ? not support 241 | return [] 242 | elif msg_type: 243 | return make_topic_strings(msg_type(), text, modules=self._modules) 244 | else: 245 | return [] 246 | except AttributeError: 247 | return [] 248 | 249 | def register_topic_by_text(self, text): 250 | _, _, topic_types = rospy.get_master().getTopicTypes() 251 | topic_dict = dict(topic_types) 252 | topic_name, attributes, array_index = find_topic_name(text, topic_dict) 253 | if not topic_name: 254 | return None 255 | topic_type_str = topic_dict[topic_name] 256 | message_class = roslib.message.get_message_class(topic_type_str) 257 | self._add_publisher_if_not_exists(topic_name, message_class) 258 | builtin_type, is_array = get_value_type( 259 | topic_type_str, attributes, modules=self._modules) 260 | return (topic_name, attributes, builtin_type, is_array, array_index) 261 | 262 | def shutdown(self): 263 | for pub in list(self._publishers.values()): 264 | pub.unregister() 265 | --------------------------------------------------------------------------------