A fewposts on discourse and stackexchange have already highlighted the difficulty of writing Python launch files for ROS 2.
In particular, the syntax may be a bit verbose and the arguments somehow hard to handle as they are not raw Python types.
The simple_launch package wraps the standard launch syntax inside a much lighter approach. In particular, groups (by namespace or conditions) are defined through the with Python syntax and thus the indentation clearly expresses the current level of namespace or condition.
The current release allows easy handling of:
namespaces
conditions
finding a file inside a package (thanks os.walk)
container and composable nodes
spawning a robot_state_publisher from a xacro file with arguments, even if all of those come from launch arguments
uploading models to Gazebo
bridging topics with Gazebo (ros_gz_bridge wrapper)
setting use_sim_time for all nodes in the launch file
using the OpaqueFunction idiom (my favorite) without seeing perform or context everywhere, in case you really need raw Python types
The package is available through apt and the next release will be focusing on event-based groups. This is already available in the repository but edge cases appear when combining events and namespaces.
The next release will make it clear what the chosen approach is.
The goal is not to be able to re-do everything in a simpler way, but to be able to do very easily what people do very often. Feel free to comment, issue or pull request.
Next releases will also be focusing on documentation and type hints that are still a bit lacking due to numerous combinations of Substitutions and raw Python types.
I haven’t used it much, so I might be wrong, but the tool I posted (launch_generator) seems to differ from simple_launch in the following ways:
While writing in simple_launch, it cannot coexist with launch or launch_ros in the same file (though it’s possible if separated as include files), and it does not support all the features of launch and launch_ros.
There’s a need to learn the syntax for simple_launch, but it offers many convenient wrapper functions.
I believe our goals are similar, so I would like to incorporate the best aspects of both.
As a ROS teacher the goal was to introduce the launch system in an easy manner. The main confusion to me comes from:
the many imports you have to do
passing arguments or parameters to nodes or included launch files
parsing the launch arguments and more generally, dealing with substitutions
using OpaqueFunction that solves so many problems but adds even more syntax
The approach is indeed different from launch_generator. You can use simple_launch without declaring any additional variables and let the with blocks give the structure.
In launch_generator such groups (namespaces, containers, events handling) appear as variables.
I believe both approaches are basically macros around launch to easily do what you often do. They propose a add_action or similar fallback that just trusts the user for what they want to add at the current level, in case an advanced function is not wrapped.
For basic launch files I guess both approaches just reduce the number of required lines.
For more complex files, some may prefer block-based or variable-based logic.
In both cases you have somehow a new syntax to learn, but they are both easier than the base launch syntax anyway and more logical as well.
Events are now part of simple_launch, you can reproduce the event handlers tutorial with the following syntax and go from 135 lines to 50 while doing a little more work (1 additional argument and 1 additional OnProcessIO callback because this one took some time to get it right).
from launch.actions import (EmitEvent, LogInfo)
from launch.events import Shutdown
from launch.substitutions import (EnvironmentVariable, LocalSubstitution)
from simple_launch import SimpleLauncher
from simple_launch.events import When, OnProcessExit, OnProcessStart, OnShutdown, OnExecutionComplete, OnProcessIO
def generate_launch_description():
sl = SimpleLauncher()
sl.declare_arg('verbosity', 'reqres')
turtlesim_ns = sl.declare_arg('turtlesim_ns', 'turtlesim1')
sl.declare_arg('use_provided_red', False)
new_background_r = sl.declare_arg('new_background_r', 200)
with sl.group(ns = turtlesim_ns):
sim = sl.node('turtlesim', 'turtlesim_node', name='sim')
with sl.group(when = When(sim, OnProcessStart)):
sl.log_info('Turtlesim started, spawning turtle')
spawn_turtle = sl.call_service('spawn', {'x': 2., 'y': 2., 'theta': 0.2},
verbosity = sl.arg('verbosity'))
with sl.group(when = When(spawn_turtle, OnExecutionComplete)):
sl.log_info('Spawn finished')
sl.set_parameters('sim', {'background_r': 120})
with sl.group(if_arg = 'use_provided_red', when = When(delay=2.)):
sl.set_parameters('sim', {'background_r': new_background_r})
with sl.group(when = When(sim, OnProcessExit)):
sl.log_info([EnvironmentVariable(name='USER'),' closed the turtlesim window']),
sl.add_action(EmitEvent(event=Shutdown(reason='Window closed')))
# these actions will activate only if verbose, otherwise the spawn is silent
with sl.group(when = When(spawn_turtle, OnProcessIO, io='stdout')):
sl.add_action(lambda event: sl.log_info('Spawn request says "{}"'.format(
event.text.decode().strip())))
sl.add_action(lambda event: LogInfo(
msg='Once again, spawn request says "{}"'.format(
event.text.decode().strip())))
with sl.group(when = When(event = OnShutdown)):
sl.log_info(['Launch was asked to shutdown: ',
LocalSubstitution('event.reason')])
return sl.launch_description()