1. Introduction
esmini is a software tool to play OpenSCENARIO files. It’s provided both as a stand alone application and as a shared library for linking with custom applications. In addition some tools have been developed to support design and analysis of traffic scenarios.
The scope of this user guide is to provide examples on how to use the tools. It also includes a programming tutorial on how to use esmini in custom applications.
2. Getting started
Download latest release from here: https://github.com/esmini/esmini/releases/latest
First time make sure to pick the demo package for your platform (Windows, Linux or Mac). In addition to application binaries it also includes some content like example scenarios and basic 3D models. The binary (bin) packages includes only executables and libraries.
On Windows, make sure the zip file is not blocked: Right click, click Properties, at bottom right check Unblock. For more info, see Blocked by Windows Defender SmartScreen.
To install the package, just unzip it anywhere. A single subfolder named esmini
is created. This is the root folder for esmini. No files are stored outside this folder structure and no system files or registry is modified in any way.
2.1. Run esmini
Try to run one of the examples:
-
go to folder esmini/run/esmini
-
double click on a .bat file, e.g.
run_cut-in.bat
or run it from a command line.
These scripts should work on all platforms (in spite extension ".bat").
You can also run the examples explicitly from a command line:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc
For common Mac issues, please see Mac issues and limitations.
2.2. Get complete esmini
The demo package contains only a subset of esmini tools and content (e.g. scenario examples, scripts and models).
To get the complete content, first download or clone the project from GitHub: https://github.com/esmini/esmini. E.g:
git clone https://github.com/esmini/esmini.git
Then either build yourself, see step 2 in next chapter Build esmini - quick guide, or:
-
Download pre-built binary package for your system (e.g. esmini-bin_win_x64.zip) from latest release: https://github.com/esmini/esmini/releases. Copy the content into the esmini-demo folder.
-
Download the complete 3D model package from here. Unpack into esmini/resources (it should end up in a
models
subfolder). These assets work on all platforms. The environment models (roads, landscape, buildings…) have been created using VIRES Road Network Editor.
2.3. Build esmini - quick guide
Supported systems: Windows, Linux and Mac.
Make sure you have a C++ compiler and CMake installed.
On Windows Visual Studio is recommended (Community/free version is good enough for building esmini). Make sure to check the "Desktop development with C++" package for installation, no more is needed.
On Linux, e.g. Ubuntu, some additional system tools and libraries are needed. Run the following command:
sudo apt install build-essential gdb ninja-build git pkg-config libgl1-mesa-dev libpthread-stubs0-dev libjpeg-dev libxml2-dev libpng-dev libtiff5-dev libgdal-dev libpoppler-dev libdcmtk-dev libgstreamer1.0-dev libgtk2.0-dev libcairo2-dev libpoppler-glib-dev libxrandr-dev libxinerama-dev curl cmake
Now we’re ready to build esmini. From esmini root folder:
mkdir build
cd build
cmake ..
cmake --build . --config Release --target install
The build process automatically downloads 3rd party library binaries and the complete 3D model package.
After successful build, the binaries will be copied into esmini/bin
folder. Try from command line:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc
That’s all. Details and variants, see Build guide.
3. Tools overview
3.1. Applications
esmini |
Play OpenSCENARIO file. Many options, e.g. view on screen, save images to file or just save log data for post processing. |
replayer |
Replay recorded esmini scenarios. Main difference from esmini is the ability to freely move forward and backward in the scenario at various speeds. See Use cases how to create recordings ( |
odrviewer |
Visualize and verify OpenDRIVE road networks. Draw road features, like reference line and lanes, on top of a 3D model (provided or generated). Optionally populate with random traffic that will randomly find its way through the road network. |
3.2. Scripts
esmini comes with a collection of scripts for various purposes, like preprocessing of scenario files and plotting or conversion of simulation output files. The scripts are located in the scripts
folder. A few examples:
- odrplot
-
Simple 2D plot of OpenDRIVE road lanes with indicated road IDs, in a two step process.
Prerequisites: Python + matplotlib
Example:
./bin/odrplot ./resources/xodr/fabriksgatan.xodr
(will create a track.csv file)
python ./EnvironmentSimulator/Applications/odrplot/xodr.py track.csv
- plot_dat
-
Simple 2D plot of scenario data
Prerequisites: Python + matplotlib
Example:
./bin/esmini --headless --osc ./resources/xosc/acc-test.xosc --fixed_timestep 0.05 --record sim.dat
(will create the .dat file)
./scripts/plot_dat.py sim.dat --param speed
- dat2csv
-
Convert esmini recording (.dat) file to standard .csv format
Example:
./scripts/dat2csv sim.dat
will create sim.csv. - osi2csv.py
-
Convert OSI trace file (from esmini) to .csv format
Example:
./scripts/osi2csv.py ./ground_truth.osi
will create ground_truth.csvSee Save OSI data how to create OSI groundtruth trace (
.osi
file) from esmini. - plot_csv
-
Simple 2D plot of scenario data, similar to plot_dat
Prerequisites: Python + matplotlib
Example:
./bin/esmini --headless --osc ./resources/xosc/acc-test.xosc --fixed_timestep 0.05 --osi_file sim.osi
(will create an osi trace file)
./scripts/osi2csv.py sim.osi
(convert to .csv)
./scripts/plot_csv.py sim.csv --param ax
will plot the x component of acceleration vector. - dat2xosc.py
-
Convert any scenario to a fixed trajectory based scenario. See more info in script.
- move_to_origin.py
-
Translate a scenario to origin. Useful for scenarios with large coordinates causing precision issues. See more info in script.
- xodr_lines2curves.py
-
Smoothen polyline roads by converting them to parametric curves. See more info in script.
- run_schema_comply.py
-
Validate xosc and xodr scenario files for compliance with the XML schema of the corresponding standard. See more info in script and section Run XML schema validation tests.
- compile_osg_apps.sh
-
Compile OSG applications for viewing and converting OpenSceneGraph 3D models. See more info section Get osgconv / Build yourself.
- run_repeat_compare.sh
-
Run a scenario multiple times and compare the results, expected to be identical. See more info in script.
- osiviewer.py
-
Visualize OSI trace files. Only supporting a small, limited subset of OSI content, e.g. lanes, stationary and moving objects.
Example:
./scripts/osiviewer.py ground_truth.osi
3.3. Shared libraries
esminiLib |
High level API for running, controlling and monitoring scenarios See headerfile esminiLib.hpp and Hello World programming tutorial |
esminiRMLib |
High level API for parsing and query road networks (only road manager) See headerfile esminiRMLib.hpp and code example rm-basic |
4. Use cases
Here follows basic examples showing some, but not all, features in esmini and companion tools. It should give an idea of the possibilities and limitations. For a full list of features and functions, see Command reference.
To quickly see available launch options, simply run the corresponding application with no arguments, for example:
./bin/odrviewer
4.1. View a scenario
4.1.1. Basic features
Specify a window and scenario file. Example:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc
Visualize trails from moving entities:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --trail_mode 3
Show entities as bounding boxes:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --bounding_boxes
4.1.2. Camera control
In addition to several OSG standard camera models esmini adds a set of special purpose camera modes that follows following scenario entities in various ways. Further, the user can add custom cameras via API or launch arguments.
Top level camera mode is selected on keys '1' to '8'. Mode 2-8 are OpenSceneGraph standard cameras. Mode 1 hands over camera control to esmini. In this mode, switch camera model by pressing 'k' which will toggle between all available esmini camera model.
The default camera model is esmini "orbit" which allows for rotating (left-mouse button), zooming (right-mouse button) and panning (scroll wheel / middle mouse button) while following the first entity.
To follow another vehicle press Tab
(next vehicle) or Shift TAB
(previous vehicle). Also Backspace
is mapped to previous vehicle, since shift-TAB do not work on all platforms.
For a complete list of cameras and functions, see Command reference.
esmini camera mode can be selected by launch argument. Here are a few common use cases:
Follow exact behind vehicle:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --camera_mode fixed
Orthogonal (no perspective) top view follow vehicle:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --camera_mode top
Follow vehicle from inside "driver" point of view:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --camera_mode driver
Follow vehicle with some flex and allow rotate, pan and zoom:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --camera_mode flex-orbit
In addition to pre-defined camera modes, the user can add custom cameras from API or via launch argument. Custom cameras can be fixed, semi-fixed (fixed position but unconstrained orientation) or relative current vehicle. Here is a few examples:
Custom camera in front of vehicle, e.g. sensor mount position:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --custom_camera 3,0,0.6,0,0
Custom camera with fixed position but dynamic orientation, always pointing at current vehicle:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --custom_fixed_camera 170,10,5
Custom fixed camera:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --custom_fixed_camera 240,15,10,3.5,0.2
Custom fixed top view camera:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --custom_fixed_top_camera 180,-1.54,2301,4.71239
4.1.3. Some key shortcut commands
Space |
Toggle pause/play simulation |
Enter |
Step simulation one frame forward |
Tab |
Move camera to next vehicle |
Shift-Tab or Backspace |
Move camera to previous vehicle |
'j' |
Toggle show entity trail points and lines (press mulitpe times to switch modes) |
',' |
Toggle bounding box modes |
Esc |
Quit |
See Command reference for a complete list of key shortcut commands.
4.1.4. Road network visualization
Visualize OpenDRIVE road features:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --road_features on
If a 3D model representation of the road network is missing (i.e. empty or missing SceneGraphFile element of the OpenSCENARIO RoadNetwork class), then esmini will generate a very simple model.
Example:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc
Add optional flat ground plane:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --ground_plane
The generated model can be saved:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --save_generated_model
Then look for generated_road.osgb
in the current directory.
4.1.5. Background color
esmini default background color is skyish, light blue. Change by launch argument --clear-color <r,g,b>, where r, g, b are the red, green, blue components as floating numbers in the range (0:1). Some examples:
Black background:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --clear-color 0,0,0
White background:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --clear-color 1,1,1
Gray background:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --clear-color 0.3,0.3,0.3
4.1.6. Anti-alias
esmini make use of OpenSceneGraph sub-sampling method for anti-alias. Default setting is 4 (samples). This can be controlled by the launch argument --aa_mode <samples>
. To disable anti-alias:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --aa_mode 0
4.2. Screenshots and video clips
esmini can save screenshots in uncompressed TGA format. These images can be converted into other image formats or even video clips in a post process.
esminiLib also supports grabbing screenshots via API, also from a pure off-screen rendering setup (requiring neither monitor nor graphics hardware). See image-capture code example.
Here follows a few examples using launch arguments and runtime key commands.
4.2.1. Save screenshots
Press 'c' at any time to store a single screenshot.
Press 'C' at any time to start storing screen shot for every frame onward. Press 'C' again to stop.
4.2.2. Create video clip of a scenario
First, run the scenario and automatically save screenshots for all frames:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --fixed_timestep 0.033 --capture_screen
Note We added the --fixed_timestep argument to make sure we get a smooth video with constant delta-time for a smooth and continuous video clip. If --fixed_timestep is skipped esmini will progress time between frames according to actual system time passed since last frame, which can vary over time for many reasons (e.g. due to esmini or other processes CPU load).
Then, create a video clip using ffmpeg:
ffmpeg -f image2 -framerate 30 -i screen_shot_%5d.tga -c:v libx264 -vf format=yuv420p -crf 20 out.mp4
The above ffmpeg command will create a MPEG4 video with reasonable compression (crf 20). To create a lossless, but still compressed, video e.g. for further post processing:
ffmpeg -f image2 -framerate 30 -i screen_shot_%5d.tga -c:v libx264 -qp 0 out.mp4
To change framerate of the output video, use the fps filter. E.g. from input 30 fps to output 15 fps:
ffmpeg -f image2 -framerate 30 -i screen_shot_%5d.tga -c:v libx264 -vf format=yuv420p,fps=15 -crf 20 out.mp4
Get ffmpeg: http://ffmpeg.org/download.html
Some details regarding H.264 encoding can be found here.
4.2.3. Off-screen rendering
Whenever esmini is launched with the --window <x-pos> <y-pos> <width> <height>
argument a viewer will be created with specified window. The --headless
flag controls whether any window will be "on screen" or only off-screen. Of course, --headless
in combination with no window (no --window argument) will skip rendering altogether, which is a very performance-saving mode to use when possible.
Example:
./bin/esmini.exe --window 60 60 4000 2000 --headless --capture_screen --osc .\resources\xosc\cut-in.xosc
will render images into a virtual frame buffer of size 4k x 2k pixels and then store to file. Note: The size of the virtual frame buffer is not limited by size of any connected display.
To run esmini completely without rendering, just omit the --window argument:
./bin/esmini.exe --osc .\resources\xosc\cut-in.xosc
--fixed_timestep 0.01 --record sim.dat
will run the specified scenario quickly and store a .dat file for later analysis or viewing (with replayer).
Note: If --fixed_timestep is omitted esmini will adapt timesteps to actual frametime, so a 30 seconds scenario will take 30 seconds - i.e. no performance gain.
4.2.4. Software rendering (no gfx hw)
By default any available 3D graphics hardware will be utilized. As fallback esmini (via OSG) will utilize Mesa3D which is a software implementation of the OpenGL graphics stack including parts normally hosted in the graphics hardware system. The Mesa3D approach is useful when running on cloud/cluster machines lacking graphics hardware, perhaps even lacking a window system (like slimmed and headless Ubuntu without X11 support).
Mesa3D on Linux
Mesa3D is normally installed with Linux distributions. Check OpenGL support with the following command:
glxinfo -B
If the command is not available, then Mesa3D utility package is probably missing. Install as:
sudo apt install mesa-utils
To run headless with a virtual frame buffer:
-
Activate xvfb:
Xvfb :99 -screen 0 1920x1080x24+32 & export DISPLAY=:99
-
Then run esmini as usual
For troubleshooting, the following command might give information about the graphics system:
lspci -vnn | grep VGA -A 12
Mesa3D on Windows
-
Grab the Mesa3D binary from here.
-
Unpack and place opengl32.dll in the same folder as esmini executable.
-
Then run esmini as usual
4.3. Logging
esmini can produce log files in different formats and for different purposes, explained next.
4.3.1. The basic text log file
By default esmini is creating a log.txt
in the folder from which esmini is launched. In case of esminiLib it will end up in the folder that the application linking esminiLib was launched from.
The log.txt includes the same information normally seen in the terminal window (stdout).
To disable output to terminal:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --disable_stdout
To disable creation of the logfile (log.txt
):
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --disable_log
Developer info: Define the symbol DEBUG_TRACE (either as compile flag or by uncomment this code line) to log more details, like what code module and line number is the origin of the log entry.
4.3.2. Scenario recording (.dat)
In addition esmini can save a .dat
file which captures the state of all entities. This file can later be used either to replay (see Replay scenario) the scenario or converted to .csv
for further analysis, e.g. in Excel.
To create a recording with regular timesteps:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --fixed_timestep 0.05 --record sim.dat
To convert the .dat file into .csv, do either of:
./bin/dat2csv sim.dat
or
python ./scripts/dat2csv.py sim.dat
Only a subset of the .dat file information is extracted. To extract some more info, e.g. road coordinates, run:
./scripts/dat2csv --extended sim.dat
You can create .dat files from multiple scenarios in a single command, like this on Linux (bash):
for f in ./my_scenarios/*.xosc; do ./bin/esmini --headless --fixed_timestep 0.05 --osc $f --record `basename $f .xosc`.dat; done
It also works in Git bash on Windows.
4.3.3. CSV logger
To create a more complete csv logfile, compared to the content of the .dat file, activate the CSV_Logger:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --fixed_timestep 0.05 --csv_logger full_log.csv
full_log.csv will contain more detailed states for all scenario entities. To also include collision detection:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --fixed_timestep 0.05 --csv_logger full_log.csv --collision
All collisions (overlap) between entity bounding boxes will be registered in the collision_ids
column of each entity. It will contain the IDs of any entities overlapping at given frame.
4.4. Replay scenario
Replay a scenario recording (.dat file):
./bin/replayer --window 60 60 800 400 --res_path ./resources --file sim.dat
The --res_path
is a special path directive should normally point to esmini/resources folder, where it will look for the OpenDRIVE file, 3D models and model_ids table (model_ids.txt). It also works to add one or multiple --path <path> directives, like for esmini.
Once loaded the scenario recording will start playing in normal speed. Here are a few key commands:
Space |
Toggle pause/play simulation |
Tab |
Move camera to next vehicle |
Shift-Tab or Backspace |
Move camera to previous vehicle |
'n' |
Toggle show active trajectories |
',' |
Toggle bounding box modes |
Esc |
Quit |
Arrow keys:
Left |
Pause and move to previous frame |
Right |
Pause and move to next frame |
Shift + Left |
Pause and jump 10 frames back |
Shift + Right |
Pause and jump 10 frames forward |
Ctrl + Left |
Jump to beginning |
Ctrl + Right |
Jump to end |
See Command reference for a complete list of key shortcut commands.
Some launch arguments:
--collision |
Pause and move to previous frame |
--quit_at_end |
Quit application when reaching end of scenario |
--repeat |
Loop scenario |
--dir <path> |
Directory containing replays to overlay, pair with "file" argument, where "file" is .dat filename match substring |
Examples:
Enable collision detection and pause playing at every occasion of collision:
./bin/replayer --window 60 60 800 400 --file sim.dat --res_path ./resources --collision
The .dat file in the clip above was created with the command:
./bin/esmini --headless --osc ./resources/xosc/ltap-od.xosc --disable_controllers --fixed_timestep 0.033 --record sim.dat
and then launched in replayer:
./bin/replayer --window 60 60 800 400 --file sim.dat --res_path ./resources --collision
Note: The --disable_controllers flag is just specified to disable the interactive controller that normally is activated in that specific scenario. For more information about controllers, see Controllers in esmini.
Combine esmini and replayer in a one-liner command:
./bin/esmini --headless --fixed_timestep 0.05 --record sim.dat --osc ./resources/xosc/cut-in.xosc;./bin/replayer --window 60 60 800 400 --res_path ./resources --file sim.dat
With bash (use Git Bash on Windows) preview all scenarios (.xosc files) in a folder, at double speed:
for f in my_scenarios/*.xosc; do ./bin/esmini --headless --fixed_timestep 0.05 --osc $f --record
basename $f .xosc
.dat;./bin/replayer --window 60 60 1200 600 --res_path ./resources --file basename $f .xosc
.dat --time_scale 2 --quit_at_end; done
just replace my_scenarios
with your folder path
4.4.1. Multiple scenarios in parallel
Next example, play multiple variants of a scenario in parallel:
./bin/replayer --window 60 60 800 400 --file sim --res_path ./resources --capture_screen --dir tmp
The --dir
argument points to a folder containing sim1.dat, sim2.dat and sim3.dat.
The --capture_screen
argument will save each frame as an image file on disk.
4.4.2. Save merged .dat files
Merged data from multiple .dat files can be saved into one single .dat file, e.g. for plotting:
./bin/replayer --file sim_ --dir . --save_merged sim_merged.dat
replayer will look in current folder (".") for any .dat file starting with "sim_", i.e. "sim_*.dat". The files will be merged and saved as sim_merged.dat.
4.5. View road network
4.5.1. Visualize OpenDRIVE geometry
By default odrviewer will create a simple 3D model of the OpenDRIVE description and populate it with sparse traffic:
./bin/odrviewer --window 60 60 800 400 --odr ./resources/xodr/fabriksgatan.xodr
Add OpenDRIVE features, like reference line and lanes, on top (toggle on 'o'):
./bin/odrviewer --window 60 60 800 400 --odr ./resources/xodr/fabriksgatan.xodr --road_features
Remove all traffic, just visualize the road geometry:
./bin/odrviewer --window 60 60 800 400 --odr ./resources/xodr/fabriksgatan.xodr --density 0
4.5.2. Evaluate OpenDRIVE connectivity
Increase traffic to exercise all junctions and lanes of the road network:
./bin/odrviewer --window 60 60 800 400 --odr ./resources/xodr/fabriksgatan.xodr --density 7 --road_features
Use custom 3D model:
./bin/odrviewer --window 60 60 800 400 --odr ./resources/xodr/fabriksgatan.xodr --model ./resources/models/fabriksgatan.osgb --density 5
Slow down traffic:
./bin/odrviewer --window 60 60 800 400 --odr ./resources/xodr/fabriksgatan.xodr --model ./resources/models/fabriksgatan.osgb --density 3 --speed_factor 0.6
4.5.3. Inspect OpenDRIVE geometry and road IDs
esmini odrplot is a small application that creates a track.csv file that can be plotted with another small Python script xodr.py:
./bin/odrplot ./resources/xodr/fabriksgatan.xodr
./EnvironmentSimulator/Applications/odrplot/xodr.py track.csv
Combine in a one-liner:
./bin/odrplot ./resources/xodr/fabriksgatan.xodr;./EnvironmentSimulator/Applications/odrplot/xodr.py track.csv
To navigate in the plot, click the four-arrow icon (see image below) and then use mouse according to:
Left button |
Pan (move left, right, up, down) |
Right button |
Zoom |
4.6. Plot scenario data
esmini can plot data in two ways: Off-line (plot content of .dat files) and runtime (a set of pre-defined values).
4.6.1. Off-line plotting
esmini provides a Python based script, plot_dat.py, to plot information in .dat files.
First, create a .dat file. For that we don’t need a window. Run esmini headless:
./bin/esmini --headless --fixed_timestep 0.05 --osc ./resources/xosc/acc-test.xosc --record sim.dat
The scenario looks like this:
Next, plot speed over time:
./scripts/plot_dat.py sim.dat --param speed
To see what parameters are available for plot:
./scripts/plot_dat.py sim.dat --list_params
will output a list similar to:
Plottable parameters:
id, model_id, obj_type, obj_category, ctrl_type, time, speed, wheel_angle, wheel_rot, centerOffsetX, centerOffsetY, centerOffsetZ, width, length, height, scaleMode, visibilityMask, x, y, z, h, p, r, roadId, laneId, offset, t, s
Most parameters does normally not make sense to plot, but it depends on the test case. Maybe it’s interesting to see when a car makes a lane change. Plot laneId over time:
./scripts/plot_dat.py sim.dat --param laneId
A plot can show multiple parameters:
./scripts/plot_dat.py sim.dat --param laneId --param speed
To plot trajectories, change X-axis parameter from time
to x
and plot y
parameter (over x):
./scripts/plot_dat.py sim.dat --x_axis x --param y
Lock axis aspect ratio, i.e. make axis scale equal:
./scripts/plot_dat.py sim.dat --x_axis x --param y --equal_axis_aspect
Plot merged .dat files (created as described in Save merged .dat files)
./scripts/plot_dat.py --param speed sim_merged.dat
Entity IDs will get an offset multiplier of 100, corresponding to the order of which the dat files were read by replayer. The mapping between ID range and dat file is printed to the console by replayer when the merged dat file is created, as the following example of four input files:
Scenarios corresponding to IDs (0:99): sim_fsm.dat Scenarios corresponding to IDs (100:199): sim_ref.dat Scenarios corresponding to IDs (200:299): sim_reg.dat Scenarios corresponding to IDs (300:399): sim_rss.dat
4.6.2. Runtime plotting
From version 2.32.0 esmini can plot some pre-configured data values during simulation. Simply add launch flag --plot
.
Features so far:
-
One or multiple entities can be selected by checkboxes on top
-
Selection of variables can be reduced by checkboxes to the right
By default the plotting is executing in a separate thread, having minimal impact on esmini performance. It is also possible to execute in synchronous mode, where plotting is part of esmini main thread and stepping is done in synch. The mode can be specified explicitly. Examples:
Simply enable plotting, default mode:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --fixed_timestep 0.05 --plot
Enforce synchronous mode:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --fixed_timestep 0.05 --plot synchronous
Asynchronous mode can be specified as well, although it’s default hence not needed:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --fixed_timestep 0.05 --plot asynchronous
Note: On macos OpenGL window must be part of main thread. Hence synchronous mode is enforced on macos.
4.7. Save OSI data
esmini can provide OSI groundtruth data in three ways:
-
Send over UDP
-
API to fetch via function call from a custom application
-
Save to OSI trace file
Only trace file way is described here. To save OSI data, add argument --osi_file [filename]
. The filename is optional. If omitted it will be named ground_truth.osi
. Example:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --osi_file
will create ground_truth.osi
in the current folder. The format is according to the OSI standard "Binary trace file", see OSI documentation 2.2.6 OSI trace files.
esmini provides a script, osi2csv.py, that converts an OSI trace file into .csv format. The script can also serve as example how to parse and extract data from an OSI trace file:
./scripts/osi2csv.py ground_truth.osi
will create ground_truth.csv.
(if file type association is not setup, try: python ./scripts/osi2csv.py ground_truth.osi
)
However the .csv file created with osi2csv will not contain all OSI information. To get a readable text file including the complete content of a OSI trace file you can make use of the Python script format/osi2read.py in the OSI project.
Use as following example:
../open-simulation-interface/format/osi2read.py --data ./ground_truth.osi --output ground_truth --type GroundTruth
it should create ground_truth.txth
which is readable in a text editor.
Note:
See OSI for Python on Windows for hints how to install dependencies (OSI and Protobuf).
The above script link points to OSI tag v3.5.0. In more recent OSI versions the script has moved to another folder: osi3trace/osi2read.py.
5. Scenario features
5.1. Speed profile
(introduced in esmini v2.23.0)
The OpenSCENARIO SpeedAction will reach a specified speed in a way described by additional attributes, e.g. shape and duration. To achieve multiple speed target over time you would need to add multiple SpeedAction events to the scenario. Moreover, there is no way to explicitly control the jerk (acceleration/deceleration rates).
However with the SpeedProfileAction (introduced in OpenSCENARIO v1.2) you can specify a series of speed targets over time, in one action. Optionally you can also specify dynamic constraints for jerk (gradually changing acceleration and deceleration), acceleration and speed.
If you run the example scenario speed-profile.xosc as follows:
./bin/esmini.exe --window 60 60 800 400 --osc ./resources/xosc/speed-profile.xosc --fixed_timestep 0.01 --record sim.dat
./scripts/plot_dat.py sim.dat --param speed
you should get the following plotted speed curves:
Note: You might need to add python
or python2
in front of the python command. And matplotlib is needed, see odrplot in Tools overview.
Tip: For quick experiments, skip the visualization and bring the plot asap by replacing the two commands with:
./bin/esmini.exe --headless --osc ./resources/xosc/speed-profile.xosc --fixed_timestep 0.01 --record sim.dat;./scripts/plot_dat.py sim.dat --param speed
The scenario includes two cars with identical speed profiles with one exception: The white Car1 use activate dynamic constraints by setting FollowingMode="follow" while the red Car2 will apply constant acceleration (linear interpolation) between speed targets by setting FollowingMode="position".
The action for white car:
<SpeedProfileAction followingMode="follow">
<DynamicConstraints
maxAcceleration = "5.0"
maxDeceleration = "10.0"
maxAccelerationRate = "4.0"
maxDecelerationRate = "3.0"
maxSpeed = "50"
/>
<SpeedProfileEntry time="0.0" speed="0.0"/>
<SpeedProfileEntry time="4.0" speed="10.0"/>
<SpeedProfileEntry time="4.0" speed="4.0"/>
<SpeedProfileEntry time="2.0" speed="8.0"/>
</SpeedProfileAction>
The time values are relative each other and start of the action. In this case the action is triggered at simulation time = 2 seconds. Initial speed for both cars is 0. First entry has therefor no effect since it applies speed = 0 at time = 2 (2 + 0). After additional 4 seconds (sim time = 6s) the speed target is 10 m/s. At sim time 10s the speed target is 4 m/s and finally after 2 more seconds the final speed target value is 8.0 m/s.
The "follow" mode deserves some additional explanation. As shown in the figure, it start and ends with zero acceleration. Then is basically will try to match the acceleration "lines" but cutting the corners according to acceleration and deceleration rate constraints. This way the intermediate speed values will not always be reached. However, the final speed value will be reached.
If the target speed or accelerations can’t be reached with given constraints the action will revert to linear mode (FollowingMode="position") for the remainder of the profile. This "failure" is logged.
Another approach would be to try to perform a best effort, but that would require additional input to decide whether to prioritize reaching specified speed targets or respect time stamps…
Note: The implementation of this feature is preliminary and experimental. Behavior and details might change.
Let’s manipulate the scenario in different ways to illustrate some special cases of the speed-profile feature.
5.1.1. Special case: Single entry
(Special case implementation introduced in esmini v2.23.1)
Compared to SpeedAction, the SpeedProfileAction offers more tools in terms of dynamic constraints. Hence it can be actually be useful also for single entry, i.e. reach a single target speed.
The implementation differs for the single entry case. Target speed will be reached if constraints allows for it. If not, the speed will still be reached, but later than specified.
There are three sub cases:
1. Speed can be reached within time
The speed profile will contain three phases: Jerk, constant acceleration, jerk.
Example:
Replace the four entries in speed-profile.xosc with the following ones:
<SpeedProfileEntry time="0.0" speed="0.0"/>
<SpeedProfileEntry time="4.0" speed="10.0"/>
Initial positive jerk will be applied until necessary acceleration is reached. Keep constant acceleration (linear segment in the speed profile) until negative jerk needs to be applied in order to reach target speed on time and at zero acceleration.
2. Speed can’t be reached in time due to acceleration constraints
This speed profile will also contain three phases: Jerk, constant acceleration, jerk.
Example:
Replace the four entries in speed-profile.xosc with the following ones:
<SpeedProfileEntry time="0.0" speed="0.0"/>
<SpeedProfileEntry time="3.0" speed="10.0"/>
Initial positive jerk will be applied until maximum acceleration is reached (or maximum deceleration). Keep constant acceleration (linear segment in the speed profile) until negative jerk needs to be applied in order to reach target speed at zero acceleration. Due to the acceleration limitation there will be a delay as well. The log file will include something like:
SpeedProfile: Constraining acceleration from 5.86 to 5.00
SpeedProfile: Extend 0.46 s
3. Speed can’t be reached in time due to jerk constraints
This speed profile will contain only two jerk phases.
Example:
Keep entries from last case, but change jerk settings as follows:
maxAccelerationRate="3.0"
maxDecelerationRate="2.0"
In this case the jerk settings are too weak to reach target speed in time. Not enough acceleration can be achieved in the given time window.
Positive jerk will be applied until negative jerk has to be applied in order to reach target speed at zero acceleration. Hence there is no room for a phase of constant acceleration. Due to the jerk limitation there will be a delay. The log file will include something like:
SpeedProfile: Can't reach target speed 10.00 on target time 3.00s with given jerk constraints, extend to 4.08s
5.1.2. What if current speed differ from the first entry?
Replace the four entries with the following ones:
<SpeedProfileEntry time="0.0" speed="3.0"/>
<SpeedProfileEntry time="4.0" speed="10.0"/>
What we see here is that for linear mode (FollowingMode="position") the speed of the first entry will apply immediately regardless of the current speed at the time of the action being triggered. For constrained mode (FollowingMode="follow") we see that the initial speed value (3.0) is overridden by the current speed (0.0). From there it will strive for the second entry, obeying the constraints.
The overall idea with the "follow" mode is to maintain continuity in the speed profile, up to jerk degree.
5.1.3. What if time is missing in entry?
Replace any entries with the following ones:
<SpeedProfileEntry speed="10.0"/>
Specified max acceleration will be applied until target speed is reached. Note: In the non-linear case and with multiple entries, the function will fail if the specified acceleration can’t be reached with given jerk constraints (maxAcceleration and maxDeceleration). Try to lower the maxAcceleration/deceleration in this case.
You can also combine entries with and without time constraint, like in following example:
<SpeedProfileEntry speed="10.0"/>
<SpeedProfileEntry time="3.0" speed="15.0"/>
Car will accelerate until speed 10 m/s is reached, then spend 3 seconds to reach 15 m/s.
5.1.4. Initial acceleration taken into account
(from release v2.23.2)
What if the acceleration is not zero when the SpeedProfileAction is started, for example interrupting an ongoing SpeedAction in Follow mode?
To maintain a continuous acceleration profile the action will use current acceleration as initial value. The standard states that the acceleration is expected to be zero at start and end of the action. The esmini interpretation is that the CHANGE is zero at start while the ACTUAL value is zero at end (since the action can only control acceleration while being active, not before).
Example: Once again starting from speed-profile.xosc, instead of setting an instant initial speed make it ramp up from 0 to 5 m/s over a duration of 4 seconds by tweaking the initial speed actions, for both entities, as below:
<SpeedActionDynamics dynamicsShape="linear" value="5.0" dynamicsDimension="time"/>
<SpeedActionTarget>
<AbsoluteTargetSpeed value="4.0"/>
</SpeedActionTarget>
The initial speed action will apply constant acceleration until time = 2.0 seconds, when the SpeedProfileAction is triggered. While the linear profile will jump to 0 m/s (as specified in first entry) the follow mode profile will just apply necessary jerk to reach target acceleration with respect to following entries.
Initial acceleration is also respected for the special case of a single entry, for example:
<SpeedProfileEntry time="3.0" speed="10.0"/>
the result becomes:
5.1.5. Sharp brake-to-stop profile
To skip jerk phase, e.g. as in emergency brake with an abrupt stop, just set AccelerationRate to a large number.
Example: Once again starting from speed-profile.xosc, change speed in the Init speed actions to 10.0 and replace the speed profile actions as below:
<SpeedProfileAction followingMode="follow">
<DynamicConstraints
maxAcceleration = "5.0"
maxDeceleration = "10.0"
maxAccelerationRate = "1E10"
maxDecelerationRate = "3.0"
maxSpeed = "50"
/>
<SpeedProfileEntry time="0.0" speed="10.0"/>
<SpeedProfileEntry time="4.0" speed="0.0"/>
</SpeedProfileAction>
Note: A drawback is that the setting will affect any acceleration phase in the complete profile. In other words, a limitation is that entries can’t have individual settings. In the example above it’s not problem since the first jerk phase is a deceleration while the second is an acceleration. If different rate values of same type are needed, it can be achieved by defining multiple SpeedProfile actions in a sequence, with individual performance settings.
5.1.6. More info
To get more understanding of the implementation, see a few slides here.
5.2. Road signs
(framework updated in esmini v2.25.0)
5.2.1. The system
Road signs are specified in the OpenDRIVE road network description file. A road sign is identified by up to four parameters:
-
country code
-
type
-
subtype
-
value
Country code is a two letter word according to the ISO 3166-1 alpha-2 system. Examples: Sweden = se, Germany = de.
Type and subtype defines the semantic meaning of the sign. The value is used when applicable to further specify information, e.g. speed or weight. Data type and meaning of the parameters may differ between countries. However a mapping to each country’s system is often feasible.
Examples: In Sweden a speed sign belongs to the category prohibition signs named "c". Within that category speed signs is a sub group with index 31. Within that group each sign has a value of speed/10, e.g. 5=50km/h, 10=100km/h. So the parameters for a Swedish speed limit 90 km/h sign becomes: se, c, 31, 9.
In Germany there are no similar categories. Instead all signs have a unique group or type number, e.g. speed signs make up group 274. Then the value defines the speed. No need for subtype. So the parameters for a German speed limit 90 km/h sign becomes: de, 274, -, 90.
5.2.2. esmini sign catalogs
Instead of hard-coding road sign support esmini will lookup the OSI code from a separate sign definition file, which is unique for each country. Name convention for the file is: country code
+ "_traffic_signals.txt". Example: Swedish catalog is named "se_traffic_signals.txt".
The file is a simple text format each line defining a sign ID and corresponding OSI type.
Swedish example: c.31-7=TYPE_SPEED_LIMIT_BEGIN
German example: 274=TYPE_SPEED_LIMIT_BEGIN
Type is mandatory. For esmini to distinguish between subtype and value different separators are used: "." before subtype and "-" before value. For some signs the value is optional, e.g. for speed signs it simply specify the speed limit. But for some signs it is used as a subtype to identify a variant of a sign, as in the German 101 type category.
Example files can be found in esmini/resources/traffic_signals.
Currently (v2.25.0) the esmini sign support is very limited. However, it should be fairly straight forward to add signs into existing catalogs and even catalogs (for more countries).
5.2.3. Sign 3D models
esmini road signs are created as follows:
-
A basic 3D geometry is modeled in Blender, e.g. round face for speed signs
-
Assign a material with any texture image mapped on the sign face
-
The 3D model is exported in
.dae
format (example here) -
Find textures of the needed sign(s) and convert them into some common format, e.g.
.png
. Recommended size is 256, but larger is OK. If possible make it size 2^n x 2^m to avoid performance penalty on low spec/old systems. -
Rename image to the same name as the texture image in the dae file (sign_image.png in the example dae)
-
Convert the .dae file into .osgb using osgconv. The texture will be embedded in the.osgb file by default.
A script is available as a starting point for automating the two last steps in the process above.
5.2.4. Example
The OpenDRIVE file straight_500m_signs.xodr includes a bunch of speed signs, some Swedish and some German ones. It shows how the signs are specified in terms of the country code, type, subtype and value attributes.
It also shows how to specify the 3D model using the name
attribute: If filename composed by country, type, subtype and value is not found, the name
attribute + ".osgb" will be used for a second and final attempt.
5.3. Expressions
From OpenSCENARIO v1.1 expressions including mathematical operations are supported in assignment of value to OpenSCENARIO parameters and XML element attributes. Examples:
<ParameterDeclaration name="PI" parameterType="double" value="3.14159"/>
<ParameterDeclaration name="KPH2MPS" parameterType="double" value="1/3.6"/>
...
<Position>
<LanePosition roadId="1" laneId="-1" s="40" offset="0">
<Orientation type="absolute" h="${sin(0.25*$PI)}"/>
</LanePosition>
</Position>
...
<SpeedActionTarget>
<AbsoluteTargetSpeed value="${110 * $KPH2MPS}"/>
</SpeedActionTarget>
h and (speed value) evaluates to 0.707106 and 30.55558 respectively
Supported expression operators and functions
Operators
|
|
|
|
|
multiply |
|
|
|
IEEE 754 remainder, see C++ std::remainder |
|
power |
|
|
|
|
|
|
|
|
|
equal |
|
not equal |
|
logical AND |
|
logical OR |
See expr.h for complete and updated list.
Functions
|
see C++ rint (Round to nearest, halfway cases to even. Aligned with IEEE 754 default) |
|
see C++ floor |
|
see C++ ceil |
|
square root |
|
see C++ pow |
Following functions are supported by esmini, but not specified in OpenSCENARIO <= 1.2, hence use with care since they may not work in other tools
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
See simple_expr.c for complete and updated list.
Strings
From v2.35.0 string concatenation is supported. Whenever a string is detected that is netiher an opeator nor a function, the complete expression is treated as a string and '+' (plus) operator will merge surrounding evaluated strings which also can be evaluated parameters.
This can be useful when running permutations of scenarios, where names of actors or storyboard elements can be made unique by including parameters values.
Examples:
${Event_overtake_ + $ActorName}
→ Event_overtake_Target4
${Event_change_speed_to_ + $Speed}
→ Event_change_speed_to_110
5.4. Parameter distributions
Parameter values and distributions can be specified in a separate Parameter value distribution file. This is a convenient way of running multiple variations, or parameter value permutations, of a single scenario.
Here’s an example: cut-in_parameter_set.xosc.
To run all permutations with esmini:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc
How to abort:
"ESC"
(in window) will terminate current run
"Ctrl-C"
(in terminal) will terminate all runs
The log files are now named according to:
log_<permutation>_of_<total#permutations>.txt
For example:
log_1_of_6.txt
relates to the 1:th run of 6, permutation index 0
log_4_of_6.txt
relates to the 4:th run of 6, permutation index 3
Of course this will apply also to custom log filenames. Example:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --logfile_path my_scenario.txt
will result in my_scenario_1_of_6.txt
…
Also any recording (.dat), osi and csv files will be named in similar way. Example:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --record sim.dat --osi_file gt.osi --csv_logger data_log.csv
will result in complete sets of files:
log_1_of_6.txt
…
sim_1_of_6.dat
…
gt_1_of_6.osi
…
data_log_1_of_6.csv
…
To run a specific permutation only, specify index like this:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --param_permutation 2
will run 3:rd permutation (of 6 available in this case)
Save the resulting OpenSCENARIO file with populated parameter values:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --save_xosc
will give cut-in_1_of_6.xosc
…
To view all results in parallel, make use of replayer like this:
./bin/esmini --headless --fixed_timestep 0.05 --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --record sim.dat ; ./bin/replayer --window 60 60 800 400 --res_path ./resources/ --file sim_ --dir .
5.4.1. Parallel execution
Making use of Python threading pool framework we can utilize any multiple CPU kernels and run scenario variants in parallel. This is handled by the script scripts/run_distribution.py.
Usage: run_distribution.py <esmini args>
Make sure to add at least the following esmini arguments:
--osc <scenario file>
--param_dist <parameter distribution file>
--fixed_timestep <timestep>
--headless
Example:
python ./scripts/run_distribution.py --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --fixed_timestep 0.05 --headless
Of course, the --headless (and --fixed_timestep) is not required. It is possible to run with viewer as well:
python ./scripts/run_distribution.py --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --window 60 60 800 400
but the windows will open on top of each other at the same location on the screen, so you need to manually move them apart to see the parallel runs. Anyway, this use case is probably not relevant so the recommendation is to run headless with fixed time step.
Putting it all together, execute the scenario permutations in parallel and then view the result in replayer:
python ./scripts/run_distribution.py --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --fixed_timestep 0.05 --headless --record sim.dat ; ./bin/replayer --window 60 60 800 400 --res_path ./resources/ --file sim_ --dir .
5.4.2. Finding out number of permutations
To find out the number of permutations of a specific scenario and parameter distribution, use the --return_nr_permutations
launch argument. Example:
./bin/esmini --osc ./resources/xosc/cut-in.xosc --param_dist ./resources/xosc/cut-in_parameter_set.xosc --return_nr_permutations
Output:
Nr permutations: 6
For scripters: esmini will also return the number of permutations as exit code, for simple use in scripts. After executing the above the exit code can be inspected/verified from shell as well:
PowerShell:
echo $LastExitCode
6
bash:
echo $?
6
5.5. Trajectories
Using OpenSCENARIO FollowTrajectoryAction you can define various types of trajectories for entities to follow.
esmini supports all three (as of OpenSCENARIO 1.2) shapes:
-
polyline
-
clothoid
-
nurbs
Here follows a few design choices for the polyline type that affect the behavior.
When FollowMode is follow
esmini will:
-
Calculate heading based on direction of the line segments. However corners are interpolated in order to look better and avoid discontinuities.
-
Calculate speed based on current speed (when action is triggered) and then with constant acceleration per segment reach destination vertex on time (or earlier if distance is too short vs time). This approach results in a continuous speed profile.
When FollowMode is position
esmini will:
-
For world coordinates, apply whatever heading is specified in the vertices (note that 0.0 is default). Lane and road coordinates will align to road direction as default.
-
For each segment, calculate constant speed needed to reach next vertex on time. This approach results in a discontinuous speed profile.
5.6. Friction
From v2.37.0 esmini supports road friction defined in OpenDRIVE lane material.
Each lane within a lane section can have one or multiple segments of individual friction values, specified by s-value and friction coefficient.
Default friction coefficient is 1.0 as of v2.37.0, but might change in later versions. Lower friction (<1.0, slippery) will be visualized as bluish color. The lower friction, the more blue color. High friction values (>1.0, "grippy") is indicated by redish color.
The friction is calculated per wheel. The values are reported in OSI ground-truth.
Example road: straight_highway_500m.xodr (used by test scenario friction_and_lane_change_edge_case.xosc)
5.7. SUMO integration
SUMO is a great open source traffic simulation tool. It has been integrated with esmini, making it possible to run SUMO simulations in esmini, and even mix or co-simulate SUMO vehicles with OpenSCENARIO vehicles.
NOTE: Even though SUMO has been linked with esmini for quite long, it’s on a experimental level and has not been used a lot. Hence, expect shortcomings and issues. The currently integrated SUMO version, as of esmini v2.37.1, is 1.6.0 while the current SUMO version is 1.19.0. There is plans to update SUMO version in esmini in a soon future.
This chapter explains how to create a SUMO simulation and then how to use it in esmini. For just using SUMO simulations there is no need to install SUMO, it is already embedded with esmini (as long as it has not been explicitly excluded, see Slim esmini - customize configration). Only to create SUMO content it has to be installed.
5.7.1. Install SUMO
-
Get SUMO from here: https://eclipse.dev/sumo/
-
Install normally, check any option to add sumo to environment path variable
5.7.2. Try example from esmini
-
Launch
sumo-gui
application -
File → Open Simulation
-
Navigate to
esmini/resources/sumo_inputs
and selectmulti_intersections.sumocfg
-
Set Delay (ms) to 100, otherwise simulation will run super-quick
-
Run (Play button or Simulation → Run)
5.7.3. Running the same scenario in esmini
-
In a terminal, from esmini root folder, run:
-
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/sumo-test.xosc
5.7.4. Create a SUMO simulation
First, there is a great complete guide at https://sumo.dlr.de/docs/
The main flow of our example is:
-
Convert an OpenDRIVE file into a SUMO road network
-
Identify some trips (start and end points) within the road network
-
From the trips, resolve complete routes wthich are assigned to vehicles and distribute over time
-
Create a configuration referring to the road network and route definition
Step-by-step:
-
Create a new folder, e.g. "my_sumo", for this example
-
Copy esmini/EnvironmentSimulator/Unittest/xodr/simple_3way_motorway.xodr into the new folder
-
Open a terminal in the new folder
-
Convert the xodr into a SUMO road network:
netconvert --opendrive fabriksgatan.xodr -o my_sumo.net.xml
-
Create some traffic using randomTrips.py (and duarouter):
randomTrips.py -n my_sumo.net.xml -e 60 -o my_sumo.trips.xml --route-file my_sumo.rou.xml
If this fails, it may be because of lacking .py file association. Try:
python3 randomTrips.py ... python randomTrips.py ... python3 tools/randomTrips.py ... python tools/randomTrips.py ...
-
Create the configuration file
Save the following lines into a new file named "my_sumo.sumocfg":
<?xml version="1.0" encoding="iso-8859-1"?> <configuration> <input> <net-file value="my_sumo.net.xml"/> <route-files value="my_sumo.rou.xml"/> </input> <time> <begin value="0"/> <end value="60"/> <step-length value="0.01"/> </time> </configuration>
-
Open Sumo, load the created config file "my_sumo.sumocfg" and run as in first example, Try example from esmini.
5.7.5. Connect a SUMO simulation to a scenario in esmini
Currently, as of esmini v2.37.1, SUMO is integrated in terms of a controller. To add a SUMO simulation, define an entity as usual, assign the SumoController and set its "filepath" property to the sumocfg file.
Now when we have a complete SUMO configuration, do the following steps in order to use it in esmini:
-
Copy the three files: my_sumo.net.xml, my_sumo.rou.xml and my_sumo.sumocfg (roadnetwork, routes, config) to
esmini/resources/sumo_inputs
-
Create a file named "my_sumo.xosc", in
esmini/resources/xosc
with the following content:<?xml version="1.0" encoding="UTF-8"?> <OpenSCENARIO> <FileHeader author="esmini team" date="2024-03-06T10:00:00" description="sumo integration example" revMajor="1" revMinor="2"/> <ParameterDeclarations/> <CatalogLocations> <VehicleCatalog> <Directory path="../xosc/Catalogs/Vehicles"/> </VehicleCatalog> <ControllerCatalog> <Directory path="../xosc/Catalogs/Controllers"/> </ControllerCatalog> </CatalogLocations> <RoadNetwork> <LogicFile filepath="../xodr/fabriksgatan.xodr"/> </RoadNetwork> <Entities> <ScenarioObject name="SumoVehicles"> <CatalogReference catalogName="VehicleCatalog" entryName="car_red"/> <ObjectController> <Controller name="SumoController"> <Properties> <File filepath="../sumo_inputs/my_sumo.sumocfg"/> </Properties> </Controller> </ObjectController> </ScenarioObject> </Entities> <Storyboard> <Init> <Actions/> </Init> <StopTrigger/> </Storyboard> </OpenSCENARIO>
-
Run from esmini root folder:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/my_sumo.xosc
6. Positioning
esmini supports all OpenSCENARIO position types. To support various use cases esmini add some mechanisms and interpretations:
-
Align by omit: Lacking position component values (x, y, z, heading, pitch, roll) in the scenario file is interpreted as "please align this component to the road surface". E.g. missing z value will project the position on the road surface, missing pitch value will ensure object is aligned with road pitch (e.g. up- and downhill).
-
PositionMode: Any position/object can be set and configured with respect to alignment via API calls. For example: x, y and heading can be set as absolute values while pitch and roll can be set as relative to the road surface.
See more details below in Position modes
-
Trajectory interpolation: Based omitted position components and followingMode setting esmini will apply different mechanisms to follow and interpolate trajectory vertices/control points.
See details below in Trajectory interpolation and alignment
6.1. Position modes
The purpose of position modes is to allow flexible and detailed control whether to interpret Z (elevation), heading, pitch and roll as absolute values or relative to the road. X and Y will always be interpreted as absolute values.
For some use cases orientation should be aligned to road surface, e.g. a vehicle simply driving along a hilly road. In some cases pitch and roll should be zero, e.g. signs and buildings along the road. External vehicle models can choose whether to report relative or absolute values. It can also be combination, e.g. explicit/absolute heading while z, roll and pitch are realative (to the road) - or any other combination.
The mode can be specified explicitly when calling a set position function. Example:
pos.SetInertiaPosMode(x, y, z, h, p, r, PosMode::Z_REL | PosMode::H_ABS | PosMode::P_REL | PosMode::R_REL);
In above example x, y, and h will be interpretated as absolute values while z, p, and r as relative to the road.
6.1.1. Persistent mode setting
In most cases the same mode setting will be used throughout the simulation for each object. For this reason, as conveience, the mode can be set once per object. Example:
pos.SetMode(Position::PosModeType::SET, PosMode::Z_REL | PosMode::H_ABS | PosMode::P_REL | PosMode::R_REL);
Then, when calling set functions, mode can be set to zero. Example:
pos.SetInertiaPosMode(x, y, z, h, p, r, 0);
or use the simple variants skipping the mode argument. Example:
pos.SetInertiaPos(x, y, z, h, p, r);
These cases will utilize the current setting.
6.1.2. Two types of settings
Note the type=SET argument of the SetMode() example above. It specifies as to which type of operations the settings applies to. There are two types of modes: SET and UPDATE. SET applies to API calls setting Cartesian coordinates. UPDATE applies to position updates by default controller and API calls setting Lane and Road coordinates.
This way a position object can, for example, maintain relative heading follow the curvature of the road when updated by the default controller, while heading can be set explicitly in terms of absolute value by API calls.
pos.SetMode(Position::PosModeType::UPDATE, PosMode::Z_REL | PosMode::H_REL | PosMode::P_REL | PosMode::R_REL);
pos.SetMode(Position::PosModeType::SET, PosMode::Z_REL | PosMode::H_ABS | PosMode::P_REL | PosMode::R_REL);
6.1.3. Default values
There will be a default setting for both SET and UPDATE which will be applied until changed by SetMode() function:
SET |
PosMode::Z_REL | PosMode::H_ABS | PosMode::P_REL | PosMode::R_REL |
UPDATE |
PosMode::Z_REL | PosMode::H_REL | PosMode::P_REL | PosMode::R_REL |
Further examples
Specify only mode for Z, reuse whatever setting for the remaining components:
pos.SetInertiaPosMode(x, y, z, h, p, r, PosMode::Z_ABS);
Set/reset default value for pitch, reuse whatever setting for the other components:
pos.SetInertiaPosMode(x, y, z, h, p, r, PosMode::Z_MASK & PosMode::Z_DEF);
6.2. Mode specification
Mode is coded in four chunks of three bits. One chunk for each of the position components Z, Heading, Pitch and Roll. Chunks are padded and aligned to segments of four bytes.
Z | Comment | Hex | Int | |
---|---|---|---|---|
SKIP |
XXXX XXXX XXXX XXX0 |
first bit is zero |
0x0000 |
0 |
Z_DEF |
XXXX XXXX XXXX XX01 |
first bit is one, second is zero |
0x0001 |
1 |
Z_ABS |
XXXX XXXX XXXX X011 |
first and second bit is one, third zero |
0x0003 |
3 |
Z_REL |
XXXX XXXX XXXX X111 |
first, second and third bits are all one |
0x0007 |
7 |
H | Comment | Hex | Int | |
---|---|---|---|---|
SKIP |
XXXX XXXX XXX0 XXXX |
first bit is zero |
0x0000 |
0 |
H_DEF |
XXXX XXXX XX01 XXXX |
first bit is one, second is zero |
0x0010 |
16 |
H_ABS |
XXXX XXXX X011 XXXX |
first and second bit is one, third zero |
0x0030 |
48 |
H_REL |
XXXX XXXX X111 XXXX |
first, second and third bits are all one |
0x0070 |
112 |
P | Comment | Hex | Int | |
---|---|---|---|---|
SKIP |
XXXX XXX0 XXXX XXXX |
first bit is zero |
0x0000 |
0 |
P_DEF |
XXXX XX01 XXXX XXXX |
first bit is one, second is zero |
0x0100 |
256 |
P_ABS |
XXXX X011 XXXX XXXX |
first and second bit is one, third zero |
0x0300 |
768 |
P_REL |
XXXX X111 XXXX XXXX |
first, second and third bits are all one |
0x0700 |
1,792 |
R | Comment | Hex | Int | |
---|---|---|---|---|
SKIP |
XXX0 XXXX XXXX XXXX |
first bit is zero |
0x0000 |
0 |
R_DEF |
XX01 XXXX XXXX XXXX |
first bit is one, second is zero |
0x1000 |
4,096 |
R_ABS |
X011 XXXX XXXX XXXX |
first and second bit is one, third zero |
0x3000 |
12,288 |
R_REL |
X111 XXXX XXXX XXXX |
first, second and third bits are all one |
0x7000 |
28,672 |
Looking up values in the table above, modes can be specified in numerous ways.
For example: To set world position with relative Z and absolute Pitch, reusing setting for the other components, any of the following identical examples will work:
pos.SetInertiaPosMode(x, y, z, h, p, r, PosMode::Z_REL | PosMode::P_ABS);
=
pos.SetInertiaPosMode(x, y, z, h, p, r, 0x7 | 0x300);
=
pos.SetInertiaPosMode(x, y, z, h, p, r, 0x307);
=
pos.SetInertiaPosMode(x, y, z, h, p, r, 0x0307);
=
pos.SetInertiaPosMode(x, y, z, h, p, r, 7 | 768);
=
pos.SetInertiaPosMode(x, y, z, h, p, r, 7 + 768);
=
pos.SetInertiaPosMode(x, y, z, h, p, r, 775);
6.2.1. Use cases
-
Simple 2D vehicle model. Report only X, Y and Heading. Align Z, pitch and roll to the road surface:
pos.SetMode(Position::PosModeType::SET, PosMode::H_ABS);
pos.SetInertiaPos(x, y, 0.0, h, 0.0, 0.0);
or use short version of the same function, applying zero to z, p, r:
pos.SetInertiaPos(x, y, h);
-
Advanced 3D vehicle model. Report all 6 DOF: X, Y, Z, Heading, Pitch and Roll. I.e. no explicit alignment to road surface:
pos.SetMode(Position::PosModeType::SET, PosMode::Z_ABS | PosMode::H_ABS | PosMode::P_ABS | PosMode::R_ABS);
pos.SetInertiaPos(x, y, 0.0, h, 0.0, 0.0);
or use short version of the same function, applying zero to z, p, r:
pos.SetInertiaPos(x, y, h);
-
Rotate a pedestrian 90 degree left to move forward facing the road driving direction, as on a hoverboard:
pos.SetHeadingRelative(1.5708);
pos.SetMode(Position::PosModeType::UPDATE, PosMode::H_REL);
Default controller will now move the pedestrian along the road but rotated 90 degree. -
Put pedestrian on a hoverboard moving sideways forward (heading=90 degrees) along the road, 1 meter above ground:
pos.SetHeadingRelative(1.57);
pos.SetZRelative(1.0);
pos.SetMode(Position::PosModeType::UPDATE, PosMode::Z_REL | PosMode::H_REL);
Default controller will now move the pedestrian along the road 1 meter above ground, with maintained heading relative to the road.
6.3. Trajectory interpolation and alignment
For convenience, esmini can calculate heading and align to the tangent of each segment (polyline and NURBS). Further, to smoothen motion esmini can interpolate corners of polyline trajectories. The behavior is based on omitted position components and followingMode
settings.
In general specified angles (heading, pitch and roll) will be interpolated over the segment between two vertices/control points. However, in many cases it’s prefered to just align heading with the tangent of the curve. For these cases it should not be necessary to specify the angles, but rather have esmini calculate them.
First, when z value is missing, i.e. elevation is not specified, esmini will project the points to the road surface. Availability of z will also affect alignment of orientation. General rule is that ground trajectories (elevation not specified) will align pitch and roll to the road surface, while these components will be set to zero for floating (specified z values) trajectories.
Missing angles will be handled according to the table below:
Input |
Result |
|
missing angle component |
z specified? |
align orientation to |
heading |
Yes |
trajectory |
heading |
No |
trajectory |
pitch |
Yes |
trajectory |
pitch |
No |
road surface |
roll |
Yes |
0.0 |
roll |
No |
road surface |
While moving along polylines, which tangent is not continuous, it’s usually preferred to interpolate/smoothen orientation along whole linear segments or at least when passing vertex corners.
The table below shows what interpolation method will be applied based on FollowingMode setting for the trajectory:
FollowingMode |
Interpolate mode |
position |
corner |
follow |
segment |
Polyline trajectory orientation interpolation can be disabled altogether by setting the disable_pline_interpolation
option (see esmini Launch commands).
6.4. Example
There is a code and scenario example illustrating the various interpolation and position modes:
Video clip:
This specific example includes four objects:
-
Pedestrian/skater on the road
Initially it’s standing still, aligned along the road x-axis (relative heading = 0). -
Box2 moving along a NURBS trajectory
Specified z will make it "floating" (not projected on ground). Omitted heading will have esmini calculate heading along the tangent. -
Box3 moving along another NURBS trajectory
Also floating. Demonstrating specified roll will be interpolated between control points. -
Box1 moving along a polyline trajectory
followingMode = position will make smoothly transitions at vertex corners.
The application will control and report the motion for the skater. At 5 seconds it will rotate 90 degree and move this way along the road, like riding a hoverboard. Then at 15 seconds it will move one meter up from road surface (z=1 relative road) and also rotate back 90 degrees (heading=0 relative road) and move this way along the road, like on a flyboard. During the complete scenario pitch and roll is set to 0 (absolute value - not aligned to road).
6.5. Relative positions and routes
For the OpenSCENARIO RelativeRoadPosition
and RelativeLanePosition
the ds
and dsLane
attributes specifies an offset along the s-axis of the road and lane respectively. In case the offset results in a s-value outside the boundary of the current road, the target location will be searched via connected roads and lanes, even through junctions.
In case the action object (the one being positioned) has been assigned a route, the route will be respected, i.e. the target location will be along the route. If the action object lacks a route, any route of the referenced entity will be respected instead. If neither objects has a route assigned the default routing rules will apply.
In the image below two cars, red and blue, are being positioned by means of RelativeRoadPosition
or RelativeLanePosition
with the white car as the reference entity. They have both the same ds
value, i.e. same longitudinal distance (along s-axis) from the white car. Since the red car has an assigned route, it ends up along that route. The blue car, lacking a route, will end up along the route of the referenced white car.
7. Controllers
7.1. Controller concept
OpenSCENARIO provides the controller concept as a way to outsource control the motion and appearance of scenario entities. For the OpenSCENARIO description of the controller concept, see OpenSCENARIO User Guide.
Controllers can be assigned to object of type Vehicle or Pedestrian. Once assigned, controllers are activated for a given domain (e.g longitudinal, lateral, Lighting, Animation) using the ActivateControllerAction. It’s also possible to activate immediately as part of the AssignControllerAction.
While the ActivateControllerAction is executing, the Controller assigned to that object will manage specified domain(s). Controllers may be internal (part of the simulator) or external (defined in another file).
Intended use cases for Controllers include:
- Specifying that a vehicle is controlled by the system under test.
- Defining smart actor behavior, where a controller takes intelligent decisions in response to the road network or other actors. Hence, controllers can be used, for example, to make agents in a scenario behave in a human-like way.
- Assigning a vehicle to direct human control.
The Controller element contains Properties, which can be used to specify controller behavior either directly or by a file reference.
Although OpenSCENARIO from v1.2 supports assignment of multiple controllers to an object, esmini is currently only supporting one controller to be assigned (and hence activated) to an object at the time.
7.2. Background and motivation
esmini version < 2.0 totally lacked support for controllers. Instead some similar functionality were implemented as part of different example-applications. For example, EgoSimulator provided interactive control of one vehicle. There was also an "external" mode which allowed for an external vehicle simulator to report current position for the typical Ego (VUT/SUT…) vehicle.
First, this approach made the example code of simple applications complex. Secondly, it limited the use cases of esmini since functionality was tightly embedded in the applications.
Controllers provides a much more flexible way of adding functionality to esmini, in a way harmonizing with the standard (OpenSCENARIO 1.X). A side effect of this "outsourcing" of functionality is that the former demo applications could be reduced to a minimum both in number and size.
7.3. Brief on implementation
Controllers was introduced in esmini v2.0. Briefly it works as follows:
There is a collection of embedded controllers coming with esmini. Each controller inherit from the base class Controller (Controller.h/cpp). In order for esmini to be aware of the existence of a controller it has to be registered. This is done through the ScenarioReader method RegisterController
. It will put the controller into a collection to have available when the scenario is being parsed. So all controllers need to be registered prior to loading the scenario.
A controller is registered with the following information:
-
Its static name which is used as identifier.
-
A pointer to a function instantiating the controller.
This architecture makes it possible for an external module to create a controller and registering it without modifying any of esmini modules. In that way it is a semi-plugin concept, you can say.
Note: Even though ScenarioReader have a helper function for registering all the embedded controllers the RegisterController can be called from any module directly, at any time prior to scenario initialization.
esmini catalog framework supports controllers as well, so controllers can be defined in catalogs as presets, just like vehicles and routes for example.
7.4. How it works for the user
Controllers are completely handled in the OpenSCENARIO file. With one exception: esmini provides the --disable-controllers option which totally ignore any controllers, just performing the scenario with DefaultControllers, which can be handy when previewing and debugging scenarios.
In terms of OpenSCENARIO a controller is assigned to an entity (object) in any of two ways:
-
As part of the Entities section and ScenarioObject definition, using the ObjectController element.
-
The AssignControllerAction which can be triggered as any action at any time during the simulation.
Once assigned the controller must finally be activated using the ActivateControllerAction which can be triggered at any time. It provides an attribute to specify which domain(s) to control: Lateral, Longitudinal or both.
Example 1: Implicit assignment of controller using the ObjectController
element:
<Entities>
<ScenarioObject name="Ego">
<CatalogReference catalogName="VehicleCatalog" entryName="$HostVehicle"/>
<ObjectController>
<Controller name="MyController" >
<Properties>
<Property name="esminiController" value="InteractiveController" />
<Property name="speedFactor" value="1.5" />
</Properties>
</Controller>
</ObjectController>
</ScenarioObject>
</Entities>
Example 2: As above, but using catalog reference:
<Entities>
<ScenarioObject name="Ego">
<CatalogReference catalogName="VehicleCatalog" entryName="$HostVehicle"/>
<ObjectController>
<CatalogReference catalogName="ControllerCatalog" entryName="interactiveDriver" />
</ObjectController>
</ScenarioObject>
</Entities>
Example 3: Activate controller in the Init
section:
<Init>
<Actions>
<Private entityRef="Ego">
<PrivateAction>
<TeleportAction>
<Position>
<LanePosition roadId="0" laneId="-3" offset="0" s="$EgoStartS"/>
</Position>
</TeleportAction>
</PrivateAction>
<PrivateAction>
<ActivateControllerAction longitudinal="true" lateral="true" />
</PrivateAction>
</Private>
</Actions>
</Init>
Example 4: Assign and activate the controller in the Storyboard
section:
<Event name="InteractiveEvent" maximumExecutionCount="1" priority="overwrite">
<Action name="AssignControllerAction">
<PrivateAction>
<ControllerAction>
<AssignControllerAction>
<CatalogReference catalogName="ControllerCatalog" entryName="interactiveDriver" />
</AssignControllerAction>
</ControllerAction>
</PrivateAction>
</Action>
<Action name="ActivateControllerAction">
<PrivateAction>
<ControllerAction>
<ActivateControllerAction longitudinal="true" lateral="true" />
</ControllerAction>
</PrivateAction>
</Action>
<StartTrigger>
<ConditionGroup>
<Condition name="" delay="0" conditionEdge="none">
<ByValueCondition>
<SimulationTimeCondition value="20" rule="greaterThan"/>
</ByValueCondition>
</Condition>
</ConditionGroup>
</StartTrigger>
</Event>
To disable any controller simply apply ActivateControllerAction on no domain, like:
<ActivateControllerAction longitudinal="false" lateral="false" />
That should work in all OpenSCENARIO supporting tools. In esmini you can also assign DefaultController
which will disconnect any assigned controller.
7.5. The ghost concept
The purpose of the ghost concept is to support external driver models. The idea is to launch a forerunner, which performs the maneuvers in the OSC file. The resulting trajectory (also called trail), including speed and heading info, is registered. The driver model can then use the ghost trail as guidance. More specifically, the driver model can probe the trail at any timestamp or distance from its current pos and use the result for both steering and speed target.
A typical use case is having an external Ego vehicle simulator, including System Under test (SUT) and a driver model. Instead of the driver model (in the external simulator) being aware of and execute scenario maneuvers it can hand over to esmini to play the maneuvers and then simply try to mimic it, following in ghost’s trail. This way the external simulator can benefit from esmini scenario engine and just concentrate on following the ghost.
The ghost feature is available in the following esmini embedded controllers:
There are two different methods to probe the ghost trail:
-
By time: Ask for ghost state (pos, heading, speed…) at specific timestamp
-
By distance: Ask for ghost state at a specific distance, from a position (typically current Ego position)
Both methods will return a RoadInfo structure including position details e.g. world position (absolute and relative Ego), road coordinates, road pitch, trail heading, heading angle to the target point and ghost speed at that point.
typedef struct
{
float global_pos_x; // target position, in global coordinate system
float global_pos_y; // target position, in global coordinate system
float global_pos_z; // target position, in global coordinate system
float local_pos_x; // target position, relative vehicle (pivot position object) coordinate system
float local_pos_y; // target position, relative vehicle (pivot position object) coordinate system
float local_pos_z; // target position, relative vehicle (pivot position object) coordinate system
float angle; // heading angle to target from and relative vehicle (pivot position object) coordinate system
float road_heading; // road heading at steering target point
float road_pitch; // road pitch (inclination) at steering target point
float road_roll; // road roll (camber) at target point
float trail_heading; // trail heading (only when used for trail lookups, else equals road_heading)
float curvature; // road curvature at steering target point
float speed_limit; // speed limit given by OpenDRIVE type entry
id_t roadId; // target position, road ID
id_t junctionId; // target position, junction ID (SE_ID_UNDEFINED if not in a junction)
int laneId; // target position, lane ID
float laneOffset; // target position, lane offset (lateral distance from lane center)
float s; // target position, s (longitudinal distance along reference line)
float t; // target position, t (lateral distance from reference line)
} SE_RoadInfo;
Time method
This approach is trivial, it will give the state of ghost at the specified timestamp using the API call:
Distance method
The API call is:
The method needs some further explanation. The basic idea is to look specified distance along the ghost trail and receive information at that target point. The tricky part is where to start measuring the distance from. This is how it works:
-
From Ego, find closest point on trail (blue point)
-
From that point, look forward x meters along trail (red dot)
-
Get info from that ”target” point
Ideally, two points are used: One closer for target speed and one more distant for steering target.
Tuning parameters:
-
Lookahead distance along trail
-
Typically speed dependent (look further at higher speeds)
-
Potentially decrease distance with increased curvature (to stay on road)
-
-
Headstart time
-
As small as possible, but enough for required lookahead distance
-
Note: If lookahead distance is too large, the steering target angle becomes irrelevant, leading to driving off road
Actions and triggers
Most actions that affects motion will be transferred from the original entity (Ego) to its ghost, for example: SpeedAction
, LaneChangeAction
and TeleportAction
. In addition, triggers will be affected in the following ways:
-
Entity triggers: Replace any occurrence of original entity with ghost in the list of
TriggeringEntities
. For example, if the vehicle should brake when reaching a specified position, then it’s the ghost that should trigger the action - not the original entity. -
Other triggers: Restart ghost from where the Ego is now, in effect rewind ghost
headStartTime
seconds and recreate trajectory from there. The purpose is to enable ghost to react on triggers that happens in the "real" scenario (which plays outheadStartTime
seconds behind the ghost) -
SimulationTime
trigger conditions for events where Ego is the actor will be adjusted wrt ghostheadStartTime
time. For example, if vehicle should brake after 20 seconds of driving (simulationTime = 20), then it will be changed to simulationTime = 17 (for headstart 3s), since the ghost has been driving for 20 seconds at that point.
Examples
The following examples shows two different triggers for a lane change action. First example uses TraveledDistanceCondition. Ghost will be the triggering entity and since this is independent of other road users, there is no need to restart ghost.
The second example, on the other hand, uses TimeHeadwayCondition which calculates the time gap between the white Ego car and the red Target car, hence depending on other road users. Since ghost is running ahead of time it does not make sense to involve ghost in such conditions, hence Ego will be the triggering entity.
Example 1: Trigger based on traveled distance - no restart
<ByEntityCondition>
<TriggeringEntities triggeringEntitiesRule="any">
<EntityRef entityRef="Ego"/>
</TriggeringEntities>
<EntityCondition>
<TraveledDistanceCondition value="20"/>
</EntityCondition>
</ByEntityCondition>
Example 2: Trigger based on time headway - restart ghost
<ByEntityCondition>
<TriggeringEntities triggeringEntitiesRule="any">
<EntityRef entityRef="Ego"/>
</TriggeringEntities>
<EntityCondition>
<TimeHeadwayCondition
entityRef="Target"
value="4.0"
freespace="false"
coordinateSystem="road"
relativeDistanceType="longitudinal"
rule="lessThan"/>
</EntityCondition>
</ByEntityCondition>
Note that the strategy results in desired behavior in probably the majority of cases, but not all. There might be exceptions where another behavior is expected or required. The manipulation described above is handled by the methods SetupGhost()
and ReplaceObjectInTrigger()
in ScenarioEngine.cpp which can be modified to fit custom needs.
Additional notes on using the ghost feature
Timestamps in log are always of same reference. Whenever a ghost is involved the time will start at -headStartTime
, in order for the actual scenario to start at simulationTime = 0. Example: Assuming headStartTime = 3s
, a log message for an Ego action like:
-2.100: SpeedChange standbyState → startTransition → runningState
means that action SpeedChange (Ego action that has been moved to ghost) is performed 0.9 (-2.1 + 3) seconds after ghost was launched. This would, for example, be the case for a simulationTime = 0.9
condition.
7.6. esmini embedded controllers
Below is a listing of some of the available controllers in esmini. Note that only DefaultController is related to the OpenSCENARIO standard. The other ones are esmini-specific and will not work in other tools (so far there is no standard plugin-architecture for controllers to enable moving between tools). For updated and complete definition of controllers and their parameters, see ControllerCatalog.xosc.
7.6.1. DefaultController
Performs actions exactly as specified in the OpenSCENARIO file. Assigned to entities by default. Can be assigned at any point to "unassign" any currently assigned controller.
Domain: |
n/a (will always take over both Lateral and Longitudinal domains) |
Properties: |
None |
Example: |
n/a (any scenario not explicitly assigning and activating another controller) |
7.6.2. InteractiveController
A simple vehicle model controlled by the user via keyboard (arrow keys).
Domain: |
longitudinal and/or lateral (independent) |
||||
Properties: |
|
||||
Example: |
7.6.3. FollowGhost
A simple driver model. A ghost-twin performing the events a few seconds ahead. The entity will then do its best to mimic the motion and speed of the ghost by follow the trajectory. The primary purpose of this controller is to provide an example, but it can be useful to smoothen out the sometimes synthetic feel of pure default controller.
Domain: |
longitudinal and lateral (in combination only). |
||||||||||||
Properties: |
|
||||||||||||
Example: |
7.6.4. ExternalController
The entity will not be moved by the scenario. Instead its state (position, rotation …) is expected to be reported from external simulator via API, e.g. SE_ReportObjectPos. Ghost trajectory can optionally be created for an external driver model to use as reference.
Domain: |
longitudinal and/or lateral (independent). |
||||||
Properties: |
|
||||||
Example: |
7.6.5. SumoController
A way of integrating SUMO controlled vehicles in a scenario. OpenSCENARIO vehicles are reported to SUMO, and SUMO vehicles are reported back to esmini. A reference to a SUMO config file is provided as input to the controller. See cut-in_sumo.xosc for an example.
Domain: |
longitudinal and lateral (in combination only). |
||
Properties: |
|
||
Example: |
7.6.6. ALKS_R157SM
Based and inspired by Regulation 157 Safety Models. Includes four safety models relating to the (UNECE ALKS regulation #157). The controller includes preliminary and experimental implementation of four models:
-
Regulation
Characteristics of expected system performance as stated in the UNECE Reg 157 Paragraph 5.2.5.2. -
ReferenceDriver
Reference model of a "typical" human driver as specified in UNECE Reg 157 Annex 4 - Appendix 3. -
RSS
Responsibility-Sensitive Safety performance model proposed and described in Shalev-Shwartz, Shai, Shaked Shammah, and Amnon Shashua. "On a formal model of safe and scalable self-driving cars." arXiv preprint arXiv:1708.06374 (2017). -
FSM
Fuzzy Safety Model proposed and described in Mattas, Konstantinos, et al. "Fuzzy Surrogate Safety Metrics for real-time assessment of rear-end collision risk. A study based on empirical observations." Accident Analysis & Prevention 148 (2020): 105794.
Domain: |
longitudinal and lateral (in combination only). Note: No advanced steering, just maintaining current lane offset. |
||||||
Properties: |
Further properties see |
||||||
Examples: |
alks_r157_cut_in_quick_brake.xosc |
Example usage
Run example alks_r157_cut_in_quick_brake.xosc:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/alks_r157_cut_in_quick_brake.xosc
Change model:
-
Open
resources/xosc/alks_r157_cut_in_quick_brake.xosc
in a text editor -
In the ALKS_R157SM_Controller, change
<Property name="model" value="Regulation"/>
to
<Property name="model" value="ReferenceDriver"/>
-
Save and run again:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/alks_r157_cut_in_quick_brake.xosc
You can hardly see any difference. Let’s run the two variants again and record for post analysis.
-
Set model to "ReferenceDriver" (by edit the file as described above)
-
Run:
./bin/esmini --headless --fixed_timestep 0.02 --osc ./resources/xosc/alks_r157_cut_in_quick_brake.xosc --record sim-ref.dat
-
Set model to "Regulation"
-
Run:
./bin/esmini --headless --fixed_timestep 0.02 --osc ./resources/xosc/alks_r157_cut_in_quick_brake.xosc --record sim-reg.dat
-
View scenarios in parallel:
./bin/replayer --window 60 60 800 400 --res_path ./resources --dir . --file sim- --view_mode boundingbox
-
Create a merged .dat file and plot it:
./bin/replayer --file sim- --dir . --save_merged sim_merged.dat
./scripts/plot_dat.py --param speed sim_merged.dat
Something like this should show:
Of course you can run also FSM and RSS models in similar way and merge all four .dat files. For more information about replayer
, see "Plot merged .dat files" example in Plot scenario data section.
7.6.7. FollowRoute
Implementation of the Lane Independent Routing Model (LIRM) developed as part of the master thesis project Olsson, Daniel; Rylander, Eric: "A Framework for Verification and Validation of Simulation Models in esmini".
From the report: "LIRM has solved the issue with lane dependent routing that esmini had, by implementing a new controller that extends the behavior of the pathfinding and route following to incorporate lane changes."
This controller enhance the capabilities for an entity to find a path through the road network according to a specified route (basically a set of waypoints). The main advantage, compared to the route handler of the default controller, is that lane changes will be injected by the path finding algorithm in search for optimal path. The model will also take route strategy (Shortest, Fastest or Least Intersections) into consideration, which the default controller does not yet support.
A good example is found in follow_route_with_lane_change.xosc. The vehicle start out in left-most lane and needs to make three lane changes in order to exit the highway.
./bin/esmini --window 60 60 800 400 --osc ./EnvironmentSimulator/Unittest/xosc/follow_route_with_lane_change.xosc --trail_mode 1
Domain: |
longitudinal and/or lateral (independent). |
||||||
Properties: |
|
||||||
Example: |
7.6.8. UDPDriverController
An UDP interface for external driver models or vehicle simulators. The idea is to allow external devices to control entities in an ongoing esmini simulation, over network.
Concept:
-
Assign the controller to any scenario vehicle (in the OpenSCENARIO file)
-
Send input to esmini controller
-
The vehicle will be updated by the controller accordingly (and bypassing esmini default controller)
-
receive ground-truth (OSI over UDP) from esmini
There are a few input mode options:
-
DRIVER_INPUT
Control a simple vehicle model in terms of pedals and steering input. Vehicle will apply latest received values until update is received.Parameters: throttle
brake
steeringAngle
-
VEHICLE_STATE_XYZHPR
Report explicit complete state, ignoring road geometry. Useful when advanced vehicle dynamics is involved and vehicle for example is pitching as result of acceleration and rolling as result of steering. Optional flag to enable dead reckoning on esmini side, in which case esmini will move entity according to latest received heading and speed (basically extrapolating).Parameters: x
y
z
h
(eading)
p
(itch)
r
(oll)
speed
wheelAngle
(wheel yaw/stering angle)
deadReckon
(flag) -
VEHICLE_STATE_XYH
Report explicit partial state.z
,pitch
androll
are aligned to the road geometry.Parameters: x
y
h
(eading)
speed
wheelAngle
(wheel yaw/stering angle)
deadReckon
(flag) -
VEHICLE_STATE_H
Minimal explicit state, useful when the driver model output is only heading and speed.Parameters: h
(eading)
speed
wheelAngle
(wheel yaw/stering angle)
deadReckon
(flag)
For more info see: UDPDriverController.pdf
Demo (running a somewhat outdated testUDPDriver.py):
7.6.9. OffroadFollowerController
A simple "follow-the-leader" controller which aims to keep a specified distance to a specified leading vehicle. The controller utilizes a simple 2D "bicycle" vehicle model. Each time-step the controller finds out the direction and distance to the lead vehicle. Based on this, the acceleration and steering input is calculated and provided to the vehicle model.
The term off-road indicates that the controller does not consider any road network features. Note that it still can run on roads, parking lots, just that it’s ignoring it in terms of lanes and road-marks.
Below is a video clip showing the controller in action on an open paved area. The white lead vehicle is controlled interactively by the user via arrow keys. The controller is assigned to the red car which then strives to follow the lead car at the distance of 20 meters.
7.7. How to add a new controller
Below are the steps to add new controller in esmini:
-
Create a sepreate controller module in the controller folder. Suggestion is to copy one of the existing and use as a starting point.
-
Register the controller in the LoadControllers() fuction in EnvironmentSimulator/Modules/ScenarioEngine/SourceFiles/ScenarioReader.cpp
-
Create Instantiate method in the new controller. The name shall be unique eg InstantiateControllerLooming
-
In the new controller, Type name shall be unique eg, CONTROLLER_LOOMING_TYPE_NAME
-
Add controller type and it shall be unique in EnvironmentSimulator/Modules/Controllers/Controller.hpp
-
Make sure GetTypeStatic() and GetTypeNameStatic() and methods returns the unique type and type name values
8. OpenSceneGraph and 3D models
esmini make use of OpenSceneGraph (OSG) for visualization of the scenario. The OpenSCENARIO files can optionally refer to existing 3D models of the static environment (scene graph) and dynamic objects (entities). If the scene graph reference is missing, esmini will try to generate a basic model based on the OpenDRIVE road network description. Currently esmini only supports OSG native .osgb 3D file format. However, there are ways to convert 3D models as described next.
OpenSceneGraph (osg) includes readers and writers for quite a few 3D file formats. It comes with a demo application, osgconv, a command line tool that simply takes one file as input and outputs the same content in a different format.
Examples:
Convert from fbx to osgb format:
osgconv car.fbx car.osgb
Convert from osgb to fbx format:
osgconv car.osgb car.fbx
osgconv has a lot of useful options. Run osgconv -h
for more info. Here’s a few examples:
osgconv in.fbx out.osgb --compressed
will compress and embed textures
osgconv in.fbx out.osgb -t 1,5,7
will translate the model (x=1, y=5, z=7)
osgconv in.fbx out.osgb -o -90-1,0,0
will rotate the model (rotate -90 deg around X-axis)
osgconv in.fbx out.osgb -o -90-1,0,0 --use-world-frame
as above but pivot point world origin
A useful environment variable is OSG_OPTIMIZER
which affects the structure and content of the scene graph. For example, if the osgb file includes cloned sub trees, like motorway railings, these might not be populated in the fbx file. To solve that set:
OSG_OPTIMIZER = FLATTEN_STATIC_TRANSFORMS_DUPLICATING_SHARED_SUBGRAPHS
Exact syntax depends on your environment. Examples:
bash |
|
powershell |
|
Then run the osgconv
command (in the same terminal where you defined the environment variable).
osg native format is osgb
which stands for OpenSceneGraph binary format. It’s a stable and compact (quick to load) format, which also can embed textures and animations. Thanks to the osgconv tool, esmini can get away with only supporting the .osgb format and still indirectly support many other 3D formats.
8.1. Get osgconv
There are pre-built binaries available, see below. But for full capabilities, including support for converting from 3D file formats .dae (collada) and .fbx (Autodesk Filmbox) you have to build OpenSceneGraph (osg) yourself.
8.1.1. Build yourself
Prerequisites
The only prerequisite step is for Linux to install some dependecies:
-
Run
software-properties-gtk
or open the "Software & Updates" application -
then under the "Ubuntu Software" tab click "Source code" and add a server of your choice, e.g. within your country
-
Run
sudo apt-get build-dep openscenegraph
Reference Step 1 & 2: https://unix.stackexchange.com/a/614082
Build
You can then try this script: compile_osg_apps.sh. It will first fetch and install FBX SDK and then build OSG with FBX and DAE support. Find more details and instructions in the header of the script.
For more info regarding building OSG for Linux, here’s a great guide:
https://vicrucann.github.io/tutorials/osg-linux-quick-install/
But note that it does not consider FBX support.
Find more info on COLLADA via this old link.
8.1.2. Pre-build binaries
Windows
Binaries including FBX but not DAE support, see here:
https://objexx.com/OpenSceneGraph.html
Linux
sudo apt-get update
sudo apt-get -y install openscenegraph
8.2. Convert osgb models for use in Unity
Here follows an example of how to convert .osgb models into .fbx format for use in the Unity3D framework.
-
Open a command prompt in the folder where your model.osgb is
-
Run command:
osgconv model.osgb out/model.fbx -s 100,100,100
"-s …" is for scaling which typically is needed for fbx files. Potentially it also needs to be oriented according to Unity coordinate system. In that case try:
osgconv model.osgb out/model.fbx -s 100,100,100 --use-world-frame -o 120—0.5773503,-0.5773503,-0.5773503
A folder named "out" should have been created and including the model.fbx plus any texture files
8.3. Import into Unity
-
Drag the resulting fbx file, and all textures, into a unity project, preferably an empty folder
-
Add the model to the Scene hierarchy
-
Select the model and int the "Inspector", select the "Materials" tab
-
Change the "Location" to "Use External Materials (Legacy)" and click "Apply"
-
Open the automatically created "Materials" folder (next to the model file)
-
Select all materials and change
for standard template: "Rendering Mode" to "Cutout" (good default option)
for HDRP template: "Surface type" to "Transparent" and check "alpha clipping"
That should basically be it.
8.4. Colors, textures and wheel rotations
See issue 63 for brief info.
And these two scripts operating on textures and material respectively, can potentially provide some inspiration:
- bake_signs.sh
- fix_dae_materials.py
8.5. Lighting
esmini use a very simple lighting model. Adding a global low ambient plus a stronger directed "sun" light. This is a simplistic model of a bright day where everything is visible but sides facing towards the sun gets brighter than those facing away.
The ambient intensity is 0.4. The "sun" x,y,z position is (-7500, 5000, 10000) and intensity 0.8.
Now, it has been reported that some custom models looks very dark on sides facing away from the sun. The main reason is often that the material definition do not have any ambient contribution.
There are two solutions:
Solution 1: Fix the material
Convert the model to Collada .dae format, example:
osgconv my_model.fbx my_model.dae
Then open the .dae file in a text editor. Look for <library_materials>
and the material references inside that element. The actual material definitions are (typically?) found under <library_effects>
. There might be many types, but two that we encountered are <phong>
and <lambert>
. They should have a diffuse element, something like:
<diffuse>
<color sid="diffuse">0.8 0.8 0.8 1.0</color>
</diffuse>`
There might be a similar one for ambient. Check color values, if they are low, e.g. 0.2, try increase to max 1.0. If element is missing, just add it. In the end, it should look something like:
<ambient>
<color sid="ambient">1.0 1.0 1.0 1.0</color>
</ambient>`
Save the file as my_model_fixed.dae.
There is a script that automates this process, but it will only recognize phong and lambert material types: fix_dae_materials.py.
Finally, convert the patched dae file to osgb:
osgconv my_model.dae my_model.osgb
Different formats have different orientation frames. Hence it may be necessary to fix rotation. In that case, try something like:
osgconv my_model.dae -o 90-1,0,0 --use-world-frame my_model.osgb
Solution 2: Add light source
Users can add up to two custom light sources, specifying position (x, y, z) and intensity. Examples:
Add one custom light source:
./bin/esmini --window 60 60 800 400 --osc .\resources\xosc\lane_change_crest.xosc --custom_light 4000,-6000,500,1.0
Add two custom light sources:
./bin/esmini --window 60 60 800 400 --osc .\resources\xosc\lane_change_crest.xosc --custom_light 4000,-6000,500,0.4 --custom_light 5000,5000,200,0.2
9. Support / Q&A
9.1. Various issues
9.1.1. Resources not found
There are no fixed structure where scenario resources, e.g. vehicle 3D model or controller catalog, needs to be stored. Instead, additional search paths can be added by launch argument --path <path>
. For example:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/slow-lead-vehicle.xosc --path ../../ --path c:/tmp/my_models
will add the relative path ../../
and absolute path c:/tmp/my_models
to the list of places where to look for resources referred to by the scenario.
9.1.2. Blocked by Windows Defender SmartScreen
When esmini is downloaded as a zip file from Internet it might be blocked, i.e. prevented from being started, by Windows to protect your system. This is indicated by the following, or similar, message popping up when attempt to start:
"Microsoft Defender SmartScreen prevented an unrecognized app from starting. Running this app might put your PC at risk."
As long as the zip was downloaded from esmini official GitHub repository (https://github.com/esmini/esmini) it is perfectly safe to use.
The simplest solution is to unblock the zip file before unpacking it:
-
Right click
-
Click Properties
-
At bottom right, check "Unblock"
Note: This method also works for individual files after unzipping. But it can only be applied for one file at a time, why it’s simpler to unblock the complete zip before unpacking.
It is also possible to unblock from Powershell command line:
-
Single file:
Unblock-File esmini-demo_Windows.zip
-
Multiple files recursively:
Get-ChildItem -Path esmini-demo_Windows -Recurse | Unblock-File
9.1.3. Mac issues and limitations
From esmini version 2.26.6 mac executables are universal binaries which means they work on both Intel and Apple Silicon based Macs. Following are some known issues and work arounds:
-
Window can’t be located in the upper part of the screen (some systems). If window won’t open, try to adjust the y value (second entry) of the window argument. 60 pixels usually works fine, e.g:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc
-
Issue has been reported that when multiple screens are connected esmini just shows a bland window. Currently only solution is to unplug the additional montitors or unplug Mac from dock station.
-
Graphics can not run in separate thread. Hence the
--thread
launch flag will have no effect. -
On Mac the zip-package might be put in quarantine, to release it:
xattr -d com.apple.quarantine file.zip
or even better:xattr -c file.zip
-
esmini executables are not signed with a developer ID - since we simply don’t have one. If you got the package directly from esmini release page on GitHub it is safe to unpack and run. In order to execute the files you need to do one of following from a terminal in the folder the demo package was extracted:
-
Remove quarantine flags:
xattr -c -r esmini-demo/bin
, or -
Sign executables for use on local machine:
codesign -f -s - esmini-demo/bin/*
-
9.1.4. OpenStreetMap (OSM) roads in esmini
SUMO comes with a great tool, netconvert, that can convert road networks from and to various formats.
Prerequisite: Download and install SUMO from here.
Example: Convert an OSM map to OpenDRIVE for use in esmini:
netconvert --osm-files city.osm --opendrive-output city.xodr --no-turnarounds
Preview in odrviewer:
./bin/odrviewer --window 60 60 800 400 --odr city.xodr
9.1.5. Update 3D model pack
Do either:
-
remove any
resources/models
folder -
from
./build
folder, runcmake ..
or
-
get the package from here and unpack files into
./resources/models
9.1.6. Update 3rd party prebuilt libraries
There are a few external dependencies that takes long time to build from scratch. Instead they are provided in compressed packages including needed headerfiles and prebuilt libraries. The packages are available for Win, Mac and Linux. For Windows and Linux they include Debug mode binaries, in addition to the Release variant.
The cmake script will check for the availability of these libraries, by simply checking for existence of corresponding folders, e.g. osg, osi, sumo, under esmini/externals. If missing, compressed packages will be downloaded and extracted.
Not often, but at some points the prebuilt libraries needs to be updated. In most cases it affects the esmini build configuration and/or code. Hence, sometimes an update of esmini comes with a need to update the external libraries as well. Lack of automatic handling, such update needs to be done manually. One way is to delete the specific folder(s) and run ordinary cmake command again. But the simplest way is to enforce redownload of all packages:
cmake .. -D FORCE_DOWNLOAD_BINARIES=TRUE
The FORCE_DOWNLOAD_BINARIES flag will be checked by the cmake script for enforced download of all packages. Upon successful download, the libraries will be replaced, one by one.
Library updates are not detected by the esmini build dependencies, why a clean rebuild of esmini should be performed manually afterwards.
9.1.7. Entity does not appear
In order to appear in the scenario, without need for AddEntity action, an entity must be represented in the Init section of the Storyboard. Common is to add actions to establish inital position and speed.
Example:
<PrivateAction>
<TeleportAction>
<Position>
<LanePosition roadId="1" laneId="-1" offset="0" s="50"/>
</Position>
</TeleportAction>
</PrivateAction>
<PrivateAction>
<LongitudinalAction>
<SpeedAction>
<SpeedActionDynamics dynamicsShape="step" dynamicsDimension="time" value="0.0"/>
<SpeedActionTarget>
<AbsoluteTargetSpeed value="30.0"/>
</SpeedActionTarget>
</SpeedAction>
</LongitudinalAction>
</PrivateAction>
9.1.8. Heading behavior in Road vs Lane Position
In esmini, the behavior of the two position types RoadPosition
and LanePosition
differ in terms of heading.
-
RoadPosition
: When not specified, the heading will align to the direction of the road (s-axis) -
LanePosition
: When not specified, the heading will align to the driving direction of the lane
In the image below, the white cars are positioned by RoadPosition
with t=-1.5
and t=1.5
respectively while the red cars are positioned by LanePosition
with laneId="-1"
and laneId="1"
respectively.
Explicitly specified heading (in Orientation
sub-element):
-
Absolute heading will be respected and treated in the same way for both position cases
-
Relative heading will be based on the default heading, i.e. road or driving direction respectively.
In next image, an Orientation
element was added with absolute heading = 0.4 for all four cars.
In next image, an Orientation
element was added with relative heading = 0.4 for all four cars.
Corresponding behavior applies also to RelativeRoadPosition
and RelativeLanePosition
.
Motivation
In some (maybe most) use cases it’s preferred that vehicles align to the driving direction of the lane. But in other cases not, example: In an overtake maneuver, the overtaking car may start in the neighbor lane. The driving direction of the lane should not affect the heading of the car.
9.1.9. Reference line and center lane while using laneOffset
In OpenDRIVE definition of laneOffset
: A lane offset may be used to shift the center lane away from the road reference line.
In esmini lane offset affects the reference line as well as center lane, which is a deviation from the OpenDRIVE standard. As consequence, t values are also shifted with the lane offset.
2+1 roads can be defined utilizing lane offset. Consider image below:
esmini
white car | red car | |
---|---|---|
laneId |
-2 |
-1 |
t |
-5.25 |
-1.75 |
OpenDRIVE
white car | red car | |
---|---|---|
laneId |
-2 |
-1 |
t |
-1.75 |
1.75 |
A 2+1 example example scenario is found here: resources/xosc/two_plus_one_road.xosc
9.1.10. OSI for Python on Windows
Follow the following steps to install OSI (and dependent Google Protobuf) for use by some esmini scripts, e.g. osi2csv.py.
Protobuf for Python (skip if already installed)
-
Open a Powershell
-
pip install protobuf==3.20.2
protoc (Protobuf compiler)
-
Download protoc-3.20.2-win64.zip
(release page: https://github.com/protocolbuffers/protobuf/releases/tag/v3.20.2) -
Unzip (at least protoc.exe) to some location
OSI for Python
-
Open a Powershell in the to-be-parent folder of OSI
-
git clone https://github.com/OpenSimulationInterface/open-simulation-interface
-
cd open-simulation-interface
-
git checkout v3.5.0
-
./convert-to-proto3.sh
-
$env:PROTOC=<location of protoc.exe>
for example:$env:PROTOC="C:/tmp/protoc-3.20.2-win64/bin/protoc.exe"
-
pip install .
This should be it.
How to remove old versions (if needed)
-
pip uninstall protobuf
-
pip uninstall open-simulation-interface
9.1.11. The scenario runs too fast or too slow
esmini can run in two different time modes: 1. Real-time and 2. Fixed-time. Default is real-time.
The following command, from esmini root, will run real-time:
./bin/esmini --window 60 60 800 400 --osc ./resources/xosc/cut-in.xosc
This one fixed-time:
./bin/esmini --window 60 60 800 400 --fixed_timestep 0.05 --osc ./resources/xosc/cut-in.xosc
The difference is basically that real-time will run with as small time-steps as possible (actually limit at 1 ms to avoid unnecessary CPU load), maxim zing precision but constrained by system clock. In other words, the scenario will play out in "natural" speed, good for previewing or demonstrating sc narios. Each timestep is calculated by esmini based on passed system-time since last frame.
The fixed-time is simpler, esmini will simply execute the scenario as fast as possible applying the specified delta-time for each step. Good for runni g tests for later post processing of resulting logs, data or OSI files.
A common use-case is to run esmini quick/headless and then utilize the replayer for viewing the result. See more info here: Replay scenario
9.1.12. Road and junction string IDs
OpenDRIVE allows for non integer string IDs. Although commonly unsigned integer is used. The standard OpenDRIVE 1.8 states that IF the ID represents an integer number, it should comply to uint32_t and stay within the given range.
esmini partly supports* string IDs, by mapping to internal integer IDs. The way it works:
If the ID is a number in the range of 0..0xfffffffe (4 294 967 294), then:
map it to the same internal ID
Else, if the number is a string or a number out of range:
map it to a unique internal ID
Note: 0xffffffff (4 294 967 295) is reserved for ID_UNDEFINED which is the default value and also used for error indication
Example: Assume an OpenDRIVE includes the following road (or junction) IDs:
"0"
"1"
"124"
"Kalle"
"12k4"
"4294967296"
"4294967295"
"4294967294"
The IDs will be mapped as follows:
"0" -> 0
"1" -> 1
"124" -> 124
"Kalle" -> 125
"4294967296" -> 126 (too large)
"4294967295" -> 127 (conflict with ID_UNDEFINED)
"4294967294" -> 4294967294 (Ok, largest acceptable number)
Now, why not make use of available numbers between 1 and 124? It’s because esmini is lazy, not spending effort (performance) to book-keep sorted list of IDs. Instead, only the largest number in use will be monitored.
In addition, there are esmini lib API functions to lookup string and internal IDs:
const char *SE_GetRoadIdString(id_t road_id);
id_t SE_GetRoadIdFromString(const char *road_id_str);
const char *SE_GetJunctionIdString(id_t junction_id);
id_t SE_GetJunctionIdFromString(const char *junction_id_str);
That way, a custom application can find out what internal IDs esmini has assigned to any non-number string IDs.
Corresponding functions exist in esmini roadmanager lib.
* String ID support was introduced in v2.37.15. By v2.39.0 internal representation changed from 32 bit signed int to unsigned int. At that point also undefined ID changed from -1 to 0xffffffff.
9.2. Further issues at esmini GitHub page
The Issues tab at esmini GitHub page is a valuable source of questions and answers. To search in all issues, make sure to set filter: is:issue
.
10. Command reference
10.1. esmini
10.1.1. Launch commands
Usage: esmini [options]
Options:
--osc <filename>
OpenSCENARIO filename (required) - if path includes spaces, enclose with ""
--aa_mode <mode>
Anti-alias mode=number of multisamples (subsamples, 0=off, 4=default)
--align_routepositions
Align t-axis of route positions to the direction of the route
--bounding_boxes
Show entities as bounding boxes (toggle modes on key ',')
--capture_screen
Continuous screen capture. Warning: Many jpeg files will be created
--camera_mode <mode>
Initial camera mode ("orbit" (default), "fixed", "flex", "flex-orbit", "top", "driver", "custom") (swith with key 'k')
--csv_logger <csv_filename>
Log data for each vehicle in ASCII csv format
--collision
Enable global collision detection, potentially reducing performance
--custom_camera <position>
Additional custom camera position <x,y,z>[,h,p] (multiple occurrences supported)
--custom_fixed_camera <position and optional orientation>
Additional custom fixed camera position <x,y,z>[,h,p] (multiple occurrences supported)
--custom_fixed_top_camera <position and rotation>
Additional custom top camera <x,y,z,rot> (multiple occurrences supported)
--custom_light <position and intensity>
Additional custom light source <x,y,z,intensity> intensity range 0..1 (multiple occurrences supported)
--disable_controllers
Disable controllers
--disable_pline_interpolation
Do not apply orientation interpolation of polyline trajectories
--disable_log
Prevent logfile from being created
--disable_stdout
Prevent messages to stdout
--enforce_generate_model
Generate road 3D model even if SceneGraphFile is specified
--fixed_timestep <timestep>
Run simulation decoupled from realtime, with specified timesteps
--follow_object <index>
Set index of intial object for camera to follow (change with Tab/shift-Tab)
--generate_no_road_objects
Do not generate any OpenDRIVE road objects (e.g. when part of referred 3D model)
--generate_without_textures
Do not apply textures on any generated road model (set colors instead as for missing textures)
--ground_plane
Add a large flat ground surface
--headless
Run without viewer window
--help
Show this help message
--hide_route_waypoints
Disable route waypoint visualization (toggle with key 'R')
--hide_trajectories
Hide trajectories from start (toggle with key 'n')
--ignore_z
Ignore provided z values from OSC file and place vehicle relative to road
--ignore_p
Ignore provided pitch values from OSC file and place vehicle relative to road
--ignore_r
Ignore provided roll values from OSC file and place vehicle relative to road
--info_text <mode>
Show on-screen info text (toggle key 'i') mode 0=None 1=current (default) 2=per_object 3=both
--logfile_path <path>
logfile path/filename, e.g. "../esmini.log" (default: log.txt)
--osc_str <string>
OpenSCENARIO XML string
--osg_screenshot_event_handler
Revert to OSG default jpg images ('c'/'C' keys handler)
--osi_file [filename] (default = ground_truth.osi)
save osi trace file
--osi_freq <frequence>
relative frequence for writing the .osi file e.g. --osi_freq=2 -> we write every two simulation steps
--osi_lines
Show OSI road lines (toggle during simulation by press 'u')
--osi_points
Show OSI road pointss (toggle during simulation by press 'y')
--osi_receiver_ip <IP address>
IP address where to send OSI UDP packages
--param_dist <filename>
Run variations of the scenario according to specified parameter distribution file
--param_permutation <index (0 .. NumberOfPermutations-1)>
Run specific permutation of parameter distribution
--pause
Pause simulation after initialization
--path <path>
Search path prefix for assets, e.g. OpenDRIVE files (multiple occurrences supported)
--player_server
Launch UDP server for action/command injection
--plot [mode (asynchronous|synchronous)] (default = asynchronous)
Show window with line-plots of interesting data
--record <filename>
Record position data into a file for later replay
--road_features <mode>
Show OpenDRIVE road features ("on", "off" (default)) (toggle during simulation by press 'o')
--return_nr_permutations
Return number of permutations without executing the scenario (-1 = error)
--save_generated_model
Save generated 3D model (n/a when a scenegraph is loaded)
--save_xosc
Save OpenSCENARIO file with any populated parameter values (from distribution)
--seed <number>
Specify seed number for random generator
--sensors
Show sensor frustums (toggle during simulation by press 'r')
--server
Launch server to receive state of external Ego simulator
--text_scale [factor] (default = 1.0)
Scale screen overlay text
--threads
Run viewer in a separate thread, parallel to scenario engine
--trail_mode <mode>
Show trail lines and/or dots (toggle key 'j') mode 0=None 1=lines 2=dots 3=both
--use_signs_in_external_model
When external scenegraph 3D model is loaded, skip creating signs from OpenDRIVE
--version
Show version and quit
Additional OSG graphics options:
--clear-color <color> Set the background color of the viewer in the form "r,g,b[,a]"
--screen <num> Set the screen to use when multiple screens are present
--window <x y w h> Set the position x, y and size w, h of the viewer window. -1 -1 -1 -1 for fullscreen.
--borderless-window <x y w h> Set the position x, y and size w, h of a borderless viewer window. -1 -1 -1 -1 for fullscreen.
--SingleThreaded Run application and all graphics tasks in one single thread.
--lodScale <LOD scalefactor> Adjust Level Of Detail 1=default >1 decrease fidelity <1 increase fidelity
For a complete list of OSG options and environment variables, see here:
https://github.com/esmini/esmini/blob/master/docs/osg_options_and_env_variables.txt
10.1.2. Runtime key shortcut commands
Key shortcuts
H (shift + h): Print this help text to console
Space: Toggle pause/play simulation
Return: Step simulation (one timestep) then pause
TAB: Move camera to next vehicle
Shift + TAB: Move camera to previoius vehicle
Delete: Same as above (Shift + TAB)
o: Toggle show / hide OpenDRIVE road feature lines
O: Toggle show / hide odr signal bounding boxes
u: Toggle show / hide OSI road lines
y: Toggle show / hide OSI road points
p: Toggle show / hide environment 3D model
r: Toggle show / hide sensor view frustums
R: Toggle route waypoint visualization
i: Toggle on-screen info text modes
j: Toggle show trails after vehicles(4 modes: none / dots / lines / both)
n: Toggle show active trajectories
, (comma): Switch entity view : Model only / Bounding box / Model + Bounding box / None
K: Print current camera position and orientation to console
ESC: quit
Arrow keys is used to drive externally controlled Ego vehicle:
Up: Accelerate
Down: Brake
Left: Steer left
Right: Steer right
1 - 9: Camera models acording to :
1: Custom camera model
2: Flight
3: Drive
4: Terrain
5: Orbit
6: FirstPerson
7: Spherical
8: NodeTracker
9: Trackball
When custom camera model(1) is activated
k: Switch between the following sub models:
- Orbit (camera facing vehicle, rotating around it)
- Fixed (fix rotation, always straight behind vehicle)
- Flex (imagine the camera attached to vehicle via an elastic string)
- Flex - orbit (Like flex but allows for roatation around vehicle)
- Top (top view, fixed rotation, always straight above vehicle)
- Driver ("driver" view, fixed at center of vehicle)
Viewer options
f: Toggle full screen mode
t: Toggle textures
s: Rendering statistics
l: Toggle light
w: Toggle geometry mode(shading, wireframe, dots)
c: Save screenshot in JPEG format - in the folder where the application was started from
C: Toggle continuous screen capture (e.g for video creation)
h: Help
Mouse control
Left: Rotate
Right: Zoom
Middle: Pan
This is typical, exact behaviour depends on active camera model.
10.2. replayer
Usage: replayer [options]
Options:
--file <filename>
Simulation recording data file (.dat)
--aa_mode <mode>
Anti-alias mode=number of multisamples (subsamples, 0=off, 4=default)
--camera_mode <mode>
Initial camera mode ("orbit" (default), "fixed", "flex", "flex-orbit", "top", "driver") (toggle during simulation by press 'k')
--capture_screen
Continuous screen capture. Warning: Many jpeg files will be created
--collision [mode] (default = pause)
Detect collisions and optionally pauses the replay <pause/continue> (pause is default)
--custom_camera <position>
Additional custom camera position <x,y,z>[,h,p] (multiple occurrences supported)
--custom_fixed_camera <position and optional orientation>
Additional custom fixed camera position <x,y,z>[,h,p] (multiple occurrences supported)
--custom_fixed_top_camera <position and rotation>
Additional custom top camera <x,y,z,rot> (multiple occurrences supported)
--dir <path>
Directory containing replays to overlay, pair with "file" argument, where "file" is .dat filename match substring
--ground_plane
Add a large flat ground surface
--generate_without_textures
Do not apply textures on any generated road model (set colors instead as for missing textures)
--headless
Run without viewer window
--hide_trajectories
Hide trajectories from start (toggle with key 'n')
--info_text <mode>
Show on-screen info text (toggle key 'i') mode 0=None 1=current (default) 2=per_object 3=both
--no_ghost
Remove ghost entities
--no_ghost_model
Remove only ghost model, show trajectory (toggle with key 'g')
--osg_screenshot_event_handler
Revert to OSG default jpg images ('c'/'C' keys handler)
--path <path>
Search path prefix for assets, e.g. model_ids.txt file (multiple occurrences supported)
--quit_at_end
Quit application when reaching end of scenario
--remove_object <id>
Remove object(s). Multiple ids separated by comma, e.g. 2,3,4.
--repeat
loop scenario
--res_path <path>
Path to resources root folder - relative or absolut
--road_features
Show OpenDRIVE road features
--save_merged <filename>
Save merged data into one dat file, instead of viewing
--start_time <ms>
Start playing at timestamp
--stop_time <ms>
Stop playing at timestamp (set equal to time_start for single frame)
--text_scale [factor] (default = 1.0)
Scale screen overlay text
--time_scale <factor>
Playback speed scale factor (1.0 == normal)
--view_mode <view_mode>
Entity visualization: "model"(default)/"boundingbox"/"both"
--use_signs_in_external_model
When external scenegraph 3D model is loaded, skip creating signs from OpenDRIVE
Additional OSG graphics options:
--clear-color <color> Set the background color of the viewer in the form "r,g,b[,a]"
--screen <num> Set the screen to use when multiple screens are present
--window <x y w h> Set the position x, y and size w, h of the viewer window. -1 -1 -1 -1 for fullscreen.
--borderless-window <x y w h> Set the position x, y and size w, h of a borderless viewer window. -1 -1 -1 -1 for fullscreen.
--SingleThreaded Run application and all graphics tasks in one single thread.
--lodScale <LOD scalefactor> Adjust Level Of Detail 1=default >1 decrease fidelity <1 increase fidelity
Key shortcuts
H (shift + h): Print this help text to console
TAB: Move camera to next vehicle
Shift + TAB: Move camera to previoius vehicle
Delete: Same as above (Shift + TAB)
Space: Toggle pause / play
g: Toggle show / hide ghost models
o: Toggle show / hide OpenDRIVE road feature lines
u: Toggle show / hide OSI road lines
y: Toggle show / hide OSI road points
p: Toggle show / hide environment 3D model
i: Toggle on-screen info text modes
n: Toggle show active trajectories
, (comma): Switch entity view : Model only / Bounding box / Model + Bounding box / None
K: Print current camera position and orientation to console
ESC: quit
Arrow keys
Left: Pause and move to previous frame(+Shift to skip 10 frames)
Right: Pause and move to next frame(+Shift to skip 10 frames)
Shift + Left: Pause and jump 0.1s back
Shift + Right: Pause and jump 0.1s forward
Shift + Ctrl Left: Pause and jump 1.0s back
Shift + Ctrl Right: Pause and jump 1.0s forward
Ctrl + Left: Pause and jump to beginning
Ctrl + Right: Pause and jump to end
Up: Increase timeScale(play faster)
Down: Decrease timeScale(play slower)
1 - 9: Camera models acording to :
1: Custom camera model
2: Flight
3: Drive
4: Terrain
5: Orbit
6: FirstPerson
7: Spherical
8: NodeTracker
9: Trackball
When custom camera model(1) is activated
k: Switch between the following sub models:
- Orbit (camera facing vehicle, rotating around it)
- Fixed (fix rotation, always straight behind vehicle)
- Flex (imagine the camera attached to vehicle via an elastic string)
- Flex - orbit (Like flex but allows for roatation around vehicle)
- Top (top view, fixed rotation, always straight above vehicle)
- Driver ("driver" view, fixed at center of vehicle)
Viewer options
f: Toggle full screen mode
t: Toggle textures
s: Rendering statistics
l: Toggle light
w: Toggle geometry mode(shading, wireframe, dots)
c: Save screenshot in JPEG format - in the folder where the application was started from
C: Toggle continuous screen capture (e.g for video creation)
h: Help
Mouse control
Left: Rotate
Right: Zoom
Middle: Pan
This is typical, exact behaviour depends on active camera model.
Recommended usage:
Run esmini headless (fast without viewer) and produce a .dat file. Then launch replayer to view it. Example in Windows PowerShell, starting from esmini/bin folder:
.\esmini --osc ..\resources\xosc\cut-in.xosc --record sim.dat --headless --fixed_timestep 0.01 ; .\replayer --file sim.dat --window 60 60 800 400 --res_path ..\resources --repeat
10.3. odrviewer
Usage: odrviewer [options]
Options:
--help
Show this help message
--odr <odr_filename>
OpenDRIVE filename (required)
--aa_mode <mode>
Anti-alias mode=number of multisamples (subsamples, 0=off, 4=default)
--capture_screen
Continuous screen capture. Warning: Many .tga files will be created
--custom_fixed_camera <position and optional orientation>
Additional custom camera position <x,y,z>[,h,p] (multiple occurrences supported)
--custom_fixed_top_camera <position and rotation>
Additional custom top camera <x,y,z,rot> (multiple occurrences supported)
--density [density] (default = 1.000000)
density (cars / 100 m)
--enforce_generate_model
Generate road 3D model even if --model is specified
--disable_log
Prevent logfile from being created
--disable_off_screen
Disable esmini off-screen rendering, revert to OSG viewer default handling
--disable_stdout
Prevent messages to stdout
--duration <duration>
Quit automatically after specified time (seconds, floating point)
--fixed_timestep <timestep>
Run simulation decoupled from realtime, with specified timesteps
--generate_no_road_objects
Do not generate any OpenDRIVE road objects (e.g. when part of referred 3D model)
--generate_without_textures
Do not apply textures on any generated road model (set colors instead as for missing textures)
--ground_plane
Add a large flat ground surface
--headless
Run without viewer window
--logfile_path <path>
logfile path/filename, e.g. "../esmini.log" (default: log.txt)
--model <model_filename>
3D Model filename
--osg_screenshot_event_handler
Revert to OSG default jpg images ('c'/'C' keys handler)
--osi_lines
Show OSI road lines (toggle during simulation by press 'u')
--osi_points
Show OSI road points (toggle during simulation by press 'y')
--path <path>
Search path prefix for assets, e.g. car and sign model files
--road_features
Show OpenDRIVE road features (toggle during simulation by press 'o')
--save_generated_model
Save generated 3D model (n/a when a scenegraph is loaded)
--seed <number>
Specify seed number for random generator
--speed_factor [speed_factor] (default = 1.000000)
speed_factor <number>
--stop_at_end_of_road
Instead of respawning elsewhere, stop when no connection exists
--text_scale [factor] (default = 1.0)
Scale screen overlay text
--traffic_rule <rule (right/left)>
Enforce left or right hand traffic, regardless OpenDRIVE rule attribute (default: right)
--use_signs_in_external_model
When external scenegraph 3D model is loaded, skip creating signs from OpenDRIVE
--version
Show version and quit
Additional OSG graphics options:
--clear-color <color> Set the background color of the viewer in the form "r,g,b[,a]"
--screen <num> Set the screen to use when multiple screens are present
--window <x y w h> Set the position x, y and size w, h of the viewer window. -1 -1 -1 -1 for fullscreen.
--borderless-window <x y w h> Set the position x, y and size w, h of a borderless viewer window. -1 -1 -1 -1 for fullscreen.
--SingleThreaded Run application and all graphics tasks in one single thread.
--lodScale <LOD scalefactor> Adjust Level Of Detail 1=default >1 decrease fidelity <1 increase fidelity
Examples:
1. View the ODR file and some random traffic on a 3D model, window mode 1000 x 500:
odrviewer --odr xodr\e6mini.xodr --model models\e6mini.osgb --window 60 60 1000 500
2. Just ODR, fullscreen
odrviewer --odr xodr\e6mini.xodr
3. Remove traffic
odrviewer --odr xodr\e6mini.xodr --model models\e6mini.osgb --density 0 --window 60 60 1000 500
4. Sparse traffic (about 0.5 vehicle per 100 meter = 1 per 200 m)
odrviewer --odr xodr\e6mini.xodr --model models\e6mini.osgb --density 0.5 --window 60 60 1000 500
Key shortcuts
H (shift + h): Print this help text to console
Space: Toggle pause/play simulation
Return: Step simulation(one timestep) then pause
TAB: Move camera to next vehicle
Shift + TAB: Move camera to previoius vehicle
Delete: Same as above (Shift + TAB)
o: Toggle show / hide OpenDRIVE road feature lines
u: Toggle show / hide OSI road lines
y: Toggle show / hide OSI road points
p: Toggle show / hide environment 3D model
i: Toggle info text showing time and speed
, (comma): Switch entity view : Model only / Bounding box / Model + Bounding box / None
K: Print current camera position and orientation to console
ESC: quit
1 - 9: Camera models acording to :
1: Custom camera model
2: Flight
3: Drive
4: Terrain
5: Orbit
6: FirstPerson
7: Spherical
8: NodeTracker
9: Trackball
When custom camera model(1) is activated
k: Switch between the following sub models:
- Orbit (camera facing vehicle, rotating around it)
- Fixed (fix rotation, always straight behind vehicle)
- Flex (imagine the camera attached to vehicle via an elastic string)
- Flex - orbit (Like flex but allows for roatation around vehicle)
- Top (top view, fixed rotation, always straight above vehicle)
- Driver ("driver" view, fixed at center of vehicle)
Viewer options
f: Toggle full screen mode
t: Toggle textures
s: Rendering statistics
l: Toggle light
w: Toggle geometry mode(shading, wireframe, dots)
c: Save screenshot in JPEG format - in the folder where the application was started from
C: Toggle continuous screen capture
h: Help
Mouse control
Left: Rotate
Right: Zoom
Middle: Pan
This is typical, exact behaviour depends on active camera model.
10.4. plot_dat.py
usage: plot_dat.py [-h] [--x_axis X_AXIS] [--equal_axis_aspect] [--derive] [--dots] (--list_params | --param PARAM) <filename>
positional arguments:
filename dat filename
optional arguments:
-h, --help show this help message and exit
--x_axis X_AXIS x-axis parameter
--equal_axis_aspect lock aspect ratio = 1:1
--derive derive values wrt x, i.e. dy/dx
--dots add dots
--list_params list available parameters in given file
--param PARAM parameter to plot (can be specified multiple times)
Note: In addition to <filename> one of the arguments --list_params and --param <PARAM> is required
11. Build guide
11.1. Build configurations in Visual Studio and Visual Studio Code
From esmini version 2.29.0 a set of cmake presets are provided. These can be utilized from Visual Studio Code and Visual Studio (from 2019 version 16.10 onwards, see more info here) with the CMake Tools extension.
11.2. Build configurations from command line
CMake tool is used to create standard make configurations. A few example "create…" batch scripts are supplied (in scripts
folder) as examples how to generate desired build setup from command line.
-
VisualStudio / win64 / Windows SDK v10 / Release and Debug
-
Ubuntu and Kubuntu (tested on 18.04) / gcc / Release and Debug
However, it should be possible to configure custom variants using cmake. For example to use Visual Studio 2019 run the following commands from command prompt (CMD or PowerShell), assuming starting point is esmini root folder:
mkdir build cd build cmake .. -G "Visual Studio 16 2019" cmake --build . --config Release --target install
This will first generate Visual Studio solution and then compile esmini using MSVC toolset v142 (default with Visual Studio 2019). Default architecture should be x64.
If you want to compile with MSVC 2017 toolset just add directive to the generator as follows:
cmake .. -G "Visual Studio 16 2019" -T v141
A complete list of supported toolsets are available here.
Provided 3rd party lib binaries are only provided for Windows x64 architecture. To make sure you build for x64 add -A x64
, For example:
cmake .. -G "Visual Studio 16 2019" -T v141 -A x64
If you want to compile with MSVC 2015 (v140) toolset and for win32 use the following generator command:
cmake -G "Visual Studio 16 2019" -T v140 -A Win32 ..
Note:
Provided 3rd party libs only available for x64 builds. If you need to build full esmini for Win32 (32bit), you need to compile the 3rd party libraries yourself. Perhaps the "scripts/generate_*_libs.sh" scripts can be a starting point for such an endeavor.
Another possibility for Win32 is to build a slim esmini with a minimum of dependencies. See Slim esmini - customize configration.
Of course, building with a specific toolset requires it to be installed. Use Visual Studio Installer. Steps:
-
choose "Modify"
-
make sure Desktop Development with C++ is checked
-
go to tab "Individual components"
-
scroll down to "Compilers, build tools, and runtimes"
-
check the MSVC versions you need, e.g. "MSVC v140 - VS 2015 C++ build tools (v14.00)" and "MSVC v141 - VS 2017 C++ x64/x86 build tools (v14.16)"
All configurations defines an "Install" build target that compiles (if needed) and copies relevant binaries into a common "esmini/bin" folder recognized by the example scripts under the "esmini/run" folder.
Note:
-
For automatic downloading of external dependencies (OSG binaries) and 3D models, CMake version 3.11.4 or above is required (FetchContent_MakeAvailable was introduced).
-
In Windows, if you get an error like "the c compiler identification is unknown", then please make sure to install "Windows Universal CRT SDK" from the Visual Studio Installer tool.
11.3. External dependencies
esmini is designed to link all dependencies statically. Main reason is to have a all-inclusive library for easy integration either as a shared library/DLL (e.g. plugin in Unity, or S-function in Simulink) or statically linked into a native application.
Note: Nothing stops you from going with all dynamic linking, it’s just that provided build scripts are not prepared for it.
CMake scripts will download several pre-compiled 3rd party packages and 3D model resource files.
Default location for these resources is Google drive. If there is an issue, try switch to the backup location at Dropbox by changing the following line in EnvironmentSimulator/CMakeLists.txt:
set ( FILE_STORAGE "google" )
to
set ( FILE_STORAGE "dropbox" )
Links to all packages can be found in EnvironmentSimulator/CMakeLists.txt.
If you need to (re)build a 3rd party lib for some reason, e.g. for an yet unsupported system or need for a specific version, these build scripts might be a starting point:
or this script that builds all three libs, combining the above ones:
11.4. Additional platform dependencies
Linux Ubuntu
sudo apt install build-essential gdb ninja-build git pkg-config libgl1-mesa-dev libpthread-stubs0-dev libjpeg-dev libxml2-dev libpng-dev libtiff5-dev libgdal-dev libpoppler-dev libdcmtk-dev libgstreamer1.0-dev libgtk2.0-dev libcairo2-dev libpoppler-glib-dev libxrandr-dev libxinerama-dev curl cmake
Also, g++ version >= 5 is needed for c++14 code support.
Windows and Mac: Install the cmake application
11.5. Run and Debug with Linux and visual studio code
To run from the visual studio, the steps below might help with setup configuration.
11.5.1. With preset
Note: Make sure cmake.preset set to auto to recognize the presets (defined in the CMakePresets.json
file). Open setting (Ctrl + ,) type cmake.preset
, set auto
in drop down box. This is a global setting.
To run:
-
Open the Command Palette (Ctrl+Shift+P) and run CMake: Configure. Select
ubuntu_release
-
Shift + F7 to select and build specific target
-
e.g.
install
to build and copy all targets to bin folder
-
-
Configure custom run arguments (create launch.json)
-
Open the run and debug window (Ctrl+Shift+D)
-
Select
create a launch.json file
-
In the drop down list, select
C++ (GDB/LLDB)
-
Select
Add Configuration…
(in the lower right corner of the sub window) -
Select
C/C++: (gdb) Launch
-
Change
program
setting as"${workspaceFolder}/build/EnvironmentSimulator/Applications/esmini/esmini"
-
Change
args
as["--window", "60", "60", "800", "400", "--osc", "../resources/xosc/cut-in.xosc"]
-
-
Press F5
For Debug,
Repeat step 1 and select ubuntu_debug
. Repeat step 2 to build debug binaries and then step 4. Breakpoints can be added to the program specified in the launch.json
file (do step 3 if not already done).
Build target can also be changed in the status bar, click Set the default build target
. If not cmake commands are visible in status bar, check global settings: Ctrl + , then write "cmake status bar" to find Status Bar Visibility
setting. Set to visible
.
Another way to set build target is via Ctrl+Shift+P, then write "set build target" to find CMake: Set Build Target
setting. Select it and choose build target in the drop down list that should appear.
11.5.2. Without preset
Make sure cmake.preset
set to never to ignore the preset file (CMakePresets.json
):
-
Open setting (Ctrl+,) type cmake.preset, select
never
in drop down box
Note: This is a global setting, i.e. will affect other VSCode instances. Reset to auto
if needed. Another way to ignore the presets is simply to remove the CMakePresets.json
file and restart VSCode.
To run:
-
Open the Command Palette (Ctrl+Shift+P) and run
CMake: Select a Kit
. Select the compiler you want to use. For example GCC 11.3.0 -
Open the Command Palette (Ctrl+Shift+P) run the
CMake: Select Variant
command. SelectRelease
-
Open the Command Palette (Ctrl+Shift+P) run the
CMake: Set Build Target
. Selectinstall
-
Open the Command Palette (Ctrl+Shift+P) and run the
CMake: Build
command, or select the Build button from the Status bar. (If cmake commands are not visible in status bar, check global settings: Ctrl + , then write "cmake status bar" to findStatus Bar Visibility
setting. Set tovisible
.) -
To configure launch arguments, see step 3 in With preset.
-
Press F5
For debug:
Repeat step 2 and select Debug
. Repeat step 3 to 6. Breakpoints can be added to the program specified in the launch.json
.
For more info see: https://code.visualstudio.com/docs/cpp/CMake-linux
11.6. Dynamic protobuf linking
When linking esmini with software already dependent on Google protobuf there might be need for dynamic linking of shared protobuf library. This can be achieved by defining cmake symbol DYN_PROTOBUF as following example:
cmake .. -D DYN_PROTOBUF=True
Then build as usual. It will link with protobuf shared library instead of linking with a static library.
When running esmini, the protobuf shared library needs to be found. Set dynamic library path environment variable to the folder where the library is. Example:
Linux:
export LD_LIBRARY_PATH=./externals/OSI/linux/lib-dyn
macOS:
export DYLD_LIBRARY_PATH=./externals/OSI/linux/lib-dyn
Note:
The dynamic versions of protobuf were added Aug 31 2021. So you might need to update the OSI library package. Get the latest from following links:
11.7. Slim esmini - customize configration
The external dependencies OSG, OSI, SUMO and implot are optional. Also the unit test suite is optional, in effect making the dependecy to googletest framework optional as well. All these options are simply controlled by the following cmake options:
-
USE_OSG
-
USE_OSI
-
USE_SUMO
-
USE_GTEST
-
USE_IMPLOT
So, for example, to cut dependency to OSG and SUMO, run:
cmake .. -D USE_OSG=False -D USE_SUMO=False
To disable OSG, SUMO, OSI and googletest, run:
cmake .. -D USE_OSG=False -D USE_SUMO=False -D USE_OSI=False -D USE_GTEST=False USE_IMPLOT=False
All options are enabled/True as default.
Note:
Disabling an external dependency will disable corresponding functionality. So, for example, disabling OSI means that no OSI data can be created by esmini. Disabling OSG means that esmini can’t visualize the scenario. However it can still run the scenario and create a .dat file, which can be played and visualized later in another esmini build in which OSG is enabled (even on another platform).
11.8. MSYS2 / MinGW-w64 support
esmini slim can be compiled and executed in the MSYS2 environment. Try following steps:
-
Download MSYS2 from: https://www.msys2.org
-
Install with default options
-
Start MSYS2 MinGW x64 (e.g. from start menu)
-
Update MSYS2 packages, run:
pacman -Syu --disable-download-timeout --noconfirm
(MSYS2 should close automatically) -
Restart MSYS2
-
Finalize update and install needed packages, run:
pacman -Su --disable-download-timeout --noconfirm
pacman -S --needed base-devel mingw-w64-x86_64-toolchain --disable-download-timeout --noconfirm
pacman -S mingw-w64-x86_64-cmake --disable-download-timeout --noconfirm
-
Optional (not needed to compile or run esmini):
pacman -S git --disable-download-timeout --noconfirm
-
Build esmini (from MSYS2 MinGW x64 command line):
cmake -G "MSYS Makefiles" -D USE_OSG=False -D USE_SUMO=False -D USE_OSI=False -D USE_GTEST=False ..
cmake --build . --config Release --target install
11.9. Build esmini project
First generate build configuration (see above)
Then it should work on all platform to build using cmake as follows:
cmake --build . --config Release --target install
Or you can go with platform specific ways of building:
Windows/Visual Studio
-
Open generated solution, build*/EnvironmentSimulator.sln
-
Select configuration, Debug or Release
-
Build CMakePredefinedTargets/INSTALL (right-click and select build)
macOS
To generate a Xcode project file, run the initial cmake command as follows:
cmake -G Xcode ..
Then build as usual:
cmake --build . --config Release --target install
or using Xcode directly:
xcodebuild -scheme install -configuration Release build
or open the generated project file in Xcode, and build from there.
To create bundles (shared library container), do from esmini root folder:
lipo -create bin/libesminiRMLib.dylib -output bin/esminiRMLib.bundle
lipo -create bin/libesminiLib.dylib -output bin/esminiLib.bundle
Linux
Once cmake ..
has created the build configuration, of course you can build by calling the gnu make
applciation directly instead of going via cmake --build
as described above.
cd build
make -j4 install
This will build all projects, in four parallel jobs, and copy the binaries into a dedicated folder found by the demo batch scripts.
11.10. CentOS 7 (Linux)
CentOS 7 has some limitations, e.g. old versions of C/C++ compiler toolkits and runtimes. So it’s not possible to link with provided 3rd party binary libraries targeting Ubuntu 18++. However, by disabling some featuers in esmini, e.g. OSI and SUMO, it can still be used for previewing scenarios.
VirtualBox image for Windows host here:
https://www.linuxvmimages.com/images/centos-7/
Follow steps below to build and run esmini on CentOS 7.
sudo yum install git
sudo yum install cmake
sudo yum install gcc-c++
sudo yum install freeglut-devel
sudo yum install fontconfig-devel
sudo yum install libXrandr-devel
sudo yum install libXinerama-devel
sudo yum install epel-release
sudo yum install p7zip
git clone https://github.com/esmini/esmini
cd esmini
cd externals
mkdir OpenSceneGraph
cd OpenSceneGraph
curl -L "https://www.dropbox.com/s/mxztf6zbgojyntp/osg_centos.7z?dl=1" -o osg_centos.7z
7za x osg_centos.7z
rm osg_centos.7z
cd ../..
mkdir build
cd build
cmake -D USE_OSG=True -D USE_SUMO=False -D USE_OSI=False -D USE_GTEST=False ..
cmake --build . --target install --config Release
cd ..
./bin/esmini.exe --headless --fixed_timestep 0.01 --record sim.dat --osc ./resources/xosc/cut-in.xosc
12. For esmini developers and contributors
Brief info on how to contribute is found here: https://github.com/esmini/esmini/blob/master/CONTRIBUTING.md
Complementing the Build guide this section contains additional information regarding branch strategy, formatting and test.
12.1. Branch strategy
From v2.30.0 the dev
branch was introduced as the new top branch for continuous integration of features and bug fixes.
dev
branch contains the latest integrated features and bug fixes. New features should be developed in branches created from dev
. master
is the default branch and primarily reserved for official releases.
Continuous integration job will be launched for pushed commits on the following branches:
-
master
-
dev
-
any other branch starting with
feature/
, e.g.feature/support_emergency_vehicles
Pull requests should be targeting the dev
branch.
Master branch represent the latest released software. It should be in synch with dev.
12.1.1. Branch out of synch
dev
and master
branches should be in perfect sync, i.e. based on common history. Unfortunately, to keep this true and to avoid messy history and lost tags, branch history might be changed in rare cases. That can lead to local branch clones out of synch. If that is the case, follow these steps to restore sync of your local branch:
-
git branch backup
(only needed if you have local commits you want to preserve for later cherry-picking) -
git fetch origin
-
git reset --hard origin/<branch name>
<branch name>
might be master
or dev
, or whatever branch to be restored.
12.2. Formatting
From esmini v2.30.0 the CI will include formatting checks, in addition to static analysis and tests. The code styles are defined by following schemes:
C++ | |
Python | |
cmake |
To run formatting (both check and apply) locally, follow steps below:
Dependencies
clang-format
esmini makes use of version 15, avoiding some known bugs in v14
Windows:
Download and install LLVM-15.0.7-win64.exe found here:
https://github.com/llvm/llvm-project/releases/tag/llvmorg-15.0.7
Linux:
Check if clang-format version 15 is installed:
clang-format-15
If you get the following message:
Command 'clang-format-15' not found, but can be installed with:
sudo apt install clang-format-15
then clang 15 must be installed.
On Ubuntu systems prior to 22.04, first install llvm version 15:
wget https://apt.llvm.org/llvm.sh
chmod +x llvm.sh
sudo ./llvm.sh 15
Source: apt.llvm.org
Then, clang v15 can be installed:
sudo apt install clang-format-15
see also: ci.yml
Install dependent Python packages
pip install -r ./support/python/requirements.txt
Check format
./support/python/src/main.py run format --clang_format_checker
./support/python/src/main.py run format --cmake_format_checker
./support/python/src/main.py run format --black_format_checker
12.2.1. Apply formatting
./support/python/src/main.py run format
or specifics:
./support/python/src/main.py run format --clang_format
./support/python/src/main.py run format --cmake_format
./support/python/src/main.py run format --black_format
12.3. Test
12.3.1. Test frameworks
There are two sets of tests on different frameworks:
-
Unit tests (white box)
-
Smoke tests (black box)
The unit tests suites are basically C++ code modules of test cases based on the Google Test framework. They range from true unit tests (testing individual classes or functions) to system level tests reading a scenario and inspecting output, e.g. log files or visualization pixel data. The key feature of the unit tests is the possibility to inspect internal objects and variables.
Example: Unittest/RoadManager_test.cpp
The smoke test suite is basically execution of a selection of scenarios with some strategic inspections of the result. Scenario elements and timing can be checked against log file while object info such as position, rotation and speed can be checked against dat file entries. A Python framework handles the execution and gathering of result for convenient checking.
The smoke test script is found here: test/smoke_test.py
12.3.2. Run tests
For easy execution of all tests, both unit tests and smoke tests, run this main test script: scripts/run_tests.sh, from esmini root folder:
./scripts/run_tests.sh
Tip: On Windows, run it from GIT bash.
12.3.3. Run XML schema validation tests
XML schema validation ensures scenario files (xosc and xodr) complies with the OpenSCENARIO and OpenDrive XSD schemas. The checks are done by the Python script run_schema_comply.py
located in the scripts
directory. The schema files are located in the resources/schema
directory. From release v2.40, all esmini scenario files are validated against the schemas as part of CI.
The script depends on the xmlschema
and lxml
Python packages. To install required packages, run:
pip install -r support/python/requirements.txt
Run the XML validation tests:
python3 ./scripts/run_schema_comply.py <path1> [path2] [path3] …
where path can be an XML file or a directory. Any directory subfolders will be included recursively.
Example 1, check single file:
python3 scripts/run_schema_comply.py resources/xosc/cut-in.xosc
Example 2, check two files:
python3 scripts/run_schema_comply.py resources/xosc/cut-in.xosc resources/xodr/two_plus_one.xodr
Example 3, check two directories:
python3 scripts/run_schema_comply.py resources EnvironmentSimulator
Example 4, check one file and one directory:
python3 scripts/run_schema_comply.py EnvironmentSimulator/Unittest/xosc/conflicting-domains.xosc resources
Tip: Use bash find
command to apply finer filtering. Example:
python3 scripts/run_schema_comply.py $(find ./resources ./EnvironmentSimulator -name "follow_route*.xosc" -or -name "*500*.xodr" -or -name "*signs.xodr")
validates all OpenSCENARIO files starting with "follow_route" and all OpenDRIVE files containing "500" or ending basename with "signs" or containing "500", in and under the resource
and EnvironmentSimulator
folders.
12.4. Sanitizers
Google sanitizers framework is utilized to detect memory related errors, for example memory leaks. It’s executed nighly as a GitHub CI actions job on dev branch. The job can also be trigged manually, but will only run checks on dev branch. Sanitizers is currently only applied to Linux builds.
To run the sanitizer checks locally, on Ubuntu:
-
Set the following environment variables, run from esmini root:
export LSAN_OPTIONS="print_suppressions=false:suppressions=$(pwd)/scripts/LSAN.supp"
export ASAN_OPTIONS="detect_invalid_pointer_pairs=1:strict_string_checks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1:fast_unwind_on_malloc=0:suppressions=$(pwd)/scripts/ASAN.supp"
-
Configure and build esmini with sanitizers enabled
cd build
cmake -G Ninja -DUSE_OSG=FALSE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DENABLE_SANITIZERS=TRUE ..
ninja install
cd ..
-
Run the test suites, including both unit- and smoke tests, from esmini root:
./scripts/run_tests.sh
12.4.1. Troubleshooting
On Ubuntu some combination of gcc and sanitizer randomly cause sanitizer run failure with error message: AddressSanitizer:DEADLYSIGNAL.
If this happens, try the following:
setarch $(uname -m) -R
bash
setarch
will affect reported architecture from system to sanitizer. bash
is to restore shell environment, it can of course be replaced by any shell program, e.g. tcsh.
To revert settings, simply run:
exit
exit
First exit will escape the latter bash environment, the second reverts effect of the setarch command.
Source of setarch tip: https://stackoverflow.com/questions/77894856/possible-bug-in-gcc-sanitizers
13. esmini lib programming
13.1. About the lib itself
The main idea with esmini lib is to provide a limited, simplifed but useful API to the complex world of esmini, OpenSCENARIO and traffic simulation. By making use of only standard C types it has been possible to wrap and use the library in numerous environments, like for example MATLAB, Simulink and Unity (C#).
esmini lib is a, so called, shared library which is linked dynamically with a custom application. This means that it’s not linked statically with the application, instead it’s loaded whenever the application is launched. An obvious advantage with shared libraries is that they can be shared between multiple applications, reducing size and potentially simplify maintenance.
An additional important advantage with dynamic linking is that the library can be updated without need to recompile the custom application making use of the library (however it can’t be updated during runtime, only in between launches). An obvious requirement is that the API is compatible when replacing a library with a different version, since the API is established at link-time. On the other side of the "same coin", a disadvantage with dynamic linking is that you need to have the shared library present at run-time.
Although esmini lib is a shared library itself, it includes everything needed in terms of 3rd party functionality. It’s actally linking all dependencies statically. Think about it as a single container including everything needed, e.g. support for 3D graphics, XML, expressions, and SUMO traffic simulation. There are two advantages with this approach: 1. Getting rid of dependency to hundreds of 3rd party shared libraries and 2. The linker makes sure to only include relevant functions which reduce the size by extreme (compared to bundle individual and complete shared libraries).
13.2. How to interact with esmini lib API
Below is a typical interaction between a custom application (e.g. vehicle simulator) and esmini (providing simulation of the environment, like road and traffic).
Frame | app time | app events | esmini time | esmini events |
---|---|---|---|---|
0 (t=0.0) |
0.0 |
Init esmini("scenario.xosc") ⇒ |
0.0 |
|
0.0 |
0.0 |
Step(dt=0.0) |
||
0.0 |
0.0 |
|
||
0.0 |
0.0 |
|
||
0.0 |
0.0 |
|
||
0.0 |
0.0 |
|
||
0.0 |
0.0 |
|
||
0.0 |
Get Ego state (initial position, speed…) ⇒ |
0.0 |
||
0.0 |
0.0 |
⇐ Return Ego initial state |
||
1 (t=0.0→0.1) |
0.1 |
Update Ego(dt=0.1) |
0.0 |
|
0.1 |
|
0.0 |
||
0.1 |
Report Ego state(state) ⇒ |
0.0 |
||
0.1 |
0.0 |
Register state of Ego, protect it from default controller (but do not apply state yet) |
||
0.1 |
Step esmini(dt=0.1) ⇒ |
0.0 |
||
0.1 |
0.0 |
Step(dt=0.1) |
||
0.1 |
0.0 |
|
||
0.1 |
0.0 |
|
||
0.1 |
0.0 |
|
||
0.1 |
0.1 |
|
||
0.1 |
0.1 |
|
||
0.1 |
0.1 |
|
||
0.1 |
0.1 |
|
||
2 (t=0.1→0.2) |
0.2 |
Update Ego(dt=0.1) |
0.1 |
|
0.2 |
|
0.1 |
||
0.2 |
Report Ego state(state) ⇒ |
0.1 |
||
0.2 |
0.1 |
Register state of Ego, protect it from default controller (but do not apply state yet) |
||
0.2 |
Step esmini(dt=0.1) ⇒ |
0.1 |
||
0.2 |
0.1 |
Step(dt=0.1) |
||
0.2 |
0.1 |
|
||
0.2 |
0.1 |
|
||
0.2 |
0.1 |
|
||
0.2 |
0.2 |
|
||
0.2 |
0.2 |
|
||
0.2 |
0.2 |
|
||
0.2 |
0.2 |
|
||
and so on… |
13.3. Functionality
No systematic documentation available. See header files esminiLib.hpp and esminiRMLib.hpp for API details.
The following section covers some specific use cases of the library.
13.3.1. Save or grab images
Screenshots can be controlled via the API:
int SE_SaveImagesToFile(int nrOfFrames);
nrOfFrames: -1 ⇒ continuously, 0 ⇒ stop, > 0 ⇒ number of frames, e.g. 1=next frame only, 2=next two frames
Images will be stored in current folder with name screen_shot_*.tga
(* = counter) for post processing.
Images can also be saved to memory for instant processing. Control the feature with:
int SE_SaveImagesToRAM(bool state);
state: true ⇒ capture images, false ⇒ don’t capture (default, might improve performance on some systems)
Grab image with:
int SE_FetchImage(SE_Image *image);
image is a reference to a SE_Image struct which will contain the image meta and image data
See image-capture code example.
Note: In order to grab a rendered frame image, it must have been saved to RAM during the post rendering phase. First frame will be saved to RAM by default. The SE_SaveImagesToRAM() function must be used to enable the feature for frames > 0.
The reason for not enable always by default is performance savings. On some system the operation to read back rendered pixels from graphics frame buffer to application RAM memory is not neglectable (depending on whether color coding or byte order transformation is needed). So a compromize is to have the feature enabled for the first init frame, and then activate by user when needed.
Finally, to disable esmini image handling altogether and revert to OSG default handling, make the following call before SE_Init():
SE_SetOffScreenRendering(false);
Note that disabling the this will permanently disable the callback mechanism for the complete esmini session (overriding any SE_SaveImagesToRAM() call).
13.3.2. Custom camera positions
Similarly as with camera related launch arguments, camera positioning can be controlled via esmini lib API.
-
SE_AddCustomCamera()
Add a camera with relative position and orientation (heading and pitch) -
SE_AddCustomFixedCamera()
Add a fixed camera at custom global position and orientation (heading and pitch) -
SE_AddCustomAimingCamera()
Add a camera with relative position looking at current entity -
SE_AddCustomFixedAimingCamera()
Add a camera at fixed location but always looking at current entity -
SE_AddCustomFixedTopCamera()
Add a top view camera with fixed position and rotation -
SE_SetCameraObjectFocus
Set/change object to focus on
More info, see headerfile.
Any number of cameras can be defined. These cameras can then be activated via function: SE_SetCameraMode()
.
Applications can even loop through the camera modes to produce images from several cameras. See code example multiple-cameras.cpp.
13.3.3. OSI data
OSI data can be retrieved in three ways:
-
Storing OSI tracefile
-
Receive in UDP packages
-
Receive via API call
OSI tracefile
Call SE_EnableOSIFile(filename)
in order to create a OSI tracefile. Specify custom filename or set 0 or "" to use default filename (currently "ground_truth.osi").
OSI via UDP
By launch argument --osi_receiver_ip <ip addr>
you can have esmini sending OSI packeges in UDP frames. The socket port is hardcoded but can of course be changed in the code (OSI_OUT_PORT in OSIReporter.cpp).
See receiver side code example osi_receiver.cpp.
OSI data via API call
Fetch OSI data by either one of the API calls:
-
SE_GetOSIGroundTruthRaw()
-
SE_GetOSIGroundTruth(int* size)
The "raw" version will simply return a pointer to the already existing OSI data structure. No additional data copying will be performed. This is the preferered option if:
-
The application is coded in C++, making the Protobuf data structures fully compatible
-
The data is read before next call to
SE_UpdateOSIGroundTruth()
, which will update the data
See code example: osi-groundtruth, also covered in User guide - Hello world tutorial OSI groundtruth.
The second function will fetch the OSI data serialized into a string. This will be a true copy and also generic format which can be parsed in any environment, independent of OS and programming language. The backside is additional resources needed for the copying and serialization.
See code examples in ScenarioEngineDLL test cases, e.g: receive_GroundTruth.
Note:
SE_UpdateOSIGroundTruth()
needs to be called after each SE_Step* in order to prepare and populate OSI data. This function will also save the OSI frame to file and send over UDP, if requested. Only after this preparation the SE_GetOSIGroundTruth* methods will get updated OSI data.
An exception is when OSI file or UDP receiver is specified by corrsponding launch arguments --osi_file [filename]
and --osi_receiver_ip <ip addr>
. Then, OSI update will automatically be updated by esmini according to specified multiplier --osi_freq <number>
(1 is default). For example, --osi_freq 3
will update OSI every third step.
The purpose of this explicit OSI update control is to support use cases where esmini is running at much higher frequency than needed for OSI data (e.g. for vehicle dynamics purposes). To optimize performance, OSI update can then be skipped most frames and only run when needed.
BTW: Any additional explicit calls to SE_UpdateOSIGroundTruth()
will be ignored. Which means that the programmer does not have to worry about additional performance penalty introduced by multiple calls to this function.
13.3.4. Simple ideal sensors
OSI provides information needed for sensor models at bounding box level for both moving and stationary objects, including ones defined in OpenDRIVE. Sensor models can then detect overlap, occlusion and collisions between those bounding boxes. Although sensor models are not within scope of esmini, there is a code example on how to retrieve and extract OSI data. It can perhaps be a starting point: osi-groundtruth.cpp
For even simpler use cases esmini provides a sensor mechanism, basically collecting scenario objects which reference point is within a sensor view frustum. This is too rough for collision detection, but can be useful for low-pass features like adaptive cruse control (ACC) or just optimizing CPU load by filtering objects for some processing.
The view frustum is defined by a horizontal field of view
combined with near
and far
limits. Basically an object is detected by a ideal sensor if the object reference point is within the view frustum, i.e:
-
angle from sensor mounting point is within the
field of view
-
distance from sensor mounting point is larger than
near
and less thanfar
The sensors are added from the code, see the esmini-dyn code example. API is found here.
Limitations:
-
OpenDRIVE stationary objects not considered
-
Only reference point checking (part of the object can be inside frustum and still not detected)
13.4. Simplest example
Below example is just to show the simplicity of using esmini library. For more information and realistic examples, e.g. involving cmake, please go ahead to Hello World programming tutorial.
If not already done, download and unpack latest esmini demo. Make sure to pick demo, not bin, for your platform.
In esmini root folder, create a file named main.cpp
and add the following lines:
Code:
#include "./EnvironmentSimulator/Libraries/esminiLib/esminiLib.hpp"
int main()
{
SE_Init("./resources/xosc/cut-in.xosc", 0, 1, 0, 0);
while (SE_GetQuitFlag() == 0)
SE_Step();
SE_Close();
return 0;
}
Compile:
-
Linux:
gcc main.cpp ./bin/libesminiLib.so -Wl,-rpath,\$ORIGIN -o bin/my_player
-
Windows (x64 Native Tools Command Prompt for Visual Studio):
cl main.cpp ./bin/esminiLib.lib /Fe:bin/my_player
Run:
from Linux terminal or Windows PowerShell:
./bin/my_player
14. Hello World programming tutorial
This tutorial shows how to utilize esmini as a shared (dynamically loaded) library for use in a custom application. It can be used with or without esmini embedded viewer.
14.1. Preparations
The simplest preparation is to get the demo package from the latest relase and then head on to the steps below.
But, of course, if you already have checked out the esmini project from GitHub and compiled it you should already be set.
Just make sure that the shared library is available in the bin
or lib
folder, which should be available directly under esmini root. Exact filename depends on the platform as follows:
Windows: |
esminiLib.dll |
Linux: |
libesminiLib.so |
Mac: |
libesminiLib.dylib |
14.2. How to build, run and debug "Hello World"
14.2.1. Windows/Visual Studio
First step: Create the solution file
Run from Hello-World_coding-example
root folder:
mkdir build
cd build
cmake ..
(will generate solution for multiple variants, including Release and Debug)
Build and run the application from command line
Build
cmake --build . --config Release
(release variant), or
cmake --build . --config Debug
(debug variant)
Run
-
open a command shell and put yourself in
Hello-World_coding-example/build
folder -
add shared library location to the environment path variable, e.g:
CMD:set path=../../bin;%path%
PowerShell:$env:path+=";../../bin"
bash:PATH=$PATH:../../bin
-
Launch application
CMD:
.\Debug\esmini-player.exe
or
.\Release\esmini-player.exe
(depending on variant)
PowerShell or bash:
./Debug/esmini-player.exe
or
./Release/esmini-player.exe
Build, run and debug the application from Visual Studio
-
Open
esmini-player.sln
in Visual Studio -
Build as usual (Ctrl-F7 or
Build
→Build Solution
) -
Set as startup project
-
Right click on esmini-player in Solution Explorer
-
Click on
Set as Startup Project
-
-
Tell the system where to find the shared library
-
Right click on esmini-player in Solution Explorer
-
Click on
Properties
-
Select Configuration →
All Configurations
-
In
Configuration Properties
→Debugging
→Environment
write:path=$(ProjectDir)/../../bin
or any other folder containing the esmini library
-
-
Run
-
Ctrl-F5
to run without debug -
F5
to debug
-
14.2.2. Linux
First step: Create the Makefile
Run from Hello-World_coding-example
root folder:
mkdir build
cd build
then:
cmake .. -DCMAKE_BUILD_TYPE=Release
for Release variant, or
cmake .. -DCMAKE_BUILD_TYPE=Debug
for Debug variant
Build and run from command line
-
Build
cmake --build .
or just
make
-
Tell system where to look for esmini shared library (maybe not necessary)
export LD_LIBRARY_PATH=../../bin
(on macOS: DYLD_LIBRARY_PATH) -
Launch application
./esmini-player
Build, run and debug from Visual Studio Code
Prerequisites: Install the following VS Code extensions:
-
C/C++
-
CMake Tools
Build
-
Open visual studio as Hello-World_coding-example as root folder
-
Open the Command Palette (Ctrl+Shift+P) and run
CMake: Select a Kit
. Select the compiler you want to use. For example GCC 11.3.0 -
Open the Command Palette (Ctrl+Shift+P) run the
CMake: Select Variant
command. Select Debug. -
Open the Command Palette (Ctrl+Shift+P) run the
CMake:Set Build Target
. Select esmini-player. -
Open the Command Palette (Ctrl+Shift+P) and run the
CMake: Build
command, or select the build button from the Status bar.
Open main.cpp file in the Hello-World_coding-example folder and optionally introduce break points.
Run
-
Open the Command Palette (Ctrl+Shift+P) and run the
CMake: Run Without Debugging
command, or -
Use play button in the status bar, or
-
Shift-F5
Debug
-
Open the Command Palette (Ctrl+Shift+P) and run the
CMake: Debug
command, or -
Use debug button in the status bar, or
-
Ctrl-F5
14.2.3. Next step
Now that you can build and run Hello World, it’s time to explore some functionality of esmini. Next section presents a few code examples to try out, e.g. by modifying main.cpp.
14.3. Examples
14.3.1. Hello world - load and play a scenario
#include "esminiLib.hpp"
int main(int argc, char* argv[])
{
SE_Init("../../resources/xosc/cut-in.xosc", 0, 1, 0, 0);
for (int i = 0; i < 500; i++)
{
SE_Step();
}
SE_Close();
return 0;
}
Exercise: Change scenario to pedestrian.xosc, then compile and run the program again.
14.3.2. Add optional argument to load any scenario
#include "esminiLib.hpp"
int main(int argc, char* argv[])
{
if (argc > 1)
{
SE_InitWithArgs(argc, const_cast<const char**>(argv));
}
else
{
SE_Init("../resources/xosc/cut-in.xosc", 0, 1, 0, 0);
}
for (int i = 0; i < 500; i++)
{
SE_Step();
}
SE_Close();
return 0;
}
You can now specify esmini arguments according to esmini launch commands.
Example:
./esmini-player --window 60 60 1000 500 --osc ../resources/xosc/pedestrian.xosc --trails
14.3.3. Fetch state of scenario objects
This example shows how to retrieve the state of scenario objects.
We also check the return code from the initialization and react on any error.
#include "stdio.h"
#include "esminiLib.hpp"
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
if (SE_Init("../resources/xosc/cut-in.xosc", 0, 1, 0, 0) != 0)
{
printf("Failed to initialize scenario\n");
return -1;
}
for (int i = 0; i < 500; i++)
{
SE_Step();
for (int j = 0; j < SE_GetNumberOfObjects(); j++)
{
SE_ScenarioObjectState state;
SE_GetObjectState(SE_GetId(j), &state);
printf("time [%.2f] object[%d] id %d pos[%.2f, %.2f] %.2f %.2f \n",
static_cast<double>(state.timestamp),
j,
SE_GetId(j),
static_cast<double>(state.x),
static_cast<double>(state.y),
static_cast<double>(state.wheel_angle),
static_cast<double>(state.wheel_rot));
}
}
SE_Close();
return 0;
}
14.3.4. External control of Ego
A silly example showing how you can just take control over vehicle state via the API. The Ego car will move one meter along the Y-axis for each frame while rotating…
Now we will also introduce the quit_flag, which lets you quit by pressing 'Esc' key.
In addition we’ll try out the alignment feature. By default objects will be aligned to road surface. This behavior can be controlled with alignment settings. In below example, the Ego vehicle will be aligned to road surface except during iterations 100-200 where it will obey the reported Z coordinate and move accordingly to elevation/Z = 10 meter.
#include "esminiLib.hpp"
#include <stdio.h>
int main(int argc, char* argv[])
{
const char* filename = argc > 1 ? argv[1] : "../resources/xosc/cut-in_external.xosc";
SE_ScenarioObjectState state;
double z = 0.0f;
if (SE_Init(filename, 0, 1, 0, 0) != 0)
{
return -1;
}
for (int i = 0; i < 500 && SE_GetQuitFlag() != 1; i++)
{
SE_ReportObjectPos(SE_GetId(0), 0.0f, 8.0f, static_cast<float>(i), static_cast<float>(z), float(1.57 + 0.01 * i), 0.0f, 0.0f);
SE_Step();
// fetch position in terms of road coordinates
SE_GetObjectState(SE_GetId(0), &state);
printf("road_id: %d s: %.3f lane_id %d lane_offset: %.3f z: %.2f\n",
state.roadId,
static_cast<double>(state.s),
state.laneId,
static_cast<double>(state.laneOffset),
static_cast<double>(state.z));
if (i == 100)
{
z = 10.0;
printf("Release relative road alignment, set absolute z = %.2f\n", z);
SE_SetObjectPositionMode(SE_GetId(0),
SE_PositionModeType::SE_SET,
SE_PositionMode::SE_Z_ABS); // release relative alignment to road surface
}
if (i == 200)
{
z = 0.0;
printf("Restore relative road alignment, set relative z = %.2f\n", z);
SE_SetObjectPositionMode(SE_GetId(0),
SE_PositionModeType::SE_SET,
SE_PositionMode::SE_Z_REL); // enforce relative alignment to road surface
}
}
SE_Close();
return 0;
}
Exercise: Change heading with i, e.g:
SE_ReportObjectPos(0, 0.0f, 8.0f, (float)i, 0.0f, 1.57f + 0.01f*i, 0.0f, 0.0f);
Yes, it looks crazy! But it demonstrates how an application totally can take control of a vehicle.
14.3.5. Control controllers
Try to run cut-in_interactive.xosc, as below.
#include "esminiLib.hpp"
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
SE_Init("../resources/xosc/cut-in_interactive.xosc", 0, 1, 0, 0);
for (int i = 0; i < 2000 && SE_GetQuitFlag() != 1; i++)
{
SE_Step();
}
SE_Close();
return 0;
}
Control the Ego vehicle with arrow keys.
To disable controllers and hand over to default scenario behavior set first argument flag (see headerfile esminiLib.hpp):
#include "esminiLib.hpp"
int main(int, char**)
{
SE_Init("../resources/xosc/cut-in_interactive.xosc", 1, 1, 0, 0);
for (int i = 0; i < 2000 && SE_GetQuitFlag() != 1; i++)
{
SE_Step();
}
SE_Close();
return 0;
}
14.3.6. Ideal sensors
#include "stdio.h"
#include "esminiLib.hpp"
#define MAX_HITS 10
int main(int, char**)
{
SE_Init("../resources/xosc/cut-in.xosc", 0, 1, 0, 0);
SE_AddObjectSensor(0, 2.0f, 1.0f, 0.5f, 1.57f, 1.0f, 50.0f, 1.57f, MAX_HITS);
SE_AddObjectSensor(0, -1.0f, 0.0f, 0.5f, 3.14f, 0.5f, 20.0f, 1.57f, MAX_HITS);
// Turn on visualization of object sensors, toggle key 'r'
SE_ViewerShowFeature(1, true);
for (int i = 0; i < 2000 && !(SE_GetQuitFlag() == 1); i++)
{
SE_Step();
int objList[MAX_HITS];
for (int j = 0; j < 2; j++) // iterate over added sensors
{
int nHits = SE_FetchSensorObjectList(j, objList);
for (int k = 0; k < nHits; k++)
{
printf("Sensor[%d] detected obj: %d\n", j, objList[k]);
// Get some info of that detected object
SE_ScenarioObjectState state;
SE_GetObjectState(objList[k], &state);
printf("object[%d] pos: (%.2f, %.2f) heading: %.2f\n",
objList[k],
static_cast<double>(state.x),
static_cast<double>(state.y),
static_cast<double>(state.h));
}
}
}
SE_Close();
return 0;
}
Note: If you want M_PI, add on top (before includes): #define _USE_MATH_DEFINES
14.3.7. Collision detection
There are two approaches to collision detection that fit different use cases:
1. By scenario
Making use of the OpenSCENARIO CollisionCondition a collision can be detected and act upon. This can be useful for example if a scenario or test case should end in case of Ego vehicle gets involved in a collision.
See example EnvironmentSimulator/Unittest/xosc/test-collision-detection.xosc. To run it from command line:
./bin/esmini --window 60 60 800 400 --osc ./EnvironmentSimulator/Unittest/xosc/test-collision-detection.xosc
Control the Ego car with keyboard arrow keys. Press "up" to accelerate, and "left" or "right" to steer. See output in console when colliding with the two other cars.
The interactive controller can be disabled. Then the Ego car will get the speed from the scenario. Run:
./bin/esmini --window 60 60 800 400 --osc ./EnvironmentSimulator/Unittest/xosc/test-collision-detection.xosc --disable_controllers
Note: In this example the action is empty, i.e. the event is triggered on collision but nothing really happens, other than logging the trigger result. In a real use case there could be an actual action, e.g. immediate stop by setting speed to zero in a single step. Another usage is to use the condition in a scenario stop trigger, e.g. to quit the test case as soon as collision happens.
2. By API
In addition, or as alternative, to detect collisions via OpenSCENARIO condition esmini can check for collisions between any objects regardless of the scenario. This is a feature that must be enabled (SE_CollisionDetection(true)
) since it’s off by default to avoid unnecessary performance penalties.
See code example:
#include "esminiLib.hpp"
#include "stdio.h"
int main(void)
{
SE_Init("../EnvironmentSimulator/Unittest/xosc/test-collision-detection.xosc", 1, 1, 0, 0);
SE_CollisionDetection(true);
for (int i = 0; i < 500; i++)
{
SE_Step();
for (int j = 0; j < SE_GetNumberOfObjects(); j++)
{
for (int k = 0; k < SE_GetObjectNumberOfCollisions(j); k++)
{
printf("Collision[%d]: %d\n", j, SE_GetObjectCollision(j, k));
}
}
}
return 0;
}
Note: It uses the same scenario file as the above (1. By scenario) but the complete "CollisionManeuver" could be removed since no CollisionCondition is needed for API based collision detection. Actually, if maneuver is present the console will show log info from both the condition and the API driven collision detections.
14.3.8. Simple speed control
As an alternative to the external control examples above, you can control the speed of an object directly. Just note that any triggered scenario actions involving the object might conflict with speed settings via lib API. Recommendation is control an object either via API or scenario file. That said, the combination can be useful for some use cases.
The code will modulate speed of Ego (first vehicle) between 2 and 20 mps.
#include "esminiLib.hpp"
#include <iostream>
int main(int argc, char **argv)
{
float acc = 10; // initial acceleration
float speed = 2.0f; // initial speed
if (argc > 1)
{
SE_Init(argv[1], 0, 1, 0, 0);
}
else
{
printf("Usage: %s <path to xosc file>\n", argv[0]);
return -1;
}
while (SE_GetQuitFlag() != 1)
{
speed += acc * SE_GetSimTimeStep(); // get latest time step from simulation
// modulate speed by changing sign of acceleration now and then
if (speed < 2.0f)
{
acc = 10;
}
else if (speed > 20.0f)
{
acc = -10;
}
// report updated speed
SE_ReportObjectSpeed(0, speed);
// step the simulation in natural speed, change to SE_Step(<time-step>) for fixed timestep
SE_Step();
}
SE_Close();
return 0;
}
Example run command, from esmini/code-examples-bin
folder:
./speed_controller ../resources/xosc/cut-in.xosc
There is also a Python version of this example:
EnvironmentSimulator/code-examples/hello_world/speed_controller.py
14.3.9. Inject actions
This example demonstrates how you can control longitudinal and lateral motion by applying the following actions:
-
SpeedAction
-
LaneChangeAction
-
LaneOffsetAction
Currently (v2.37.0) the action injection feature is limited to these three actions, but it’s rather easy to extend if needed.
The code will apply three actions according as follows:
-
after 2 seconds, make a slight movement (lane offset) of 0.45m to the right
-
after 7 seconds, make a lane change to the left
-
after 8 seconds, consider to do another lane change, but skip since another is already ongoing
-
after 9.5 seconds, brake softly (braking will start before lane change is done)
-
after 11.0 seconds, brake harder to a stop (will cancel the ongoing soft brake action)
#include "esminiLib.hpp"
#include <string.h>
#include <iostream>
int main(int argc, char **argv)
{
float dt = -1.0f; // default to variable timestep
if (argc > 2)
{
if (SE_InitWithArgs(argc, const_cast<const char **>(argv)) != 0)
{
printf("Error: Could not initialize scenario %s\n", argv[1]);
return -1;
}
for (int i = 2; i < argc; i++)
{
if (strcmp(argv[i], "--fixed_timestep") == 0 && i < argc - 1)
{
dt = static_cast<float>(atof(argv[i + 1])); // use any provided fixed timestep
}
}
}
else if (argc > 1)
{
if (SE_Init(argv[1], 0, 1, 0, 0) != 0)
{
printf("Error: Could not initialize scenario %s\n", argv[1]);
return -1;
}
}
else
{
printf("Usage: %s <path to xosc file>\n", argv[0]);
return -1;
}
int state = 0;
while (SE_GetQuitFlag() == 0 && SE_GetSimulationTime() < 17.0f)
{
if (state == 0 && SE_GetSimulationTime() > 2.0f)
{
printf("Injecting lane offset action\n");
SE_LaneOffsetActionStruct lane_offset;
lane_offset.id = 0; // id of object to perform action
lane_offset.offset = -0.45f; // target offset in m
lane_offset.maxLateralAcc = 0.5f; // max lateral acceleration in m/s^2
lane_offset.transition_shape = 0; // cubic
SE_InjectLaneOffsetAction(&lane_offset);
state++;
}
else if (state == 1 && SE_GetSimulationTime() > 7.0f)
{
printf("Injecting lane change action\n");
SE_LaneChangeActionStruct lane_change;
lane_change.id = 0; // id of object to perform action
lane_change.mode = 1; // relative (own vehicle)
lane_change.target = 1; // target lane id (relative)
lane_change.transition_shape = 2; // sinusoidal
lane_change.transition_dim = 2; // time
lane_change.transition_value = 3.0f; // s
SE_InjectLaneChangeAction(&lane_change);
state++;
}
else if (state == 2 && SE_GetSimulationTime() > 8.0f)
{
if (SE_InjectedActionOngoing(5)) // 5 = LAT_LANE_CHANGE
{
printf("Lane change already ongoing, skipping second lane change\n");
}
else
{
printf("Injecting lane change action 2\n");
SE_LaneChangeActionStruct lane_change;
lane_change.id = 0; // id of object to perform action
lane_change.mode = 1; // relative (own vehicle)
lane_change.target = -1; // target lane id (relative)
lane_change.transition_shape = 2; // sinusoidal
lane_change.transition_dim = 2; // time
lane_change.transition_value = 3.0f; // s
SE_InjectLaneChangeAction(&lane_change);
}
state++;
}
else if (state == 3 && SE_GetSimulationTime() > 9.5f) // slightly overlapping lane change action
{
printf("Injecting speed action - soft brake\n");
SE_SpeedActionStruct speed;
speed.id = 0; // id of object to perform action
speed.speed = 0.0f; // target speed in m/s
speed.transition_shape = 0; // cubic
speed.transition_dim = 1; // rate
speed.transition_value = 3.0f; // m/s^2
SE_InjectSpeedAction(&speed);
state++;
}
else if (state == 4 && SE_GetSimulationTime() > 11.0f) // slightly overlapping lane change action
{
printf("Injecting speed action - hard brake\n");
SE_SpeedActionStruct speed;
speed.id = 0; // id of object to perform action
speed.speed = 0.0f; // target speed in m/s
speed.transition_shape = 1; // cubic
speed.transition_dim = 1; // rate
speed.transition_value = 8.0f; // m/s^2
SE_InjectSpeedAction(&speed);
state++;
}
if (dt < 0.0f)
{
SE_Step();
}
else
{
SE_StepDT(dt);
}
}
SE_Close();
return 0;
}
Example run command, from esmini/code-examples-bin
folder:
./action_injection ../resources/xosc/cut-in.xosc
There is also a Python version of this example:
EnvironmentSimulator/code-examples/hello_world/action_injection.py
14.3.10. Driver model
Using a simple vehicle model this example demonstrates how a driver model can interact with the scenario in terms of pedal and steering input. This time we use the ExternalController
again to ensure any scenario actions is not conflicting with the driver model.
To show different variants we also show how to set scenario parameters during intialization, via callbacks. In effect this is a way to achieve runtime variants of the same basic scenario.
Before heading into the application code we will look into the scenario file EnvironmentSimulator/code-examples/test-driver/test-driver.xosc.
Now, let’s have a look inside it to see how to activate the ExternalController, which will prevent the DefaultController to interfere with the Ego vehicle and instead hand over exclusive control to our application. You can skip this and go to the C++ code example below if you’re not interested in the controller setup.
-
Open test-driver.xosc
-
Look in the Entities section, under the
<ScenarioObject name="Ego"\>
element. Here the controller is defined and assigned to the Ego vehicle.
<ObjectController>
<Controller name="MyExternalControllerWithGhost">
<Properties>
<Property name="esminiController" value="ExternalController" />
<Property name="useGhost" value="$GhostMode" />
<Property name="headstartTime" value="2" />
</Properties>
</Controller>
</ObjectController>
Note: The GhostMode parameter is set to true or false in the ParameterDeclarations section in the top of the scenario file.
-
Then the initial position is set. This could instead be done by the application, but it’s convenient to specify it in the scenario file.
<PrivateAction>
<TeleportAction>
<Position>
<LanePosition roadId="1" laneId="-1" offset="0" s="50"/>
</Position>
</TeleportAction>
</PrivateAction>
-
Finally we need to activate the controller on both lateral and longitudinal domains.
<PrivateAction>
<ActivateControllerAction longitudinal="true" lateral="true" />
</PrivateAction>
The reason for putting it AFTER the positioning is that oterwise the position action would have no effect, since once the controller is activated all scenario actions will be ignored. The controller then having exclusive control.
Now let’s head into the code example. It will run the scenario three times:
-
External controller without ghost mode
-
just look ahead and follow along current lane. Find point along the lane and steer towards it.
-
-
External controller with ghost mode and time based lookahead
-
find the state of ghost at given time offset. Use it for steering and speed target.
-
-
External controller with ghost mode and position based lookahead
-
given the current Ego position, find ghost state at some look ahead distance along its trail. Use it for steering and speed target.
-
Now, study the code and perhaps play around mainpulating some values.
#include "stdio.h"
#include "math.h"
#include <string>
#include "esminiLib.hpp"
void paramDeclCB(void* user_arg)
{
bool ghostMode = *(static_cast<bool*>(user_arg));
SE_LogMessage((std::string("Running with ghostMode = ").append(ghostMode == true ? "true" : "false")).c_str());
SE_SetParameterBool("GhostMode", ghostMode);
}
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
const double defaultTargetSpeed = 50.0;
const double curveWeight = 30.0;
const double throttleWeight = 0.1;
const float duration = 35.0f;
bool ghostMode[3] = {false, true, true};
void* vehicleHandle = 0;
SE_SimpleVehicleState vehicleState = {0, 0, 0, 0, 0, 0, 0, 0};
SE_ScenarioObjectState objectState;
SE_RoadInfo roadInfo;
for (int i = 0; i < 3; i++)
{
float dt = 0.0f;
SE_RegisterParameterDeclarationCallback(paramDeclCB, &ghostMode[i]);
if (SE_Init("../EnvironmentSimulator/code-examples/test-driver/test-driver.xosc", 0, 1, 0, 0) != 0)
{
SE_LogMessage("Failed to initialize the scenario, quit\n");
return -1;
}
// Lock object to the original lane
// If setting to false, the object road position will snap to closest lane
SE_SetLockOnLane(0, true);
// Initialize the vehicle model, fetch initial state from the scenario
SE_GetObjectState(0, &objectState);
vehicleHandle = SE_SimpleVehicleCreate(objectState.x, objectState.y, objectState.h, 4.0, 0.0);
SE_SimpleVehicleSteeringRate(vehicleHandle, 6.0f);
// show some road features, including road sensor
SE_ViewerShowFeature(4 + 8, true); // NODE_MASK_TRAIL_DOTS (1 << 2) & NODE_MASK_ODR_FEATURES (1 << 3),
// Run for specified duration or until 'Esc' button is pressed
while (SE_GetSimulationTime() < duration && SE_GetQuitFlag() != 1)
{
// Get simulation delta time since last call (first will be minimum timestep)
dt = SE_GetSimTimeStep();
// Get road information at a point some speed dependent distance ahead
double targetSpeed;
if (ghostMode[i] == true)
{
// ghost version
float ghost_speed = 0.0f;
float timestamp = 0.0f;
if (i % 2 == 0) // alternate between time based and position based look ahead modes
{
// Time based look ahead
SE_GetRoadInfoGhostTrailTime(0, SE_GetSimulationTime() + 0.25f, &roadInfo, &ghost_speed);
}
else
{
// Position based Time based look ahead
SE_GetRoadInfoAlongGhostTrail(0, 5 + 0.75f * vehicleState.speed, &roadInfo, &ghost_speed, ×tamp);
}
targetSpeed = ghost_speed;
}
else
{
// Look ahead along the road, to establish target info for the driver model
SE_GetRoadInfoAtDistance(0, 5 + 0.75f * vehicleState.speed, &roadInfo, 0, true);
// Slow down when curve ahead - CURVE_WEIGHT is the tuning parameter
targetSpeed = defaultTargetSpeed / (1 + curveWeight * static_cast<double>(fabs(roadInfo.angle)));
}
// Steer towards where the point
double steerAngle = roadInfo.angle;
// Accelerate or decelerate towards target speed - THROTTLE_WEIGHT tunes magnitude
double throttle = throttleWeight * (targetSpeed - static_cast<double>(vehicleState.speed));
// Step vehicle model with driver input, but wait until time > 0
if (SE_GetSimulationTime() > 0 && !SE_GetPauseFlag())
{
SE_SimpleVehicleControlAnalog(vehicleHandle, dt, throttle, steerAngle);
}
// Fetch updated state and report to scenario engine
SE_SimpleVehicleGetState(vehicleHandle, &vehicleState);
// Report updated vehicle position and heading. z, pitch and roll will be aligned to the road
SE_ReportObjectPosXYH(0, 0, vehicleState.x, vehicleState.y, vehicleState.h);
// The following values are not necessary to report.
// If not reported, esmini will calculate based on motion over time
// but for accuracy it's recommendeded to report if available.
// wheel status (revolution and steering angles)
SE_ReportObjectWheelStatus(0, vehicleState.wheel_rotation, vehicleState.wheel_angle);
// speed (along vehicle longitudinal (x) axis)
SE_ReportObjectSpeed(0, vehicleState.speed);
// Finally, update scenario using same time step as for vehicle model
SE_StepDT(dt);
}
SE_Close();
}
return 0;
}
Don’t worry about the stationary red car on the road, you’ll just drive through it.
Try experimenting with the driver settings, e.g. increase lookahead distance factor from 0.75 to 1.75.
When running the application, press key 'j' to toggle ghost trail dots and lines visualization.
Challenge: Attach a front looking sensor to detect it and have the driver to brake to avoid collision…
Ghost concept in brief
In the first loop the driver model is just capable of driving along the specified lane. It’s totally detached from any scenario events.
The ghost concept is a solution for a driver model to perform scenario actions. Basically it will launch a fore-runner to perform the scenario actions. The trajectory, including speed, is registered into a "trail" which can then be used as reference for steering and speed target.
Ghost mode is a parameter of the external controller. Normally this is set in the scenario, for example in test-driver.xosc, change line:
<ParameterDeclaration name="GhostMode" parameterType="boolean" value="false"/>
to:
<ParameterDeclaration name="GhostMode" parameterType="boolean" value="true"/>
However, in the example code this parameter is controlled by the paramDeclCB()
callback. Once it’s registered with SE_RegisterParameterDeclarationCallback()
it will be called each time esmini is initialized (SE_Init()
).
14.3.11. OSI groundtruth
To access OSI data we first need to complement the build script (CMakeLists.txt) with OSI include and library info. For example:
Windows:
include_directories(. ../include ../EnvironmentSimulator/Libraries/esminiLib ../externals/osi/v10/include) link_directories(. ../bin ../externals/osi/v10/lib) target_link_libraries(${TARGET} esminiLib libprotobuf open_simulation_interface_pic)
Linux:
include_directories(. ../include ../EnvironmentSimulator/Libraries/esminiLib ../externals/osi/linux/include) link_directories(. ../bin ../externals/osi/linux/lib) target_link_libraries(${TARGET} esminiLib protobuf open_simulation_interface_pic)
Note:
-
Replace foldername "v10" with linux or mac depending on your platform.
-
If linking with custom applications or libraries: OSI and Google Protobuf versions needs to be consistent (at least major version nr), also with the versions used in esmini (see here).
Then run cmake ..
from the build folder to apply the changes in CMakeFiles.cxx.
The following code will update, fetch and print some OSI data each frame.
#include "esminiLib.hpp"
#include "osi_common.pb.h"
#include "osi_object.pb.h"
#include "osi_groundtruth.pb.h"
#include "osi_version.pb.h"
int main(int argc, char* argv[])
{
(void)argc;
(void)argv;
const osi3::GroundTruth* gt;
SE_EnableOSIFile(0); // 0 or "" will result in default filename, ground_truth.osi
SE_Init("../resources/xosc/cut-in_simple.xosc", 0, 1, 0, 0);
// You could now retrieve the initial state of all objects before stepping the scenario
// Initial update of complete Ground Truth, including static things
SE_UpdateOSIGroundTruth();
// Fetch initial OSI struct
gt = reinterpret_cast<const osi3::GroundTruth*>(SE_GetOSIGroundTruthRaw());
// Lane boundaries (static road info only available in first OSI frame
printf("lane boundaries: %d\n", gt->lane_boundary_size());
for (int j = 0; j < gt->lane_boundary_size(); j++)
{
printf(" lane boundary %d, nr of boundary points: %d\n", j, gt->lane_boundary(j).boundary_line_size());
#if 0 // change to 1 in order to print all boundary points
for (int k = 0; k < gt->lane_boundary(j).boundary_line_size(); k++)
{
printf(" (%.2f, %.2f)\n",
gt->lane_boundary(j).boundary_line(k).position().x(),
gt->lane_boundary(j).boundary_line(k).position().y());
}
#endif
}
for (int i = 0; i < 4; i++)
{
SE_StepDT(0.01f);
// Further updates will only affect dynamic OSI stuff
SE_UpdateOSIGroundTruth();
// Fetch OSI struct
gt = reinterpret_cast<const osi3::GroundTruth*>(SE_GetOSIGroundTruthRaw());
// Print timestamp
printf("Frame %d timestamp: %.2f\n",
i + 1,
static_cast<double>(gt->timestamp().seconds()) + 1E-9 * static_cast<double>(gt->timestamp().nanos()));
// Static content such as lane boundaries should be 0 at this point
printf("lane boundaries: %d\n", gt->lane_boundary_size());
// Road markings, e.g. zebra lines
printf("road markings: %d\n", gt->road_marking_size());
// Moving objects
printf("moving objects: %d\n", gt->moving_object_size());
#if 1 // change to 1 in order to print some moving object state info
// Print object id, position, orientation and velocity
for (int j = 0; j < gt->moving_object().size(); j++)
{
printf(" obj id %u pos (%.2f, %.2f, %.2f) orientation (%.2f, %.2f, %.2f) vel (%.2f, %.2f, %.2f) acc (%.2f, %.2f, %.2f)\n",
static_cast<unsigned int>(gt->moving_object(j).id().value()),
gt->moving_object(j).base().position().x(),
gt->moving_object(j).base().position().y(),
gt->moving_object(j).base().position().z(),
gt->moving_object(j).base().orientation().yaw(),
gt->moving_object(j).base().orientation().pitch(),
gt->moving_object(j).base().orientation().roll(),
gt->moving_object(j).base().velocity().x(),
gt->moving_object(j).base().velocity().y(),
gt->moving_object(j).base().velocity().z(),
gt->moving_object(j).base().acceleration().x(),
gt->moving_object(j).base().acceleration().y(),
gt->moving_object(j).base().acceleration().z());
}
#endif
// Moving objects
printf("stationary objects: %d\n", gt->stationary_object_size());
}
SE_Close();
return 0;
}
14.4. Python binding
A Python wrapper for esmini can easily be created using "ctypes" (thanks David Kaplan for the tip!). This section presents a few examples to get you started.
-
Make sure the esminiLib is present in
esmini/bin
folder -
Run the script examples from the
esmini/Hello-World_coding-example
folder
14.4.1. Minimalistic esmini Python player
import ctypes
se = ctypes.CDLL("../bin/esminiLib.dll")
se.SE_Init(b"../resources/xosc/cut-in.xosc", 1, 1, 0, 0, 2)
for i in range (500):
se.SE_Step()
Note: Library name varies with the platform as mentioned above (see Preparations)
Below is a bit more flexible variant:
-
Portable: Works on all supported platforms
-
Provide scenario via argument
-
Quit at press Escape or end of scenario
import ctypes
import sys
if sys.platform == "linux" or sys.platform == "linux2":
se = ctypes.CDLL("../bin/libesminiLib.so")
elif sys.platform == "darwin":
se = ctypes.CDLL("../bin/libesminiLib.dylib")
elif sys.platform == "win32":
se = ctypes.CDLL("../bin/esminiLib.dll")
else:
print("Unsupported platform: {}".format(sys.platform))
quit()
if (len(sys.argv) < 2):
print('Usage: {} <xosc file>'.format(sys.argv[0]))
exit(-1)
se.SE_Init(sys.argv[1].encode('ascii'), 0, 1, 0, 0)
while not se.SE_GetQuitFlag():
se.SE_Step()
14.4.2. Initialize esmini by flexible argument list
Instead of using the limited SE_Init()
function you can pass any arguments using SE_InitWithArgs()
# initialize esmini with arguments instead of a few limited arguments
# using ideas from: https://comp.lang.python.narkive.com/RUdYlz64/trying-to-pass-sys-argv-as-int-argc-char-argv-using-ctypes
import ctypes as ct
import sys
if sys.platform == "linux" or sys.platform == "linux2":
se = ct.CDLL("../bin/libesminiLib.so")
elif sys.platform == "darwin":
se = ct.CDLL("../bin/libesminiLib.dylib")
elif sys.platform == "win32":
se = ct.CDLL("../bin/esminiLib.dll")
else:
print("Unsupported platform: {}".format(platform))
quit()
# specify arguments types of esmini function
se.SE_InitWithArgs.argtypes = [ct.c_int, ct.POINTER(ct.POINTER(ct.c_char))]
# create the list of arguments
args = [
"--window", "60", "60", "800", "400",
"--osc", "../resources/xosc/cut-in.xosc",
]
# prepare argument list for ctypes use
argv = (ct.POINTER(ct.c_char) * len(args))()
argc = len(argv)
for i, arg in enumerate(args):
argv[i] = ct.create_string_buffer(arg.encode())
# init esmini
if se.SE_InitWithArgs(argc, argv) != 0:
exit(-1)
# execute esmini until end of scenario or user requested quit by ESC key
while se.SE_GetQuitFlag() == 0:
se.SE_Step()
14.4.3. Forward command line arguments
How to just forward any argument provided from command line:
# forward command line arguments to esmini
# using ideas from: https://comp.lang.python.narkive.com/RUdYlz64/trying-to-pass-sys-argv-as-int-argc-char-argv-using-ctypes
import ctypes as ct
import sys
if sys.platform == "linux" or sys.platform == "linux2":
se = ct.CDLL("../bin/libesminiLib.so")
elif sys.platform == "darwin":
se = ct.CDLL("../bin/libesminiLib.dylib")
elif sys.platform == "win32":
se = ct.CDLL("../bin/esminiLib.dll")
else:
print("Unsupported platform: {}".format(platform))
quit()
# specify arguments types of esmini function
se.SE_InitWithArgs.argtypes = [ct.c_int, ct.POINTER(ct.POINTER(ct.c_char))]
# fetch command line arguments
argc = len(sys.argv)
argv = (ct.POINTER(ct.c_char) * (argc + 1))()
for i, arg in enumerate(sys.argv):
argv[i] = ct.create_string_buffer(arg.encode('utf-8'))
# init esmini
if se.SE_InitWithArgs(argc, argv) != 0:
exit(-1)
# execute esmini until end of scenario or user requested quit by ESC key
while se.SE_GetQuitFlag() == 0:
se.SE_Step()
14.4.4. Get object states
How to retrieve the state of all scenario objects:
import ctypes
from sys import platform
if platform == "linux" or platform == "linux2":
se = ctypes.CDLL("../bin/libesminiLib.so")
elif platform == "darwin":
se = ctypes.CDLL("../bin/libesminiLib.dylib")
elif platform == "win32":
se = ctypes.CDLL("../bin/esminiLib.dll")
else:
print("Unsupported platform: {}".format(platform))
quit()
# Definition of SE_ScenarioObjectState struct
class SEScenarioObjectState(ctypes.Structure):
_fields_ = [
("id", ctypes.c_int),
("model_id", ctypes.c_int),
("control", ctypes.c_int),
("timestamp", ctypes.c_float),
("x", ctypes.c_float),
("y", ctypes.c_float),
("z", ctypes.c_float),
("h", ctypes.c_float),
("p", ctypes.c_float),
("r", ctypes.c_float),
("roadId", ctypes.c_int),
("junctionId", ctypes.c_int),
("t", ctypes.c_float),
("laneId", ctypes.c_int),
("laneOffset", ctypes.c_float),
("s", ctypes.c_float),
("speed", ctypes.c_float),
("centerOffsetX", ctypes.c_float),
("centerOffsetY", ctypes.c_float),
("centerOffsetZ", ctypes.c_float),
("width", ctypes.c_float),
("length", ctypes.c_float),
("height", ctypes.c_float),
("objectType", ctypes.c_int),
("objectCategory", ctypes.c_int),
("wheelAngle", ctypes.c_float),
("wheelRot", ctypes.c_float),
]
se.SE_Init(b"../resources/xosc/cut-in.xosc", 0, 1, 0, 0)
obj_state = SEScenarioObjectState() # object that will be passed and filled in with object state info
for i in range(500):
for j in range(se.SE_GetNumberOfObjects()):
se.SE_GetObjectState(se.SE_GetId(j), ctypes.byref(obj_state))
print('Frame {} Time {:.2f} ObjId {} roadId {} laneId {} laneOffset {:.2f} s {:.2f} x {:.2f} y {:.2f} heading {:.2f} speed {:.2f} wheelAngle {:.2f} wheelRot {:.2f}'.format(
i, obj_state.timestamp, obj_state.id, obj_state.roadId, obj_state.laneId, obj_state.laneOffset,
obj_state.s, obj_state.x, obj_state.y, obj_state.h, obj_state.speed * 3.6, obj_state.wheelAngle, obj_state.wheelRot))
se.SE_Step()
14.4.5. Example use of esminiRMLib (RoadManager)
-
Make sure the esminiRMLib is present in
esmini/bin
folder -
In the code, check filepath to the OpenDRIVE file to load (current search path should work when script is copied to and run from the esmini/Hello-World_coding-example folder)
import ctypes as ct
import sys
if sys.platform == "linux" or sys.platform == "linux2":
rm = ct.CDLL("../bin/libesminiRMLib.so")
elif sys.platform == "darwin":
rm = ct.CDLL("../bin/libesminiRMLib.dylib")
elif sys.platform == "win32":
rm = ct.CDLL("../bin/esminiRMLib.dll")
else:
print("Unsupported platform: {}".format(sys.platform))
sys.exit(-1)
# Definition of RM_PositionData struct - should match esminiRMLib::RM_PositionData struct
class RM_PositionData(ct.Structure):
_fields_ = [
("x", ct.c_float),
("y", ct.c_float),
("z", ct.c_float),
("h", ct.c_float),
("p", ct.c_float),
("r", ct.c_float),
("h_relative", ct.c_float),
("road_id", ct.c_int),
("junction_id", ct.c_int), # -1 if not in a junction
("lane_id", ct.c_int),
("lane_offset", ct.c_float),
("s", ct.c_float),
]
# Specify argument types to a few functions
rm.RM_SetWorldPosition.argtypes = [ct.c_int, ct.c_float, ct.c_float, ct.c_float, ct.c_float, ct.c_float, ct.c_float]
rm.RM_SetLanePosition.argtypes = [ct.c_int, ct.c_int, ct.c_int, ct.c_float, ct.c_float]
# Initialize esmini RoadManger with given OpenDRIVE file
odr = '../resources/xodr/straight_500m.xodr'
if rm.RM_Init(odr.encode()) == -1: # encode() converts string to pure byte array
print("Failed to load OpenDRIVE file ", )
sys.exit(-1)
rm_pos = rm.RM_CreatePosition() # create a position object, returns a handle
rm_pos_data = RM_PositionData() # object that will be passed and filled in with position info
# test a few positions
x = 65
y = -1.7
rm.RM_SetWorldPosition(rm_pos, x, y, 0.0, 0.0, 0.0, 0.0)
rm.RM_GetPositionData(rm_pos, ct.byref(rm_pos_data))
print('road_id {} lane_id {} lane_offset {:.2f} s {:.2f}'.format(
rm_pos_data.road_id, rm_pos_data.lane_id, rm_pos_data.lane_offset, rm_pos_data.s))
road_id = 1
lane_id = 1
lane_offset = -0.2
s = 40
rm.RM_SetLanePosition(rm_pos, road_id, lane_id, lane_offset, s)
rm.RM_GetPositionData(rm_pos, ct.byref(rm_pos_data))
print('x {:.2f} y {:.2f} h (yaw) {:.2f}'.format(
rm_pos_data.x, rm_pos_data.y, rm_pos_data.h))
14.4.6. Storyboard element state callback
The following code shows how to get notification of storyboard events using StoryBoardElementStateChangeCallback.
# Storyboard element types (see OSCTypeDefs/OSCAction.hpp)
sbe_type = {
0:"UNDEFINED_ELEMENT_TYPE",
1:"STORY_BOARD",
2:"STORY",
3:"ACT",
4:"MANEUVER_GROUP",
5:"MANEUVER",
6:"EVENT",
7:"ACTION"
}
# Storyboard element states (see OSCTypeDefs/OSCAction.hpp)
sbe_state = {
0:"UNDEFINED_ELEMENT_STATE",
1:"STANDBY",
2:"RUNNING",
3:"COMPLETE"
}
# Define callback for scenario storyboard element state changes
# according to function type: void (*fnPtr)(const char* name, int type, int state)
# required by SE_RegisterStoryBoardElementStateChangeCallback()
def callback(name, type, state, full_path):
# just print the received info
print(type)
print("callback: {} ({}) state: {} full path: {}".format(codecs.decode(name), sbe_type[type], sbe_state[state], codecs.decode(full_path), ))
callback_type = ct.CFUNCTYPE(None, ct.c_char_p, ct.c_int, ct.c_int, ct.c_char_p)
callback_func = callback_type(callback)
# Initialize esmini before register the callback
se.SE_AddPath(b"../../../resources/xosc") # search path if run from original location
se.SE_Init(b"../resources/xosc/cut-in.xosc", 0, 1, 0, 1)
# register the callback function
se.SE_RegisterStoryBoardElementStateChangeCallback(callback_func)
14.4.7. OSI groundtruth
OSI groundtruth can also be retrieved and processed via Python. The following code shows one way of doing it. This example also shows how the simulation can be encapsulated in a class.
'''
Python dependencies:
pip install protobuf==3.20.2
Note: No need to install OSI as long as
esmini/scripts/osi3 folder is available
'''
import os
import sys
import ctypes as ct
# Add scripts root directory to module search path in order to find osi3
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../', 'scripts')))
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'scripts'))) # if script moved to Hello World folder
from osi3.osi_groundtruth_pb2 import GroundTruth
# Import esmini lib
if sys.platform == "linux" or sys.platform == "linux2":
se = ct.CDLL("../bin/libesminiLib.so")
elif sys.platform == "darwin":
se = ct.CDLL("../bin/libesminiLib.dylib")
elif sys.platform == "win32":
se = ct.CDLL("../bin/esminiLib.dll")
else:
print("Unsupported platform: {}".format(platform))
quit()
# Specify argument and return types of some esmini functions
se.SE_GetOSIGroundTruth.restype = ct.c_void_p
se.SE_GetOSIGroundTruth.argtypes = [ct.c_void_p]
se.SE_StepDT.argtypes = [ct.c_float]
class Sim:
def __init__(self, scenario):
self.msg = GroundTruth() # OSI groundtruth message
self.msg_size = ct.c_int()
if se.SE_Init(scenario, 0, 1, 0, 0) != 0:
print('Failed to open ', scenario)
exit(-1)
self.GetAndPrintOSIInfo() # Initial state
# Retrieve OSI message and print some info
def GetAndPrintOSIInfo(self):
se.SE_UpdateOSIGroundTruth()
msg_string = se.SE_GetOSIGroundTruth(ct.byref(self.msg_size))
self.msg.ParseFromString(ct.string_at(msg_string, self.msg_size))
# Print some info
print("Timestamp: {:.2f}".format(self.msg.timestamp.seconds + 1e-9 * self.msg.timestamp.nanos))
for j, o in enumerate(self.msg.moving_object):
print(' Object[{}] id {}'.format(j, o.id.value))
print(' pos.x {:.2f} pos.y {:.2f} rot.h {:.2f}'.format(\
o.base.position.x,\
o.base.position.y,\
o.base.orientation.yaw))
print(' vel.x {:.2f} vel.y {:.2f} rot_rate.h {:.2f}'.format(\
o.base.velocity.x,\
o.base.velocity.y,\
o.base.orientation_rate.yaw))
print(' acc.x {:.2f} acc.y {:.2f} rot_acc.h {:.2f}'.format(\
o.base.acceleration.x,\
o.base.acceleration.y,\
o.base.orientation_acceleration.yaw))
def Run(self):
while se.SE_GetQuitFlag() != 1:
se.SE_StepDT(0.05)
self.GetAndPrintOSIInfo() # Updated state
se.SE_Close()
if __name__ == '__main__':
# Execute when the module is not initialized from an import statement.
sim = Sim(b"../resources/xosc/cut-in.xosc")
sim.Run()
14.4.8. Object state callback
Advanced
Controllers and callbacks can also be utilized via Python. Following code drafts how to grab state of the first scenario object and then, in a callback, manipulate it and report back. If used in combination with ExternalController in mode=additive the scenario action are applied first. On the contrary, if used with mode=override, the scenario actions will not be applied giving exclusive control to the external callback function.
# Definition of SE_ScenarioObjectState struct
class SEScenarioObjectState(ct.Structure):
_fields_ = [
("id", ct.c_int),
("model_id", ct.c_int),
("control", ct.c_int),
("timestamp", ct.c_float),
("x", ct.c_float),
("y", ct.c_float),
("z", ct.c_float),
("h", ct.c_float),
("p", ct.c_float),
("r", ct.c_float),
("roadId", ct.c_int),
("junctionId", ct.c_int),
("t", ct.c_float),
("laneId", ct.c_int),
("laneOffset", ct.c_float),
("s", ct.c_float),
("speed", ct.c_float),
("centerOffsetX", ct.c_float),
("centerOffsetY", ct.c_float),
("centerOffsetZ", ct.c_float),
("width", ct.c_float),
("length", ct.c_float),
("height", ct.c_float),
("objectType", ct.c_int),
("objectCategory", ct.c_int),
("wheel_angle", ct.c_float),
("wheel_rot", ct.c_float),
]
# Define callback for scenario object enabling manipulating the state AFTER scenario step but BEFORE OSI output
# Use in combination with ExternalController in mode=additive in order for scenario actions to be applied first
def callback(state_ptr, b):
state = state_ptr.contents
print("callback for obj {}: x={:.2f} y={:.2f} h={:.2f}".format(state.id, state.x, state.y, state.h))
se.SE_ReportObjectPosXYH(ct.c_int(state.id), ct.c_float(state.timestamp), ct.c_float(state.x + 0.02), ct.c_float(state.y), ct.c_float(state.h + 0.001))
callback_type = ct.CFUNCTYPE(None, ct.POINTER(SEScenarioObjectState), ct.c_void_p)
callback_func = callback_type(callback)
# Initialize esmini before register the callback
se.SE_AddPath(b"../../../resources/xosc") # search path if run from original location
se.SE_Init(b"../resources/xosc/cut-in.xosc", 0, 1, 0, 1)
# register callback for first object (id=0)
se.SE_RegisterObjectCallback(0, callback_func, 0)
14.5. C# binding
A C# wrapper for the esminiLib is provided (ESMiniWrapper.cs). Coverage is limited, but it might serve as a good starting point.
14.5.1. OSI groundtruth with C#
using System;
using System.Runtime.InteropServices;
using ESMini;
using Google.Protobuf;
using static Osi3.GroundTruth;
namespace esmini_csharp
{
class Program
{
static void Main(string[] args)
{
// intitialize esmini
if (ESMiniLib.SE_Init("../resources/xosc/cut-in.xosc", 0, 1, 0, 0) != 0)
{
Console.WriteLine("failed to load scenario");
return;
}
ESMiniLib.SE_UpdateOSIGroundTruth();
int size = 0;
while (ESMiniLib.SE_GetQuitFlag() != 1)
{
// Step esmini
ESMiniLib.SE_Step();
ESMiniLib.SE_UpdateOSIGroundTruth();
// Get OSI message
IntPtr int_ptr = ESMiniLib.SE_GetOSIGroundTruth(ref size);
Byte[] byte_array = new Byte[size];
Marshal.Copy(int_ptr, byte_array, 0, size);
Osi3.GroundTruth gt_msg = Osi3.GroundTruth.Parser.ParseFrom(byte_array);
// Write some info from OSI message
Console.WriteLine("Time: {0:N3}", gt_msg.Timestamp.Seconds + 1e-9 * gt_msg.Timestamp.Nanos);
foreach (Osi3.MovingObject o in gt_msg.MovingObject)
{
Console.WriteLine(" Object[{0}], Pos: {1:N2}, {2:N2}, {3:N2}",
o.Id.Value, o.Base.Position.X, o.Base.Position.Y, o.Base.Position.Z);
}
}
}
}
}
This example shows, one way, how to access OSI data in a C# environment.
It’s dependent on the ESMiniWrapper.cs, and targeting Windows environment. If the wrapper is modified to work on Linux (referring .so instead of DLL) it might work there as well, but it’s not been tested.
Build
mkdir build
cd build
cmake ..
cmake --build . --config Release
Run
To have the correct relative path to the scenario file and also access to esminiLib we move into esmini/bin
folder for running the example.
cd ..\..\..\..\bin
..\EnvironmentSimulator\code-examples\osi-groundtruth-cs\build\Release\osi-groundtruth.exe
How to create the osi3 C# files
For this example the osi3 C# files were provided. If you need or want to compile them yourself:
-
Get protoc compiler
Suggested version: 3.15.2
Either build it yourself or getprotoc-3.15.2-win64.zip
from release:
https://github.com/protocolbuffers/protobuf/releases/tag/v3.15.2 -
Get OSI
https://github.com/OpenSimulationInterface/open-simulation-interface/releases/tag/v3.5.0 -
OSI proto files need to be converted for protobuf v3. This is easily done with the provided script:
./convert-to-proto3.sh
-
Compile for C#
-
open a command line in
open-simulation-interface
root folder -
c:\my_path_to_protoc\protoc.exe *.proto --csharp_out=.
will create C# versions of all proto files
-
15. Run ASAM OpenSCENARIO examples
With some limitations (see details here) esmini can play the example scenarios provided with the ASAM OpenSCENARIO v1.1 release bundle.
-
If you don’t have esmini already, download latest demo package for your platform from here.
-
Download the standard from ASAM here (register and download is free of charge).
-
Extract to any folder.
-
Run the examples from command line in esmini root folder, for example:
./bin/esmini --window 60 60 800 400 --osc ../../openscenario-v1.1.0/Examples/DoubleLaneChanger.xosc
or with absolute path:
./bin/esmini --window 60 60 800 400 --osc c:/stuff/openscenario-v1.1.0/Examples/DoubleLaneChanger.xosc
16. About this document
The source of this document is written in the asciidoc format. The html is generated with asciidoctor. Code snippets, images and other resources are included in the process. Also the version numbers are automatically inserted to avoid "human errors".
Install tools:
Linux |
|
Windows |
|
Generate html: asciidoctor user_guide.adoc
17. Version
ESMINI_GIT_TAG="v2.40.2"
ESMINI_GIT_REV="v2.40.2-0-f46dfe92-dirty"
Full commit history is found here