Simplifying launch argument declaration and initialization in launch files?

Hello!

I was watching this video: ROS2 Driver for Universal Robots - YouTube
and I agree, it’s quite strange how much you need to add an argument into a launch file.
So I was thinking it shouldn’t be so hard to at least simplify that down quite a bit…

I tried it in the UR ROS2 drivers ur_bringup launchfile, and just for context, this is how it looked in before I added my changes.

def launch_setup(context, *args, **kwargs):
    # Initialize Arguments
    ur_type = LaunchConfiguration("ur_type")
    ... 
    # creating all the other args and nodes_to_start,  then using the LaunchConfiguration for the nodes_to_start.
    ...
    return nodes_to_start

def generate_launch_description():
    declared_arguments = []
    # UR specific arguments
    declared_arguments.append(
        DeclareLaunchArgument(
            "ur_type",
            description="Type/series of used UR robot.",
            choices=["ur3", "ur3e", "ur5", "ur5e", "ur10", "ur10e", "ur16e"],
        )
    )
    ....
    return LaunchDescription(declared_arguments + [OpaqueFunction(function=launch_setup)])

So I created a class that extends LaunchConfiguration, so it can be used as a Substitution, with the created declaration and a getter for said declaration:


import collections.abc
from typing import List
from typing import Optional
from typing import Text
from launch.some_substitutions_type import SomeSubstitutionsType
from launch.substitutions import LaunchConfiguration
from launch.actions import DeclareLaunchArgument

class SimpleLaunchArgument(LaunchConfiguration):

	def __init__(
		self,
		name: Text,
		*,
		default_value: Optional[SomeSubstitutionsType] = None,
		description: Optional[Text] = None,
		choices: List[Text] = None,
		**kwargs
	) -> None:
		"""Declare the launch argument,"""
		self.__declaration = DeclareLaunchArgument(name=name, default_value=default_value, description=description, choices=choices, **kwargs)
		"""Then initialize it."""
		super().__init__(variable_name=name, default=default_value)

	@property
	def declaration(self) -> DeclareLaunchArgument:
		return self.__declaration

Basically the argument can be called as any LaunchConfiguration, and it’s declaration is automatically created and easily added.

So the end result would no longer need the OpaqueFunction, at least not in this case

def generate_launch_description():
	# Initialize Arguments
	ur_type = SimpleLaunchArgument(
		"ur_type", 
		description="Type/series of used UR robot.",
		choices=["ur3", "ur3e", "ur5", "ur5e", "ur10", "ur10e", "ur16e"]
	)
	# creating all the other args and nodes_to_start, then using the SimpleLaunchArgument as a LaunchConfiguration for the nodes_to_start.
	launch_arg_declarations = [
		ur_type.declaration,
		.... # All the other args
	return LaunchDescription(launch_arg_declarations + nodes_to_start)

I find this much easier to understand how the arguments are both being declared and used.
My suggestion would be to add this to the “launch.substitutions” library.
I was thinking of just doing a pull request of this to ros2/launch, but it said that I should bring it up here for discussion, so here ya go.

Any ideas, suggestions, objections?

1 Like

Some of this effort is already done by a third-party integration:

2 Likes

That’s a nice integration too, so it doesn’t seem like it’s an unknown issue. Anyone know what’s the plan, is it to leave these functionalities 3rd party or to integrate them into the ROS2 packages, aka, is it something that should be prepared into a pull request?

I already worked on it a bit, had issue with circular dependency if I added it as a substitution module, so instead of overcoming that I was thinking it might not even really be a substitution itself (even though I had it as a subclass of one), so I changed it to a “description” module, where it’s not a subclass, but a object of both the declaration and variable.

So except for linting and some small stuff left, it’s all ready for implementation, as it’s quite small…