diff options
author | Kavindra Palaraja <kpalaraja@luxoft.com> | 2019-08-01 21:26:33 +0200 |
---|---|---|
committer | Kavindra Palaraja <kpalaraja@luxoft.com> | 2019-08-06 17:38:00 +0200 |
commit | a657189eaf144d79575718ca5021cf298cd17f63 (patch) | |
tree | e0ce0af080bd5602fd25a5ba1316e713cd26a39e /src/ivicore/doc | |
parent | 013230e3101cec6894cd8c187d07a4a9c2fc0053 (diff) | |
download | qtivi-a657189eaf144d79575718ca5021cf298cd17f63.tar.gz |
[docs] Review IVI Tutorial
Change-Id: Ieaf85dac8946879392a0849aa4807fa82d28b705
Reviewed-by: Robert Griebl <robert.griebl@pelagicore.com>
Diffstat (limited to 'src/ivicore/doc')
-rw-r--r-- | src/ivicore/doc/src/examples-qface-tutorial.qdoc | 1187 |
1 files changed, 617 insertions, 570 deletions
diff --git a/src/ivicore/doc/src/examples-qface-tutorial.qdoc b/src/ivicore/doc/src/examples-qface-tutorial.qdoc index a574d94..214d856 100644 --- a/src/ivicore/doc/src/examples-qface-tutorial.qdoc +++ b/src/ivicore/doc/src/examples-qface-tutorial.qdoc @@ -26,692 +26,739 @@ ****************************************************************************/ /*! -\example ivicore/qface-tutorial -\brief <<< ADD A GOOD BRIEF HERE >>> -\ingroup qtivicore-examples -\title Qt IVI Generator Tutorial -\image examples_qface_tutorial.png - -This tutorial will show you how to extend your QML Application with your own autogenerated -middleware API. This will be done using a existing QML Instrument Cluster application and consists -of the following steps: - -\list 1 - \li Basic Interface integration without backend, but with QML plugin ? (extra step ?) - \li Extending the Interface, add annotations - \li Adding a simulation backend (and simulation annotations) (explain linking) - \li Adding a custom simulation behavior - \li Adding a simulation server and using it from a qtro_backend - \li Developing a production backend which connects to a dbus interface -\endlist + \example ivicore/qface-tutorial + \brief Demonstrates step-by-step how to generate a Middleware API based on a QML application. + \ingroup qtivicore-examples + \title Qt IVI Generator Tutorial + \image examples_qface_tutorial.png + + This tutorial demonstrates how you can extend a QML application with your own autogenerated + Middleware API. We use an existing QML Instrument Cluster application and proceed through the + following steps: + + \list 1 + \li Integrate a basic interface integration without a backend + \li Extend the interface and add annotations + \li Add a simulation backend and corresponding simulation annotations; with a QML plugin + \li Add a custom simulation behavior + \li Add a simulation server and use it from a Qt Remote Objects Backend + \li Develop a production backend that connects to a DBus interface + \endlist + + Before we start the actual Middleware integration, let's take a look at the existing Instrument + Cluster QML code and all the features it supports: + \list + \li \c images -- This folder contains all images used in the QML code. + \li \c Cluster.qml -- The main QML file that assembles all other QML components together. + \li \c Dial.qml -- The base component to show values like speed or Revolutions per Minute + (RPM), using a needle. + \li \c Fuel.qml -- The component to show the actual fuel level. + \li \c Label.qml -- A small helper component which sets all common settings used to display + text. + \li \c LeftDial.qml -- Shows the current speed using the Dial component and as text, as + well as the current metric in miles per hour (mph) or kilometers per hour (km/h). + \li \c RightDial.qml -- Shows the current RPM and offers a way to show warning indicators. + \li \c Top.qml -- The top bar that shows the current date and the current temperature. + \endlist + + Next, we use our Middleware API to add support for the following features: + \list + \li Show the current speed in the left dial. + \li Show the current RPM in the right dial. + \li Change between different metrics. + \li Show the current temperature in the top bar. + \li Show different warnings on the right dial. + \li Indicate whether the instrument cluster is connected and show real data. + \endlist + + The ultimate goal is to connect all of these features together to simulate a real-time driving + experience like this: + + \image examples_qface_tutorial_final.gif + + \section1 Basic Middlware API with the help of the IVI Generator + + In this chapter we integrate a Middleware API into the existing Instrument Cluster QML code. + Instead of manually writing all of these parts ourselves, which is done in most basic + \l{https://doc.qt.io/qt-5/qtquick-codesamples.html}{QML examples}, we'll use the IVI Generator + to autogenerate the required parts. + + \target define-speed-property + \section2 Interface Definition Language + + To be able to autogenerate the Middleware API, the IVI Generator needs some input on what to + generate. This input is given in form of an Interface Definition Language (IDL), QFace, which + describes the API in a very simple way. + + Let's start to define a very simple interface which provides us with a speed property: + + \quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface + \printuntil } + + First, we need to define which module we want to describe. The module acts as a namespace, + because the IDL file can contain multiple interfaces. -Before we start the actual Middleware integration, let's take a look at the existing Instrument -Cluster QML code and all the features it supports and our Middleware API should support as well. + \quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface + \printuntil module -\list - \li \c images This folder contains all images used in the QML code - \li \c Cluster.qml The main QML file assembling all other QML components together - \li \c Dial.qml The base component for showing values like speed or rpm using a needle - \li \c Fuel.qml A component to show the actual fuel level - \li \c Label.qml A small helper component which sets all common settings used to display Text - \li \c LeftDial.qml Shows the current speed using the Dial component and as Text and the current metric (mph or km/h) - \li \c RightDial.qml Shows the current rpm and offers a way to show warning indicators. - \li \c Top.qml The top bar showing the current Date and the current temperature. -\endlist + The most important part of the module is its interface definition. -In the following chapters we will integrate the following features: -\list - \li Show the current speed in the left dial - \li Show the current rpm in the right dial - \li Support to change between different metrics - \li Show the current temperature in the top bar - \li Show different warnings on the right dial - \li Indicate whether the instrument cluster is connected and shows real data -\endlist + \quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface + \skipto interface + \printuntil } -All this will be connected together to simulate a real driving experience and should look like -this: + In this case, we define an interface named \c InstrumentCluster that consists of one property. + Each property definition must contain at least a type and a name. Most of the basic types are + built-in and can be found in the \l{QFace IDL Syntax}. -\image examples_qface_tutorial_final.gif - -\section1 Basic Middlware API with the help of IVI Generator + \section2 Autogeneration -In this chapter we integrate a Middleware API into the existing Instrument Cluster QML code. -Instead of manually writing all parts ourself which is done in most other basic QML examples -(link), we will use the IVI Generator to autogenerate the needed parts. + Now that our first version of the IDL file is ready, it's time to autogenerate API from it, + using the \l{Qt IVI Autogenerator}{IVI Generator tool}. Similar to + \l{https://doc.qt.io/qt-5/moc.html}{moc}, this autogeneration process is integrated into the + qmake Build System and is done on compile time. -\section2 Interface Definition Language + In the following \c{.pro} file we build a C++ library based on our IDL file: -To be able to autogenerate the Middleware API the IVI Generator needs some input on what to -generate. This input is given in form of a Interface Definition Language (IDL) which describes the -API in a very simple form. + \quotefromfile ivicore/qface-tutorial/chapter1-basics/frontend/frontend.pro + \printto CONFIG += install_ok -Let's start to define a very simple IDL which provides us with a speed property: + Most of the \c{.pro} file is a standard setup to define a C++ library, using "lib" \c TEMPLATE + and defining the required file name in the \c TARGET variable. The \c qtLibraryTarget function + that we use helps to append the "d" postfix on the filename correctly, for a library that + provides debugging information. In the future, we need to link this file, so we set the + \c DESTDIR to the upper directory to simplify this. -\quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface -\printuntil } + \note Windows searches for libraries in the same directory automatically. -First, we need to define which module we want to describe. The module acts as a namespace, because -the IDL file can contain multiple interfaces. + Activating the IVI Generator integration requires the \c CONFIG variable to specify the + \c ivigenerator option. This makes sure the IVI Generator is called during the build process, + using the QFace file that we specify in \c QFACE_SOURCES. For more information, see + \l{qmake integration}. -\quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface -\printuntil module + To make sure the library we build works on Windows, it's important to add the + \c QT_BUILD_EXAMPLE_IVI_INSTRUMENTCLUSTER_LIB to the \c DEFINES variable. This way, all symbols + are exported when building the library, but imported when linking against it. For more + information, see \l{https://doc.qt.io/qt-5/sharedlibrary.html}{Creating Shared Libraries}. -The most important part of the module is its interface definition. + \section2 Which Files are Autogenerated -\quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface -\skipto interface -\printuntil } + The IVI Generator works based on generation templates. These templates define what content + should be generated from a QFace file. If no \c QFACE_FORMAT is defined, this automatically + defaults to "frontend" template. For more details on these templates, see \l{Use the Generator}. -In this case, we define an interface named InstrumentCluster consisting of one property. Each -property definition must contain at least a type and a name. Most of the basic types are built-in -and can be found in the QFace IDL syntax. + In short, the "frontend" template generates: + \list + \li a C++ class derived from QIviAbstractFeature for every interface in the QFace file + \li one module class that helps to register all interfaces to QML and stores global types + and functions. + \endlist -\section2 Autogeneration + To inspect the C++ code yourself, you can view these files in the your library's build folder. -Now as our first version of the IDL file is ready, it's time to autogenerate API out of it. This is -done by the IVI Generator tool. Similar to moc this autogeneration is integrated into the qmake -Build System and is done on compile time. In the following pro file we build a C++ library based on -our IDL file: + Right now, the most important autogenerated file for us, is the resulting C++ class for our + defined interface. It looks like this: -\quotefromfile ivicore/qface-tutorial/chapter1-basics/frontend/frontend.pro -\printto CONFIG += install_ok + \quotefile ivicore/qface-tutorial/chapter1-basics/frontend/instrumentcluster.h -Most of the qmake file is a standard setup to define a C++ library, using 'lib' TEMPLATE and -defining the wanted file name in the TARGET variable. The used qtLibraryTarget function helps to -correctly append the 'd' postfix on the filename for a library providing debugging information. -Because we need to link this file in the future we set the DESTDIR to the upper directory to make -this a bit easier (Windows is searching for libraries in the same directory automatically). + As you can see, the autogenerated C++ class implements a \c speed property, that we previously + defined in the QFace file. By using the \c Q_OBJECT and \c Q_PROPERTY macros, the class is now + ready for use directly in your QML code. -To activate the actual IVI Generator integration, the CONFIG needs to have the ivigenerator option. -By enabling this config the generator will be called during the build process using the qface file -provided in QFACE_SOURCES. See (link) for more information about the qmake integration. + \section2 Integrate the Frontend Library with the QML Code -Because we are building a library which is also supposed to work on Windows it's important to add -the QT_BUILD_EXAMPLE_IVI_INSTRUMENTCLUSTER_LIB to the DEFINES variable. This makes sure that all -symbols are exported when building the library, but imported when linking against it. See Creating -Shared Libraries for more information on this topic. + For this integration, we use the autogenerated frontend library from the QML code. For the sake + of simplicity, we follow the standard Qt example pattern and use a small C++ main function + which registers our autogenerated types to QML and loads the Instrument Cluster QML code into + the QQmlApplicationEngine: -\section2 What will be autogenerated ? + \quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster/main.cpp + \skipto #include "instrumentclustermodule.h" + \printuntil } -The ivigenerator is based on generation templates. These templates define what content is generated -from the IDL file. When no QFACE_FORMAT is defined this will automatically default to use the -"frontend" template. What a template generates is explained here (link to the frontend template). + All we need now is the actual integration of the InstrumentCluster QML element and connecting + the \c speed property to the \c leftDial. This is done by instantiating the element first with + the id instrumentCluster. -In short the 'frontend' template will generate a C++ class derived from QIviAbstractFeature for -every interface in the IDL file and one module class which helps to register all interfaces to QML -and which stores global types and functions for. More on that in a later chapter. + \quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster/Cluster.qml + \skipto import + \printuntil InstrumentCluster + \printuntil } + \codeline -To inspect the C++ code yourself, you can either checkout the files in the build folder of your -library. + Lastly, we can create a Binding for the \c LeftDial Item's \c value property to our + InstrumentCluster API's \c speed property. -The most important file for us right now which is autogenerated is the resulting C++ class for our -defined interface. It looks like this: + \printuntil } -\quotefile ivicore/qface-tutorial/chapter1-basics/frontend/instrumentcluster.h + \section1 Chapter 2 -As you can see does the autogenerated C++ class implement a "speed" property as defined in the IDL -file. By using the Q_OBJECT macro and Q_PROPERTY the class is already now ready to be used directly -in your QML code. + In this chapter we extend our Middleware API with more properties via enums and by defining our + own structure. -\section2 Integrating the frontend library into the QML code + \section2 Define Speed as a Read-only Property -The last part of this chapter is to use the autogenerated frontend library from the QML code. To -keep this integration simple we follow the standard Qt example way and use a small C++ main -function which registers our autogenerated types to QML and loads the Instrument Cluster QML code -into the QQmlApplicationEngine: + \l{define-speed-property}{Previously}, we defined the speed property in our QFace file in the + following way: -\quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster/main.cpp -\skipto #include "instrumentclustermodule.h" -\printuntil } + \quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface + \printuntil } -What's still missing is the actual integration of the InstrumentCluster QML element and connecting -the speed property to the leftDial. This can be done by instantiating the element first with the id -instrumentCluster. + This property is defined as readable and writable, as we didn't use any extra specifiers. + However, it's not necessary for our Instrument Cluster example to have a writable \c speed + property because it's not used to accelerate the car, but just to visualize the current state. -\quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster/Cluster.qml -\skipto import -\printuntil InstrumentCluster -\printuntil } -\codeline + To define the property as read-only, use the \c readonly keyword. -Now we can create a Binding for the value property of the LeftDial Item to the speed property of -our InstrumentCluster API. + \quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface + \printuntil readonly + \skipto } + \printuntil } -\printuntil } + When we build our app again, the build system recognizes this change and runs the IVI + Generator to generate an updated version of the C++ code. After the IVI Generator is done, + open the \c instrumentcluster.h from the build folder and notice that the generated + \c speed property changed -- it no longer has a setter anymore and is now read-only. -\section1 Chapter 2 + \quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/frontend/instrumentcluster.h + \skipto class Q_EXAMPLE + \printuntil Q_PROPERTY + \dots + \skipto }; + \printuntil }; -In the second chapter we will extend our Middleware API with more properties, using enums and -defining our own struct. + \section2 Extend the Interface -\section2 Defining speed as readonly property + To reach our goal to provide a full simulation for the Instrument Cluster, we need to add more + properties to our QFace file: \c rpm, \c fuel and \c temperature: -In the previous chapter we defined the speed property in our QFace IDL file in the following way: + \quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface + \printuntil readonly real temperature + \skipto } + \printuntil } -\quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster.qface -\printuntil } + You might have noticed that we use a different type for the \c fuel and \c temperature + properties. We use \c real here, as we would like to show the temperature as a floating point + number, and the current fuel level as a value between 0 and 1. -Because we didn't use any extra specifier this property is defined as being readable and writable. -For our Instrument Cluster example having a writable speed property doesn't make much sense, as it -is not supposed to be used to accelerate the car, but just to visualize the current state. + \section2 Define a New Enum Type -We can define the property to be read only by using the "readonly" keyword. + One useful feature is to be able to switch between the metric and the imperial system, so we + need to define a property for the system we currently use. Using a boolean property would work, + but doesn't offer a nice API, so we define a new enum type in the QFace file and use it as the + type for our new \c system property: -\quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface -\printuntil readonly -\skipto } -\printuntil } + \quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface + \printuntil readonly SystemType + \skipto } + \printuntil enum + \printuntil } -Starting to build our app again, the build system will recognize the changed qface file and start -the IVI Generator to generate an updated version of the C++ code. After the IVI Generator is done -you can open the instrumentcluster.h from within the build folder and you will see that the -generated C++ property changed to NOT have any setter anymore and by that is now read only. + In the autogenerated code, this results in an enum which is part of the module class, making it + possible for the same enum to be used by multiple classes which are part of the same module: -\quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/frontend/instrumentcluster.h -\skipto class Q_EXAMPLE -\printuntil Q_PROPERTY -\dots -\skipto }; -\printuntil }; + \quotefile ivicore/qface-tutorial/chapter2-enums-structs/frontend/instrumentclustermodule.h -\section2 Extending the interface + \section2 Add a New Structure -To reach our goal to provide a full simulation for the Instrument Cluster, we need to add more -properties to our IDL file. Adding additional properties for 'rpm', 'fuel' and 'temperature' should -be a easy job and result in this: + To display warnings on the Instrument Cluster's right dial, we'd like to use a structure that + stores color, icon, and text for the warning; instead of using 3 independent properties. + Similar to defining an interface, we can use the \c struct keyword in our QFace file: -\quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface -\printuntil readonly real temperature -\skipto } -\printuntil } + \quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface + \skipto struct + \printuntil } -You might have noticed that we used a different type for the 'fuel' and 'temperature' properties. -We use the type real here as we would like to show the temperature as a floating point number and -the current fuel level between 0 and 1. + Using this new structure as a type for a property, works in the same way as when using an enum. + The QFace file should now look like this: -\section2 Defining a new enum type + \quotefile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface -As we like to be able to switch between the metric and the imperial system, we also need to define -a property for the system we currently use. Using a boolean property would work, but doesn't offer -a nice API, so we define a new type in the IDL and use it as the type for the new 'system' -property: + \section2 Integrate the New Properties -\quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface -\printuntil readonly SystemType -\skipto } -\printuntil enum -\printuntil } + Like in the previous chapter, actually integrating the newly introduced properties involves + creating Bindings. The \c rpm property can be directly connected to the \c rightDial Item's + \c value property; the same is done for the top Item's \c temperature property. To control + which unit is displayed in the left Dial, the \c leftDial Item provides \c metricSystem, a bool + property. As we used an enum in our QFace file, we need to convert the value first by testing + the \c sytemType property for the "Metric" value. -In the autogenerated code, this will create a enum which is part of the module class. This makes it -possible that the same enum can be used by multiple classes which are part of the same module: + \quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster/Cluster.qml + \skipto LeftDial + \printuntil } + \codeline -\quotefile ivicore/qface-tutorial/chapter2-enums-structs/frontend/instrumentclustermodule.h + These enums are part of the module class, which is also exported to QML as + InstrumentClusterModule. To trigger a warning in the \c rightDial Item, we use 3 bindings to + connect to the 3 member variables in the structure: -\section2 Add a new struct + \printuntil } -To display warnings on the right dial of the Instrument Cluster we would like to use a struct which -stores color, icon and text of the warning instead of using 3 independent properties for this. -Similar to define a interface, we can use the struct keyword in the IDL file: + \section1 Chapter 3 -\quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface -\skipto struct -\printuntil } + In the previous two chapters, we wrote a Middleware API using a QFace file and used the IVI + Generator to autogenerate a C++ API in the form of a library. Now, in this chapter, we extend + this further by introducing a simulation backend and using annotations to define default values + for our simulation. -Using this new struct for as a type for a property works in the same way as when using a enum, the -final IDL file should now look like this: + \section2 Separation between the Frontend and Backend -\quotefile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster.qface + Both QtIvi and the IVI Generator enable you to write code that separates the frontend from the + backend -- to split an API from its actual implementation. Already, Qt uses this concept in a + lot of areas, most prominently in the underlying window system technology on various Qt + platforms like XCB on Linux and Cocoa on macOS. -\section2 Integrating the new properties + The same separation is done for our Middleware API, where the frontend provides the API as + a library; the backend provides an implementation of this API. This implementation is based on + QtIvi's \l{Dynamic Backend System} which enables us to switch between such backends at runtime. + + \image feature-backend.png + + \section2 Add a Simulation Backend + + For our Instrument Cluster, we'd like to add such a backend to provide actual values. For now, + we'd like to just have some simulation behavior as we can't connect it easily to a real car. + This is why such backends are called "simulation backend". To add this type of backend, once + again, we use the IVI Generator to do the heavy lifting for us and generate one. This work + is done in a similar way to when we generated a library with the "frontend" template. But now, + we are using the "backend_simulator" template: + + \quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/backend_simulator/backend_simulator.pro + \printto DESTDIR + \skipto QT + \printuntil CONFIG + \skipto QFACE_FORMAT + \printto CONFIG += install_ok + + Just like for the frontend library, the project file builds a \c lib and defines the library + name using \c qtLibraryTarget to also support the Windows debug postfix. One important aspect + here is that the library name ends with "_simulation", which is a way to tell QtIvi that this + is a simulation backend. When a "production" backend is available, it is preferred over the + "simulation" one. For more information, see \l{Dynamic Backend System}. + + Enabling the IVI Generator is also done in the same way as we did earlier: by using the same + \c QFACE_SOURCE variable, but defining \c QFACE_FORMAT to "backend_simulator", to use the + correct generation template. In addition, we need to add 'plugin' to the \c CONFIG variable, + to make this library a Qt plugin which can be easily loaded at runtime. + + \section2 Link Settings and Locating Plugins + + Trying to build the project file just as it is, right now, would result in compilation and + linking errors. This is because: to do the frontend and backend separation, we need to have the + backend implement a defined interface class, that is known to the frontend. This interface is + aptly called "backend interface" and is automatically generated as part of the frontend + library. Because this class provides signals and slots and uses QObject for its base class, you + need to link to the frontend library when you inherit from it. As this is needed for the + backend plugin, we need to add the following lines in addition: + + \quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/backend_simulator/backend_simulator.pro + \skipuntil CONFIG + \printuntil INCLUDEPATH + + Now the project should build fine and create the plugin in your build folder; or the plugin + folder if you don't use a shadow build. When you start the Instrument Cluster again, you should + see the following message: + + \badcode + There is no production backend implementing "Example.IVI.InstrumentCluster.InstrumentCluster" . + There is no simulation backend implementing "Example.IVI.InstrumentCluster.InstrumentCluster" . + No suitable ServiceObject found. + \endcode + + This message indicates that QtIvi is still unable to find the simulation plugin we just created. + Here, you need to know a little bit more about Qt's Plugin System, especially how it it finds + plugins. + + Qt searches for it's plugins in multiple directories, the first one is the plugin folder, + \c plugins, which comes with your Qt installation. Within the plugins folder, every plugin type + has it's own sub-folder, such as \c platforms, for the platform plugins used to talk to the + underlying platform API and the windowing system. + + Similarly, QtIvi searches for its backend plugins in the \c qtivi folder. To make sure our + simulation backend ends up in such a folder, we add the following \c DESTDIR definition. + + \quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/backend_simulator/backend_simulator.pro + \skipto DESTDIR + \printuntil DESTDIR + + You might wonder how creating a \c qtivi folder in the upper directory solves this problem of + finding the plugin as it's not part of the system plugins folder. But Qt supports searching in + multiple folders for such plugins and one of those folders is the path to where the executable + itself is located. + + Alternatively, we could add an additional plugin path using the QCoreApplication::addLibraryPath() + function or using the \c QT_PLUGIN_PATH environment variable. For more information, see + \l{https://doc.qt.io/qt-5/plugins-howto.html}{How to create Qt Plugins}. + + \section2 Export the QML Types in a QML Plugin + + In the first chapter, we extended our \c main.cpp to register all types of our autogenerated + Middleware APIs. Although this works fine, in bigger projects it's common to use a QML Plugin + instead and be able to use qmlscene for development. Although the code for doing this is + not complex, the IVI Generator supports this as well and makes it even easier. + + From the first chapter, we know that the module name is used for the QML import URI. This is + important for a QML plugin as the QmlEngine expects the plugin in a specific folder to + follow the module name, where every section of the module name is a sub-folder. Our project + file to generate a QML plugin looks like this: + + \quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/imports/imports.pro + \printto target.path + + All lines until \c QFACE_SOURCES should be familiar. We use \c CONFIG to build a plugin, then + define the settings for the linker to link against our frontend library. Then, we use + \c QFACE_FORMAT to define "qmlplugin" as the generation template. Instead of adding + \c ivigenerator to \c CONFIG, this time we use + \l{https://doc.qt.io/qt-5/qmake-test-function-reference.html#load-feature} + {qmake's load() function} to explicitly load the feature. This enables us to use the \c URI + variable which is part of the "qmlplugin" generation template. This URI can be used to define + a \c DESTDIR by replacing all dots with slashes. + + In addition to the folder structure, the QmlEngine also needs a \c qmldir file which indicates + what files are part of the plugin, and under which URI. For more information, see + \l{https://doc.qt.io/qt-5/qtqml-modules-qmldir.html}{Module Definition qmldir Files}. Both this + \c qmldir file and a \c plugins.qmltypes file which provides information about code-completion, + are autogenerated by the IVI Generator; but they need to be placed next to the library. To do + so, we add the files to a scope similar to an \c INSTALL target, but add it to the \c COPIES + variable instead. This makes sure that the files are copied when the plugin is built. + + Now the plugin is ready for use, but our Instrument Cluster application doesn't know where to + search for it and is still using the old hardcoded registration. So, we can now remove the + linking step in the \c instrument-cluster.pro file and change our main file accordingly: + + \quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster/main.cpp + \skipto #include + \printuntil } + + What has changed is that we've now added an additional import path with the \c addImportPath + function, which points to the "imports" folder next to the binary's location. + + \section1 Chapter 4 + + So far, we've created a Middleware API and integrated it into our Instrument Cluster QML code, + extended it with a QML plugin, and generated a simulation backend. In the background, quite a + lot has happened to support us; but on the UI side not much has changed till now. This chapter + is about bringing our simulation backend to life by defining sane default values and starting + to simulate a real car ride. + + \section2 Define Default Values + + We start by defining default values for our properties, using annotations in our QFace file. + An annotation is a special kind of comment which adds extra data to an interface, method, + property, and so on. For this use case we use the \c config_simulator annotation. For more + information, see \{annotations-yaml}{Annotations}. + + Currently, in our Instrument Cluster, the temperatur defaults to 0. Let's change this to a + temperature in spring, 15 degrees Celsius, with the following YAML fragment: + + \quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/instrument-cluster.qface + \printuntil } + + Compile the plugin again for this temperature change to be reflected in our Instrument Cluster. + Let's see how this actually works: when starting the IVI Generator, the config_simulator + annotation was transformed into a JSON file that's now part of the "simulation backend" build + folder. This JSON file looks like this: + + \quotefile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/instrumentcluster.json + + But how is this JSON file related to the actual simulation backend code? The autogenerated + simulation backend code uses QIviSimulationEngine, that reads the JSON file and provides its + data to a QML simulation file. A default QML file is also autogenerated and loaded from the + QIviSimulationEngine. This default QML file provides the behavior of what should happen in the + the simulation backend. + + Later, in the next section, we take a look at the QML file and how we can change it. But first, + let's see how we can change the default values in a more dynamic way. + + The QIviSimulationEngine allows us to override which JSON file should be loaded into the + engine, when we set the \c QTIVI_SIMULATION_DATA_OVERRIDE environment variable. Since there can + be multiple engines run by different backends, we need to define which engine we're referring + to. In the autogenerated code, the module name is always used as the engine specifier. For this + chapter, we already prepared a second JSON file which is part of our source directory. Setting + the environment variable as follows, changes the \c systemType to mph instead of km/h: + + \badcode + QTIVI_SIMULATION_DATA_OVERRIDE=instrumentcluster=<path-to-file>/miles.json + \endcode + + \section2 Define a QML Behavior + + Before we define our custom behavior, let's see what's been autogenerated for us. There are two + QML files: The first is \c instrumentcluster_simulation.qml and rather simple. It defines an + entry point that istantiates the second file, an \c InstrumentClusterSimulation.qml file. This + split is done as there can be multiple interfaces defined as part of the same module. + + \note A QML Engine can only have one entry point. While QIviSimulationEngine has this same + limitation, if you have a module with multiple interfaces, you want to have multiple simulation + files -- one per interface. This is why the first QML file merely instantiates the QML files for + all interfaces that it supports. In the case of our example, it's only one interface. + + The InstrumentClusterSimulation.qml file is very interesting: + + \quotefile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/InstrumentClusterSimulation.qml + + First, there's a \c settings property, that's initialized with the return value from the + \l{IviSimulator::findData}{IviSimulator.findData} method, which takes the + \l{IviSimulator::simulationData}{IviSimulator.simulationData} and a string as input. The + \c simulationData is the JSON file represented as a JavaScript object. + + The \c findData method helps us to extract only the data that is of interest for this + interface, \c InstrumentCluster. The properties that follow help the interface to know whether + the default values are set. The \c LoggingCategory is used to identify the log output from this + simulation file. + + Afterwards, the actual behavior is defined by instantiating an \c InstrumentClusterBackend Item + and extending it with more functions. The \c InstrumentClusterBackend is the interface towards + our \c InstrumentCluster QML frontend class. But, apart from the frontend, these properties are + also writable to make it possible to change them to provide a useful simulation. + + Each time a frontend instance connects to a backend, the \c initialize() function is called. + The same applies to the QML simulation: as the \c initialize() C++ function forwards this to + the QML instance. This also applies to all other functions, like setter and getters, for + properties or methods. For more details, see \l{QIviSimulationEngine. -Like in the previous chapter, doing the actual integration of the new introduced properties should -be pretty straightforward by creating Bindings. The rpm property can be directly connected to the -value property of the rightDial Item and in the same way the temperature property of the top Item -can be connected. To control what unit is displayed in the left Dial, the leftDial Item provides a -bool property named metricSystem. As we used a enum in our IDL file we need to convert the value -first by testing the 'sytemType' property for the 'Metric' value. + Inside the QML \c initialize() function, we call \c{IviSimulator.initializeDefault()}, to read + the default values from the \c simulationData object and initialize all properties. This is + done only \b once, as we don't want the properties be reset to default when the next frontend + instance connects to the backend. Lastly, the base implementation is called to make sure that + the \c initializationDone signal is sent to the frontend. -\quotefromfile ivicore/qface-tutorial/chapter2-enums-structs/instrument-cluster/Cluster.qml -\skipto LeftDial -\printuntil } -\codeline + Similarly, a setter function is defined for each property; they use the + \c{IviSimulator.checkSettings()} to read specific constraint settings for the property from + the \c simulationData and check whether these constraints are valid for the new value. If + these constraints aren't valid, then \{IviSimulator.constraint()} is used to provide a + meaningful error message to the user. -Like seen above the enums are part of the module class, which is also exported to QML as -InstrumentClusterModule. To trigger a warning in the rightDial Item, we can also use 3 bindings to -connect to the 3 member variables of the struct: - -\printuntil } - -\section1 Chapter 3 - -In the previous two chapters we wrote a Middleware API in form of a IDL file and used the IVI -Generator to autogenerate a C++ API in form of a library. In this chapter we will extend this by -introducing a simulation backend and use annotations to define default values for our simulation. - -\section2 Frontend / Backend Separation - -QtIvi and the IVI Generator enables you to write code which use a Frontend / Backend Separation. -Now what does that actually mean ? The separation is done to split an API from the actual -implementation of this API. Qt uses this already in a lot of areas, most prominently the underling -window system technology on the various supported Qt platforms e.g. xcb on linux and cocoa on mac. -The same separation is done for our Middleware API, where the frontend provides the API in form of -a library and the backend provides a implementation of this API. This is based on the Dynamic -backend System of QtIvi which enables to switch between such backends at runtime. - -\image feature-backend.png - -\section2 Adding a simulation Backend - -For our Instrument Cluster we would like to add such a backend to provide actual values. For now we -would like to just have some simulation behavior as we can't connect it easily to a real car, -that's why such backends are called "simulation backend". To add such a backend we can use the help -of the IVI Generator again and let it do the heavy lifting for us and generate one. This is done in -a similar way as we already generated the library using the 'frontend' template, but we are now -using the 'backend_simulator' template: - -\quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/backend_simulator/backend_simulator.pro -\printto DESTDIR -\skipto QT -\printuntil CONFIG -\skipto QFACE_FORMAT -\printto CONFIG += install_ok - -Like for the frontend library, the project file builds a lib and defines the library name using -qtLibraryTarget to also support windows debug postfix. One important aspect here is that the -library name ends with '_simulation' which is a way to tell QtIvi that this is a simulation backend -and once a 'production' backend is available prefer to use this over the 'simulation'. See <link> -for more information on this. - -Enable the IVI Generator is also done in the same way by using the same QFACE_SOURCE, but defining -QFACE_FORMAT to 'backend_simulator' to use the correct generation template. In addition we need to -add 'plugin' into the CONFIG variable to make this library a Qt plugin which can easily be loaded -at runtime. - -\section2 Link settings and how plugins are found. - -Trying to build the project file just as it is now, would result in compilation and linking errors. -The reason for that is, in order to do the Frontend / Backend Separation there needs to be a -defined interface class which needs to be implemented by the backend and is known to the frontend. -This interface is called 'backend interface' and is automatically generated as part of the frontend -library. Because this class provides signals and slots and uses QObject as the base class you need -to link to the frontend library when you inherit from it. As this is needed for the backend plugin, -we need to add the following lines in addition: - -\quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/backend_simulator/backend_simulator.pro -\skipuntil CONFIG -\printuntil INCLUDEPATH - -Now the project should build fine and create the plugin in your build folder (or the plugin folder -in case you don't use a shadow build). If you would start the Instrument Cluster again you should -still the following message: - -\badcode -There is no production backend implementing "Example.IVI.InstrumentCluster.InstrumentCluster" . -There is no simulation backend implementing "Example.IVI.InstrumentCluster.InstrumentCluster" . -No suitable ServiceObject found. -\endcode - -This indicates that QtIvi is still not able to find our just created simulation plugin. For this to -understand you need to know a little bit more about Qts Plugin System and how it it finds plugins. -Qt searches for it's plugins in multiple directories, the first one is the plugin folder which -comes with your Qt installation and is called 'plugins'. Within the plugins folder every plugin -type has it's own sub-folder e.g. 'platforms' for the platform plugins used to talk to the -underlying platform API and the windowing system. QtIvi searches for its backend plugins in the -'qtivi' folder. To make sure our simulation backend ends up in such a folder we add the following -DESTDIR definition. - -\quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/backend_simulator/backend_simulator.pro -\skipto DESTDIR -\printuntil DESTDIR - -You might wondering how creating a qtivi folder in the upper directory should solve this problem of -finding the plugin as it's not part of the system plugins folder. As said Qt supports searching in -multiple folder for such plugins and one of those folders is the path of the executable itself. -Another way would be to add an additional plugin path using the QCoreApplication::addLibraryPath() -function or using the QT_PLUGIN_PATH environment variable. See "How to create Qt Plugins" for more -information about this. - -\section2 Exporting the QML types in a QML Plugin - -In the first chapter we extended our main.cpp to register all types of our autogenerated Middleware -API. Although this works fine, in bigger projects it's common to use a QML Plugin instead and be -able to use qmlscene for the development. Although the code for doing this is relative simple, the -IVI Generator supports to do this as well and makes it even easier. We already learned in the first -chapter that for the QML import uri the module name is used. For a QML plugin this is important -as the QmlEngine expects the plugin on a specific folder following the module name, where every section -of the module name is a sub-folder. Our project file to generate a QML plugin looks like this: - -\quotefromfile ivicore/qface-tutorial/chapter3-simulation-backend/imports/imports.pro -\printto target.path - -All lines until QFACE_SOURCES should be know. We use CONFIG to build a plugin. Define settings for -the linker to link against our frontend library and use QFACE_FORMAT to define 'qmlplugin' as the -used generation template. Instead of adding 'ivigenerator' to CONFIG we use the load() function to -explicitly load the feature, this enables us to use the URI variable which is part of the 'qmlplugin' -generation template. This URI can be used to define a DESTDIR by replacing all dots with slashes. - -In addition to the folder structure the QmlEngine also needs a qmldir file which indicates what -files are part of the plugin under which uri. See <link> for more information about this. This -qmldir file as well as a plugins.qmltypes files which provides information about code-completion is -autogenerated by the IVI Generator, but needs to be put next to the library. To do this we add -the files to a scope similar to a INSTALL target, but instead add it to the COPIES variable, which -makes sure the files are copied when building the plugin. - -Now the plugin is ready to be used, but our Instrument Cluster application doesn't know where to -search for it and is still using the old hardcoded registration. We can now remove the linking step -in the instrument-cluster.pro file and change our main file to the following: - -\quotefromfile ivicore/qface-tutorial/chapter1-basics/instrument-cluster/main.cpp -\skipto #include -\printuntil } - -The important part which was changed is that we added an additional import path by using the -addImportPath function, which points to the 'imports' folder next to the binary location. - -\section1 Chapter 4 - -So far we created a Middleware API and integrated it into our Instrument Cluster QML code, extended -it by using a QML plugin and generated a simulation backend. Quite a lot happened in the background -to support us, but on the UI side not much has changed till now. This chapter is about bringing our -simulation backend to life by defining sane default values and starting to simulate a real car -ride. - -\section2 Defining default values - -We will start by defining default values for our properties. This is done using annotations in our -qface IDL file. An annotation is a special kind of comment which adds extra data to a interface, -method, property etc. For this use case we will use the 'config_simulator' annotation. More -information what other kind of annotations are available can be found here. At the moment the -temperature of our Instrument Cluster defaults to 0. Lets change this to a spring temperature and -change it to 15 degree celsius. This is done by adding the following YAML fragment: - -\quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/instrument-cluster.qface -\printuntil } - -After compiling the plugin again the temperature change should now be reflected in our Instrument -Cluster. Let's take a look how this actually works. What happened when starting the IVIGenerator is -that the config_simulator annotation was transformed into a JSON file which is now part of the -simulation backend build folder. This JSON file looks like this: - -\quotefile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/instrumentcluster.json - -So how is this JSON file related to the actual simulation backend code ? The autogenerated -simulation backend code is using the QIviSimulationEngine. The QIviSimulationEngine reads the JSON -file and provides it's data to a QML simulation file. A default QML file is also autogenerated and -loaded from the QIviSimulationEngine. This QML file provides the behavior of what should happen in -the simulation backend. We will take a look at the QML file and how we can change in the next -section, let's first see how we can change the default values in a more dynamic fashion. - -The QIviSimulationEngine allows us to override which JSON file should be loaded into the engine by -setting the QTIVI_SIMULATION_DATA_OVERRIDE environment variable. As there can be multiple engines -running by different backends we need to define which engine we mean. In the autogenerated code -always the module name is used as the engine specifier. For this chapter we already prepared a -second JSON file which is part of our source directory. Setting the environment variable like -follows, will change the systemType to Mph instead of km/h: - -\badcode -QTIVI_SIMULATION_DATA_OVERRIDE=instrumentcluster=<path-to-file>/miles.json -\endcode - -\section2 How to define a QML behavior ? - -Before we define our own behavior, let's first take a look on what has been autogenerated already -for us: There are two QML files. The first file instrumentcluster_simulation.qml is the entry point -and just instantiates a InstrumentClusterSimulation.qml file. This is done as there can be multiple -interfaces defined as part of the same module. The InstrumentClusterSimulation.qml file is way more -interesting: - -\quotefile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/InstrumentClusterSimulation.qml - -First there is a 'settings' property, this is initialised with the return value of the -IviSimulator.findData method, which takes the IviSimulator.simulationData and a string as input. -The simulationData is the JSON file represented as a JavaScript object. The findData method helps -us to extract just the data which is of interest for this interface. In this case for the -"InstrumentCluster". The following properties helps the behavior to know whether the default -values are set and a LoggingCategory to identify the log output of this simulation file. - -Afterwards the actual behavior is defined by instantiating a InstrumentClusterBackend Item and -extending it with more functions. The InstrumentClusterBackend is our interface towards our -InstrumentCluster QML frontend class, but other than the frontend the properties are also writable -to make it possible to change them to provide a useful simulation. - -Every time a frontend instance connects to a backend the initialize() function is called. The same -happens for the QML simulation, as the initialize() C++ function forwards this to the QML instance. -The same happens for all other functions like setter and getters for properties or the methods. See -QIviSimulationEngine for more information. - -Inside the QML initialize() function we call the IviSimulator.initializeDefault() function, which -will read the default values from the simulationData object and initialises all properties. This is -only done once, as we don't want the properties be reset to default when the next frontend instance -connects to the backend. Last the base implementation is called to make sure the initializationDone -signal is sent to the frontend. - -Similar a setter function is defined for every property. These setter functions use the -IviSimulator.checkSettings() function which reads specific constraint settings for the property -from the simulationData and checks whether these constraints are valid for the new value. In case -the constraints are not valid, the IviSimulator.constraint() function is used to provide a -meaningful error message to the user. - -\section2 Defining our own QML simulation - -As explained above does the InstrumentClusterBackend Item provide all the properties of our IDL -file. This can be used to simulate a behavior by changing the properties to the values we want. -This simplest form for this would be a value assignment, but this would be rather static and is not -what we would like to achieve. Instead we use QML Animation objects to change the values over time: - -\quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/simulation.qml -\skipto NumberAnimation -\printuntil } - -This will change the speed property to 80 over 4000 seconds and simulates a accelerating car. -Extending this to the other properties and by using sequential and parallel animations a full -simulation could look like this: - -\quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/simulation.qml -\skipto property var animation -\printuntil property: "fuel" -\printuntil property: "fuel" -\printuntil } -\printuntil } + \section2 Define Our Own QML Simulation -To also provide a nice simulation for the 'rpm' property, we use a binding which does some calculations -based on the current speed. The complete simulation file looks like this: + As mentioned above, does the InstrumentClusterBackend Item provide all the properties of our + QFace file. This can be used to simulate a behavior by changing the properties to the values + we want. The simplest form for this would be value assignment, but this would be rather static + not exactly what we'd like to achieve. Instead, we use QML Animation objects to change the + values over time: -\quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/simulation.qml -\skipto import + \quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/simulation.qml + \skipto NumberAnimation + \printuntil } -The next step is to tell the IVI Generator and the QIviSimulationEngine about the new simulation file. -Similar to QML files, the best aproach here is to put the simulation file into a resource file. In -our example we added a new file called simulation.qrc which contains our simulation.qml using the -/ prefix. + The code snippet above changes the speed property to 80 over 4000 seconds and simulates an + accelerating car. Extending this to the other properties, and combining both sequential and + parallel animations, we can create a full simulation: -This location now needs to be added in form of an annotation to our QFace file: + \quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/simulation.qml + \skipto property var animation + \printuntil property: "fuel" + \printuntil property: "fuel" + \printuntil } + \printuntil } -\quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/instrument-cluster.qface -\printuntil module -\dots + Then, to provide a nice simulation for the \c rpm property, we use a binding which does some + calculations based on the current speed. The complete simulation file looks like this: -Rebuilding the simulation backend now will embed the simulation file into the plugin and give the -file to the QIviSimulationEngine, which will start with the simulation once loaded. + \quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/backend_simulator/simulation.qml + \skipto import -\section1 Chapter 5 + The next step is to tell the IVI Generator and the QIviSimulationEngine about our new + simulation file. Similar to QML files, the best aproach here is to put the simulation file into + a resource file. In our example, we add a new file called \c simulation.qrc which contains our + \c simulation.qml using the \c{/} prefix. -In this chapter we will extend our Instrument Cluster to use a IPC mechanism and use two processes. -At the moment the simulation is loaded as a plugin and because of that part of the same service. -Although this is good enough for a small example application, it is not how it works in modern -multi-process architectures, where multiple processes need to be able to access the same value and -react to changes. We could now write a second Application which is using the same Middleware API, -but instead we can achieve the same thing by just starting the Instrument Cluster twice and check -whether the animations are in sync, which they are not. + In our QFace file, this location now needs to be added in the form of an annotation: -<< gif >> ???? + \quotefromfile ivicore/qface-tutorial/chapter4-simulation-behavior/instrument-cluster.qface + \printuntil module + \dots -\section2 Adding a QtRemoteObjects integration + Now, rebuilding the simulation backend embeds the simulation file into the plugin and hands the + file over to the QIviSimulationEngine, which starts the simulation when loaded. -The IPC for this example is QtRemoteObjects as the IVI Generator already provides support for this -out of the box. To use QtRemoteObjects we generate a second plugin, this time it is a "production" -backend and because of that automatically preferred over the simulation backend we introduced -before. This is done by the following project file: + \section1 Chapter 5 -\quotefromfile ivicore/qface-tutorial/chapter5-ipc/backend_qtro/backend_qtro.pro -\printto CONFIG += install_ok + In this chapter we extend our Instrument Cluster to use an Inter-Process Communication (IPC) + mechanism and use two processes. At the moment, the simulation is loaded as a plugin that + causes it to be part of the same service. Although this is good enough for a small example + application, it's not how it's done in modern multi-process architectures, where multiple + processes need to be able to access the same value and react to changes. We could write a + second Application that uses the same Middleware API. However, we can achieve the same thing + just by starting the Instrument Cluster twice and checking whether the animations are in sync. + Currently, they're not. -This pro file looks almost identical to the pro file we used for our simulation backend. For now we -will just highlight what has been changed. The name for the plugin doesn't end with "_simulation" -to indicate that this is a "production" backend and the QFACE_FORMAT has been changed to -"backend_qtro" to generate a backend which uses QtRemoteObjects Replicas to connect to a -QtRemoteObjects Source providing the values. + << gif >> ???? -In addition to a QtRemoteObject based backend, we also need a QtRemoteObject based server. This -part can also be autogenerated using the IVI Generator in a similar fashion: + \section2 Add a QtRemoteObjects Integration -\quotefromfile ivicore/qface-tutorial/chapter5-ipc/simulation_server/simulation_server.pro -\printto target.path + The IPC for this example is QtRemoteObjects, because the IVI Generator already supports it + out of the box. To use QtRemoteObjects we generate a second plugin, a "production" backend, + this time. Production backends are automatically preferred over the simulation backend we + introduced before. -Because we would like to generate a server binary, the TEMPLATE needs to be set to app instead of -lib and similar to the plugin the server also needs to link against our library as it needs to have -access to the defined enums, structs and other types. The template we use to generate a simulation -server is called "server_qtro_simulator". + This is done with the following project, \c{.pro}, file: -\section2 Reusing the existing simulation behavior + \quotefromfile ivicore/qface-tutorial/chapter5-ipc/backend_qtro/backend_qtro.pro + \printto CONFIG += install_ok -If you start the server now and then the Instrument Cluster you don't see the simulation anymore, -which we added in the previous chapter. The reason for this, is that the simulation code is part of -our simulation backend, but this backend is not used anymore as we added the QtRemoteObjects based -"production" backend. + This \c{.pro} file is almost identical to the one we used earlier for our simulation backend. + For now we highlight what's changed. -Because we used the "server_qtro_simulator" generation template, this can easily be fixed, as the -generated server code is also using the QIviSimulationEngine and supports to use the same -simulation file than our simulation backend. We just need to extend the project file in the same -way as we did before and are also able to use the same resource file for this. + The name of the plugin doesn't end with "_simulation" to indicate that this is a "production" + backend. The \c QFACE_FORMAT is now changed to "backend_qtro" to generate a backend that uses + QtRemoteObjects Replicas to connect to a QtRemoteObjects Source that provides the values. In + addition to a QtRemoteObject-based backend, we also need a QtRemoteObject-based server. This + part can also be autogenerated using the IVI Generator in a similar fashion: -\quotefromfile ivicore/qface-tutorial/chapter5-ipc/simulation_server/simulation_server.pro -\skipto RESOURCES -\printuntil RESOURCES + \quotefromfile ivicore/qface-tutorial/chapter5-ipc/simulation_server/simulation_server.pro + \printto target.path -In the same way we can also use the other simulation data JSON file we defined in the previous -chapter by using the same environment variable. We just need to pass it to the server instead of -our Instrument Cluster application. + Because we'd like to generate a server binary, the \c TEMPLATE needs to be set to "app" instead + of "lib". Similar to the plugin, the server also needs to link against our library to give it + access to the defined enums, structures, and other types. The template we use to generate a + simulation server is called "server_qtro_simulator". -Doing the final test, starting two Instrument Cluster instances should now show the animations in -sync: + \section2 Reuse the Existing Simulation Behavior -<< gif >> ??? + Now, if you start the server and then the Instrument Cluster, you don't see the simulation + from our previous chapter anymore. The reason for this, is that the simulation code is part of + our simulation backend, but this backend is no longer used as we added the + QtRemoteObjects-based "production" backend. -\section1 Chapter 6 + Because we used the "server_qtro_simulator" generation template, this can easily be fixed, as + the generated server code is also using the QIviSimulationEngine and supports to use the same + simulation file than our simulation backend. We just need to extend the project file in the + same way as we did before and are also able to use the same resource file for this. -In the previous chapter we extended our Instrument Cluster code by using QtRemoteObjects as IPC and -autogenerated a backend for it, as well as a server providing the simulation. In this chapter we -would like to manually write our own backend which is using DBus as IPC. For this we already -prepared a working D-Bus server which provides a very limited simulation. + \quotefromfile ivicore/qface-tutorial/chapter5-ipc/simulation_server/simulation_server.pro + \skipto RESOURCES + \printuntil RESOURCES -Let's first take a short look at the server code and see what is done there and then write the -backend connecting to it. + In the same way, we can also use the other simulation data JSON file that we defined in the + previous chapter, by using the same environment variable. We just need to pass it to the + server instead of our Instrument Cluster application. -\section2 D-Bus Server + Let's do the final test: starting two Instrument Cluster instances should now show the + animations in sync: -As said above we will use D-Bus for this chapter and we already have a XML file describing the -D-Bus interface, similar to our qface IDL: + << gif >> ??? -\quotefile ivicore/qface-tutorial/chapter6-own-backend/demo_server/instrumentcluster.xml + \section1 Chapter 6 -This xml file is used to let qmake generate a base class which is extended by the server with -actual functionality. See the QtDBus documentation for more information about this works in detail. + Previously, we extended our Instrument Cluster code by using QtRemoteObjects as IPC and + autogenerated a backend for it, as well as a server that provides the simulation. In this + chapter, we'd like to write our own backend \b manually using D-Bus as IPC. -Our D-Bus server starts on the session bus on the / path and provides a interface name -"Example.IVI.InstrumentCluster". To simulate some values, we keep it simple and just use a timer -event to change the speed value each 100 milliseconds and start at 0 again once the maximum of 250 -is reached. In a similar way the rpm value is increased until 5000. For all other properties we -simply provide hardcoded values. + We've already prepared a working D-Bus server which provides limited simulation. -\quotefromfile ivicore/qface-tutorial/chapter6-own-backend/demo_server/instrumentcluster.cpp -\skipto timerEvent -\printuntil } + First, let's look at the server code and see what's done there; then write the backend that + connects to it. -\section2 Our own D-Bus Backend + \section2 D-Bus Server -Let's start with a project file for our backend. This is very similar to previous project files, -but doesn't use the IVI Generator, but instead uses DBUS_INTERFACES to autogenerate some client -code which sends and receives messages over D-Bus. First we need to define the entry point for our -plugin. This plugin class needs to derive from QIviServiceInterface and implement two functions: + As mentioned above, we use D-Bus for this chapter and we already have an XML file that + describes the D-Bus interface, similar to our QFace file: -\list - \li \c {QStringList interfaces()} Returns a list of all interfaces supported by this plugin - \li \c {QIviFeatureInterface *interfaceInstance(const QString &interface)} Returns a instance of the requested interface -\endlist + \quotefile ivicore/qface-tutorial/chapter6-own-backend/demo_server/instrumentcluster.xml -In addition we also need to provide the list of interfaces we support as plugin metadata in form of -a JSON file which looks like this: + This XML file is used to let qmake generate a base class which is extended by the server with + actual functionality. For more information, see \l{QtDBus}. -\quotefile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentcluster_dbus.json + Our D-Bus server starts on the session bus, on the \c{/} path, and provides an interface named + "Example.IVI.InstrumentCluster". To simulate some values, we keep it simple and use a timer + event to change the speed value every 100 milliseconds. Then, we start from 0, once the + maximum of 250 is reached. Similarly, the \c rpm value is increased to 5000. For all other + properties, we provide hardcoded values. -This is needed as it gives QtIvi the chance to know which interfaces a backend supports before -instantiating it and only load the plugins which are needed for by the app code. + \quotefromfile ivicore/qface-tutorial/chapter6-own-backend/demo_server/instrumentcluster.cpp + \skipto timerEvent + \printuntil } -Our plugin code looks like this: + \section2 Write Our own D-Bus Backend -\quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterplugin.cpp -\skipto #include -\printuntil eof + Let's start with a \c{.pro} file for our backend. This is very similar to previous \c{.pro} + files, but it doesn't use the IVI Generator. Instead, it uses \c DBUS_INTERFACES to + autogenerate some client code which sends and receives messages over D-Bus. -In interfaces() we use the IID which is defined in instrumentclusterbackendinterface.h from our -autogenerated library. In insterfaceInstance() we check for the correct string and return a -instance of our implemented instrument cluster backend. + Now, we need to define an entry point for our plugin. This plugin class needs to derive from + QIviServiceInterface and implement two functions: -This backend will be defined in instrumentclusterbackend.h and derives from -InstrumentClusterBackendInterface. In our InstrumentClusterBackend class we need to implement all -pure virtual functions from InstrumentClusterBackendInterface and derived classes. For our example -this is pretty simple, as we just need to implement the initialize() function. If our IDL would use -writable properties or methods, we would need to implement these as well. We don't need to -implement getters for our properties, because QtIvi uses the changed signals during the -initialization phase to get informed about the current state. Although the generated DBus interface -class would provides getters to retrieve the properties from our server, it is not recommended to -use these when developing a backend. These getters are implemented by using synchronous calls, which -means they will block the event loop until an answer is received by the client. This can lead to -performance issues and is the reason why we recommend to use async calls instead. + \list + \li \c {QStringList interfaces()} -- that returns a list of all interfaces this plugin + supports. + \li \c {QIviFeatureInterface *interfaceInstance(const QString &interface)} -- that returns + an instance of the requested interface. + \endlist -In our backend we define a fetch function for every property which is implemented like this: + Additionally, we also need to provide a list of interfaces we support as plugin metadata, in + the form of a JSON file which looks like this: -\quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterbackend.cpp -\skipto ::fetchSpeed -\printto ::fetchRpm + \quotefile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentcluster_dbus.json -First we add the property to a list to know which properties have been fetched successfully. In the -next step we use the asyncCall() function to call the getter for the 'speed' property and use a -QDBusPendingCallWatcher to wait for the result. Once the result is ready the lambda will remove the -property again from our fetchList use the onSpeedChanged() function to store the value and notify -the frontend about it. As we don't need the watcher anymore we delete it in the next event loop -run using deleteLater() and call the checkInitDone() function. + We need this list, as it gives QtIvi the chance to know which interfaces a backend supports, + before instantiating it and loading only the plugins which the application code needs. -The checkInitDone() function is defined as follows: + Our plugin code looks like this: -\quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterbackend.cpp -\skipto ::checkInitDone -\printto onSpeedChanged + \quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterplugin.cpp + \skipto #include + \printuntil eof -It makes sure the initializationDone() signal is sent to the frontend once all our properties were -fetched from the server and the initialization is complete. + In \c interfaces() we use the IID which is defined in \c{instrumentclusterbackendinterface.h} + from our autogenerated library. In \c insterfaceInstance() we check for the correct string and + return an instance of the instrument cluster backend we implemented. -In addition to retrieving the current state from the server, we also need to inform our frontend -about every property change. This is done by emitting the corresponding change signal once the -server changed one of it's properties. For this we define a slot for every property. This slot -will save the property in our class an emit the change signal: + This backend is defined in \c instrumentclusterbackend.h and derives from + \c InstrumentClusterBackendInterface. In our \c InstrumentClusterBackend class, we need to + implement all pure virtual functions from InstrumentClusterBackendInterface and derived classes. -\quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterbackend.cpp -\skipto void InstrumentClusterBackend::onSpeedChanged(int speed) -\printto onRpmChanged + For our example, this isn't complex, as we just need to implement the initialize() function. + If our XML file would use writable properties or methods, then we'd need to implement those as + well. We don't need to implement getters for our properties, because QtIvi uses the changed + signals during the initialization phase to get information about the current state. Although + the generated D-Bus interface class would provide getters to retrieve the properties from our + server, it's not recommended to use these when you develop a backend. These getters are + implemented by using synchronous calls, which means they will block the event loop until an + answer is received by the client. Since this can lead to performance issues, we recommend to + use \b asynchronous calls instead. -The same slot is also used during the initialization phase to save and emit the value. + In our backend, we define a fetch function for each property that's implemented like this: -You might think now why saving the value is needed at all and why not just emit the signal. The -reason for this is that the backend plugin is used directly by every instance of the InstrumentCluster -class and every instance calls the initialize() function to retrieve the current state. Instead -of fetching all properties again the second initialize() call will just emit the already saved -values and the slots will keep them uptodate. + \quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterbackend.cpp + \skipto ::fetchSpeed + \printto ::fetchRpm -When starting the Instrument Cluster now, our backend should connect to our D-Bus server and now -look like this: + First, we add the property to a list, to know which properties have been fetched successfully. + Next, we use the \c asyncCall() function to call the getter for the \c speed property and use a + \c QDBusPendingCallWatcher to wait for the result. Once the result is ready, the lambda removes + the property again from our \c fetchList, uses the \c onSpeedChanged() function to store the + value and notifies the frontend about it. Since we don't need the watcher anymore, we delete it + in the next event loop run using \c deleteLater(), and call the \c checkInitDone() function. + + The \c checkInitDone() function is defined as follows: + + \quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterbackend.cpp + \skipto ::checkInitDone + \printto onSpeedChanged + + It ensures that the \c initializationDone() signal is sent to the frontend once all our + properties are fetched from the server, and the initialization is complete. + + In addition to retrieving the current state from the server, we also need to inform our frontend + every time a property changes. This is done by emitting the corresponding change signal when the + server changes one of its properties. To handle this, we define a slot for each property. This + slot saves the property in our class an emits the change signal: + + \quotefromfile ivicore/qface-tutorial/chapter6-own-backend/backend_dbus/instrumentclusterbackend.cpp + \skipto void InstrumentClusterBackend::onSpeedChanged(int speed) + \printto onRpmChanged + + The same slot is also used during the initialization phase to save and emit the value. + + You might wonder why saving the value is needed at all, if we can just emit the signal. This is + because the backend plugin is used directly by every instance of the \c InstrumentCluster class + and every instance calls the \c initialize() function to retrieve the current state. Instead of + fetching all properties again, the second \c initialize() call just emits values that were + already saved; and the slots keep them up to date. + + Now, when we start the Instrument Cluster, our backend should connect to our D-Bus server and + look like this: < gif > |