Build a Studio Plugin

1. PolySync Core Studio Plugin

Studio is designed to dynamically load plugins at runtime. The default install provides four plugins: 3D View, Trace, Video, and System Hierarchy. Core also enables you to create plugins for custom visualization. This tutorial demonstrates the use of polysync-generate to create scaffolding for a functional Studio plugin that visualizes ps_diagnostic_trace_msg timestamps as they’re published and received by Studio.

1.1 Prerequisites

To build and develop your plugin, you will have to download and install Qt. It is a requirement that the Qt versions of the plugin match exactly with Studio, which is currently developed against Qt v5.5.1. See How to Create Qt Plugins for more detail.

  • Download Qt 5.5.1
  • cd ~/Downloads && chmod +x qt-opensource-linux-x64-5.5.1.run
  • ./qt-opensource-linux-x64-5.5.1.run

This article assumes the default install location is ~/Qt5.5.1.

2. Generate plugin scaffolding

To generate the scaffolding for a new plugin, open a new terminal and type:

$ polysync-generate plugin

This will create a project directory called MyPlugin.

2.1 Command line options

Print the command line options with the help flag. You can specify the node name, output directory, and which messages to subscribe to with a few flags.


$ polysync-generate plugin -h
Usage: polysync-generate plugin [options]

Options:
    -n, --name NAME     name of the generated plugin [optional]
    -o, --output DIR    path in which to place generated files [optional]
    -s, --subscribe MESSAGE
                        message type(s) for the plugin to subscribe to
                        [optional]
    -l, --list          list valid PolySync messages [optional]
    -h, --help          print this help menu

2.2 Build and use plugin

Set environment variable telling CMake where to find Qt packages:


$ export CMAKE_PREFIX_PATH=~/Qt5.5.1/5.5/gcc_64

Compile the source:


$ mkdir ./MyPlugin/build 
$ cd MyPlugin/build
$ cmake .. && make

Copy the plugin to the polysync install:


$ sudo cp libMyPlugin.so /usr/local/polysync/plugins/

Run Studio:


$ polysync-core-studio

Open The New Plugin:

Open Plugin

Toggle Control

Open Plugin

3 About the code

MyPlugin contains all of the necessary methods every PolySync Core Studio plugin should have. This is the working directory:

MyPlugin
├── CMakeLists.txt
├── include
│   └── MyPlugin.hpp
├── resource
│   ├── plugin.png
│   └── resource.qrc
└── src
    └── MyPlugin.cpp

The MyPlugin class inherits from /usr/local/polysync/include/BasicPlugin.hpp which is a convenience wrapper around the abstract interface class defined in /usr/local/polysync/include/Plugin.hpp.

The BasicPlugin class also inherits from /usr/local/polysync/include/PolySyncDataSubscriber.hpp.

3.1 Architecture

Plugins provide two widgets, display and control, to Studio’s QApplication context. Display is responsible for data visualization, and control is responsible for user input. The QApplication context, plugin, and Studio are all operating in separate threads. Plugins pass the display and control widgets to Studio using polysync::Studio::pluginUtility which returns the polysync::studio::PluginUtilityProvider singleton for Studio.

3.1.1 Display widget

The display widget is the “view” widget for our data. In this case, MyPlugin::initDisplay creates QLabel and connects MyPlugin::signalDataToDisplay to QLabel::setText. When our plugin receives new data, it packages the data to a string and sends it to the display to be rendered. The plugin adds the display widget to the Studio workspace using polysync::studio::PluginUtilityProvider::addWindow.


void MyPlugin::initDisplay()
{
    auto display = new QLabel;

    display->setStyleSheet( "color: #999999" );

    QObject::connect(
        this, &MyPlugin::signalDataToDisplay, display, &QLabel::setText );

    _utilities->addWindow( display );
}

3.1.2 Control widget

The control widget handles user input. MyPlugin::initControl creates a simple QPushButton, connects the clicked signal to MyPlugin::slotControlClicked and adds the control widget to the Studio control panel using polysync::studio::PluginUtilityProvider::addSidebarSection.


void MyPlugin::initControl()
{   
    auto control = new QPushButton{ "Control Button" };
    
    QObject::connect(
        control, &QPushButton::clicked, this, &MyPlugin::slotControlClicked );
    
    control->setCheckable( true );
    
    control->setChecked( true );
    
    control->setStyleSheet(
        "border: 1px solid black;"
        "background-color: #777777" );
    
    control->setMaximumHeight( 30 );
    
    _utilities->addSidebarSection( control );
}

3.2 Thread safety

MyPlugin::connectSubscriber connects message passing from polysync::Node context to Plugin context. MyPlugin::messageHandler will therefore be called from a different thread and is not reentrant.


void MyPlugin::connectSubscriber()
{
    connectSubscriberMethod< MyPlugin >(
                "ps_diagnostic_trace_msg",
                &MyPlugin::messageHandler,
                this );

    _utilities->attachToDataNotifier( this );
}

Plugins ensure thread-safety using Qt Signals/Slots. MyPlugin::connectToPolySync calls MyPlugin::connectSubscriber and then connects MyPlugin::signalMessage to MyPlugin::slotMessage to enable passing the message across the thread barrier.


void MyPlugin::connectToPolySync()
{
    connectSubscriber();

    QObject::connect( 
        this, &MyPlugin::signalMessage, this, &MyPlugin::slotMessage );
}

Messages, as they’re received by Studio, are passed to MyPlugin::messageHandler which then emits the message using a Qt Signal. The message is queued and will make it’s way to MyPlugin::slotMessage (see signal/slot connection above).


void MyPlugin::messageHandler(
    std::shared_ptr< polysync::Message > message )
{
    emit signalMessage( message );
}


4 Additional resources

See The Qt Resource System for detail on managing assets within a Qt application. MyPlugin uses the resource system to provide plugin.png as an icon to the application in binary form. This is the standard method for managing assets as binaries in Qt. See the snippet from CMakeLists.txt below for how this is done using cmake:

4.1 CMakeLists.txt

Automatically generate necessary Qt binaries using AUTORCC:


set( CMAKE_AUTORCC ON )

Add resource.qrc to the compiled executable or library:


add_library( 
    ${PROJECT_NAME}
        SHARED
            ${SRC}
            ${INCLUDE}
            resource/resource.qrc )