Inner workings
Main modules
The class diagram below shows the main modules of esmini.
It illustrates how the main esmini application uses PlayerBase to orchestrate the simulation. PlayerBase in turn utilizes ScenarioEngine for the simulation logic and ViewerBase for visualization. Both ScenarioEngine and ViewerBase depend on RoadManager for road network data. Controllers are managed by the ScenarioEngine, and CommonMini provides utility functions used by several other modules.
RoadManager
Implementation of OpenDRIVE data model and interface. Coverage is not complete at all. The goal is to provide needed and most useful features for esmini. Functionality grows continuously. While being based on 1.4 version it will probably support newer features when needed, so we avoid tagging a specific version.
ScenarioEngine
This is where the dynamics of the scenario takes place. It parses the OpenSCENARIO XML file, creates a data model of the entities, triggers and actions. And finally steps the scenario, evaluating the triggers and executing the actions accordingly.
ScenarioEngine also takes care of reporting ground truth on OSI standard protocol in various ways: 1. API functions to fetch the OSI structures 2. Saving to OSI trace file 3. Sending OSI data over IP/UDP to an external host.
Another feature is ideal sensors, defined by a view frustum positioned anywhere on an entity via the API. It will detect any other entity being inside the view frustum. Please note current limitation that only entity reference point is considered for detection, so an entity might be partly inside the frustum and still not detected.
Controllers
Implementation of OpenSCENARIO controller concept, which is a way of handing over control of individual entities such as vehicles and pedestrians to an external/custom function instead of the Default Controller, which is embedded the ScenarioEngine.
The base class Controller defines the interface (API) for a controller and also implements some common functionality, such as assigning and activating the controller.
Then there is a collection of more or less useful controllers. Some might provide useful functionality, e.g. interactive driving with arrow keys, while other can at least provide ideas on how to implement a custom controller.
More information about the controller concept and the provided controllers, please see User guide - Controllers.
ViewerBase
Provides a basic 3D viewer to preview scenarios. It’s based on the excellent OpenSceneGraph open source graphics library. Let’s say that it prioritizes stability, performance and portability over flashiness - fitting the purposes of esmini.
Briefly the module provides the following features:
-
Optional 3D visual representation of the road network and surroundings.
.osgb(OpenSceneGraph binary) file format is directly supported, but indirectly many file formats are supported by conversion using the osgconv tool (not included in esmini). See more info in User guide - OpenSceneGraph and 3D models. -
3D visual representation of scenario entities. Either using supplied 3D models or esmini will create stand-in dummy model, e.g. a 3D bounding box according to specified dimensions.
-
Road feature visualization. E.g. OpenDRIVE geometries, lanes and road marks. These features are indicated by points and lines in a very simple way. So it does not replace the value of a 3D model, but at least it gives some guidance when 3D model is not available. It can also be useful for debugging issues in the OpenDRIVE road network definition.
-
Keyboard input. First OSG provides a set of key shortcuts to control visual features, e.g. enable/disable textures or wireframe/shading mode. Then esmini adds a set of shortcuts to control various functionality such as toggling road features visualization or changing camera behavior. A complete list of keyboard shortcuts is found in docs/readme.txt. The keyboard input is also routed to Controllers, which is useful for interactive driving modes.
PlayerBase
This module ties together the ScenarioEngine with the Viewer also providing high a level API for initializing, stepping and controlling a scenario in a custom player application.
The initial purpose of this module was to collect common code from various early example applications.
CommonMini
Collection of handy functions shared between modules and applications, for example: - Argument parser - Timers - Threads and Mutex - Math operations - Logger
Parameters
esmini supports three parameter types: integer, double and string. A parameter is declared in the OpenSCENARIO file, in the global ParameterDeclaration. Values can be set by ParameterAction/ParameterSetAction. And it can be used in ParameterCondition to trig storyboard elements. For a simple example see use of "DummyParameter" in lane_change.xosc and esmini-dyn/main.cpp (enable DEMONSTRATE_PARAMETER to test).
To set and read values via esminiLib/C++ you can do as following examples:
integer
int number = 99;
SE_Parameter param;
param.name = "MyIntParameter";
param.value = &number;
SE_SetParameter(param);
SE_GetParameter(¶m);
printf("param value: %d\n", number);
// or by casting the value
printf("param value: %d\n", *((int*)param.value));
double
double number = 1.5;
SE_Parameter param;
param.name = "MyDoubleParameter";
param.value = &number;
SE_SetParameter(param);
SE_GetParameter(¶m);
printf("param value: %.2f\n", number);
// or by casting the value
printf("param value: %.2f\n", *((double*)param.value));
string
std::string myString = "Hello";
SE_Parameter param;
param.name = "MyStringParameter";
param.value = &myString;
SE_SetParameter(param);
SE_GetParameter(¶m);
printf("param value: %s\n", myString.c_str());
// or by casting the value
printf("param value: %s\n", (*((std::string*)param.value)).c_str());
How the modules interact
Consider a custom application/tool utilizing esmini as a library. The interaction is illustrated below through a few sequence diagrams. Note that the signals are named conceptually. For exact function names and arguments, see header files and code examples.
First we look at high level interaction between app and esmini lib.
A simplistic scenario player application (C/C++/C#/Python) utilizing esmini lib:
A typical test tool involving a system under test (Ego):
Now, let’s zoom in. Adding some features and looking inside esminiLib to see interaction between modules:
On execution flow
Actions are executed in order of appearance in the storyboard structure. All state updates are applied directly on the entities. So, if an action is based on the state of another entity, it will depend on whether the action is executed before or after the other entity’s state is updated. This is especially important to consider when using relative actions, like SpeedAction with RelativeTargetSpeed, which is based on the speed of another entity. The resulting speed will depend on whether the referenced entity has been updated or not.
Example:
SpeedAction (assigned to entity A) with RelativeTargetSpeed, refering to entity B, in continuous mode and step dynamics (instant change to target speed).
<SpeedActionTarget>
<RelativeTargetSpeed entityRef="B"
value="1.1"
speedTargetValueType="factor"
continuous="true"/>
</SpeedActionTarget>
You expect speed of A to be exactly 10% faster than B. Now, assume speed of B is suddenly updated from 100 to 50 km/h. In the same timestep, resulting speed of A will depend on whether it’s being calculated before or after speed change for B.
-
If the A action is applied first: speed(A) = 110
unexpected result due to B has not been updated yet, still speed = 100 -
If the B action is applied first: speed(A) = 55
expected result since B has already new speed = 50 when evaluating A action
Conclusion: Consider order of the storyboard elements when designing scenarios, especially when involving relative actions.