I'm new to ROS 2, and I'm trying to create a simple GUI using an RQT plugin. To do this I'm following along with the process described in this guide:
https://robobe.github.io/blog/ROS2/rqt/custom_plugin/step1/
Please note that I'm using ros2 iron with Python 3.10.12, and the operating system is Linux Mint.
Right now, the project structure is as follows:
~/ros2_custom_rqt_plugin $ tree
.
└── src
└── rqt_mypkg
├── LICENSE
├── package.xml
├── plugin.xml
├── resource
│ └── rqt_mypkg
├── rqt_mypkg
│ ├── __init__.py
│ └── my_module.py
├── setup.cfg
├── setup.py
└── test
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
5 directories, 11 files
In the my_module.py file, I have the following:
#!/usr/bin/env python3
# File: rqt_mypkg/rqt_mypkg/my_module.py
from rqt_gui_py.plugin import Plugin
from PyQt5.QtWidgets import QWidget, QVBoxLayout, QPushButton, QLabel
class MyPlugin(Plugin):
def __init__(self, context):
super(MyPlugin, self).__init__(context)
self.setObjectName('MyPlugin')
# GUI initialization code
self._widget = MyWidget()
self._widget.setObjectName('UniqueWidget')
self.setWidget(self._widget)
class MyWidget(QWidget):
def __init__(self):
super(MyWidget, self).__init__()
# Create a layout
layout = QVBoxLayout(self)
# Create a button
self.button = QPushButton('Click Me!', self)
self.button.clicked.connect(self.on_button_click)
# Create a label for text display
self.label = QLabel('Hello, World!', self)
# Add the button and label to the layout
layout.addWidget(self.button)
layout.addWidget(self.label)
def on_button_click(self):
self.label.setText('Button Clicked!')
In this code, the MyPlugin class is supposed to inherit from rqt_gui_py.Plugin and create an instance of the MyWidget class.
In the package.xml file, I have:
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>rqt_mypkg</name>
<version>0.0.0</version>
<description>My RQT plugin</description>
<maintainer email="[email protected]">Sam</maintainer>
<license>Apache License 2.0</license>
<depend>rclpy</depend>
<depend>rqt_gui</depend>
<depend>rqt_gui_py</depend>
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
<export>
<build_type>ament_python</build_type>
<rqt_gui plugin="${prefix}/plugin.xml"/>
</export>
</package>
Staying consistent with the linked guide, I've added the line <rqt_gui plugin="${prefix}/plugin.xml"/> inside the <export> tag.
In plugin.xml I have:
<library path="src">
<class name="My Plugin" type="rqt_mypkg.my_module.MyPlugin" base_class_type="rqt_gui_py::Plugin">
<description>
An example Python GUI plugin to create a great user interface.
</description>
<qtgui>
<group>
<label>Visualization</label>
</group>
<label>My first Python Plugin</label>
<icon type="theme">system-help</icon>
<statustip>Great user interface to provide real value.</statustip>
</qtgui>
</class>
</library>
This code is exactly like the example provided in the linked guide.
In setup.py I have:
from setuptools import find_packages, setup
package_name = 'rqt_mypkg'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml', 'plugin.xml']),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='sam',
maintainer_email='[email protected]',
description='TODO: Package description',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
Notably, I've modified the data_files section to include plugin.xml
To build the project and load the plugin, I tried executing the following:
~/ros2_custom_rqt_plugin $ colcon build
~/ros2_custom_rqt_plugin $ source install/setup.bash
~/ros2_custom_rqt_plugin $ rqt --force-discover --standalone rqt_mypkg
The project appears to build without issues, but when I try to load the plugin I get the following:
~/ros2_custom_rqt_plugin $ rqt --force-discover --standalone rqt_mypkg
PluginManager._load_plugin() could not load plugin "rqt_mypkg/My Plugin":
Traceback (most recent call last):
File "/opt/ros/iron/lib/python3.10/site-packages/qt_gui/plugin_handler.py", line 102, in load
self._load()
File "/opt/ros/iron/lib/python3.10/site-packages/qt_gui/plugin_handler_direct.py", line 55, in _load
self._plugin = self._plugin_provider.load(self._instance_id.plugin_id, self._context)
File "/opt/ros/iron/lib/python3.10/site-packages/qt_gui/composite_plugin_provider.py", line 72, in load
instance = plugin_provider.load(plugin_id, plugin_context)
File "/opt/ros/iron/lib/python3.10/site-packages/qt_gui/composite_plugin_provider.py", line 72, in load
instance = plugin_provider.load(plugin_id, plugin_context)
File "/opt/ros/iron/lib/python3.10/site-packages/rqt_gui_py/ros_py_plugin_provider.py", line 69, in load
return super(RosPyPluginProvider, self).load(plugin_id, ros_plugin_context)
File "/opt/ros/iron/lib/python3.10/site-packages/qt_gui/composite_plugin_provider.py", line 72, in load
instance = plugin_provider.load(plugin_id, plugin_context)
File "/opt/ros/iron/lib/python3.10/site-packages/rqt_gui/ros_plugin_provider.py", line 107, in load
return class_ref(plugin_context)
File "/home/sam/ros2_custom_rqt_plugin/install/rqt_mypkg/lib/python3.10/site-packages/rqt_mypkg/my_module.py", line 16, in __init__
self.setWidget(self._widget)
AttributeError: 'MyPlugin' object has no attribute 'setWidget'
Failed to delete datawriter, at ./src/publisher.cpp:45 during '__function__'
Warning: class_loader.ClassLoader: SEVERE WARNING!!! Attempting to unload library while objects created by this loader exist in the heap! You should delete your objects before attempting to unload the library or destroying the ClassLoader. The library will NOT be unloaded.
at line 127 in ./src/class_loader.cpp
From this message, it looks like the plugin couldn't be loaded due to an AttributeError:
AttributeError: 'MyPlugin' object has no attribute 'setWidget'
I had thought that the setWidget attribute was part of rqt_gui_py.plugin, so the MyPlugin class should be able to access it. What changes can I make to load the plugin correctly?
UPDATE: I was able to get the plugin working by putting the following in my_modules.py