Development
Contribute
Before pushing your code, please ensure that the code formatting is correct by running:
make
In case of errors you can run the autoformatter by executing:
make format
Testing
To run only specific tests:
#using py-test
colcon build --packages-up-to scenario_execution_ros && reset && pytest-3 -s scenario_execution_ros/test/<TEST>.py
#manual run
colcon build --packages-up-to scenario_execution_ros && reset && ros2 launch scenario_execution_ros scenario_launch.py scenario:=<...> debug:=True
#using colcon
colcon test --packages-select scenario_execution \
--event-handlers console_direct+ \
--return-code-on-test-failure \
--pytest-args -k <TEST> \
-s
#using colcon with filtered test
colcon test --packages-select scenario_execution \
--event-handlers console_direct+ \
--return-code-on-test-failure \
--pytest-args test/test_parameter_override.py::TestParameterOverride::test_base_params_success \
-s
Versioning
All packages share one version, kept in both package.xml and setup.py. Bump it
across the whole workspace with a single command:
make set_version VERSION=1.6.0 # set an explicit version
make set_version VERSION=minor # or bump major|minor|patch from the current version
This updates every source package.xml and setup.py (the canonical version is read
from scenario_execution/package.xml). Review the diff, update the CHANGELOG.rst
headings, then commit and tag, e.g. git commit -am 1.6.0 && git tag 1.6.0.
Changelog
Each package keeps a CHANGELOG.rst in the ROS (catkin) format. It is the single
source of truth for both the ROS release (consumed by bloom) and the PyPI release: the
scenario_execution package ships its CHANGELOG.rst in the sdist and links to it from
PyPI via the Changelog project URL, so there is no separate file to keep in sync.
Update the changelogs from the git history before tagging a release:
catkin_generate_changelog --all # fills the "Forthcoming" section of every CHANGELOG.rst
# review/edit the entries, then set the version + date heading
Releasing
A release goes to two places from the same version and changelogs: PyPI (the core
scenario_execution package) and the ROS build farm (the ROS packages). The changelog
and version bump go through a pull request; tagging and publishing happen on main
after that pull request is merged.
First, prepare the release on a branch and open a pull request:
make release_tools # 0. one-time: install the PyPI build tooling (build + twine)
git switch -c release-1.6.0
make ros_changelog # 1. fill each CHANGELOG.rst from git history;
# then edit every "Forthcoming" -> "<version> (<date>)"
make set_version VERSION=minor # 2. bump version across all package.xml + setup.py
# (or VERSION=1.6.0 for an explicit version)
make release_test # 3. dry run: build, validate, and upload to TestPyPI
git commit -am "Release 1.6.0" && git push -u origin release-1.6.0 # 4. open a PR, get it reviewed + merged
Then, once the pull request is merged, tag and publish from main:
git switch main && git pull
git tag 1.6.0 && git push origin 1.6.0 # 5. tag the merged release commit
git tag jazzy-1.6.0 1.6.0 && git push origin jazzy-1.6.0 # 5b. distro tag bloom releases from
make release # 6. publish to PyPI
make ros_release ROS_DISTRO=jazzy # 7. publish to the ROS build farm (bloom)
Notes:
make release(andrelease_test) first checks that the versions inscenario_execution/setup.pyandscenario_execution/package.xmlmatch, builds the sdist/wheel and validates them. Usemake release_checkto build and validate without uploading. Upload credentials are taken from twine (e.g.~/.pypircor theTWINE_USERNAME/TWINE_PASSWORDenvironment variables).make ros_releaseis interactive, needs a configured release repository, and opens arosdistropull request. It takes the rosdistro repository key (ROS_REPO, defaultscenario_execution), not individual package names;ROS_DISTROdefaults tojazzy.bloom exports the upstream sources from a distro-prefixed
<distro>-<version>tag (e.g.jazzy-1.6.0), not the bare<version>tag. Both must exist on the upstream repository before runningmake ros_release— if the distro tag is missing,bloom-export-upstreamfails with'<distro>-<version>' is not a tag in the upstream repositoryand no tarball is created. Step 5b above creates it alongside the bare tag.Only a subset of the workspace is published to ROS; the rest (examples,
*_testpackages, simulation helpers, and the docker/kubernetes/moveit2/pybullet/floorplan_dsl libraries) are intentionally not released. The released set is listed inROS_RELEASE_PACKAGESin theMakefileand must mirror therelease/packageslist for this repository in rosdistro. Runmake ros_release_packagesto print the released vs. disabled split before opening a rosdistro pull request, and keep the disabled packages out of that list.
Developing and Debugging with Visual Studio Code
To prevent certain issues, please use the following command for building (remove /build and /install` if another command was used before).
colcon build --symlink-install
In VSCode create new debugging configuration file: Run -> "Add Configuration..."
Add the following entry to the "configurations" element within the previously created launch.json file (replace the arguments as required):
{
"name": "scenario_execution_ros",
"type": "python",
"request": "launch",
"program": "./install/scenario_execution_ros/lib/scenario_execution_ros/scenario_execution_ros",
"console": "integratedTerminal",
"cwd": "${workspaceFolder}",
"args": ["-l", "TEST_SCENARIO.osc"],
}
Create an .env file by executing:
source /opt/ros/humble/setup.bash
source install/setup.bash
echo PYTHONPATH=$PYTHONPATH > .env
echo HOME=$HOME >> .env
echo AMENT_PREFIX_PATH=$AMENT_PREFIX_PATH >> .env
echo LD_LIBRARY_PATH=$LD_LIBRARY_PATH >> .env
In vscode, open user settings and enable the following settings:
"python.terminal.activateEnvInCurrentTerminal": true
To execute the debug configuration either switch to debug view (on the left) and click on "play" or press F5.
Best known Methods
Implement an Action
If an action's
setup()fails, raise an exceptionUse a state machine, if multiple steps are required
Implement a
shutdown()method to cleanup on scenario end.- For debugging/logging:
Make use of
self.feedback_messageMake use of
kwargs['logger'], available insetup()If you want to draw markers for RViz, use
kwargs['marker_handler'], available insetup()(with ROS backend)
Use arguments from
__init__()for a longer running initialization insetup()and the arguments fromexecute()to set values just before executing the action.__init__()andsetup()are called once,execute()might be called multiple times.osc2 arguments can only be consumed once, either in
__init__()orexecute(). Exception: If anassociated_actorexists, it's an argument of both methods.Arguments that need late resolving (e.g. referring to variables or external methods) need to be consumed in
execute().setup()provides several arguments that might be useful: -input_dir: Directory containing the scenario file -output_dir: If given on command-line, contains the directory to save output to -node: (scenario_execution_rosonly): ROS node to utilize (e.g. create subscribers)If your action makes use of variables, set
resolve_variable_reference_arguments_in_executeinBaseAction.__init()toFalse. Theexecute()method arguments will then contain resolved values as before, except for variable arguments which are accessible asVariableReference(with methodsset_value()andget_value()).
Implement an Action with Complex Behavior Tree
For actions that need to provide their own complex behavior tree implementation, inherit from BaseActionSubtree instead of BaseAction:
Override
create_subtree()method instead ofupdate()create_subtree()should return a completepy_trees.behaviour.Behaviourthat implements the action logicAll OSC2 parameters are passed to
create_subtree()method, similar toexecute()inBaseActionThe subtree is created once during action initialization and managed internally
Use
BaseActionSubtreewhen you need composite behaviors, decorators, or complex state machines that are better expressed as behavior trees rather than a single behavior'supdate()methodArguments follow the same rules as
BaseAction: can be consumed in__init__()orcreate_subtree(), but not both