Search code examples
pythonevent-handlingzeromqpyforms

combining ZMQ event loop with QT / Pyforms event loop


I'm trying to implement both, zmq and a Pyforms GUI which both requires there own event loop. The task is to have a Pyforms GUI with a textfield, that displays the incoming zmq messages. This is the simplified code that I'm trying to get to work.

import pyforms
from   pyforms          import BaseWidget
from   pyforms.controls import ControlTextArea
from   pyforms.controls import ControlButton
import threading
import zmq
from zmq.asyncio import Context
from zmq.eventloop.zmqstream import ZMQStream
from zmq.eventloop import ioloop


class SimpleExample1(BaseWidget):

    def __init__(self):
        super(SimpleExample1,self).__init__('Simple example 1')

        #Definition of the forms fields
        self._controltextarea     = ControlTextArea('textarea to show incoming zmq messages')
        self._button        = ControlButton('Press this button')

        def echo(msg):
            self._controltextarea.__add__(msg) #this should add a line in the Textbox with the message "msg"


        context = Context.instance()
        s = context.socket(zmq.PULL)
        s.connect('tcp://127.0.0.1:5014')
        stream = ZMQStream(s)
        stream.on_recv(echo)  #this calls the function echo from the zmq Ioloop when something is recived

#Execute the application
if __name__ == "__main__":
    #here is where I have tried a lot to make both loops work simultaniously, without success
    guiThread = threading.Thread(target=pyforms.start_app( SimpleExample1 ))
    zmqThread = threading.Thread(target=lambda: ioloop.IOLoop.current().start())
    zmqThread.setDaemon(True)
    guiThread.start()
    zmqThread.start()

This is the ZMQ sender.

import zmq
import time

context = zmq.Context()
publisher = context.socket(zmq.PUSH)
publisher.bind('tcp://127.0.0.1:5014')

while True:
    publisher.send_string('something')
    #print('sended')
    time.sleep(1)

I see 2 possible solutions. First it could work with threads like in the code above. But I haven't found a way to start both event loops. Either one statement blocks the other or I get error messages when I don't use lamda etc. Or it's just not working. - here is a reference I tried to implement for this without success, describing a similar task: github maartenbreddels

The second option is to add the zmq function call of echo() into the eventloop of Pyforms (which is based on QT as far as I know). This could be the most elegant but I don't know how to implement or add something to the GUI's event loop.

I've tried a lot for both solutions without success.
The most valuable information I could find is here:

pyzmq readthedocs

zeromq org

pyforms readthedocs

I've not a lot experience and try to understand things like futures, promises and coroutines but also frameworks like asyncio, green in python without success so far. A simple function call for "echo", as soon as a message is received, is what I'm looking for.

Any Ideas how to make it work? Am I doing something silly ?


Solution

  • Thank you Bazza for your input. Your answer helped me to find a solution for my problem. After searching how I can emit a Qevent; I found the following example example and solved the problem. The final code looks like this:

    import pyforms
    from   pyforms          import BaseWidget
    from   pyforms.controls import ControlTextArea
    from   pyforms.controls import ControlButton
    import threading
    import zmq
    from PyQt5 import QtCore
    
    class ZeroMQ_Listener(QtCore.QObject):
    
        message = QtCore.pyqtSignal(str)
    
        def __init__(self):
    
            QtCore.QObject.__init__(self)
    
            # Socket to talk to server
            context = zmq.Context()
            self.socket = context.socket(zmq.PULL)
            self.socket.connect('tcp://127.0.0.1:5014')
            print('connected!')
            self.running = True
    
        def loop(self):
            while self.running:
                string = self.socket.recv_string()
                self.message.emit(string)
    
    
    class SimpleExample1(BaseWidget):
    
        def __init__(self):
            super(SimpleExample1,self).__init__('Simple example 1')
    
            #Definition of the forms fields
            self._controltextarea     = ControlTextArea('textarea to show incoming zmq messages')
            self._button        = ControlButton('Press this button')
    
            message = QtCore.pyqtSignal(str)
            self.thread = QtCore.QThread()
            self.zeromq_listener = ZeroMQ_Listener()
    
            self.zeromq_listener.moveToThread(self.thread)
    
            self.thread.started.connect(self.zeromq_listener.loop)
            self.zeromq_listener.message.connect(self.signal_received)
    
            QtCore.QTimer.singleShot(0, self.thread.start)
    
    
        def signal_received(self, message):
            self._controltextarea.__add__(message)
    
    #Execute the application
    if __name__ == "__main__":
        guiThread = threading.Thread(target=pyforms.start_app( SimpleExample1 ))
    
        guiThread.start()
    

    Thanks a lot and best regards!!!