Architecture

Overview of Scenario Execution

Overview of Scenario Execution

Scenario execution is built as a Python library on top of two open-source components: the generic scenario description language OpenSCENARIO DSL and PyTrees. In general, the user defines a scenario in the OpenSCENARIO DSL language, scenario execution parses the scenario, translates it to a behavior tree, executes it and finally gathers the test results.

Architecture of Scenario Execution

Architecture of Scenario Execution

Our implementation is highly modular separating the core components from simulation- and/or middleware-specific modules realized through a plugin-based approach. In principle, any additional feature that is required by a specific scenario and that can be implemented in Python could be realized as additional library. A library typically provides an OpenSCENARIO DSL file with additional definitions and may provide code implementing additional functionality such as conditions or actions.

Currently, the following sub-packages and libraries are available:

Design for Modularity

Scenario execution is designed to be easily extensible through libraries. An example is available here: Create Scenario Library.

The entry points are defined like this:

entry_points={
 'scenario_execution.actions': [
     'custom_action = example_library.custom_action:CustomAction',
 ],
  'scenario_execution.osc_libraries': [
      'example = example_library.get_osc_library:get_example_library',
  ]
}

Scenario Parsing

Architecture of Scenario Parsing

Architecture of Scenario Parsing

The Internal Model Builder, implemented as a Model Listener does an initial check of the model by checking for supported language features. The Internal Model Resolver, implemented as a Model Visitor is used for type/variable resolving and does an in depth consistency check of the model.

Modules

  • scenario_execution: The base package for scenario execution. It provides the parsing of OpenSCENARIO DSL files and the conversion to py-trees. It's middleware agnostic and can therefore be used as a basis for more specific implementations (e.g. ROS or step-based simulation). It also provides basic OpenSCENARIO DSL libraries and actions.

  • scenario_execution_ros: This package uses scenario_execution as a basis and implements a ROS2 version of scenario execution. It provides a OpenSCENARIO DSL library with basic ROS2-related actions like publishing on a topic or calling a service.

  • scenario_execution_control: Provides code to control scenario execution (in ROS2) from another application such as RViz.

  • scenario_execution_coverage: Provides tools to generate concrete scenarios from abstract OpenSCENARIO DSL scenario definition and execute them.

  • scenario_execution_gazebo: Provides a Gazebo-specific OpenSCENARIO DSL library with actions.

  • scenario_execution_interfaces: Provides ROS2 interfaces, more specifically, messages and services, which are used to interface ROS2 with the scenario_execution_control package.

  • scenario_execution_rviz: Contains several rviz plugins for visualizing and controlling scenarios when working with ROS2.

  • simulation/gazebo_tf_publisher: Publish ground truth transforms from simulation within TF.

  • simulation/tb4_sim_scenario: Run Turtlebot4 within simulation, controlled by scenario execution.

  • tools/message_modification: ROS2 nodes to modify messages.

  • tools/scenario_status: Publish the current scenario status on a topic (e.g. to be capture within a ROS bag).

Step-based Simulation

scenario_execution supports step-based simulators (e.g. MuJoCo, PyBullet, custom hardware-in-the-loop setups) through the SimulationInterface abstraction. This allows scenario authors to run scenarios while retaining full use of the OpenSCENARIO DSL, including time-based directives such as wait elapsed() and timeout().

Clock abstraction

In normal (wall-clock) mode the framework uses time.sleep() between ticks. In step-based mode there is no sleeping: the loop runs as fast as the simulator allows. Time is tracked by a SimulationClock that advances by exactly dt seconds per tick, so wait elapsed(1s) maps to exactly 1 / dt simulation steps regardless of the system clock.

The WallClock is used as fallback when no simulation is configured, preserving backward compatibility.

┌─────────────────────────────────────────────────────┐
│                  ScenarioExecution                  │
│                                                     │
│  run_with_simulation(sim)                           │
│    sim.setup()                                      │
│    sim.reset()        ← once before the scenario    │
│    while running:                                   │
│      sim.step()       ← advance the simulator       │
│      clock.advance()  ← advance SimulationClock     │
│      tree.tick()      ← advance the behavior tree   │
│    sim.shutdown()                                   │
└─────────────────────────────────────────────────────┘

API alignment with ros-simulation/simulation_interfaces

The SimulationInterface is conceptually aligned with the ros-simulation/simulation_interfaces standard, making it straightforward to implement adapters for compliant simulators.

See Step-based simulation for usage instructions and a complete example.