ROS 2: Launch substitution with XML support
This is just a quick example of how to make a custom ROS 2 launch substitution available to XML launch files.
TLDR: Add the @expose_substitution annotation to the class, implement the Class.parse() method.
You should already have a python class implementing a substitution, which might look something like this:
class RobotParameterFile(launch.Substitution):
def __init__(self,
config_package: launch.SomeSubstitutionsType,
robot_name: launch.SomeSubstitutionsType,
param_file_path: launch.SomeSubstitutionsType,
) -> None:
super().__init__()
# ...
def perform(self, context: launch.LaunchContext) -> Text:
# ...
return "test"
The goal is to use it from an XML launch file, like such:
<?xml version="1.0" encoding="UTF-8"?>
<launch version="0.1.1">
<arg name="arg_a" />
<arg name="arg_b" />
<node pkg="diagnostic_remote_logging" exec="influx" name="diagnostics_influxdb_bridge">
<param from="$(robot-param-file $(var arg_a) $(var arg_b) diagnostics.yaml)" />
</node>
</launch>
To enable this, it is necessary to add the @expose_substitution("robot-param-file") annotation to the class, where the argument is the desired name/command in the XML launch file, as well as the parse(cls, data: Sequence[launch.SomeSubstitutionsType]) class method.
The purpose of the parse method is to convert the list of arguments inside the $(...) into constructor arguments of the class:
from launch.frontend import expose_substitution
@expose_substitution("robot-param-file")
class RobotParameterFile(launch.Substitution):
def __init__(self,
config_package: launch.SomeSubstitutionsType,
robot_name: launch.SomeSubstitutionsType,
param_file_path: launch.SomeSubstitutionsType,
) -> None:
super().__init__()
# ...
def perform(self, context: launch.LaunchContext) -> Text:
# ...
return "test"
@classmethod
def parse(cls, data: Sequence[launch.SomeSubstitutionsType]):
"""Parse a RobotParameterFile substitution."""
if not data or len(data) != 3:
raise AttributeError(
"robot-param-file substitution expects 3 arguments: config package, robot name, param file"
)
kwargs = {
"config_package": data[0],
"robot_name": data[1],
"param_file_path": data[2],
}
return cls, kwargs
As shown, this also works when the arguments in data are themselves substitutions (which must be accounted for in __init__).
Is is then necessary to register the package containing this substitution as providing launch extensions, which is done by adding the following to setup.cfg (replace package_name with the actual package name):
[options.entry_points]
launch.frontend.launch_extension =
package_name = package_name
I was building this package with cmake, so i also had to make sure to install setup.cfg:
ament_python_install_package(package_name
PACKAGE_DIR package_name
SETUP_CFG setup.cfg
)