-
Notifications
You must be signed in to change notification settings - Fork 249
Connecting external applications to BlueSky
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.
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.
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.
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)
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.