aboutsummaryrefslogtreecommitdiffstats
path: root/src/ivicore/doc/src/examples-qface-ivi-remote.qdoc
blob: a652c362c0ba393dba0fe92cf142cc15b5084c67 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
/****************************************************************************
**
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
** This file is part of the documentation of the QtIvi module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:FDL-QTAS$
** Commercial License Usage
** Licensees holding valid commercial Qt Automotive Suite licenses may use
** this file in accordance with the commercial license agreement provided
** with the Software or, alternatively, in accordance with the terms
** contained in a written agreement between you and The Qt Company.  For
** licensing terms and conditions see https://www.qt.io/terms-conditions.
** For further information use the contact form at https://www.qt.io/contact-us.
**
** GNU Free Documentation License Usage
** Alternatively, this file may be used under the terms of the GNU Free
** Documentation License version 1.3 as published by the Free Software
** Foundation and appearing in the file included in the packaging of
** this file. Please review the following information to ensure
** the GNU Free Documentation License version 1.3 requirements
** will be met: https://www.gnu.org/licenses/fdl-1.3.html.
** $QT_END_LICENSE$
**
****************************************************************************/

/*!
\example ivicore/qface-ivi-remote
\brief This Example shows how to use the Qt IVI Generator to create remote backends.
\ingroup qtivicore-examples
\title Qt IVI Generator Remote Objects Example
\image examples_qface_ivi_remote.png

\section1 Introduction

This example shows how to create a remote backend and the client side components
using the Qt IVI Generator. The communication between the client and server is done with
QtRemoteObjects. Based on a single qface IDL file, it will generate:

\list
\li a shared library with the front-end code
\li a back-end plugin that implements a client for connecting to the server
\li a server that runs the actual remote backend logic
\li a demo application that connects to the server and provides and UI for using the service
\endlist

In addition to the generated C++ code, the backend-plugin and the server contain also an
intermediate .rep -file that is further processed by Qt’s replica compiler to produce the actual
source and replica classes.

\section2 Walkthrough

The IDL file used in the example represents an imaginary remote service for processing data . It
contains a single interface with a single property and a single method.

First we need to define which \e module we want to describe. The \e module acts as a namespace,
because the IDL file can contain multiple interfaces.

\code
module Example.IVI.Remote 1.0;
\endcode

The most important part is the definition of the \e interface.

\code

interface ProcessingService {

    string lastMessage;

    int process(string data);
}

\endcode

In this case, we define an \e interface named \b ProcessingService consisting of a single property
and a single method. Every property and method definition needs to contain at least a type and a
name. Most of the basic types are builtin and can be found in the \l {QFace IDL syntax} {reference}.


\section1 Frontend library

Now we want to use the IVI Generator to generate a shared library containing a C++ implementation of
our module and its interface. For this, the \e frontend template is used. This will generate a class
derived from \c {QIviAbstractZonedFeature} including all the specified properties. The generated
library will use the \l {Dynamic Backend System} from QtIviCore and by that provide an easy way to
change the behavior implementations.

In order to call the autogenerator for our shared library, the qmake project file needs to use the
\e ivigenerator qmake feature. The following snippet shows how it can be added:

\code
CONFIG += ivigenerator
QFACE_SOURCES = ../example-ivi-climate.qface
\endcode

By adding \e ivigenerator to the \e CONFIG variable, the \e ivigenerator feature file will be
loaded and interpret the \e QFACE_SOURCES variable similar to \e SOURCES variable of normal qmake
projects.
Activating the qmake feature using the \e CONFIG variable has the disadvantage that it doesn't
report any errors if the feature is not available. Because of this, it is encouraged to use the
following additional code to report errors:

\code
QT_FOR_CONFIG += ivicore
!qtConfig(ivigenerator): error("No ivigenerator available")
\endcode

The other part of the project file is a normal library setup which is supposed to work on
Linux, macOS and Windows.


\section1 RemoteObjects Backend Plugin

As mentioned above, the \e frontend library will use the \l {Dynamic Backend System}. This means
that for the library to provide some functionality, we also need a \e backend plugin. The generated
plugin here will work as a client that connects to the server using the Qt Remote Objects. The qmake
integration works in the same way, but it is using the \e QFACE_FORMAT variable to tell the
ivigenerator to use a different generation template, \e backend_qtro:

\code
CONFIG += ivigenerator plugin
QFACE_FORMAT = backend_qtro
QFACE_SOURCES = ../example-ivi-climate.qface
\endcode

The generated backend plugin code is usable as is, and doesn't require further changes by the
developer. As we want to generate a plugin instead of a plain library, we need to instrument qmake
to do so by adding \e plugin to the \e CONFIG variable. For the plugin to compile correctly it needs
to get the backend interface header from the previously created library. As this header is also
generated, it is not part of our source tree, but part of the build tree. We do this by adding it to
the include path using the following construct:

\code
INCLUDEPATH += $$OUT_PWD/../frontend
\endcode

Most of the code in the backend plugin is generated by the IVI Generator, but some of it is
generated by the Qt's remote object compiler (repc). This is achieved by IVI Generator producing an
intermediate .repc file that is further processed by the repc compiler. The repc is called via the
generated .pri file, found in the build directory (notice, that you have to call qmake on the
project at least once to have the generated files available).


As our application doesn't know about the existence of our backend plugin, we need to put this
plugin in a folder where our application searches for plugins. By default Qt either search in the
\b plugins folder within Qt's installation directory or in the current working directory of the
application. For QtIvi plugins to be found, they need to be provided within a \b qtivi sub-folder.
This is achieved automatically by adding the following line to our backend project file:

\code
DESTDIR = ../qtivi
\endcode


\section1 RemoteObjects Server

The server is an independent, GUIless application that contains the actual business logic of our
backend and hence needs to have most of its implementation written by the developer. Nevertheless,
the generator produces some code to simplify the development. We can generate server side code by
using the IVI Generator with \e server_qtro template:

\code
TEMPLATE = app
QT -= gui
CONFIG += c++11 ivigenerator
...
QFACE_FORMAT = server_qtro
QFACE_SOURCES = ../example-ivi-remote.qface
\endcode

To use the generated remote source, we need to inherit from one of the classes defined in the
generated rep_processingservice_source.h file. In this example we implement our servers logic in the
ProcessingService class and use the ProcessingServiceSimpleSource as the base class:

\code
// server_qtro/processingservice.h
#include "rep_processingservice_source.h"

class ProcessingService : public ProcessingServiceSimpleSource
{
public:
    ProcessingService();

    int process(const QString & data) override;
};
\endcode

Please notice, that the base class contains already the definitions for property accessors, but any
custom method or slot needs to be overridden and defined by the developer. Our implementation of the
process function merely counts and returns the length of the passed data and updates the lastMessage
property:

\code
// server_qtro/processingservice.cpp
ProcessingService::ProcessingService()
{
    setLastMessage("Service online.");
}

int ProcessingService::process(const QString & data)
{
    setLastMessage("Processed data \'" +data+"\'");
    return data.length();
}
\endcode

In order to make the class \b ProcessingService accessible remotely, we need to share it. This is
done with the QRemoteObjectNode::enableRemoting() function. The generated Core class provides a
preconfigured instance of a remotenode that is used for the remoting. In order for the plugin to
connect to the right object, use an identifier in the format ModuleName.InterfaceName, which in this
case is "Example.IVI.Remote.ProcessingService". All this is done in the main()-function, along with
the start of the main event loop:

\code
// server_qtro/main.cpp
#include <QCoreApplication>

#include "processingservice.h"
#include "core.h"

int main(int argc, char *argv[])
{
    QCoreApplication app(argc, argv);

    ProcessingService service;
    Core::instance()->host()->enableRemoting(&service,"Example.IVI.Remote.ProcessingService");

    return app.exec();
}
\endcode

Implementing a service that is accessible remotely is as simple as that; use the properties as usual
and provide the method implementations. The QtRemoteObjects library takes care of the communication.


\section1 Demo Client Application

The demo application presents a simple QML GUI for using the remote service over
the generated interface.

As we do not provide a QML plugin, the application needs to link to the generated frontend library
and call the \e {RemoteModule::registerTypes} and \e {RemoteModule::registerQmlTypes} methods
that are generated in the module singleton to register all autogenerated interfaces and types with
the QML engine.

In our QML application, we still need to import the module using the name we provided to \e
{RemoteModule::registerQmlTypes}. Afterwards the interface can be instantiated like any other
QML item.

\code
// demo/main.qml
import IviRemote 1.0

Window {

...

    UiProcessingService {
        id: processingService
    }

...

}
\endcode

Every method call that is made through a generated API, is asynchronous. This means,
that instead of directly returning a return value, a \c {QIviPendingReply} object is returned.
Using the \l QIviPendingReply::then() method on the returned object, we may assign callbacks to it
that are called when the method call has been successfully finished or if it has failed.

\code
// demo/main.qml
Button {
    text: "Process"

    onClicked: processingService.process(inputField.text).then(
           function(result) { //success callback
                resultLabel.text = result
           },
           function() { //failure callback
                resultLabel.text = "failed"
           }
       )
}
\endcode

In case of properties, we just use bindings as usual:

\code
// demo/main.qml
Row {
    Text { text: "Last message: " }
    Text {
        id: serverMessage
        text: processingService.lastMessage
    }
}
\endcode


\section1 Running the Example

In order to see the whole functionality of the demo, run both the server and the demo application
at the same time. You may leave the server running and restart the application, or vice versa,
to see that the reconnection works. Run the demo application alone without the server running,
to test how the remote method call fails when there is no connection.

*/