├── 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 |
--------------------------------------------------------------------------------