Introduction

Overview

Sinspekto is a module that provides Qt modeling language (QML) components for interacting with applications that use the data distribution service standard OMG DDS as communication middleware.

Sinspekto consists of classes that define various QML Object Attributes, which are translated to / from DDS topics. This means that by using sinspekto QML elements in your application, it is possible to interact with other systems using DDS.

Currently, sinspekto supports a limited number of DDS structures, which are defined in Namespace fkin. The DDS structures have accompanying adapter classes, which expose the DDS structure in QML. The Component diagram below displays the core functionality of sinspekto.

../_images/container.svg

Component diagram for Sinspekto.

Usage

C++ setup

Once sinspekto is compiled and installed, it has to be linked to your application and you need to include a header in your executable source file. You also have to call sinspekto::LoadSinspektoQmlTypes() before instancing the QML engine.

  1. Link to sinspekto with some CMake code lines.

    find_package(sinspekto CONFIG REQUIRED)
    add_executable(your-app main.cpp)
    target_link_libraries(your-app
      Qt5::Widgets
      Qt5::Quick
      # and other libraries you use
      sinspekto::sinspekto)
    
  2. Add header and call sinspekto::LoadSinspektoQmlTypes().

    #include <QApplication>
    #include <QQmlEngine>
    #include <QQmlComponent>
    #include <QQuickWindow>
    #include <QUrl>
    #include <QDebug>
    
    ///////// <<HERE>> ////////
    #include "sinspekto/SinspektoQml.hpp"
    
    int main(int argc, char *argv[]){
    
      QApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
      QApplication app(argc, argv);
      app.setOrganizationName("The Organization");
      app.setApplicationName("your-app");
    
      ///////// <<HERE>> ////////
      sinspekto::LoadSinspektoQmlTypes();
      QQmlEngine engine;
    
      QQmlComponent component(&engine);
      QQuickWindow::setDefaultAlphaBuffer(true);
      component.loadUrl(QUrl(QStringLiteral("qrc:/your-app.qml")));
    
      if (component.isReady() )
        component.create();
      else
        qWarning() << component.errorString();
    
      return app.exec();
    
    }
    

Minimal QML example

Below is a Minimal example. It contains a functional pattern for using sinspekto together with QML elements. In this particular example there is a toggle switch, whose checked signal is connected to a DdsBitPublisher. Whenever the toggle switch changes, it is sent on the DDS topic testBit. On the line below there is a DdsBitSubscriber of the same topic. Its signal property is connected to a toggle switch, so it mirrors the first switch, but the data flow goes via the DDS communication bus, which makes it silly, but illustrative.

../_images/minimal.gif

Minimal QML example.

The key steps to use sinspekto are:

  • Import the sinspekto module with import fkin.Dds 1.0.

  • Create a QtToDds and call its init() function and then initialize all sinspekto DDS adapters.

  • Use a sinspekto element’s property attributes to connect to visual (or non-visual) QML elements.

import QtQuick 2.11
import QtQuick.Window 2.11
import QtQuick.Layouts 1.11
import QtQuick.Controls 2.4
import QtQuick.Controls.Material 2.4
import QtCharts 2.2

/// Loads sinspekto dds elements
import fkin.Dds 1.0

ApplicationWindow {
    id: windowg
    title: qsTr("Minimal QML example")
    width: 480
    height: 200
    visible: true

    /// Makes an instance of QtToDds
    QtToDds {
    id: ddsParticipant;
    readonly property int domain: 0;

    Component.onCompleted: {
    /// initialize participant first, then the dds signals
    init(domain);

    /// initialize DDS adapters by calling their init functions.
    ddsSubscriber.init(ddsParticipant, "testBit");
    ddsPublisher.init(ddsParticipant, "testBit", aSwitch.checked, true);
    }
    }

    GridLayout {
    anchors.fill: parent;
    anchors.margins: 20;
    columns: 2;

    Label { text: "DDS bit publisher"; }
    Switch {
        id: aSwitch;
        text: qsTr("Send with DDS");
        checked: true;
        onToggled: {
            console.log("Switch signal is: " + ddsPublisher.signal);
        }

        /// sinspekto::DdsBitPublisher's signal is connected to the switch
        DdsBitPublisher {
            id: ddsPublisher;
            signal: aSwitch.checked;
        }
    }
    Label { text: "DDS bit subscriber"; }
    Switch {
        id: aSwitchInput;
        text: qsTr("Received from DDS");
        checked: ddsSubscriber.signal;

        /// sinspekto::DdsBitSubscriber's signal is connected to the switch
        DdsBitSubscriber {
            id: ddsSubscriber;
        }
    }
    }
}

Integrating sinspekto with QtChart

“Qt Charts module provides a set of easy to use chart components”, QtCharts. The chart types make use of QAbstractSeries-derived data structure to store data. The sinspekto module has classes that allow DDS data to be buffered and connected to QAbstractSeries instances so that DDS data can be visualized with Qt Charts. In particular, DdsDoubleBuffer and DdsTimepointBuffer are used in buffer classes designed specifically for supported DDS data structures, like for instance DdsIdVec1dBuffer for the fkin::IdVec1d data type.

The following example shows how to use such a buffer class to store DDS data and visualize it with a Qt Chart element. We will add a slider, which is connected to a DdsIdVec1dPublisher. The same DDS topic is subscribed to by DdsIdVec1dBuffer. This buffered data are visualized with a LineSeries element, see Chart QML example.

../_images/chart.gif

Chart QML example.

In the code blocks below we only show an excerpt of the QML code, please consult File chart_example.qml for the whole example implementation.

Make an instance of QtToDds and instantiate the DDS adapters:

QtToDds {
  id: ddsParticipant;
  readonly property int domain: 0;

  Component.onCompleted: {
    /// Initialize participant first, then the dds signals
    init(domain);

    /// Initialize the IdVec1d Publisher, on topic 'anInput' with recipient 'Test'.
    ddsSlide.init(ddsParticipant, "anInput", "Test", theSlider.value, true);
    /// Initialize the Subscriber buffer on same topic, with a buffer size of 50 values.
    ddsSlideBuffer.init(ddsParticipant, "anInput", "Test", 50, false);
  }
}
Item {
  DdsIdVec1dPublisher { id: ddsSlide; }
  DdsIdVec1dBuffer { id: ddsSlideBuffer; }
}

Create a slider and connect its value to the DDS publisher:

Slider {
    id: theSlider;
    from: 10;
    value: 15;
    to: 30;
    stepSize: 1;
    snapMode: Slider.SnapOnRelease;
    onValueChanged: { ddsSlide.value = theSlider.value; }
}

Create a ChartView, attach a LineSeries and create necessary connections:

ChartView {
    id: depthChart;
    // .. misc. options

    /// Sets signals and slots connections for the ddsSlideBuffer instance.
    Connections {
    target: ddsSlideBuffer;

    // When new data have arrived on DDS, update slideSignal's QAbstractSeries data structures.
    onNewData: {

      /// Updates the axes of slideSignal with the time axis and x value of IdVec1d
      /// buffer. If there were more attributes in the DDS structure, more calls to
      /// updateSeries with other series and e.g. FKIN.Y may be needed.
      ddsSlideBuffer.updateSeries(slideSignal, FKIN.T, FKIN.X);
      // ddsSlideBuffer.updateSeries(otherSeries, FKIN.T, FKIN.Y); // e.g. in case of IdVec2d.
    }

    /// Ensures a sliding time range
    function ensureTimeHorizon(timeChart, horizonMS, futureMS, t_min, t_max)
    { ... }

    // Update the viewable data range (y-axis)
    onRangeChanged: {
        if(dim == FKIN.X) {
            slideSignal.axisY.min = ddsSlideBuffer.rangeX.x;
            slideSignal.axisY.max = ddsSlideBuffer.rangeX.y;
        }
    }

    // Update the viewble time range (x-axis)
    onRangeTChanged: {
        slideSignal.axisX.min = ddsSlideBuffer.rangeTmin;
        slideSignal.axisX.max = ddsSlideBuffer.rangeTmax;

        ensureTimeHorizon(
            depthChart,
            slideSignal.horizonMS,
            slideSignal.futureMS,
            ddsSlideBuffer.rangeTmin,
            ddsSlideBuffer.rangeTmax);
    }
    }

    /// The LineSeries chart. The x-axis is a DateTimeAxis, which makes use of QDateTime
    LineSeries {
    id: slideSignal;
    name: qsTr("Slider Signal");
    // .. options
    property int futureMS: 1000;
    property int horizonMS: 10000 - futureMS;

    axisX: DateTimeAxis {
        titleText: qsTr("Time");
        format: "hh:mm:ss";
        labelsFont: window.plotFont;
        tickCount : 3;
        min: new Date();
        max: new Date();
    }
    axisY: ValueAxis {
        titleText: qsTr("Value") + " [-]";
        labelsFont: window.plotFont;
        reverse: true;
        min: 0;
        max: 0.1;
    }
    }
}

Warning

Newer version of Qt > 5.12(?) deprecates implicitly defined properties in Connections, so instead of onRangeChanged: {..}, the new syntax function onRangeChanged(range, dim){..} should be used, with signature equal to the slot function, see qml connections.

Implementing new sinspekto components

Currently, adding support for new DDS data types require a bit of manual coding. The best way to get started is to explore existing source code and copy similar patterns. There are some steps to keep in mind when adding new QML components.

  • Create a new <new>.hpp and <new>.cpp file in include/sinspekto/ and src/sinspekto/, respectively.

  • Add them in src/CMakeLists.txt.

  • Remember to inherit from QObject and add desired signals, slots, and properties.

  • Make sure that the IDL type is defined in an idl file from which the idl-compiler creates c++ headers. This typically means any type you include from RatatoskIDL.

  • For a DDS Subscriber, add std::unique_ptr<sinspekto::Reader<YOUR_NEW_DDS_TYPE>> as a member variable.

  • For a DDS Publisher, add std::unique_ptr<sinspekto::Writer<YOUR_NEW_DDS_TYPE>> as a member variable.

  • Forward declare the IDL-generated data type in include/sinspekto/QtToDds.hpp.

  • If you have created a new IDL-file with namespace <MODULE>, be sure to include <MODULE>_types_DCPS.hpp in src/sinspekto/QtToDds.cpp.

  • Include your new header file in src/sinspekto/SinspektoQml.cpp and to register the QML type with qmlRegisterType<YOUR_CLASS>("fkin.Dds", 1, 0, "YOUR_CLASS").

sinspekto::Reader and sinspekto::Writer are merely convenience classes that encapsulates common DDS settings and QoS configurations. By default, they are set up with a default Quality of service. This is usually good enough, but for more custom setups, you will need to implement your own reader and writer classes as needed.