Tags

, , ,

Share it

The goal for today is to stream data from my Erlang application to a PyQt based front end application. The idea is that this will serve as a template for cross-platform desktop applications, and possibly mobile apps at some point.

Screen Shot 2013-03-15 at 12.25.24 PM

I’m already thinking ahead and there are a few things I don’t want:

  • No polling. I want soft real time behavior. The Erlang application pushes updates to the UI.
  • No other dependencies. I’d like to deploy to Windows, Linux and Mac. This combination may be challenging enough already.
  • No 0MQ -> Router -> raw socket. I want to work with 0MQ sockets end to end. Besides, traffic goes two ways and I don’t think there’s a way to do raw socket -> 0MQ, but I could be wrong.

Bonus if 0MQ can hook straight into Qt’s Signal/Slot mechanism.
Extra bonus if I can copy and paste a sample, change the IP address and port number and be in business.

I first ran into some articles mentioning gevent and Twisted. It seems counterintuitive that something like a reactor would be needed. Surely Qt has threads, networking and events already built in?

Then I stumbled on on Writing a client for a zeromq service by David Boddie, someone who I don’t know at all. But I’m glad evolve his code!

So let me show my version of it:

import sys
import zmq

from PyQt4 import QtCore, QtGui        
                
class ZeroMQ_Listener(QtCore.QObject):
    message = QtCore.pyqtSignal(str)
    def __init__(self):
        QtCore.QObject.__init__(self)
        
        # ZeroMQ endpoint
        context = zmq.Context()
        self.socket = context.socket(zmq.PULL)
        self.socket.connect ("tcp://127.0.0.1:5561")
        
        self.running = True
    
    def loop(self):
        while self.running:
            string = self.socket.recv()
            self.message.emit(string)
            
class ZeroMQ_Window(QtGui.QMainWindow):
    def __init__(self, parent=None):
        QtGui.QMainWindow.__init__(self, parent)
        frame = QtGui.QFrame()
        label = QtGui.QLabel("listening")
        self.text_edit = QtGui.QTextEdit()
        layout = QtGui.QVBoxLayout(frame)
        layout.addWidget(label)
        layout.addWidget(self.text_edit)
        self.setCentralWidget(frame)

        # ZeroMQ hook up listener to Qt
        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.text_edit.append("%s\n"% message)

    def closeEvent(self, event):
        self.zeromq_listener.running = False
        self.thread.quit()
        self.thread.wait()

if __name__ == "__main__":
    app = QtGui.QApplication(sys.argv)
    mw = ZeroMQ_Window()
    mw.show()
    sys.exit(app.exec_())

In order to fully understand it (and to make these blog articles more fun to read), I’ve made another little diagram to illustrate:

Screen Shot 2013-03-15 at 1.17.58 PM

I’m assuming that the cross-thread call is thread-safe because David seems like a knowledgeable guy. But moreover, he’s using QThread and Qt Signal. Sounds good to me!

Let’s see if it works.

First, I start up my GUI:

$ python gui.py

Then, in the Erlang shell:

1> my_gen_server:send(<<"Ponies">>).
ok
2> my_gen_server:send(<<"Everywhere">>).
ok
3> 

Voilà, we got ponies!

Screen Shot 2013-03-15 at 1.24.18 PM

Oh, I forgot to show the Erlang side. Things are pretty basic. These are the only changes in a normal gen_server:

% public API
send(Msg) ->
	gen_server:call(?SERVER, {message, Msg}).

init(_Args) ->
    {ok, Context} = erlzmq:context(),
	{ok, Publisher} = erlzmq:socket(Context, [push, {active, false}]),
	ok = erlzmq:bind(Publisher, "tcp://127.0.0.1:5561"),
	{ok, Publisher}.

% callback
handle_call({message, Msg}, _From, Socket) ->
	erlzmq:send(Socket, Msg),
	{reply, ok, Socket};

% the default handle_call below here

Wah! That’s it? That’s it. I’m pushing messages from Erlang to a PyQt GUI!

To do:
I promised to do triangles, and you got UTF8 Ponies. Sorry, I’m not going to do triangles anymore.

Instead, I’ll be designing a way to synchronize an object scene in Erlang with custom drawn GUI widgets in PyQt.
Specifically, new objects will be spawned based on external events and user actions. Each object will be represented by a gen_server. Objects will be able to change from one type into another, each having unique capabilities. This is the reason why I haven’t picked e2 for this example, because apparently it doesn’t support live code updates.

For this morphing functionality that I just described, I’m heavily inspired by the nugget on page 300 in Programming Erlang Software for a Concurent World by Joe Armstrong. Quoting:

A few years ago, when I had my research hat on, I was working with PlanetLab. I had access to the PlanetLab network, so I installed “empty” Erlang servers on all the PlanetLab machines (about 450 of them). I didn’t really know what I would do with the machines, so I just set up the server infrastructure to do something later.
Once I had gotten this layer running, it was an easy job to send messages to the empty servers telling them to become real servers.
The usual approach is to start (for example) a web server and then install web server plug-ins. My approach was to back off one step and install an empty server. Later the plug-in turns the empty server into a web server. When we’re done with the web server, we might tell it to become something else.

So my objects will be morphing into various types of computing units, each with unique behavior, and they will stream drawing instructions to a canvas. Oh that’s as much as I can say, it’s top secret stuff!