基于ROS订阅数据重新绘制图表

0 投票
1 回答
1446 浏览
提问于 2025-04-18 05:49

架构

我有一个图表,里面有一条曲线。我在文件中有一个节点和一个订阅者。这个订阅者会订阅一些浮点数据,这些数据会被发布。每当有新数据发布时,我就会通过把这个新数据点添加到现有的数据集中来更新曲线。

问题

图表没有正确更新。因为每秒都有数据进来,图形用户界面(GUI)变得很卡,过一段时间后,GUI会因为出现段错误(Segmentation Fault)而崩溃。

代码

def initUI(self):

    # x11.XInitThreads()

    # xlib.XInitThreads()

    # initialising the window

    QtGui.QWidget.__init__(self)

    # self.setGeometry(300, 300, 160, 1000)
    # self.setWindowTitle('Visualizer')

    # main layout

    self.layout = QtGui.QVBoxLayout(self)

    # Creating the elements in this widget

    a = QtGui.QLabel("Navigation", self)

    a.setStyleSheet("QLabel{ background-color: white; color: black; font-size: 25px; }")

    self.plot = Qwt.QwtPlot(self)
    self.plot.setCanvasBackground(Qt.black)
    self.plot.setAxisTitle(Qwt.QwtPlot.xBottom, 'Time')
    self.plot.setAxisScale(Qwt.QwtPlot.xBottom, 0, 10, 1)
    self.plot.setAxisTitle(Qwt.QwtPlot.yLeft, 'Temperature')
    self.plot.setAxisScale(Qwt.QwtPlot.yLeft, 0, 250, 40)
    self.plot.replot()

    self.curve = Qwt.QwtPlotCurve('')
    self.curve.setRenderHint(Qwt.QwtPlotItem.RenderAntialiased)
    pen = QPen(QColor('limegreen'))
    pen.setWidth(2)
    self.curve.setPen(pen)
    self.curve.attach(self.plot)

    self.layout.addWidget(a)
    self.layout.addWidget(self.plot)

def listener(self):       

    rospy.init_node('listener', anonymous=True)

    rospy.Subscriber(TOPIC_NAME, String, self.callback)

def callback(self, data):

    self.xData.append(self.counter + 1)
    self.yData.append(int(str(data.data)))
    self.counter += 1

    self.curve.setData(self.xData, self.yData)
    self.plot.replot()

调用这些函数时 :-

self.listener()
self.initUI()

一旦监听器被调用,订阅者就会自动和回调函数关联。回调函数会查看新数据,把它添加到y轴上,然后重新绘制图表。

错误

每当发布一个新数据点时,我都会收到这个错误 :-

QCoreApplication::sendPostedEvents: Cannot send posted events for objects in another thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPixmap: It is not safe to use pixmaps outside the GUI thread
QPainter::begin: Paint device returned engine == 0, type: 2
QPainter::setPen: Painter not active
QPainter::setBrush: Painter not active
QPainter::drawRects: Painter not active
QPainter::begin: Paint device returned engine == 0, type: 2
QPainter::translate: Painter not active
QPainter::save: Painter not active
QPainter::setRenderHint: Painter must be active to set rendering hints
QPainter::save: Painter not active
QPainter::setPen: Painter not active
QPainter::restore: Unbalanced save/restore
QPainter::restore: Unbalanced save/restore
QPainter::end: Painter not active, aborted

我不明白这个错误是什么意思。

关于发布者

ROS遵循发布-订阅模式。我已经创建了一个节点来发布随机整数。这个整数应该被绘制在图表上。

规格

Ubuntu 12.04
ROS Hydro
PyQt4
Qwt5

1 个回答

2

你的 callback 方法是在一个线程中运行的。你不能从另一个线程更新 Qt 的图形界面对象。这就是为什么你会看到错误和出现段错误(segfault)的原因。

解决方法有:

  • 在回调函数中,把数据添加到一个列表里。然后在主线程中使用一个 QTimer 定时器,定期检查这个列表有没有更新,并重新绘制图表(这不是最理想的解决方案,但可能能解决问题)。

  • 在回调函数中,把数据放到一个 Python 的 Queue.Queue() 中。然后让一个 QThread 线程阻塞在读取这个队列,每次从 Queue 中读取到数据时,就发出一个 Qt 信号(里面带着数据)。在主线程中连接一个方法到这个 Qt 信号。这样,主线程中的方法就能获取数据,并更新图表。

这里还有一些其他的 Stack Overflow 问题,它们做的事情类似(将数据从一个线程发送到 Qt 的主线程,以避免段错误),或者在深入了解多线程的 PyQt 应用时会很有用:

撰写回答