SCRIMMAGE / ROS Integration¶
For more intricate simulation setups, you may want to be able generate scrimmage mission files more dynamically. This is especially important when using scrimmage with ROS. For example, we may want to launch several ROS autonomies in their own ROS namespaces and start scrimmage with a mission file that matches the ROS configuration. In this case, scrimmage will generate the sensor data required by the ROS autonomy, the ROS autonomy will publish a control signal, scrimmage will subscribe to the control signal, and scrimmage will apply the control signal to the simulated motion model. The scrimmage sensor plugins that provide the sensor data publish to ROS topics and the scrimmage autonomy plugins subscribe to the control signals being published by the ROS autonomy. In other words, the ROS nodes are performing the real decision making for the agent and the scrimmage autonomy plugin is just a translator from ROS messages to internal scrimmage data structures.
In this tutorial, we will be using the scrimmage python project and the scrimmage_ros repository to generate a scrimmage mission file at run-time and launch multiple ROS 2D navigation stacks in their own ROS namespaces.
Build SCRIMMAGE with Python and ROS¶
To get started, let’s build scrimmage with ROS plugins and python bindings in a virtual environment.
# Setup a python virtual environment
$ cd /path/to/scrimmage
$ mkdir -p build && cd build
$ python3 -m venv env # Create the python environment
$ . ./env/bin/activate # Activate the environment
# Install scrimmage python package dependencies
$ pip install -r ../python/requirements.txt
# Enable scrimmage dependencies
$ . /opt/scrimmage/*/setup.sh
# Enable the ROS dependencies. Replace ${ROS_VERSION} with "kinetic",
# "melodic", etc.
$ . /opt/ros/${ROS_VERSION}/setup.sh
# Configure scrimmage to build ROS plugins and python 3.5+
$ cmake .. -DBUILD_ROS_PLUGINS=ON -DPYTHON_MIN_VERSION=3.5
$ make -j7 # Build scrimmage
# Install the scrimmage python package
$ pip install -e ../python
After successfully building scrimmage, let’s run a simple test of the scrimmage Mission Generator python class
# Activate the locally built scrimmage environment, which puts the scrimmage
# executable on the $PATH and updates the $SCRIMMAGE_PLUGIN_PATH.
$ . ~/.scrimmage/setup.sh
# Run the Mission Generator test, which generates a mission and runs it
# with scrimmage.
$ python ../python/tests/test_MissionGenerator.py
The MissionGenerator
class uses template files, the jinja2
python
package, and a mission.yaml
file to generate scrimmage mission files. For
example, the test_MissionGenerator.py
test program, uses the template files in scrimmage/python/tests/templates and
mission.yaml.
The MissionGenerator
class constructor takes the path to the
mission.yaml
file as input:
mg = MG.MissionGenerator('/path/to/mission.yaml')
You can access the contents of the generated scrimmage mission XML through the
MissionGenerator
class’ mission
member variable.
mission_xml = mg.mission
Typically, the string that contains the contents of the scrimmage mission file is written to a temporary file:
with tempfile.NamedTemporaryFile('w') as fp:
fp.write(mg.mission)
fp.flush()
and then the python subprocess
package can be used to execute
scrimmage
, where the path to the temporary file is passed as an argument to
scrimmage
.
cmd = "scrimmage %s" % fp.name
subprocess.check_call(cmd.split(), env=os.environ.copy())
Mission YAML File Format¶
Let’s take a look at the mission.yaml
file format to understand how the
MissionGenerator
class generates a scrimmage mission file from template
files.
version: "1.0"
# Relative to current file
template_directories:
- .
# Configuration for overall mission file
mission_file:
template: mission.template.xml
config:
time_warp: 0
latitude_origin: 35.721025
longitude_origin: -120.767925
altitude_origin: 0.0
# Configuration for each entity that will be injected into the mission file
entities:
- template: entity.template.xml
config:
id: 1
x: -10
y: 10
z: -5
heading: 270
- template: entity.template.xml
config:
id: 2
x: 10
y: 10
z: -5
heading: 0
The version
specifies the YAML format version number for the
MissionGenerator
class. The template_directories
variable specifies a
list of absolute or relative (to the mission.yaml
file) paths to be used
when searching for template files. In this case, the template files are located
in the same directory as the mission.yaml
file since the .
path is
included. The mission_file
specifies the top-level scrimmage mission file
template (mission.template.xml
) and the config
values that should be
used during variable substitution by jinja2
. For example, the
mission.template.xml
file contains the line:
time_warp="{{ config.time_warp }}"
and the config
block contains the value of 0
for the time_warp
:
config:
time_warp: 0
After mission file generation, the scrimmage mission file will contain the text:
time_warp="0"
The mission.yaml
file also contains a list of entities
, where each
entity is specified by a template
file and a variable config
block that
is used during variable substitution.
entities:
- template: entity.template.xml
config:
id: 1
x: -10
y: 10
z: -5
heading: 270
- template: entity.template.xml
config:
id: 2
x: 10
y: 10
z: -5
heading: 0
The top-level scrimmage mission template file (mission.template.xml
) uses a
jinja2
for-loop to generate each entity:
{% for entity in config.entities %}
{{ entity }}
{% endfor %}
An advantage to this template process is that entity templates can be reused across multiple scrimmage mission files without having to copy and paste XML blocks.
Launching SCRIMMAGE and ROS in Parallel¶
We will now use the scrimmage_ros
package to launch two ROS agents running
the 2D navigation stack, generate a scrimmage mission file, and pass the
generated mission file to the scrimmage
executable.
Build scrimmage_ros¶
$ mkdir -p ~/catkin_ws/src && cd ~/catkin_ws/src
$ git clone git@github.com:SyllogismRXS/scrimmage_ros.git
# Source the ROS, scrimmage, and scrimmage python environments
$ . /opt/ros/${ROS_VERSION}/setup.sh # ROS
$ . ~/.scrimmage/setup.bash # scrimmage
$ . ~/scrimmage/scrimmage/build/env/bin/activate # scrimmage python
# Build scrimmage_ros
$ cd ~/catkin_ws
$ catkin_make
Now that the scrimmage_ros
package has been built, source the catkin_ws
environment and run the python script that will launch the ROS agents and
scrimmage:
$ . devel/setup.sh
$ python ./src/scrimmage_ros/src/scrimmage_ros/test/test_entity_launch.py
Enter CTRL+c to end the simulation. Let’s take a look at the
test_entity_launch.py
script and the associated template files to see how
the simulation was configured with the EntityLaunch
class:
entity_launch = EntityLaunch(args.sim_mission_yaml_file,
args.processes_yaml_file,
run_dir, None, None)
The EntityLaunch
takes a mission.yaml
file and a processes.yaml
file as inputs, generates the scrimmage mission file, executes scrimmage with
the generated mission file, and executes the processes in the processes yaml
file.
Finally, the processes are actually executed in parallel with the run()
method:
# Run the processes. Blocking.
entity_launch.run()
Processes YAML File Format¶
Let’s take a look at the processes yaml file to understand how it helps us launch scrimmage in parallel with ROS autonomies:
version: "1.0"
# Specify default values
defaults:
terminal: gnome
environment:
some_variable: some_value
# Processes that are run before entities are launched
processes:
- name: roscore
command: roscore
terminal: none
post_delay: 1.5
- name: map_server
command: stdbuf -oL roslaunch scrimmage_ros map_server.launch
terminal: none
# Processes that are run for each entity type
entity_processes:
- type: car
processes:
- name: 'entity{{ id }}'
command: 'stdbuf -oL roslaunch scrimmage_ros entity_nav_2d.launch team_id:=1 entity_id:={{ id }}'
# Clean up commands run for each entity
clean_up:
- name: hello
command: 'echo "Goodbye, Entity {{ id }}'
# Overall clean up commands
clean_up:
The processes
list specifies the processes that will be launch before the
individual entities’ ROS systems are launched. The entity_processes
list
specifies the processes that will be launched for each entity. In this case, we
will call roslaunch
for each entity. Each entity has an optional
clean_up
list of commands and we can specify a global clean_up
for the
entire system. The processes for each entity are specified with a type
tag. In this case, we use the type
tag, car
. We will also have to
augment our mission yaml file with the type
tag, so that the processes that
are launched can be matched with the entities in the scrimmage mission
file. For example, our new mission yaml file will look like the following:
version: "1.0"
# Relative to current file
template_directories:
- .
# Configuration for overall mission file
mission_file:
template: mission.template.xml
config:
time_warp: 0
latitude_origin: 35.721025
longitude_origin: -120.767925
altitude_origin: 0.0
# Configuration for each entity that will be injected into the mission file
entities:
- type: car
template: entity.template.xml
config:
id: 1
x: -10
y: 10
z: -5
heading: 270
- type: car
template: entity.template.xml
config:
id: 2
x: 10
y: 10
z: -5
heading: 0
Running processes in docker¶
The processes specified in processes.yaml
can be run in docker as well, keeping in
mind that there is typically no display available (as specified in the DISPLAY
environment
variable), which can affect certain terminal programs. The main scrimmage process uses the
default terminal, which may need to be set to none
for running in docker. And any other
process with its own terminal specified may also need to be set to none
, or another
terminal program that can run in docker:
version: "1.0"
defaults:
terminal: none
environment:
some_variable: some_value
processes:
- name: process_with_gui
command: process_with_gui
terminal: none