Dynamic Backend System
Modern automotive systems are very complex, usually developed on a strict timeframe. Consequently, it is efficient to reuse parts of previously developed systems, for a new system. At the same time, the main development is done by independent companies (tier-1). To make it possible to reuse code from previous projects, and also incorportae code from tier-1 partners, the APIs are split into two layers: a frontend and a backend. In Qt IVI, the frontend API is called a feature, as usually a specific class is responsible for a specific feature area, such as QIviClimateControl, which controls the climate feature area.
To function correctly, every feature needs to have a backend connected to it. This backend must implement a corresponding feature backend interface. Only then, you can create a valid connection between the feature and its backend.
Usually, every feature has only one backend interface class, which the backend needs to implement for the feature to work. Every backend interface is derived from QIviFeatureInterface, which provides generic functions and signals that every feature needs, such as error handling.
The backend implementations are grouped together and implemented inside a Qt plugin. This makes it easy to provide multiple backends simultaneously and switch the backend at runtime. These backend plugins are loaded through QtIviCore. A plugin can provide backend implementations for multiple features; there is no need to create a separate plugin for each feature.
Qt IVI also distinguishes between two types of backends:
- production - On a production system, you want to have only production backends running.
- simulation - During the development phase, it may be useful to have a simulation backend that you can use for frontend development, until the backend services are usable.
Qt IVI uses a simple naming scheme to identify whether a plugin provides simulation or production backends. Every simulation plugin needs to have either "simulation" or "simulator" in its name. Alternatively, you can also set the "simulation" key in the plugin's metadata. This is especially useful for static plugins.
The QtIviCore module provides all the classes that are needed to glue the parts together. In addition to providing the base classes like QIviAbstractFeature or QIviServiceObject, this module also provides the QIviServiceManager, responsible for loading the necessary backend plugins.
The QIviServiceManager is the central part of QtIviCore, keeping a record of all the available backends and their exported interfaces. The service manager scans through all available plugins and their accompanying metadata. This process gives it the ability to only load plugins that a Feature requires, to reduce the startup time. All of this information is collected in the service manager in the form of a model, which enables developers to choose the plugin they want to use.
The ServiceObject concept keeps the features flexible, and makes it possible to switch between backends at runtime. A QIviServiceObject is a handle, which the feature uses to connect to the correct backend interface. This handle provides methods to query the backend interfaces available, which the ServiceObject implements. ServiceObjects automatically wrap around plugins, making it possible to share the ServiceObject between multiple features and to explicitly select which backend to use for your feature instance.
Based on the diagram above, the ServiceObject is the handle for a specific plugin. Feature A and Feature B both use the same ServiceObject, which returns an instance of Feature_A_Interface for Feature A and Feature_B_Interface for Feature B. The Feature classes derive from QIviAbstractFeature; the backend interfaces derive from QIviFeatureInterface.
In contrast to the normal QIviServiceObject, which represents a handle to a backend plugin, the QIviProxyServiceObject doesn't need a plugin to work. It can be instantiated on the application side and filled with any QIviFeatureInterface derived class. QIviProxyServiceObject is useful for scenarios when a backend implementation of a feature should not be done inside a separate plugin, but inside the application's code base itself.
Typically, all Features use the auto discovery mode. From QML, you can set the QIviAbstractFeature::discoveryMode property; from C++, you can start this with QIviAbstractFeature::startAutoDiscovery(). This property asks the QIviServiceManager for all the backends available, that implement the required interface for your feature. The manager then chooses the first matching backend and connects the feature to it. QIviAbstractFeature always asks for production backends first; if none are available, it falls back to a simulation backend. This behavior can be controlled using the QIviAbstractFeature::discoveryMode, that defaults to QIviAbstractFeature::AutoDiscovery. The resulting backend type can be retrieved via QIviAbstractFeature::discoveryResult. After the feature has loaded a backend successfully, the QIviAbstractFeature::serviceObject property holds the loaded ServiceObject and QIviAbstractFeature::isValid returns
Based on the aforementioned climate control example, the detailed connection would be as follows:
- A ClimateControl element is created in QML.
- ClimateControl calls QIviAbstractFeature::startAutoDiscovery upon completion.
- QIviAbstractFeature::startAutoDiscovery queries QIviServiceManager for all backends available.
- QIviServiceManager searches for all plugins available and the interfaces they implement; this search is only done once.
- QIviAbstractFeature accepts the first QIviServiceObject and connect to the corresponding interface.
- The ClimateControl element is ready to be used.
If you don't want your feature to use the auto discovery mechanism, set the discoveryMode to QIviAbstractFeature::NoAutoDiscovery. After that, the feature won't search for a backend anymore, and you need to assign a ServiceObject manually.
For features like the climate control example, the auto discovery mechanism is fitting, as there is usually a 1:1 mapping between a feature and a backend providing the implementation for that feature. For more generic interfaces like a media player, this might not be sufficient: you could control a built-in media player backend with this, but you might also want to control the media player running on your mobile phone over bluetooth.
For this to work, first, you would need to discover the devices available and then pass the ServiceObject of the selected device to the media player interface. The discovery of the available mobile phones can be done using a DiscoveryModel. This provides you with a ServiceObject for each device found. The concept of a discovery model is not limited to mobile phones, it can be used for all backends that are not hard wired to the system, like Internet services or controlling multiple rearseat systems.
Zones are a standard way to provide a single API for multiple points in the vehicle. For instance, climate control commonly has a driver zone and passenger zone; or even a back seat zone. The same concept applies to wheels, doors, mirrors, windows, and more.
A common pattern is to combine zones with property attributes to handle small differences in capabilities between zones; for example, there's no steering wheel heater in a car's passenger side.
Technically, a zoned feature consists of multiple instances of the same feature, a top-level instance which provides the zone-independent API and access to instances for a specific zone. The feature needs to derive from QIviAbstractZonedFeature and implement createZoneFeature() to provide zone-specific instances.
The top-level interface can provide vehicle wide settings. For example, whether recirculation should be used in a climate control API. In contrast, the zoned interface provides per-zone functions, such as the desired temperature.
Building a zoned feature requires the backend interface to be derived from QIviZonedFeatureInterface. This class provides the backend with an interface to enumerate the zones available. This interface also includes the necessary QIviZonedFeatureInterface::initialize method to initialize any properties.
© 2020 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.