Qt IVI Simulation System

When you develop new APIs, a service that the API requires may not exist yet. This is because, the API is already designed but the service itself is still being developed. For example, with new concepts like Autonomous Driving, the UI interaction and its API are designed, but the Autonomous Drive Service is not yet completed. This development cycle requires the need to separate the API developemnt from the service. The Dynamic Backend System provides the architecture to enable this separation.

Once we have this separation in place, the next step for devleoping the API is a way to simulate its behavior to mirror the original service. The Qt IVI Simulation System enables the following use cases:

  • An easy to define simulation logic in QML
  • A flexible system to provide simulation for any C++ API
  • A clear split between simulation data and simulation logic
  • An override mechanism to change the simulation at runtime; useful for autotests
  • An integration with the IVIGenerator tools

Architecture

Since the simulation system builds on top of the Dynamic Backend System, the API split follows the same schema:

"Relationship between the Feature and the Backend"

Each backend plugin needs to implement the backend interface to provide the necessary functionality to the frontend. For example, the QIviClimateControlBackendInterface class for the QIviClimateControl frontend API.

In the backend, every call from the frontend is forwarded to QML, where we can script a simulation behavior.

"QtIvi Simulation System"

QML API

The core of the Qt IVI Simulation System is the QIviSimulationEngine. This engine extends QQmlApplicationEngine and provides the extra functionality to connect C++ and QML logic together.

Each backend uses its own simulation engine to separate the frontend code from the backend QML code. To provide a binding between QML and C++ objects, the C++ instance must be registered with the QIviSimulationEngine under a certain name. For each registered C++ instance, the engine creates a proxy object and exposes it as a QML type. These QML types can be used to provide the behavior for functions or to update properties.

For example, suppose your feature has a function called increment() and a property called counter. The actual behavior for increment() is implemented in the backend:

void increment() {
    counter++;
}

When we autogenerate classes, the actual bevhavior of increment() cannot be autogenerated, because there's no way to tell the autogenerator the kind of behavior this function should have. To define this behavior, you need to implement a complete backend in C++.

The QIviSimulationEngine makes this task more flexible, as it forwards all of the C++ calls to QML, allowing you to use QML to define the behavior, via scripting. Consequently, you can override the behavior and also script a default behavior without touching any of the C++ code.

For more information on working with the simulation engine, see QIviSimulationEngine.

Separate Data from Logic

The simulation system makes it possible to separate the simulation business logic from the simulation data. The simulation data is stored in JSON files, for the QIviSimulationEngine::loadSimulationData() function to load. Once the simulation data is loaded, the IviSimulator global object provides the content to all QML simulation files.

For example, you can use the IviSimulator::findData function to read the data for a specific interface only:

property var settings : IviSimulator.findData(IviSimulator.simulationData, "QIviClimateControl")

Boundary Checks

The IviSimulator global object also provides functions to make boundary checks easier. The property boundaries are defined in the JSON files, while the QML code stays generic, to work with multiple different boundary checks:

function setAirConditioningEnabled(airConditioningEnabled) {
    if (IviSimulator.checkSettings(airConditioningEnabled, settings["airConditioningEnabled"])) {
        console.log("SIMULATION airConditioningEnabled changed to: " + airConditioningEnabled);
        backend.airConditioningEnabled = airConditioningEnabled
    } else {
        setError("SIMULATION changing airConditioningEnabled is not possible: provided: " + airConditioningEnabled + " constraint: " + IviSimulator.constraint_string(settings["airConditioningEnabled"]));
    }
}

Use the IviSimulator::checkSettings() function to check the specified airConditioningEnabled value against the boundaries defined in the JSON file. If the value is within the boundaries, then it is updated; otherwise an error is returned together with the constraint in a human-readable form.

For more information about simulation and the data format, see IviSimulator.

Override Mechanism

For app development or unit testing, it is often useful to trigger a certain behavior in the backend. For example, when implementing message boxes for error recovery, the app developer may need a way to easily trigger this exact error condition. Suppose that the simulation behavior provided by the backend developer, for this use case, is not be sufficient.

In this case, the Qt IVI Simulation System provides an override system to load your own simulation behavior file or data file, via an environment variable.

Each QIviSimulationEngine can have an additional identifier to override the default behavior file or data file, using the following environment variables:

QTIVI_SIMULATION_OVERRIDE=<identifier>=<file>[;<identifier>=<file>]
QTIVI_SIMULATION_DATA_OVERRIDE=<identifier>=<file>[;<identifier>=<file>]

Integrate with IVIGenerator

The simulation system is already integrated into the IVIGenerator tools and it is used automatically when generating code with the backend_simulator format.

The autogenerated plugin uses the QFace module name as the QIviSimulationEngine identifier, to allow overriding at runtime.

All boundary annotations defined in config_simulator are then transformed into a JSON file, and embedded as a resource file into the backend.

For each interface, a QML simulation file is created, providing a default implementation to check the boundaries of each property.

Define Your Own Simulation Files

It's not always convenient to use the autogenerated QML simulation files. You can also define your own QML file by using the simulationFile annotation.

Note: If your QFace file provides multiple interfaces, the corresponding simulation file must provide a simulation for all of these interfaces.

To reuse the autogenerated simulation files from an existing interface as a starting point, you can load these autogenerated simulation files using a QML import statement:

import 'qrc:/simulation/'

Afterwards you should be able to load your modified simulation files like a regular QML file.

© 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.