The Easiest Way of (inter) Thread-Communication for Python

Python Jul 30, 2013

Connecting VTK and ROS or ROS and GTK by using Python always causes some  kind of trouble because there is a main-thread and some other thread.  Whereby, everyone (ROS, VTK, GTK, ETC.) wants to be the main-thread and  calling a function/method from another thread is mostly the  main-problem. There is one pretty simple solution to all of these  problems, namely publish-subscribe or in other words the Python pubsub module. Its usage is quite similar to the usage of pub/sub in ROS.

#!/usr/bin/python
# -*- coding: utf-8 -*-

import sys, time
from pubsub import pub

from PyQt4 import QtGui
from PyQt4.QtCore import QThread

import roslib
import rospy
from std_msgs.msg import String

# ROS - Worker Thread
class RosThread(QThread):
	def __init__(self, topic, parent = None):
		QThread.__init__(self, parent)
		self.sub = rospy.Subscriber(topic, String, self.callback)

	def run(self):        
		rospy.spin()

	def callback(self, msg):
		pub.sendMessage('update', string=msg.data )

# Qt - Worker Thread
class WorkerThread(QThread):
	def __init__(self, name, sleep, parent = None):
		QThread.__init__(self, parent)
		self.sleep = sleep
		self.name  = name
	def run(self):        
		while(True):
			time.sleep(self.sleep)
			pub.sendMessage('update', string="update: "+self.name)

# Minimal GUI
class Gui(QtGui.QWidget):    
	def __init__(self):
		super(Gui, self).__init__()

		self.button = QtGui.QPushButton('Change', self)
		self.button.move(20, 20)
		self.button.clicked.connect(self.showDialog)

		self.text = QtGui.QLabel(self)
		self.text.setText('XXXXXXXXXXXXXXXXXXXXXXXX')
		self.text.move(100, 22)

		self.setGeometry(300, 300, 290, 60)
		self.show()

        # Update the Gui from the GUI
	def showDialog(self):
		text, ok = QtGui.QInputDialog.getText(self, 'Change Label',  'String:')
		if ok:
			self.text.setText(str(text))
        # callback method for pubsub    
	def setLabel(self, string):
		self.text.setText(string)

# create a GUI
app = QtGui.QApplication(sys.argv)
gui = Gui()

# connect method 'gui.setLabel' with topic 'update'
pub.subscribe(gui.setLabel, 'update')

# create two simple Worker Threads
worker1 = WorkerThread("worker1", 5)
worker2 = WorkerThread("worker2", 2.5)
worker1.start()
worker2.start()

# create ROS-node
rospy.init_node('test')
ros1 = RosThread("sub1")
ros1.start()

# run main Thread
sys.exit(app.exec_())

The example from above executes two Qt worker  threads, one ROS thread, and of course one main thread that is  responsible for the GUI.

The GUI consists of a single button and a label, which is updated by the  user or the different threads. See line 67, the command  ‘pub.subscribe(method, topic)’is everything to connect any method with a  certain topic. Afterwards this methods can be called from any thread  with command ‘pub.sendMessage(topic, data)’ see lines 24 and 35. Test  messages to the ROS node can be send with the following shell-command:

$ rostopic pub sub1 std_msgs/String '{"data":"test"}'

the same works also with VTK, GTK, …