<< to CrossControl homepage

Support & Service Center

Data Engine connection with Qt5 Quick application

Printer-friendly versionPrinter-friendly versionPDF versionPDF version

In this article, one approach will be presented how signals in the Data Engine can be connected to QML components. Signal values can either be presented in the GUI for a user or be updated through user input with the GUI.
Goal:

  • Set signal value from GUI (qml) and send the value to the Data Engine
  • Read value from Data Engine and view it in the GUI (qml)

Achieved by:

  • Write a C++ backend to handle communication with Data Engine
  • Class with Q Properties defines the signals 
  • QML-binds to properties in the signal class handler

The following structure will be used for both projects to achieve the previously mentioned goal:

  • Viewer

    • The Viewer is what the user will see and interact with. It is written with QML and property bindings will be used to bind certain component properties. It communicates with the backend parts Data Engine Control or Data Engine Signal.

  • Data Engine Signal

    • Defines the signals which should be used by the application. Signals are created as properties which the Viewer can bind to. The name of the signals must follow a specific pattern for the current implementation. Will be presented further on in the article.

  • Data Engine Control

    • A central piece for this project and the backend. It handles the communication with the Data Engine through the sapcore interface or by receiving data from the observer. It is also responsible to update signals defined as properties in Data Engine Signal.

  • Observer

    • Receives data from the sapcore interface, such as subscribe result or data for updated signals. Pushes the information to the Data Engine Control which then handles the received data. It’s a part of the Data Engine Control.

Viewer

The visible layer and what the user will interact with. This example includes two projects: one will act the sender and the other as the receiver. A slider component is used to change the values which should be transmitted to the receiver. In total there are five different signals used, each representing a different data type. The selected value range can be increased or decreased, selected values are for demonstration purpose.

A central part of QML is use to bind properties to each other, and this is what we will utilize to set values and read values from the Data Engine.

Sender

To send data we will use the signals which are defined in Data Engine Signal for the SignalSender project and use the event “onValueChanged” for each slider to set a value to one of the available signals.

Slider { 
   id: boolSlider value: 0 
   minimumValue: 0 
   maximumValue: 1 
   stepSize: 1 
   onValueChanged: dataEngineSignal.boolValue_OUT = value 
}

When the event “onValueChanged” is triggered, it will set a value to the property called “boolValue_OUT”, defined in our Data Engine Signal class. By defining the signal name with “_OUT” in the end, we indicate that this signal should be transmitted to the Data Engine. More information will be presented in the Data Engine Signal section.

Receiver

The second project acts as the receiver of data. The QML code is very similar to the sender but utilize the bindings in a different manner and the names of the signals are also slightly modified.

Text { 
   id: boolValue 
   text: dataEngineSignal.boolValue_IN 
   font.pixelSize: 12 
}

With the sender project the a value was set to the property “boolValue_OUT”, in this case we bind the property “boolValue_IN” found in Data Engine Signal to the text property of Text in our QML file. When the property value is updated all properties bound to the signal will be updated, in this case a text field. Another difference is that the signal name how has “_IN” at the end of the, this indicates that the signal should be read and no data should be written to it from the GUI. More about this will be presented further on.

Data Engine Signal

This class is used in both projects to define all the signals that should be subscribed, either as producing signals or as consuming, The content of the files are almost identical, but there are differences.  Signals are created as properties with write, read and notify methods by using the macro Q_PROPERTY. The fastest way to define new signals is with code completion. Start type Q_PROPERTY and Qt Creator will suggest completing it; press the tab-key on the keyboard to do so. Then fill in the data type and the name of the signal. Lastly let Qt Creator create all missing methods and variables by right-clicking on property and select Refactor -> Generate Missing Q_PROPERTY Members...

Naming of signals

As mentioned earlier, signal names have been given a specific ending to define if it should be received (consumed) or written to (produce). This is required for this specific example and is used to instruct the Data Engine Control class which settings it should use when subscribing to all the signals defined in the Data Engine Signal class. The actual signal name used for subscription will be without the trailing “_IN/OUT”. Other types are also valid and here is the complete list:

  • _IN – Consumer
  • _OUT – Producer
  • _INOUT – Bidirectional
  • _MANYOUT – Multiple Producers

It is also possible to add “_P” in the end to indicate the signal is persistent, but this is not used in this example.

Receiver

Five signals are defined as properties and created with the method previously described. Since all the signals should be received to the application, they all have “_IN” added to the end of the signal name. All the signals are then available for binding. Definition and declaration is mainly kept in the .h-file but initial values for the signals are set on the constructor found in the .cpp-file. These values are read during initialization of the GUI.  When a new value is written to one of the signals, the set method will notify the GUI a new value is available and force an update. In this case, it is the Data Engine Control which writes to the signal by using the set method for the signal which needs to be updated.

Q_PROPERTY(bool boolValue_IN READ boolValue_IN WRITE setboolValue_IN NOTIFY boolValue_INChanged)

Sender

The same method is used to define the signals which should be sent to the receiver, but with “_OUT” instead of “_IN”. Besides this difference there is an important step in order to make it possible to write new signal values to the Data Engine. When the GUI sets a new value for a property, the associated WRITE method will be called for that property. For example, setboolValue_OUT:

Q_PROPERTY(bool boolValue_OUT READ boolValue_OUT WRITE setboolValue_OUT NOTIFY boolValue_OUTChanged)

In our case for this property, a method called setboolValue_OUT will be called. This method is generated using the refactor function pointed out earlier. It is slightly modified with the introduction of an additional signal emit, sendSignal (value, name), which is used to send a Qt signal to the Data Engine Control which has a slot paired with the emitted signal. http://doc.qt.io/qt-5/signalsandslots.html . Data Engine Control then takes over the request and sends the value if possible.

void setboolValue_OUT(bool arg) 
{ 
   if (m_boolValue_OUT != arg) 
   { 
      m_boolValue_OUT = arg; 
      emit boolValue_OUTChanged(arg); 
      emit sendSignal(arg, "boolValue"); 
   } 
} 

With this solution it is important that the setboolValue method is only called through the GUI. If Data Engine Control would use this method (done when a new value is received) then the incoming value would be sent back to the Data Engine (an echo). This is why “_IN” signals should only be read, and “_OUT” only be set.

Data Engine Control

This part of the backend has the task handle the connection with Data Engine and take care of signals which has been updated.

Similar to the Data Engine Signal class, Control also has a couple of properties which the GUI can access and read. Such as the status of the connection with Data Engine, name of the client, used host name and port number. There are also a few slots which the GUI could use to perform various tasks, but there are not used in this example.

When the application starts, three methods will be called from main.cpp to Data Engine Control to initialize it:

dataEngineContext.InitConnection("localhost", 0x1235, "Receiver-GUI");
dataEngineContext.SetSignalHandle(signal);
dataEngineContext.SubscribeToSignals(); 
  • Firstly, the connection needs to have the correct settings: where the Data Engine is running, on which port and what the client should call itself. Once these values have been set, the method will attempt to connect to the Data Engine using the provided information.
  • Secondly the Data Engine Control needs a handle to the signals defined in Data Engine Signal. This is a requirement to pass incoming data to the correct property.
  • Lastly, it will try to subscribe to the signals found by the handle. What the code does is that it lists all the properties found through the handle, and for each one of them a series of steps are taken do determine the name of the signal, data type, direction… before it is finally used for a subscription.

Receiver

Signal updates are received from the Observer which is a part of the Data Engine Control. Once a new value arrives, the ID will be checked in one of the used dictionaries to find the corresponding signal name. Once found, it will use the signal handle previously set during the initialization to update the correct property in Data Engine Signal. The property is updated using the WRITE method associated to the signal, which will trigger the GUI to read the new value thanks to the notify signal.

Sender

When Data Engine Signals emits the signal to send data, the slot connected to the signal will be activated, in this case sendToServer(value,name). The method needs to get the ID associated with the name of the signal, this is done by reading the name paired with the key value ID. Once the ID is known, it is just a matter to set the value using the SAP method SetValue(id,value) available in the sapcore-library.

Observer

The observer is a part of the Data Engine Control class and will handle signal updates. When it receives a new value for one of the subscribed signals, it will notify the Data Engine Signal class by emitting a signal to one of the associated slots available in the Data Engine Control class (one for each data type). After this, it is up to the Data Engine Control and the slot method to process the received information and act accordingly. 

Flowcharts and structure

AttachmentSize
Binary Data Project files9.79 KB
whatever