ValueError while looping spin_once() in a separate thread from a custom pyqtgraph widget
I am trying to write a subscriber node within a custom PyQt5 widget (via the module pyqtgraph
). The widget is simple: a dot rotating in xy space, subscribing to a Twist message as a command velocity.
Because I am using pyqtgraph, my python process is run by the Qt event loop (app.exec()
, called by pyqtgraph as pg.exec()
). This prevents me from using rclpy.spin()
to manage the subscriber node, so I am using rclpy.spin_once()
on a timed loop clocked by the Qt timer. This also forces my program to be structured like a PyQt application where my ROS node is an attribute of the MainWindow object, as opposed to defining a subscriber class that inherits rclpy.Node
. I understand this might not be the best practice, but the features and performance provided by the Qt event loop are important to my full application, for which the rotating dot is just a temporary stand-in.
If I run my program as described above, the plotting is choppy, as Qt needs to wait for spin_once
to complete. To achieve smoother plotting, I decided to run all calls to spin_once
in a separate thread operated by PyQt's QRunnable class. As a result, I am making frequent (~50 Hz) calls to spin_once
in a thread pool run by PyQt. When I run it this way, I get the following runtime error:
File "/opt/ros/humble/.../rclpy/executors.py", line 690, in wait_for_ready_callbacks
return next(self._cb_iter)
ValueError: generator already executing
I assume this error comes from the subscriber node being called more frequently than it can handle, but I also know that 50 Hz is not an unreasonable rate for topic subscription. How could I structure this program so that I can run the Qt app.exec() and also subscribe to a topic? Is there a better way to run repeated calls to a subscriber in a separate thread?
I am still a novice to ROS and concepts like threading and multiprocessing, but I'm open to rewriting this in a different structure so long as I am still able to use my pyqtgraph GUI widget.
I am running ros2 Humble on Ubuntu Jammy (22.04) on an x86 Dell XPS 13, with kernel 5.15.0-46-generic. My full program is written below:
import rclpy
import pyqtgraph as pg
from geometry_msgs.msg import Twist
from PyQt5 import QtWidgets, QtCore
from PyQt5.QtCore import QRunnable, QThreadPool, pyqtSlot
from rclpy.node import Node
class Worker(QRunnable):
'''
This is a utility for running functions in a thread outside the Qt event loop
'''
def __init__(self, fn, *args, **kwargs):
super(Worker, self).__init__()
# Store constructor arguments (re-used for processing)
self.fn = fn
self.args = args
self.kwargs = kwargs
@pyqtSlot()
def run(self):
# Initialise the runner function with passed args, kwargs.
self.fn(*self.args, **self.kwargs)
class MainWindow(pg.GraphicsLayoutWidget):
'''
This is a pyqtgraph widget that plots a circular dot in xy space.
'''
def __init__(self,parent=None, show=True, size=None, title=None, **kargs ...
It will depend a bit on what you want/need to do exactly, but I've found StefanFabian/qml_ros2_plugin to be really convenient for creating UIs that visualise data coming in over ROS topics.
I'll check this out, thank you!