cumbia-websocket 1.x
Qt widgets on top of the cumbia C++ library
Qt, cumbia, websocket, WebAssembly application setup and test

Enabling multi engine support for a cumbia app allows executing one application to rely on different engines without any code modification. For instance, on platforms where Tango or EPICS control systems cannot be built, the application can fetch data through a proxy server over WebSockets or http. Applications that targeted only a specific enging (e.g. Tango), need a small modification to be ported to the corresponding multi engine version.

Note

It is recommended to enable the multi engine support for all projects, even for those intended to run on a specific engine only. This leaves the application open to rely on new engines in the future or face particular test environments.

Setup Qt and cumbia for WebAssembly

If you haven't done it yet, prepare and install the Qt and cumbia builds for WebAssembly described here.

Note

In order to use the cumbia tools for development and testing, like cumbia new project, cuuimake, cumbia read or cumbia client, the cumbia-libs for C++ must be installed.

If they are installed under

/home/test/.local/cumbia-libs/

you can execute

‍source /home/test/.local/cumbia-libs/bin/cusetenv.sh

to set up the environment in the current shell

Build the websocket server for test purposes.

The websocket server transforms client requests over WebSockets into engine-specific (Tango, EPICS) read and write operations. Results are sent back to the client over the same WebSocket. The websocket server is a C++ application that must have access to the desired control systems (Tango, EPICS) of which it is thus a client.

Disclaimer

cumbia-websocket-proxy-server is a small Qt project uniquely aimed at testing websocket based C++ and WebAssembly applications and not intended for production.

Download and build cumbia-websocket-proxy-server

Download the sources

‍cd ~/devel

‍git clone https://github.com/ELETTRA-SincrotroneTrieste/cumbia-websocket-proxy-server.git

‍cd cumbia-websocket-proxy-server

NOTE C++ qmake is used

‍qmake INSTALL_ROOT=/home/test/.local/cumbia-libs

‍make -j9 && make install

Start the cumbia-websocket-proxy-server

The server can be run with the following command

‍cumbia-websocket-proxy-server

if the bash source /home/test/.local/cumbia-libs/bin/cusetenv.sh instruction has been previously imparted.

Application setup for WebAssembly and websocket engine

1. Start a new project with cumbia-websocket engine support

Start the new project wizard

‍cumbia new project

In the top right Support box select multi-engine

Fill in the required fields and click on the Create button.

Let's now inspect the skeleton generated by the wizard:

Header file

The header file exploits the multi engine functionalities offered by the CumbiaPool and CuControlsFactoryPool:

private:
Ui::MyClass *ui;
CumbiaPool *cu_pool;
CuControlsFactoryPool m_ctrl_factory_pool;

CPP file

The CPP skeleton file comes with plenty of comments. Substantially, since the application must be enabled to rely on different engines at runtime, it must be designed so that this adaptation does not require code change and recompilation.

QCommandLineParser is used to determine whether the user launched the program with a parameter specifying the websocket-url. If so, cumbia-websocket engine is set up and cumbia-tango and cumbia-epics are left out. Exclusive engine loading allows the same syntax for sources and targets across the native and proxied engines (for instance my/tango/device/attribute, $1/my_attribute).

Websocket compatibility of source syntax with the Tango one is made possible by the CuWsTangoReplaceWildcards class, that mimics the CuTangoReplaceWildcards, allowing for the $x/attribute and $y->command wildcards. Source patterns are provided by the CuWsTangoHelper.srcPatterns, that copies the src patterns defined by the Tango engine.

In the following section, the generated C++ file will be further commented.

Once the skeleton code is generated, you can proceed to editing. When the application is launched with the server address as option, the WebSocket engine is used. The native Tango (EPICS) engine is used otherwise.

2. Migrate an engine specific project to a multi-engine support including cumbia-websocket

If an existing cumbia project was created with a single engine in mind, a few code modifications are needed to port it to the multi engine form. In this example, we assume to be working on a Tango app and we want to support the websocket and tango engines only.

qmake project file

Add the support for the necessary engines

isEmpty(CUMBIA_ROOT) {
CUMBIA_ROOT=/usr/local/cumbia-libs
}
linux-g++ {
exists ($${CUMBIA_ROOT}/include/qumbia-tango-controls/qumbia-tango-controls.pri) {
include ($${CUMBIA_ROOT}/include/qumbia-tango-controls/qumbia-tango-controls.pri)
}
} else {
# include cumbia-qtcontrols for necessary qt engine-unaware dependency (widgets, qwt, ...)
include ($${CUMBIA_ROOT}/include/cumbia-qtcontrols/cumbia-qtcontrols.pri)
include ($${CUMBIA_ROOT}/include/cumbia-websocket/cumbia-websocket.pri)
}

Dependencies from qtx11extras should be removed or moved within the linux-g++ scope. Removing qtx11extras support implies removing or conditionally include sections of code related to X, like for example those using QX11Info, calling XSetCommand and so on.

Header File

The following lines

// - to be removed
#include <cumbiatango.h>
// -
class MyTangoApp : public QWidget
{
Q_OBJECT
public:
// - to be replaced with another version of the constructor
explicit MyTangoApp(CumbiaTango *cut, QWidget *parent = 0);
// - to be removed
CumbiaTango *cu_t;
CuTReaderFactory cu_tango_r_fac;
CuTWriterFactory cu_tango_w_fac;
// -

must be replaced by

#include <cucontrolsfactorypool.h>
class CumbiaPool;
class MyClass : public QWidget
{
Q_OBJECT
public:
explicit MyClass(CumbiaPool *cu_p, QWidget *parent = 0); // the constructor changes
// ...
CumbiaPool *cu_pool; // we need CumbiaPool
CuControlsFactoryPool m_ctrl_factory_pool; // we need CuControlsFactoryPool

The main.cpp file

The old main.cpp file contained these lines

// - to be removed
#include <cumbiatango.h>
int main() {
// - this must be removed
CumbiaTango *cu_t = new CumbiaTango(new CuThreadFactoryImpl(), new QThreadsEventBridgeFactory());
MyClass *w = new MyClass(cu_t, NULL); // - and this
// ...
int ret = qu_app.exec();
// delete resources and return
delete w;
// - and this must be removed
delete cu_t;
}
int main(int argc, char *argv[])

and the new main.cpp requires the following additions:

// + add
#include <cumbiapool.h>
// -
int main() {
// + this must be added
CumbiaPool *cu_p = new CumbiaPool();
MyClass *w = new MyClass(cu_p, nullptr);
// -
w->show();
int ret = qu_app.exec();
// delete resources and return
delete w;
// + add these lines according to engines allocated in cpp class file (see below)
Cumbia *c = cu_p->get("tango");
if(c) delete c;
c = cu_p->get("ws");
if(c) delete c;
}

The cpp file

The old constructor

MyTangoApp::MyTangoApp(CumbiaTango *cut, QWidget *parent) : // this is CumbiaTango specific
QWidget(parent)

will be replaced by the multi engine constructor (with CumbiaPool as first parameter)

MyClass::MyClass(CumbiaPool *cumbia_pool, QWidget *parent) : QWidget(parent), ui(new Ui::MyClass)

this is removed

ui->setupUi(this, cu_t, cu_tango_r_fac, cu_tango_w_fac);

and a new piece of code needed to implement what described in the previous section must be introduced:

  • includes: you can see the multiple engines support. In this document we reduce the number of engines to the Tango and WebSocket ones. You can look at a skeleton generated by cumbia new project for a more complete example. qumbia-apps/qumbia-client and qumbia-apps/qumbia-reader are good examples as well.

Note

Tango engine is registered if the application has not been started with a parameter indicating a websocket address (see comments to code, (1) and (2)) In this way applications can rely either on websocket or Tango without changing the definition of their sources (i.e. without code changes and rebuild)

// + to register log service
#include <cuserviceprovider.h>
#ifdef CUMBIA_WEBSOCKET_VERSION
#endif
#ifdef QUMBIA_TANGO_CONTROLS_VERSION
#endif

The constructor becomes

MyClass::MyClass(CumbiaPool *cumbia_pool, QWidget *parent) : QWidget(parent), ui(new Ui::MyClass)
{
Cumbia *cuws = nullptr, *cuta = nullptr;
m_log = new CuLog(&m_log_impl);
// cumbia-tango
#ifdef CUMBIA_WEBSOCKET_VERSION
QCommandLineParser parser;
if(wsre.hasCmdOption(&parser, qApp->arguments())) { // (1)
cuws = wsre.registerWithDefaults(cumbia_pool, m_ctrl_factory_pool);
static_cast<CumbiaWebSocket *>(cuws)->openSocket();
cuws->getServiceProvider()->registerService(CuServices::Log, m_log);
}
#endif
#ifdef QUMBIA_TANGO_CONTROLS_VERSION
// (2)
if(cuws != nullptr) {
cuta = tare.registerWithDefaults(cu_pool, m_ctrl_factory_pool);
cuta->getServiceProvider()->registerService(CuServices::Log, m_log);
}
#endif
ui = new Ui::MyClass;
// + setupUi with pools
ui->setupUi(this, cumbia_pool, m_ctrl_factory_pool);
}
CumbiaTango * registerWithDefaults(CumbiaPool *cu_pool, CuControlsFactoryPool &fpoo)
Definition: cuwsregisterengine.h:15
CumbiaWebSocket * registerWithDefaults(CumbiaPool *cu_pool, CuControlsFactoryPool &fpoo)
Definition: cuwsregisterengine.cpp:27
bool hasCmdOption(const QStringList &args) const
Definition: cuwsregisterengine.cpp:66
Definition: cumbiawebsocket.h:311

Note 1

You may want to review the previous section, Start a new project with cumbia-websocket engine support.

Note 2

The example code needs to be inserted within the constructor of the application being ported to its multi engine version.

Note 3

Non necessary engines can be omitted by removing the sections within the ifdef sections, e.g. Tango. Further engines can be added in a similar way shown for Tango and websocket*.

For example, you only need modify the MyClass constructor in the cpp file:

#ifdef QUMBIA_EPICS_CONTROLS_VERSION
#endif
MyClass::MyClass(CumbiaPool *cumbia_pool, QWidget *parent) : QWidget(parent), ui(new Ui::MyClass)
{
Cumbia *cuepi = nullptr;
// ..
#ifdef QUMBIA_EPICS_CONTROLS_VERSION
cuep = epre.registerWithDefaults(cumbia_pool, m_ctrl_factory_pool);
#endif
// ..
}
CumbiaEpics * registerWithDefaults(CumbiaPool *cu_pool, CuControlsFactoryPool &fpoo)

and include the qumbia-epics-controls.pri file in the qmake .pro project file:

include (/usr/local/cumbia-libs/include/qumbia-epics-controls/qumbia-epics-controls.pri)

Note 2

No other change in the code should be necessary, unless you need to grab references to specific Cumbia objects, i.e. CumbiaTango or CumbiaWebSocket.

In that case, the code to use will look like:

CumbiaTango *c = static_cast<CumbiaTango *>(cu_pool->get("tango"));
if(c) {
// ...
}

and the old attributes referring to the CumbiaTango, CuTReaderFactory or CuTWriterFactory and declared in the header file will have to be removed.

Build the application for WebAssembly

Run the C++ application for testing

Run the application in the browser

‍cd ~/devel

‍git clone https://github.com/emscripten-core/emsdk.git

‍cd emsdk

Qt and cumbia libraries have been tested with chromium browser, the Emscripten version 1.38.47 and Qt 5.14

‍./emsdk install emscripten-1.38.47-fastcomp

‍./emsdk activate 1.38.47-fastcomp

Qt build and installation

‍cd ~/devel

‍git clone https://github.com/qt/qt5.git

‍cd qt

‍git checkout 5.14.2

‍./init-repository

init-repository excluding some modules:

‍./init-repository –module-subset=all,-qtwayland,-qtx11extras,-qtwebengine,-qtpim,-qtquick3d,-qtmacextras

Set the environment for emscripten

‍source ~/devel/emsdk/emsdk_env.sh

Command line arguments patch

Check whether the following patch has been applied or not: it is important in order to pass command line arguments to applications

https://codereview.qt-project.org/c/qt/qtbase/+/248624/6/src/plugins/platforms/wasm/qtloader.js#420

Configure, build and install

Configure Qt with -feature-thread, skip tests and examples Further options can be found executing *./configure –help*

‍./configure -xplatform wasm-emscripten -prefix /usr/local/qt-5.14.2-wasm -feature-thread -skip qtwebengine -skip qtquick3d -skip qtpurchasing -skip qtserialport -nomake examples -nomake tests -opensource -confirm-license && make -j9 && make install

‍make -j9

If you prefer, only a subset of modules can be built: make module-qtbase module-qtdeclarative [other modules]

‍[sudo] make install