Skip to content

Connecting external applications to BlueSky

Joost Ellerbroek edited this page Mar 18, 2020 · 3 revisions

Within BlueSky, the different components communicate with each other over tcp/ip using the ZMQ library. Messages sent over these connection consist of nested dicts, lists, and numpy arrays. These data are serialised using the MSGPack library. If you want your application to communicate with BlueSky, you have to use or reimplement BlueSky's Client class. If your application is also written in Python, the easiest way is to import and subclass BlueSky's Client class. An example of this is given below. In other languages you will have to make your own implementation.

Example implementation: A simple text client

In this example we'll create a simple Qt window with an input line to enter stack commands that are sent to BlueSky, and a text box where echo lines coming from BlueSky are printed. A complete implementation can be found here.

This example consists of two parts: subclassing BlueSky's Client class, and creating a Qt window with two text boxes. In this example we use Qt because communications to and from BlueSky are performed asynchronously, which is difficult to do in a plain text console.

Step 1: subclassing Client

To create your own client that is able to handle data coming from BlueSky, and can send commands back, you have to subclass BlueSky's client:

   from bluesky.network import Client


   class TextClient(Client):

Your derived class should reimplement at least the event() function, which is called whenever a new event is received. This function takes three arguments: name, data, and sender_id. Here, name is a byte string which indicates the type of message coming in, data is the actual content of the event, and sender_id gives the unique id of the originating simulation node. In this example we will only catch ECHO messages.

def event(self, name, data, sender_id):
    ''' Overridden event function to handle incoming ECHO commands. '''
    if name == b'ECHO':
        text = data['text']
        # Do something with text

In our example, we will make a second function that we can use to send commands back to BlueSky. These stack commands are simple text strings, sent using the send_event function, which takes two arguments: a byte string containing the event type, and the data to send, which in this case is a single python string.

    def stack(self, text):
        ''' Stack function to send stack commands to BlueSky. '''
        self.send_event(b'STACKCMD', text)

The final action our subclassed client needs to perform is to periodically check for incoming data. In this example we'll use Qt's QTimer to periodically call the receive function of our client.

from PyQt5.QtCore import QTimer

class TextClient(Client):
    def __init__(self):
        super().__init__()
        self.timer = QTimer()
        self.timer.timeout.connect(self.receive)
        self.timer.start(20)

Whenever receive gets incoming data, it will call the reimplemented event function to allow you to handle this incoming event.

Step 2: Creating the gui

In this example we'll make a simple Qt window with two QTextEdit widgets; one to display incoming messages from BlueSky, and one that serves as input line to type stack commands that can be sent to BlueSky. The easiest implementation is to create two derived classes of QTextEdit; one for the incoming messages, and one for the command line. In the constructor we'll set some parameters relating to size, scrolling, and focus. The echo box will get a function to add text coming from BlueSky, and the command line will override Qt's keyPressEvent to catch Enter-presses and send data to BlueSky.

class Echobox(QTextEdit):
    ''' Text box to show echoed text coming from BlueSky. '''
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMinimumHeight(150)
        self.setReadOnly(True)
        self.setFocusPolicy(Qt.NoFocus)

    def echo(self, text, flags=None):
        ''' Add text to this echo box. '''
        self.append(text)
        self.verticalScrollBar().setValue(self.verticalScrollBar().maximum())


class Cmdline(QTextEdit):
    ''' Wrapper class for the command line. '''
    def __init__(self, parent=None):
        super().__init__(parent)
        self.setMaximumHeight(21)
        self.setFocusPolicy(Qt.StrongFocus)
        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)

    def keyPressEvent(self, event):
        ''' Handle Enter keypress to send a command to BlueSky. '''
        if event.key() == Qt.Key_Enter or event.key() == Qt.Key_Return:
            if bsclient is not None:
                bsclient.stack(self.toPlainText())
                echobox.echo(self.toPlainText())
            self.setText('')
        else:
            super().keyPressEvent(event)

Step 3: Putting it all together

What remains now is construction of our three custom objects, a Qt window, connecting to BlueSky, and starting the Qt main loop.

if __name__ == '__main__':
    # Construct the Qt main object
    app = QApplication([])

    # Create a window with a stack text box and a command line
    win = QWidget()
    win.setWindowTitle('Example external client for BlueSky')
    layout = QVBoxLayout()
    win.setLayout(layout)

    echobox = Echobox(win)
    cmdline = Cmdline(win)
    layout.addWidget(echobox)
    layout.addWidget(cmdline)
    win.show()

    # Create and start BlueSky client
    bsclient = TextClient()
    bsclient.connect(event_port=11000, stream_port=11001)

    # Start the Qt main loop
    app.exec_()

Download a full version of this example here.

Clone this wiki locally